@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
|
@@ -32,9 +32,9 @@ export interface LoadedDefinition {
|
|
|
32
32
|
}
|
|
33
33
|
export interface LoadAgentDefinitionOptions {
|
|
34
34
|
/**
|
|
35
|
-
* Working directory whose ancestors are walked for context files (segment ②). Default = `agentDir
|
|
36
|
-
*
|
|
37
|
-
*
|
|
35
|
+
* Working directory whose ancestors are walked for context files (segment ②). Default = `agentDir`.
|
|
36
|
+
* The opener passes the workspace instead, so an agent that lives in `agentDir` picks up the
|
|
37
|
+
* project's AGENTS.md up the tree (core.md scenario grid).
|
|
38
38
|
*/
|
|
39
39
|
cwd?: string;
|
|
40
40
|
env?: ExecutionEnv;
|
|
@@ -42,31 +42,12 @@ export interface LoadAgentDefinitionOptions {
|
|
|
42
42
|
/** Read an agent definition. persona.md/skills come from `agentDir`; ② context = pi's loadProjectContextFiles({ cwd, agentDir }). */
|
|
43
43
|
export declare function loadAgentDefinition(agentDir: string, options?: LoadAgentDefinitionOptions): Promise<LoadedDefinition>;
|
|
44
44
|
/**
|
|
45
|
-
* Whether `targetPath` lives inside `baseDir` (same path counts).
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
* the tree). Whether a relative override lands in-tree is a cwd question — see `resolveStateRoot`.
|
|
45
|
+
* Whether `targetPath` lives inside `baseDir` (same path counts). Used to ask "did an override move
|
|
46
|
+
* this OUT of the agent?" — the startup report's redeploy notes, `add`'s printed `.env` label, and the
|
|
47
|
+
* dev watcher's "your .env is not watched" warning all turn on that fact. Reporting only: fastagent
|
|
48
|
+
* does not act on where a user's paths point.
|
|
50
49
|
*/
|
|
51
50
|
export declare function isUnderDir(targetPath: string, baseDir: string): boolean;
|
|
52
|
-
/**
|
|
53
|
-
* The single owner of the self-ignore MECHANISM: iff the resolved state ROOT lands inside the workspace
|
|
54
|
-
* tree, write `<stateRoot>/.gitignore="*"` — which then covers EVERYTHING under it (sessions, auth.json,
|
|
55
|
-
* every channel's `channels/<kind>` home). `ensureStateDirSelfIgnored` is private, so a caller cannot
|
|
56
|
-
* write a `.gitignore` bypassing this.
|
|
57
|
-
*
|
|
58
|
-
* ROOT-based, not path-based: everything derives from the state root (config.ts), so protecting the
|
|
59
|
-
* root protects all of it — INCLUDING a custom in-tree root (a `FASTAGENT_STATE_DIR` inside the agent
|
|
60
|
-
* dir), the case a path-based (`.fastagent`-only) guard would leak. A per-path override
|
|
61
|
-
* (`--sessions-dir`/`--auth-path`) is operator-owned: pointed at an external volume it is out-of-tree
|
|
62
|
-
* (correctly not ours to ignore); pointed at a custom in-tree dir WE DON'T OWN, we do not write a
|
|
63
|
-
* `.gitignore` into it (it may be a directory the operator deliberately tracks).
|
|
64
|
-
*
|
|
65
|
-
* Excludes the user's HOME-global `~/.fastagent` (e.g. `login`/`dev` run from `$HOME`): self-ignore is
|
|
66
|
-
* for protecting state inside an agent PROJECT tree, not for writing a `.gitignore` into the user's
|
|
67
|
-
* home, which a dotfiles repo may track. The global credential file there was never self-ignored.
|
|
68
|
-
*/
|
|
69
|
-
export declare function ensureStateRootSelfIgnored(dir: string, stateRoot: string): Promise<void>;
|
|
70
51
|
/** Resolve to a canonical (symlink-free) absolute path so comparisons match `process.cwd()`'s realpath.
|
|
71
52
|
* A non-existent path can't be realpath'd, so it stays as the plain absolute resolve. */
|
|
72
53
|
export declare function canonicalPath(p: string): string;
|
|
@@ -14,12 +14,11 @@
|
|
|
14
14
|
* (bad skill files, name collisions) are returned as data. An unreadable ② context file only warns (pi).
|
|
15
15
|
*/
|
|
16
16
|
import { realpathSync } from "node:fs";
|
|
17
|
-
import { mkdir, writeFile } from "node:fs/promises";
|
|
18
|
-
import { homedir } from "node:os";
|
|
19
17
|
import { isAbsolute, join, relative, resolve } from "node:path";
|
|
20
18
|
import { loadSkills } from "@earendil-works/pi-agent-core";
|
|
21
19
|
import { NodeExecutionEnv } from "@earendil-works/pi-agent-core/node";
|
|
22
20
|
import { loadProjectContextFiles } from "@earendil-works/pi-coding-agent";
|
|
21
|
+
import { assertInsideAgentDir } from "../../paths.js";
|
|
23
22
|
/** Read an agent definition. persona.md/skills come from `agentDir`; ② context = pi's loadProjectContextFiles({ cwd, agentDir }). */
|
|
24
23
|
export async function loadAgentDefinition(agentDir, options = {}) {
|
|
25
24
|
// One resolved default for the working directory (env cwd AND the context-walk start), so they can
|
|
@@ -44,7 +43,9 @@ export async function loadAgentDefinition(agentDir, options = {}) {
|
|
|
44
43
|
}
|
|
45
44
|
const persona = personaRead.ok ? personaRead.value : undefined;
|
|
46
45
|
// Skills come ONLY from the definition's own skills/ (no external/global mount), so the same
|
|
47
|
-
// definition loads the same skills on every machine
|
|
46
|
+
// definition loads the same skills on every machine — and, like tools/channels/schedules, a symlink
|
|
47
|
+
// that escapes the agent dir is refused rather than followed (the fourth of four surfaces).
|
|
48
|
+
await assertInsideAgentDir(root, "skills");
|
|
48
49
|
const { skills: raw, diagnostics } = await loadSkills(e, [join(root, "skills")]);
|
|
49
50
|
const byName = new Map();
|
|
50
51
|
const collisions = [];
|
|
@@ -60,62 +61,15 @@ export async function loadAgentDefinition(agentDir, options = {}) {
|
|
|
60
61
|
return { contextFiles, persona, skills: [...byName.values()], diagnostics, collisions, dir: root };
|
|
61
62
|
}
|
|
62
63
|
/**
|
|
63
|
-
* Whether `targetPath` lives inside `baseDir` (same path counts).
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
* the tree). Whether a relative override lands in-tree is a cwd question — see `resolveStateRoot`.
|
|
64
|
+
* Whether `targetPath` lives inside `baseDir` (same path counts). Used to ask "did an override move
|
|
65
|
+
* this OUT of the agent?" — the startup report's redeploy notes, `add`'s printed `.env` label, and the
|
|
66
|
+
* dev watcher's "your .env is not watched" warning all turn on that fact. Reporting only: fastagent
|
|
67
|
+
* does not act on where a user's paths point.
|
|
68
68
|
*/
|
|
69
69
|
export function isUnderDir(targetPath, baseDir) {
|
|
70
70
|
const rel = relative(baseDir, targetPath);
|
|
71
71
|
return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel));
|
|
72
72
|
}
|
|
73
|
-
/**
|
|
74
|
-
* Self-ignore a state dir: create it if missing, then write `<stateDir>/.gitignore` = "*" (idempotent
|
|
75
|
-
* — an existing one is kept), so a workspace that runs dev/start never shows machine state as
|
|
76
|
-
* untracked. Creates the dir because a caller may self-ignore it before anything else populates it
|
|
77
|
-
* (e.g. `login` writing auth.json into a not-yet-created root).
|
|
78
|
-
*
|
|
79
|
-
* Module-PRIVATE on purpose: the only entry to the leak guard is {@link ensureStateRootSelfIgnored}
|
|
80
|
-
* (home exclusion + containment). Keeping this unexported makes that single-owner claim hold at the
|
|
81
|
-
* type level — a sibling command can't bypass those checks by writing a `.gitignore` directly.
|
|
82
|
-
*/
|
|
83
|
-
async function ensureStateDirSelfIgnored(stateDir) {
|
|
84
|
-
await mkdir(stateDir, { recursive: true });
|
|
85
|
-
await writeFile(join(stateDir, ".gitignore"), "*\n", { flag: "wx" }).catch((e) => {
|
|
86
|
-
if (e.code !== "EEXIST")
|
|
87
|
-
throw e;
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
/**
|
|
91
|
-
* The single owner of the self-ignore MECHANISM: iff the resolved state ROOT lands inside the workspace
|
|
92
|
-
* tree, write `<stateRoot>/.gitignore="*"` — which then covers EVERYTHING under it (sessions, auth.json,
|
|
93
|
-
* every channel's `channels/<kind>` home). `ensureStateDirSelfIgnored` is private, so a caller cannot
|
|
94
|
-
* write a `.gitignore` bypassing this.
|
|
95
|
-
*
|
|
96
|
-
* ROOT-based, not path-based: everything derives from the state root (config.ts), so protecting the
|
|
97
|
-
* root protects all of it — INCLUDING a custom in-tree root (a `FASTAGENT_STATE_DIR` inside the agent
|
|
98
|
-
* dir), the case a path-based (`.fastagent`-only) guard would leak. A per-path override
|
|
99
|
-
* (`--sessions-dir`/`--auth-path`) is operator-owned: pointed at an external volume it is out-of-tree
|
|
100
|
-
* (correctly not ours to ignore); pointed at a custom in-tree dir WE DON'T OWN, we do not write a
|
|
101
|
-
* `.gitignore` into it (it may be a directory the operator deliberately tracks).
|
|
102
|
-
*
|
|
103
|
-
* Excludes the user's HOME-global `~/.fastagent` (e.g. `login`/`dev` run from `$HOME`): self-ignore is
|
|
104
|
-
* for protecting state inside an agent PROJECT tree, not for writing a `.gitignore` into the user's
|
|
105
|
-
* home, which a dotfiles repo may track. The global credential file there was never self-ignored.
|
|
106
|
-
*/
|
|
107
|
-
export async function ensureStateRootSelfIgnored(dir, stateRoot) {
|
|
108
|
-
// Compare CANONICAL paths for the home check: `dir` arrives realpath-resolved (it is `process.cwd()`
|
|
109
|
-
// or `resolve(".")`) but `homedir()` returns the raw `$HOME`, so a symlinked home would slip past raw
|
|
110
|
-
// equality and we'd write a `.gitignore` into the real `~/.fastagent` — the very thing the doc forbids
|
|
111
|
-
// (chat.ts canonicalizes for the same reason).
|
|
112
|
-
if (canonicalPath(dir) === canonicalPath(homedir()))
|
|
113
|
-
return;
|
|
114
|
-
// Containment on RAW paths: stateRoot is resolve()'d (config.ts) and `dir` is absolute, so it is exact
|
|
115
|
-
// by construction. An external-volume root resolves outside the tree → skip (not ours to ignore).
|
|
116
|
-
if (isUnderDir(stateRoot, dir))
|
|
117
|
-
await ensureStateDirSelfIgnored(stateRoot);
|
|
118
|
-
}
|
|
119
73
|
/** Resolve to a canonical (symlink-free) absolute path so comparisons match `process.cwd()`'s realpath.
|
|
120
74
|
* A non-existent path can't be realpath'd, so it stays as the plain absolute resolve. */
|
|
121
75
|
export function canonicalPath(p) {
|
|
@@ -60,6 +60,55 @@ export interface PiHarnessFactoryOptions {
|
|
|
60
60
|
skills?: Skill[];
|
|
61
61
|
}>;
|
|
62
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* Retry policy for generated compaction/branch-summary model calls (pi ≥0.81.1, #6901). OPT-IN
|
|
65
|
+
* upstream — an undefined policy means no retries — so both compaction paths pass it explicitly:
|
|
66
|
+
* the harness config (auto-compaction inside a run) and the manual `compact()` dispatch in
|
|
67
|
+
* session-control. Values mirror pi's own app defaults (maxRetries 3, base 2s exponential).
|
|
68
|
+
*/
|
|
69
|
+
export declare const SUMMARIZATION_RETRY_POLICY: {
|
|
70
|
+
readonly enabled: true;
|
|
71
|
+
readonly maxRetries: 3;
|
|
72
|
+
readonly baseDelayMs: 2000;
|
|
73
|
+
};
|
|
74
|
+
export declare const THINKING_LEVELS: ReadonlySet<ThinkingLevel>;
|
|
75
|
+
/** The shape both override consumers walk — a session entry, structurally. */
|
|
76
|
+
export interface OverrideEntryLike {
|
|
77
|
+
type: string;
|
|
78
|
+
provider?: string;
|
|
79
|
+
modelId?: string;
|
|
80
|
+
thinkingLevel?: string;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* The session's durable override FACTS — the ONE walk both surfaces consume (`state()` reports the
|
|
84
|
+
* recorded truth; `resolveHarnessOverrides` below applies registry/scale fallbacks on top). The
|
|
85
|
+
* LAST entry of each kind wins, and a malformed record reads as ABSENT for that kind — never
|
|
86
|
+
* skipped over to an earlier record: the reporting surface and the execution surface must agree on
|
|
87
|
+
* which record is "the" override.
|
|
88
|
+
*/
|
|
89
|
+
export declare function lastOverrideEntries(entries: OverrideEntryLike[]): {
|
|
90
|
+
model?: {
|
|
91
|
+
provider: string;
|
|
92
|
+
modelId: string;
|
|
93
|
+
};
|
|
94
|
+
thinkingLevel?: string;
|
|
95
|
+
};
|
|
96
|
+
/**
|
|
97
|
+
* Resolve the session's model/thinking OVERRIDES for a fresh harness — same shape as the
|
|
98
|
+
* active-tools resolve above: pi writes `model_change`/`thinking_level_change` entries on explicit
|
|
99
|
+
* setModel/setThinkingLevel (the control plane's `set_model`/`set_thinking` append them directly)
|
|
100
|
+
* but a fresh harness never reads them back. Override facts come from {@link lastOverrideEntries};
|
|
101
|
+
* this adds the EXECUTION fallbacks: a recorded model no longer in this deployment's registry falls
|
|
102
|
+
* back to the default with a deduped warn (fail visibly without bricking the session — the
|
|
103
|
+
* conversation must survive a registry change across deploys); an unknown thinking level likewise.
|
|
104
|
+
*/
|
|
105
|
+
export declare function resolveHarnessOverrides(entries: OverrideEntryLike[], models: Models, defaults: {
|
|
106
|
+
model: AnyModel;
|
|
107
|
+
thinkingLevel: ThinkingLevel;
|
|
108
|
+
}, sessionId: string): {
|
|
109
|
+
model: AnyModel;
|
|
110
|
+
thinkingLevel: ThinkingLevel;
|
|
111
|
+
};
|
|
63
112
|
export declare function resolveHarnessActiveToolNames(recorded: string[] | null, tools: AgentTool[], sessionId: string): string[] | undefined;
|
|
64
113
|
/** Open-or-create the session per invoke: existing → open (history via buildContext); missing → create. */
|
|
65
114
|
export declare function piHarnessFactory(options: PiHarnessFactoryOptions): PiHarnessFactory;
|
|
@@ -38,6 +38,13 @@ export function harnessSession(harness) {
|
|
|
38
38
|
* so a mid-stream failure surfaces as a `failed` event.
|
|
39
39
|
*/
|
|
40
40
|
const PROVIDER_MAX_RETRIES = 2;
|
|
41
|
+
/**
|
|
42
|
+
* Retry policy for generated compaction/branch-summary model calls (pi ≥0.81.1, #6901). OPT-IN
|
|
43
|
+
* upstream — an undefined policy means no retries — so both compaction paths pass it explicitly:
|
|
44
|
+
* the harness config (auto-compaction inside a run) and the manual `compact()` dispatch in
|
|
45
|
+
* session-control. Values mirror pi's own app defaults (maxRetries 3, base 2s exponential).
|
|
46
|
+
*/
|
|
47
|
+
export const SUMMARIZATION_RETRY_POLICY = { enabled: true, maxRetries: 3, baseDelayMs: 2000 };
|
|
41
48
|
/**
|
|
42
49
|
* The serving default for reasoning effort, pinned to what pi's TUI defaults to (its
|
|
43
50
|
* DEFAULT_THINKING_LEVEL) — NOT inherited from the bare harness, whose own fallback is "off": an
|
|
@@ -69,6 +76,84 @@ const DEFAULT_THINKING_LEVEL = "medium";
|
|
|
69
76
|
* (like L2's findings memo), not session state — the resolve stays derived from the session.
|
|
70
77
|
*/
|
|
71
78
|
const warnedRestores = new Set();
|
|
79
|
+
/** pi's ThinkingLevel scale as a checkable set — THE single source for fastagent (session entries
|
|
80
|
+
* store plain strings; session-control's dispatch validation and capabilities derive from this).
|
|
81
|
+
* The `satisfies Record<ThinkingLevel, …>` anchor makes it EXHAUSTIVE against pi's union: pi
|
|
82
|
+
* adding a level turns this into a type error instead of a silent drift where `set_thinking`
|
|
83
|
+
* rejects a value pi supports. */
|
|
84
|
+
const ALL_THINKING_LEVELS = {
|
|
85
|
+
off: true,
|
|
86
|
+
minimal: true,
|
|
87
|
+
low: true,
|
|
88
|
+
medium: true,
|
|
89
|
+
high: true,
|
|
90
|
+
xhigh: true,
|
|
91
|
+
max: true,
|
|
92
|
+
};
|
|
93
|
+
export const THINKING_LEVELS = new Set(Object.keys(ALL_THINKING_LEVELS));
|
|
94
|
+
/**
|
|
95
|
+
* The session's durable override FACTS — the ONE walk both surfaces consume (`state()` reports the
|
|
96
|
+
* recorded truth; `resolveHarnessOverrides` below applies registry/scale fallbacks on top). The
|
|
97
|
+
* LAST entry of each kind wins, and a malformed record reads as ABSENT for that kind — never
|
|
98
|
+
* skipped over to an earlier record: the reporting surface and the execution surface must agree on
|
|
99
|
+
* which record is "the" override.
|
|
100
|
+
*/
|
|
101
|
+
export function lastOverrideEntries(entries) {
|
|
102
|
+
let model;
|
|
103
|
+
let modelSeen = false;
|
|
104
|
+
let thinkingLevel;
|
|
105
|
+
let thinkingSeen = false;
|
|
106
|
+
for (let i = entries.length - 1; i >= 0 && !(modelSeen && thinkingSeen); i--) {
|
|
107
|
+
const e = entries[i];
|
|
108
|
+
if (!modelSeen && e?.type === "model_change") {
|
|
109
|
+
modelSeen = true;
|
|
110
|
+
if (e.provider !== undefined && e.modelId !== undefined)
|
|
111
|
+
model = { provider: e.provider, modelId: e.modelId };
|
|
112
|
+
}
|
|
113
|
+
if (!thinkingSeen && e?.type === "thinking_level_change") {
|
|
114
|
+
thinkingSeen = true;
|
|
115
|
+
if (e.thinkingLevel !== undefined)
|
|
116
|
+
thinkingLevel = e.thinkingLevel;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return { model, thinkingLevel };
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Resolve the session's model/thinking OVERRIDES for a fresh harness — same shape as the
|
|
123
|
+
* active-tools resolve above: pi writes `model_change`/`thinking_level_change` entries on explicit
|
|
124
|
+
* setModel/setThinkingLevel (the control plane's `set_model`/`set_thinking` append them directly)
|
|
125
|
+
* but a fresh harness never reads them back. Override facts come from {@link lastOverrideEntries};
|
|
126
|
+
* this adds the EXECUTION fallbacks: a recorded model no longer in this deployment's registry falls
|
|
127
|
+
* back to the default with a deduped warn (fail visibly without bricking the session — the
|
|
128
|
+
* conversation must survive a registry change across deploys); an unknown thinking level likewise.
|
|
129
|
+
*/
|
|
130
|
+
export function resolveHarnessOverrides(entries, models, defaults, sessionId) {
|
|
131
|
+
let model = defaults.model;
|
|
132
|
+
let thinkingLevel = defaults.thinkingLevel;
|
|
133
|
+
const warnOnce = (key, message) => {
|
|
134
|
+
const emit = warnedRestores.has(key) ? log.debug : log.warn;
|
|
135
|
+
warnedRestores.add(key);
|
|
136
|
+
emit(message);
|
|
137
|
+
};
|
|
138
|
+
const recorded = lastOverrideEntries(entries);
|
|
139
|
+
if (recorded.model) {
|
|
140
|
+
const found = models.getModel(recorded.model.provider, recorded.model.modelId);
|
|
141
|
+
if (found)
|
|
142
|
+
model = found;
|
|
143
|
+
else {
|
|
144
|
+
warnOnce(`${sessionId}\u0000model\u0000${recorded.model.provider}/${recorded.model.modelId}`, `[fastagent] session ${sessionId}: recorded model override ${recorded.model.provider}/${recorded.model.modelId} is not in this deployment's registry — using the configured default`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
if (recorded.thinkingLevel !== undefined) {
|
|
148
|
+
if (THINKING_LEVELS.has(recorded.thinkingLevel)) {
|
|
149
|
+
thinkingLevel = recorded.thinkingLevel;
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
warnOnce(`${sessionId}\u0000thinking\u0000${recorded.thinkingLevel}`, `[fastagent] session ${sessionId}: recorded thinking level "${recorded.thinkingLevel}" is unknown — using the configured default`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return { model, thinkingLevel };
|
|
156
|
+
}
|
|
72
157
|
export function resolveHarnessActiveToolNames(recorded, tools, sessionId) {
|
|
73
158
|
const anyDeferred = tools.some(isDeferredTool);
|
|
74
159
|
const initial = tools.filter((t) => !isDeferredTool(t)).map((t) => t.name);
|
|
@@ -99,17 +184,20 @@ export function piHarnessFactory(options) {
|
|
|
99
184
|
const { systemPrompt } = options;
|
|
100
185
|
const prompt = fresh ? fresh.systemPrompt : typeof systemPrompt === "function" ? systemPrompt() : systemPrompt;
|
|
101
186
|
const skills = fresh ? fresh.skills : options.skills;
|
|
187
|
+
// Session overrides (set_model / set_thinking) win over the assembly defaults — same entry walk.
|
|
188
|
+
const overrides = resolveHarnessOverrides(entries, options.models, { model: options.model, thinkingLevel: options.thinkingLevel ?? DEFAULT_THINKING_LEVEL }, sessionId);
|
|
102
189
|
const harness = new AgentHarness({
|
|
103
190
|
env: options.env,
|
|
104
191
|
session,
|
|
105
192
|
models: options.models,
|
|
106
|
-
model:
|
|
107
|
-
thinkingLevel:
|
|
193
|
+
model: overrides.model,
|
|
194
|
+
thinkingLevel: overrides.thinkingLevel,
|
|
108
195
|
tools: options.tools,
|
|
109
196
|
activeToolNames: resolveHarnessActiveToolNames(activated.length > 0 ? activated : null, options.tools ?? [], sessionId),
|
|
110
197
|
systemPrompt: prompt,
|
|
111
198
|
resources: skills ? { skills } : undefined,
|
|
112
199
|
streamOptions: { maxRetries: PROVIDER_MAX_RETRIES },
|
|
200
|
+
retry: SUMMARIZATION_RETRY_POLICY,
|
|
113
201
|
});
|
|
114
202
|
harnessSessions.set(harness, session);
|
|
115
203
|
return harness;
|
|
@@ -1,5 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The turn mechanism (request-time): fan pi AgentHarness's two ports (subscribe event side-channel
|
|
3
|
+
* + prompt final value) into SPEC's single event stream, under a single-writer-per-session lease.
|
|
4
|
+
*
|
|
5
|
+
* §1 Lease — single-writer concurrency floor (injectable port + in-process default)
|
|
6
|
+
* §2 translate — the single pi↔SPEC translation point (both directions)
|
|
7
|
+
* §3 EventQueue — push→pull plumbing for pi's two-port shape
|
|
8
|
+
* §4 createPiAgentFromHarness — composes §1–§3 into Agent.invoke
|
|
9
|
+
*
|
|
10
|
+
* Concurrency: at most one in-flight turn per session; a second invoke fails fast with
|
|
11
|
+
* `failed{retryable}` ("session busy"), leaving dedupe/queueing/steering to the channel. Each
|
|
12
|
+
* invoke builds a fresh harness bound to the session and discards it (stateless multi-session).
|
|
13
|
+
*/
|
|
14
|
+
import type { AgentHarnessEvent } from "@earendil-works/pi-agent-core";
|
|
1
15
|
import type { AssistantMessage } from "@earendil-works/pi-ai";
|
|
2
|
-
import { type Agent, type AgentEvent } from "../../agent.ts";
|
|
16
|
+
import { type Agent, type AgentEvent, type Prompt } from "../../agent.ts";
|
|
17
|
+
import type { SessionEvent } from "../../session.ts";
|
|
3
18
|
import { type PiHarnessFactory } from "./harness.ts";
|
|
4
19
|
export type Release = () => void;
|
|
5
20
|
export interface Lease {
|
|
@@ -12,17 +27,55 @@ export declare function classifyRetryable(details: string, signal: {
|
|
|
12
27
|
status?: number;
|
|
13
28
|
code?: unknown;
|
|
14
29
|
}): boolean;
|
|
30
|
+
/**
|
|
31
|
+
* In-stream event mapping — pi events are translated ONCE into the rich `SessionEvent` vocabulary;
|
|
32
|
+
* the SPEC `AgentEvent` stream is a narrow {@link projectAgentEvent} of it (design §6: one
|
|
33
|
+
* translation plus one projection, never two parallel translations). pi events with no session
|
|
34
|
+
* vocabulary yet (turn_start, agent_start, …) are dropped.
|
|
35
|
+
*/
|
|
36
|
+
export declare function toSessionEvent(pe: AgentHarnessEvent, runId: string): SessionEvent | null;
|
|
37
|
+
/** The SPEC projection of the rich stream. Events with no `AgentEvent` counterpart (progress,
|
|
38
|
+
* message boundaries, run boundaries) project to null — the invoke terminal is produced from the
|
|
39
|
+
* resolved message ({@link toTerminal}), not from `run_settled`. */
|
|
40
|
+
export declare function projectAgentEvent(se: SessionEvent): AgentEvent | null;
|
|
41
|
+
/** Live modulation handles for one active run — what the control plane's `dispatch` routes to.
|
|
42
|
+
* Built inside the invoke closure (it owns the harness); registered with the observer at
|
|
43
|
+
* run_started, gone after run_settled. RACE WINDOW (all three commands, symmetric): the run may
|
|
44
|
+
* resolve between the settled-check and the engine call landing — an accepted `abort` can still
|
|
45
|
+
* settle `completed`, and an accepted `steer`/`followUp` can settle without the prompt ever being
|
|
46
|
+
* consumed. Acceptance is not outcome; the settlement is the truth. */
|
|
47
|
+
export interface RunControls {
|
|
48
|
+
steer(prompt: Prompt): Promise<void>;
|
|
49
|
+
followUp(prompt: Prompt): Promise<void>;
|
|
50
|
+
abort(): Promise<void>;
|
|
51
|
+
}
|
|
52
|
+
/** The DATA-plane observation seam: every rich event of every run, pushed as it happens. `run`
|
|
53
|
+
* carries the live {@link RunControls}, attached to the `run_started` event only. A hub
|
|
54
|
+
* (session-control.ts) implements this to serve `events()`/`state()`/`dispatch`; absent = zero
|
|
55
|
+
* overhead. Scope: RUN events only — the hub's own boundary-mutation events (`state_changed`,
|
|
56
|
+
* `compaction_*`) originate in the hub and reach full-vocabulary taps via the hub's `tap` option,
|
|
57
|
+
* not this seam. TRUST BOUNDARY: since Phase 2a this seam hands every wired observer the run's
|
|
58
|
+
* modulation handles — it is the trusted hub seam, not a public fan-out point. Do not wire
|
|
59
|
+
* untrusted taps here; give third parties the read-only `events()` stream instead. */
|
|
60
|
+
export type SessionObserver = (session: string, event: SessionEvent, run?: RunControls) => void;
|
|
15
61
|
/**
|
|
16
62
|
* Terminal mapping, decided by the resolved message's stopReason: pi's prompt() resolves a message
|
|
17
63
|
* with stopReason "error"/"aborted" rather than throwing, so relying on catch alone would miss this
|
|
18
64
|
* entire failure class (violating SPEC MUST 1).
|
|
19
65
|
*/
|
|
20
66
|
export declare function toTerminal(message: AssistantMessage): AgentEvent;
|
|
21
|
-
export declare function errorToTerminal(error: unknown): AgentEvent
|
|
67
|
+
export declare function errorToTerminal(error: unknown): Extract<AgentEvent, {
|
|
68
|
+
type: "failed";
|
|
69
|
+
}>;
|
|
22
70
|
export interface CreatePiAgentFromHarnessOptions {
|
|
23
71
|
harnessFactory: PiHarnessFactory;
|
|
24
72
|
/** Single-writer lease. Defaults to the in-process per-session fail-fast lease. */
|
|
25
73
|
lease?: Lease;
|
|
74
|
+
/** Observation-plane tap (see {@link SessionObserver}). Optional; invoke behavior is identical
|
|
75
|
+
* with or without it — the SPEC stream is a projection of what the observer sees. */
|
|
76
|
+
observer?: SessionObserver;
|
|
77
|
+
/** Working directory exposed to FastAgent-defined tools. Defaults to process.cwd(). */
|
|
78
|
+
cwd?: string;
|
|
26
79
|
}
|
|
27
80
|
/** "From a harness factory": engine wired by the caller; adds only the concurrency/stream shell. */
|
|
28
81
|
export declare function createPiAgentFromHarness(options: CreatePiAgentFromHarnessOptions): Agent;
|