@fastagent-sh/fastagent 0.22.0 → 0.23.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/dist/channels/agentcore-protocol.d.ts +2 -1
- package/dist/channels/agentcore-service.js +10 -18
- package/dist/channels/agentcore.js +6 -3
- package/dist/channels/control.js +23 -0
- package/dist/channels/slack/config-api.d.ts +2 -2
- package/dist/channels/slack/config-api.js +2 -2
- package/dist/channels/slack/onboard.js +2 -2
- package/dist/channels/slack/register-webhook.js +2 -2
- package/dist/channels/slack/slack-api.js +4 -4
- package/dist/cli/add-feishu.js +4 -2
- package/dist/cli/auth-view.d.ts +15 -6
- package/dist/cli/auth-view.js +24 -8
- package/dist/cli/box-login.d.ts +33 -0
- package/dist/cli/box-login.js +162 -0
- package/dist/cli/commands/deploy/agentcore.js +48 -11
- package/dist/cli/commands/deploy/docker.js +24 -13
- package/dist/cli/commands/deploy/fly.js +30 -7
- package/dist/cli/commands/deploy/railway.js +21 -7
- package/dist/cli/commands/deploy/shared.d.ts +18 -17
- package/dist/cli/commands/deploy/shared.js +20 -21
- package/dist/cli/commands/destroy.js +2 -2
- package/dist/cli/commands/info.js +9 -6
- package/dist/cli/commands/invoke.js +2 -2
- package/dist/cli/commands/login.d.ts +7 -1
- package/dist/cli/commands/login.js +123 -10
- package/dist/cli/commands/logs.js +2 -2
- package/dist/cli/commands/models.d.ts +4 -1
- package/dist/cli/commands/models.js +37 -4
- package/dist/cli/commands/routine-run.js +2 -2
- package/dist/cli/commands/start.d.ts +0 -5
- package/dist/cli/commands/start.js +3 -13
- package/dist/cli/kernel.d.ts +2 -0
- package/dist/cli/kernel.js +1 -1
- package/dist/cli/login-relay.d.ts +38 -0
- package/dist/cli/login-relay.js +121 -0
- package/dist/cli/program.js +46 -8
- package/dist/cli/shared.d.ts +4 -4
- package/dist/cli/shared.js +24 -30
- package/dist/deploy/agentcore/plan.d.ts +10 -11
- package/dist/deploy/agentcore/plan.js +8 -14
- package/dist/deploy/agentcore/run.d.ts +8 -1
- package/dist/deploy/agentcore/run.js +20 -14
- package/dist/deploy/agentcore/shell.d.ts +25 -0
- package/dist/deploy/agentcore/shell.js +202 -0
- package/dist/deploy/box-shell.d.ts +43 -0
- package/dist/deploy/box-shell.js +28 -0
- package/dist/deploy/build-context.js +16 -7
- package/dist/deploy/channel-ingress.d.ts +20 -1
- package/dist/deploy/channel-ingress.js +34 -7
- package/dist/deploy/container.d.ts +14 -0
- package/dist/deploy/container.js +45 -1
- package/dist/deploy/docker/plan.d.ts +7 -3
- package/dist/deploy/docker/plan.js +9 -14
- package/dist/deploy/docker/run.d.ts +14 -7
- package/dist/deploy/docker/run.js +58 -35
- package/dist/deploy/fly/plan.d.ts +6 -3
- package/dist/deploy/fly/plan.js +6 -8
- package/dist/deploy/fly/run.d.ts +8 -2
- package/dist/deploy/fly/run.js +15 -25
- package/dist/deploy/preflight.d.ts +9 -7
- package/dist/deploy/preflight.js +74 -20
- package/dist/deploy/railway/plan.d.ts +6 -3
- package/dist/deploy/railway/plan.js +7 -9
- package/dist/deploy/railway/run.d.ts +10 -6
- package/dist/deploy/railway/run.js +29 -13
- package/dist/deploy/runner.d.ts +16 -0
- package/dist/deploy/runner.js +31 -0
- package/dist/deploy/secrets.d.ts +4 -13
- package/dist/deploy/secrets.js +8 -27
- package/dist/dev-supervisor.js +5 -2
- package/dist/engines/pi/agent-models.d.ts +70 -0
- package/dist/engines/pi/agent-models.js +93 -0
- package/dist/engines/pi/auth.d.ts +56 -3
- package/dist/engines/pi/auth.js +63 -16
- package/dist/engines/pi/config.d.ts +0 -25
- package/dist/engines/pi/config.js +3 -37
- package/dist/engines/pi/create.d.ts +22 -16
- package/dist/engines/pi/create.js +28 -28
- package/dist/engines/pi/entry-images.d.ts +14 -0
- package/dist/engines/pi/entry-images.js +23 -0
- package/dist/engines/pi/invoke-session.js +26 -0
- package/dist/engines/pi/login.d.ts +20 -13
- package/dist/engines/pi/login.js +19 -12
- package/dist/engines/pi/models.d.ts +115 -36
- package/dist/engines/pi/models.js +218 -48
- package/dist/engines/pi/open.d.ts +42 -11
- package/dist/engines/pi/open.js +53 -21
- package/dist/engines/pi/service.d.ts +2 -2
- package/dist/engines/pi/service.js +1 -0
- package/dist/engines/pi/session-builder.d.ts +3 -2
- package/dist/engines/pi/session-builder.js +28 -58
- package/dist/engines/pi/session-control.js +14 -3
- package/dist/engines/pi/turn-kit.js +11 -5
- package/dist/open-url.d.ts +15 -1
- package/dist/open-url.js +35 -3
- package/dist/paths.d.ts +4 -2
- package/dist/paths.js +6 -2
- package/dist/pi.d.ts +5 -3
- package/dist/pi.js +3 -2
- package/dist/service.d.ts +5 -8
- package/dist/session-remote.js +21 -0
- package/dist/session.d.ts +39 -6
- package/package.json +2 -1
|
@@ -4,84 +4,57 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { existsSync, readFileSync } from "node:fs";
|
|
6
6
|
import { resolve } from "node:path";
|
|
7
|
-
import { NodeExecutionEnv } from "@earendil-works/pi-agent-core/node";
|
|
8
7
|
import { SessionManager, SettingsManager, createAgentSessionRuntime, createAgentSessionServices, getAgentDir, } from "@earendil-works/pi-coding-agent";
|
|
9
8
|
import { bindPiSession, definitionResourceLoaderOptions, reportExtensionErrors } from "./agent-session-factory.js";
|
|
10
9
|
import { readMachine } from "./machine.js";
|
|
11
10
|
import { resolveModel } from "./config.js";
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import { resolveAgentAssembly } from "./open.js";
|
|
11
|
+
import { canonicalPath } from "./definition.js";
|
|
12
|
+
import { reportToolCollisions } from "./report.js";
|
|
13
|
+
import { assembleFront, resolveAgentAssembly } from "./open.js";
|
|
14
|
+
import { resolvePlacement } from "../../paths.js";
|
|
17
15
|
/**
|
|
18
|
-
* Build pi's interactive runtime
|
|
19
|
-
*
|
|
16
|
+
* Build pi's interactive runtime over the SAME assembly `dev`/`start` serve ({@link assembleFront}): model
|
|
17
|
+
* registry, prompt, tools, skills, reasoning effort and credentials all come from it, so what differs is only the
|
|
18
|
+
* session's shape (one resident runtime rather than one session per invoke).
|
|
20
19
|
*/
|
|
21
20
|
export async function buildAgentSessionRuntime(dir, options = {}, sessionManager) {
|
|
22
|
-
async function resolveAssembly(
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const {
|
|
26
|
-
|
|
27
|
-
//
|
|
28
|
-
|
|
29
|
-
// divergence the layer exists to remove.
|
|
30
|
-
const modelRuntime = await createPiModelRuntime({ auth, agentDir, stateRoot });
|
|
31
|
-
const env = new NodeExecutionEnv({ cwd });
|
|
32
|
-
const definition = await loadAgentDefinition(agentDir, { cwd, env });
|
|
33
|
-
reportFindingsIfChanged(definition.dir, definition);
|
|
34
|
-
// Assembly-time, like serving's: this whole function is memoized, so the scan and its warnings happen once per
|
|
35
|
-
// runtime rather than per session rebuild (/new, /resume, fork).
|
|
36
|
-
const extensionPaths = await loadExtensionPaths(agentDir, { cwd, env });
|
|
37
|
-
// base + instructions ONLY — pi appends the skill section and env (cwd) itself (including them here would
|
|
38
|
-
// duplicate them).
|
|
39
|
-
const systemPrompt = assembleSystemPrompt({
|
|
40
|
-
base: piBasePrompt({ tools, persona: definition.persona }),
|
|
41
|
-
contextFiles: definition.contextFiles,
|
|
42
|
-
});
|
|
43
|
-
return {
|
|
44
|
-
modelRuntime,
|
|
45
|
-
modelSpec,
|
|
46
|
-
// Serving honors config.thinkingLevel (config → L2); the resident session must too (fidelity).
|
|
47
|
-
thinkingLevel: config.thinkingLevel,
|
|
48
|
-
definition,
|
|
49
|
-
extensionPaths,
|
|
50
|
-
tools,
|
|
51
|
-
systemPrompt,
|
|
52
|
-
};
|
|
21
|
+
async function resolveAssembly() {
|
|
22
|
+
const front = await resolveAgentAssembly(dir, options);
|
|
23
|
+
reportToolCollisions(front.toolCollisions);
|
|
24
|
+
const { assembly } = await assembleFront(front);
|
|
25
|
+
// Read ONCE per runtime: a rebuild (/new, fork) keeps the startup snapshot, because config and tools stay in the
|
|
26
|
+
// import cache and a half-refreshed agent is worse than a stale one. Restart chat to pick up edits.
|
|
27
|
+
return { modelSpec: front.modelSpec, assembly, definition: await assembly.readDefinition() };
|
|
53
28
|
}
|
|
54
|
-
//
|
|
55
|
-
|
|
29
|
+
// The workspace, like serving's: where tools run and what session records are keyed to. Canonical, because pi's
|
|
30
|
+
// process.cwd() fallback is a realpath and a symlinked workspace would otherwise mismatch it.
|
|
31
|
+
const rootCwd = canonicalPath(resolvePlacement(dir).workspace);
|
|
32
|
+
// pi calls the factory again on /new, /resume, switch, and fork; the assembly is built once.
|
|
56
33
|
let assembly;
|
|
57
34
|
const assemblyFor = (cwd) => {
|
|
58
|
-
// Canonical paths: pi's process.cwd() fallback is a realpath, so a symlinked workspace would otherwise mismatch a
|
|
59
|
-
// non-realpath rootCwd.
|
|
60
35
|
const activeCwd = canonicalPath(cwd);
|
|
61
36
|
if (activeCwd !== rootCwd) {
|
|
62
37
|
throw workspaceScopeError(activeCwd);
|
|
63
38
|
}
|
|
64
|
-
assembly ??= resolveAssembly(
|
|
39
|
+
assembly ??= resolveAssembly();
|
|
65
40
|
return assembly;
|
|
66
41
|
};
|
|
67
42
|
const createRuntime = async ({ cwd, sessionManager, sessionStartEvent }) => {
|
|
68
|
-
const {
|
|
43
|
+
const { modelSpec, assembly, definition } = await assemblyFor(cwd);
|
|
69
44
|
// Per session, NOT memoized with the assembly.
|
|
70
|
-
const machine = await readMachine(cwd);
|
|
45
|
+
const [modelRuntime, machine] = await Promise.all([assembly.modelRuntime(), readMachine(cwd)]);
|
|
71
46
|
const loaded = await createAgentSessionServices({
|
|
72
47
|
cwd,
|
|
73
|
-
// fastagent's models + auth hub replaces pi's default (~/.pi-backed) one — the auth unification point
|
|
74
|
-
// header.
|
|
48
|
+
// fastagent's models + auth hub replaces pi's default (~/.pi-backed) one — the auth unification point.
|
|
75
49
|
modelRuntime,
|
|
76
50
|
// PACKAGELESS, for the loader: fastagent never installs a pi package (machine.ts). Handed pi's own settings, the
|
|
77
51
|
// loader resolves `packages` itself — installing a missing one, and failing chat's start when that fails.
|
|
78
52
|
settingsManager: machine.settingsManager(),
|
|
79
|
-
// Chat's assembly is fixed for the life of the runtime (a rebuild makes a new one), so these read constants.
|
|
80
53
|
resourceLoaderOptions: definitionResourceLoaderOptions({
|
|
81
|
-
systemPrompt: () => systemPrompt,
|
|
54
|
+
systemPrompt: () => definition.systemPrompt,
|
|
82
55
|
skills: () => definition.skills,
|
|
83
56
|
machine,
|
|
84
|
-
extensionPaths,
|
|
57
|
+
extensionPaths: assembly.extensionPaths,
|
|
85
58
|
}),
|
|
86
59
|
});
|
|
87
60
|
// ...while the SESSION keeps pi's own file-backed settings, so `/settings` in the TUI still saves. pi persists by
|
|
@@ -97,13 +70,10 @@ export async function buildAgentSessionRuntime(dir, options = {}, sessionManager
|
|
|
97
70
|
sessionManager,
|
|
98
71
|
sessionStartEvent,
|
|
99
72
|
model,
|
|
100
|
-
//
|
|
101
|
-
//
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
// fastagent.config.ts), unlike the engine knobs around it.
|
|
105
|
-
thinkingLevel: thinkingLevel ?? DEFAULT_THINKING_LEVEL,
|
|
106
|
-
tools,
|
|
73
|
+
// Always set, never left for pi to fill: pi resolves an ABSENT level from the machine's settings, and the effort
|
|
74
|
+
// is the definition's (`thinkingLevel` in fastagent.config.ts), like serving's.
|
|
75
|
+
thinkingLevel: assembly.thinkingLevel,
|
|
76
|
+
tools: assembly.tools,
|
|
107
77
|
// A tool must see one spelling of the workspace, including when opened through a symlink.
|
|
108
78
|
cwd: rootCwd,
|
|
109
79
|
recordActivations: false,
|
|
@@ -12,6 +12,7 @@ import { SESSION_BUSY_CODE } from "../../agent.js";
|
|
|
12
12
|
import { BOUNDARY_COMMAND_FAILED_CODE, INVALID_COMMAND_CODE, isAddressableSession, NO_ACTIVE_RUN_CODE, NOTHING_TO_COMPACT_CODE, NO_SUCH_SESSION_CODE, PARTIAL_UPDATE_CODE, UPDATE_FIELDS, RUN_COMMAND_FAILED_CODE, UNSUPPORTED_CAPABILITY_CODE, } from "../../session.js";
|
|
13
13
|
import { listModels } from "./config.js";
|
|
14
14
|
import { forkProvenance, isNavigable, publishedLeaf } from "./session-markers.js";
|
|
15
|
+
import { entryImages, imageAt } from "./entry-images.js";
|
|
15
16
|
import { startCompaction } from "./agent-session-factory.js";
|
|
16
17
|
import { toRetryScheduledEvent } from "./retry-event.js";
|
|
17
18
|
import { THINKING_LEVELS, activePath, resolveSessionSettings } from "./session-settings.js";
|
|
@@ -79,12 +80,15 @@ function toSessionEntry(entry, parentId) {
|
|
|
79
80
|
};
|
|
80
81
|
if (entry.type === "message") {
|
|
81
82
|
const m = entry.message;
|
|
82
|
-
if (m.role === "user")
|
|
83
|
-
return { ...base, kind: "user", data: { text: contentText(m.content, "") } };
|
|
83
|
+
if (m.role === "user") {
|
|
84
|
+
return { ...base, kind: "user", data: { text: contentText(m.content, ""), ...entryImages(entry.id, m) } };
|
|
85
|
+
}
|
|
84
86
|
if (m.role === "assistant") {
|
|
87
|
+
// `args` is what `tool_started` carries live (pi emits the call's recorded `arguments` there), so a reopened
|
|
88
|
+
// conversation shows the same call a watcher saw.
|
|
85
89
|
const toolCalls = m.content
|
|
86
90
|
.filter((b) => b.type === "toolCall")
|
|
87
|
-
.map((b) => ({ id: b.id ?? "", name: b.name ?? "" }));
|
|
91
|
+
.map((b) => ({ id: b.id ?? "", name: b.name ?? "", args: b.arguments ?? {} }));
|
|
88
92
|
const data = { text: contentText(m.content, "") };
|
|
89
93
|
if (toolCalls.length > 0)
|
|
90
94
|
data.toolCalls = toolCalls;
|
|
@@ -99,6 +103,7 @@ function toSessionEntry(entry, parentId) {
|
|
|
99
103
|
toolName: m.toolName,
|
|
100
104
|
isError: m.isError ?? false,
|
|
101
105
|
text: contentText(m.content, ""),
|
|
106
|
+
...entryImages(entry.id, m),
|
|
102
107
|
},
|
|
103
108
|
};
|
|
104
109
|
}
|
|
@@ -354,6 +359,11 @@ export function createPiSessionControl(options) {
|
|
|
354
359
|
}
|
|
355
360
|
return { entries, ...(leafEntryId ? { leafEntryId } : {}) };
|
|
356
361
|
},
|
|
362
|
+
async image(session, ref) {
|
|
363
|
+
// ponytail: opens (parses) the whole record per image; an index when long image-heavy sessions make it show.
|
|
364
|
+
const opened = await sessions.openIfExists(session);
|
|
365
|
+
return opened ? imageAt(opened, ref) : undefined;
|
|
366
|
+
},
|
|
357
367
|
events(session) {
|
|
358
368
|
// ONE CALL IS ONE SUBSCRIPTION, and `ready` is its boundary: the moment after which nothing emitted for this
|
|
359
369
|
// session can be missed (session.ts, SessionEventStream). A second iteration of the same stream would be a
|
|
@@ -837,6 +847,7 @@ export function createPiSessionControl(options) {
|
|
|
837
847
|
id: session,
|
|
838
848
|
state: () => reads.state(session),
|
|
839
849
|
entries: (options) => reads.entries(session, options),
|
|
850
|
+
image: (ref) => reads.image(session, ref),
|
|
840
851
|
events: () => reads.events(session),
|
|
841
852
|
update: (patch) => updateOf(session, patch),
|
|
842
853
|
steer: (prompt) => runAction(session, { type: "steer", prompt }),
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ABORTED_CODE } from "../../agent.js";
|
|
2
|
+
import { log } from "../../log.js";
|
|
2
3
|
export function inProcessLease() {
|
|
3
4
|
const busy = new Set();
|
|
4
5
|
return {
|
|
@@ -105,11 +106,16 @@ export async function toPiPromptOptions(prompt, delivery) {
|
|
|
105
106
|
return { images: prompt.images.map((img) => ({ type: "image", data: img.data, mimeType: img.mimeType })) };
|
|
106
107
|
}
|
|
107
108
|
const { resizeImage } = await import("@earendil-works/pi-coding-agent");
|
|
108
|
-
const images = await Promise.all(prompt.images.map(async (img) => {
|
|
109
|
-
const resized = await resizeImage(Buffer.from(img.data, "base64"), img.mimeType)
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
109
|
+
const images = await Promise.all(prompt.images.map(async (img, i) => {
|
|
110
|
+
const resized = await resizeImage(Buffer.from(img.data, "base64"), img.mimeType);
|
|
111
|
+
if (resized)
|
|
112
|
+
return { type: "image", data: resized.data, mimeType: resized.mimeType };
|
|
113
|
+
// `null` covers an image it cannot decode or shrink under the limit AND an image backend that did not load,
|
|
114
|
+
// and does not say which. pi's own tool-result path keeps the original in that case rather than drop what the
|
|
115
|
+
// sender attached, and so does this; a provider that refuses it fails the turn with its own message.
|
|
116
|
+
log.warn(`[fastagent] queued image ${i + 1} (${img.mimeType}) could not be resized; sending it as given — ` +
|
|
117
|
+
"the provider may refuse it");
|
|
118
|
+
return { type: "image", data: img.data, mimeType: img.mimeType };
|
|
113
119
|
}));
|
|
114
120
|
return { images };
|
|
115
121
|
}
|
package/dist/open-url.d.ts
CHANGED
|
@@ -1,2 +1,16 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* How to hand `url` to the default browser on `platform`, or undefined when it must not be handed over at all.
|
|
3
|
+
*
|
|
4
|
+
* Only a web page: `https:`, or `http:` on a loopback host. Some callers pass URLs they did not build — a deployed box's
|
|
5
|
+
* login relays its own (`cli/login-relay.ts`), and that box runs code its agent can rewrite — and the platform openers
|
|
6
|
+
* open anything: a local path, a `file:`/`smb:` URL, an argument that starts with `-`. Parsed and re-serialized, so
|
|
7
|
+
* what reaches the opener is what was checked. No shell anywhere: on Windows `start` needs one, and cmd reads the `&`
|
|
8
|
+
* every OAuth URL carries as a command separator, so `rundll32 url.dll,FileProtocolHandler` takes the URL as one
|
|
9
|
+
* argument instead.
|
|
10
|
+
*/
|
|
11
|
+
export declare function browserCommand(url: string, platform: NodeJS.Platform): {
|
|
12
|
+
cmd: string;
|
|
13
|
+
args: string[];
|
|
14
|
+
} | undefined;
|
|
15
|
+
/** Best-effort open a URL in the default browser (callers print it too). A URL that is not a web page is refused, visibly. */
|
|
2
16
|
export declare function openExternalUrl(url: string): void;
|
package/dist/open-url.js
CHANGED
|
@@ -1,6 +1,38 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
-
/**
|
|
2
|
+
/**
|
|
3
|
+
* How to hand `url` to the default browser on `platform`, or undefined when it must not be handed over at all.
|
|
4
|
+
*
|
|
5
|
+
* Only a web page: `https:`, or `http:` on a loopback host. Some callers pass URLs they did not build — a deployed box's
|
|
6
|
+
* login relays its own (`cli/login-relay.ts`), and that box runs code its agent can rewrite — and the platform openers
|
|
7
|
+
* open anything: a local path, a `file:`/`smb:` URL, an argument that starts with `-`. Parsed and re-serialized, so
|
|
8
|
+
* what reaches the opener is what was checked. No shell anywhere: on Windows `start` needs one, and cmd reads the `&`
|
|
9
|
+
* every OAuth URL carries as a command separator, so `rundll32 url.dll,FileProtocolHandler` takes the URL as one
|
|
10
|
+
* argument instead.
|
|
11
|
+
*/
|
|
12
|
+
export function browserCommand(url, platform) {
|
|
13
|
+
let parsed;
|
|
14
|
+
try {
|
|
15
|
+
parsed = new URL(url);
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return undefined; // not a URL: nothing to open
|
|
19
|
+
}
|
|
20
|
+
const loopback = ["localhost", "127.0.0.1", "[::1]"].includes(parsed.hostname);
|
|
21
|
+
if (!(parsed.protocol === "https:" || (parsed.protocol === "http:" && loopback)))
|
|
22
|
+
return undefined;
|
|
23
|
+
const href = parsed.href;
|
|
24
|
+
if (platform === "darwin")
|
|
25
|
+
return { cmd: "open", args: [href] };
|
|
26
|
+
if (platform === "win32")
|
|
27
|
+
return { cmd: "rundll32", args: ["url.dll,FileProtocolHandler", href] };
|
|
28
|
+
return { cmd: "xdg-open", args: [href] };
|
|
29
|
+
}
|
|
30
|
+
/** Best-effort open a URL in the default browser (callers print it too). A URL that is not a web page is refused, visibly. */
|
|
3
31
|
export function openExternalUrl(url) {
|
|
4
|
-
const
|
|
5
|
-
|
|
32
|
+
const command = browserCommand(url, process.platform);
|
|
33
|
+
if (!command) {
|
|
34
|
+
console.error(`[fastagent] not opening ${JSON.stringify(url)}: only an https URL, or http on this machine, opens`);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
spawn(command.cmd, command.args, { stdio: "ignore", detached: true }).on("error", () => { });
|
|
6
38
|
}
|
package/dist/paths.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** The directory name `init` gives an agent (`<workspace>/fastagent/`) unless `--agent-dir` names another. */
|
|
2
2
|
export declare const DEFAULT_AGENT_DIRNAME = "fastagent";
|
|
3
|
-
/** The user-global machinery home
|
|
4
|
-
export declare
|
|
3
|
+
/** The user-global machinery home, `~/.fastagent`: the machine's credentials, models.json and model catalog. */
|
|
4
|
+
export declare function globalHome(): string;
|
|
5
5
|
/**
|
|
6
6
|
* The secrets segment inside an agent dir (or the global home). Every path fastagent resolves — `.env`,
|
|
7
7
|
* `.env.example`, auth.json, the scaffold's write — derives from it, so they cannot drift apart.
|
|
@@ -19,6 +19,8 @@ export declare const STATE_DIRNAME = ".state";
|
|
|
19
19
|
export declare const AGENT_CONFIG_FILE = "fastagent.config.ts";
|
|
20
20
|
/** The optional custom-model-endpoint file inside an agent dir (pi's models.json schema). */
|
|
21
21
|
export declare const AGENT_MODELS_FILE = "models.json";
|
|
22
|
+
/** The agent's model catalog: pi.dev's models newer than pi's bundled catalog, fetched by `models --refresh`. */
|
|
23
|
+
export declare const AGENT_MODEL_CATALOG_FILE = "models-store.json";
|
|
22
24
|
export interface ResolvedPlacement {
|
|
23
25
|
/**
|
|
24
26
|
* The AGENT directory — where the definition (persona.md/skills/tools/channels/routines), the config, and the
|
package/dist/paths.js
CHANGED
|
@@ -8,8 +8,10 @@ import { homedir } from "node:os";
|
|
|
8
8
|
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
9
9
|
/** The directory name `init` gives an agent (`<workspace>/fastagent/`) unless `--agent-dir` names another. */
|
|
10
10
|
export const DEFAULT_AGENT_DIRNAME = "fastagent";
|
|
11
|
-
/** The user-global machinery home
|
|
12
|
-
export
|
|
11
|
+
/** The user-global machinery home, `~/.fastagent`: the machine's credentials, models.json and model catalog. */
|
|
12
|
+
export function globalHome() {
|
|
13
|
+
return join(homedir(), ".fastagent");
|
|
14
|
+
}
|
|
13
15
|
/**
|
|
14
16
|
* The secrets segment inside an agent dir (or the global home). Every path fastagent resolves — `.env`,
|
|
15
17
|
* `.env.example`, auth.json, the scaffold's write — derives from it, so they cannot drift apart.
|
|
@@ -27,6 +29,8 @@ export const STATE_DIRNAME = ".state";
|
|
|
27
29
|
export const AGENT_CONFIG_FILE = "fastagent.config.ts";
|
|
28
30
|
/** The optional custom-model-endpoint file inside an agent dir (pi's models.json schema). */
|
|
29
31
|
export const AGENT_MODELS_FILE = "models.json";
|
|
32
|
+
/** The agent's model catalog: pi.dev's models newer than pi's bundled catalog, fetched by `models --refresh`. */
|
|
33
|
+
export const AGENT_MODEL_CATALOG_FILE = "models-store.json";
|
|
30
34
|
/**
|
|
31
35
|
* The definition paths an agent LOADS content from — the surface a second agent must not be scaffolded inside
|
|
32
36
|
* ({@link agentDefinitionOwner}), because the outer agent would read it as its own skills/tools.
|
package/dist/pi.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ export { z } from "zod";
|
|
|
5
5
|
export type { AgentTool, ExecutionEnv, Skill, SkillDiagnostic } from "@earendil-works/pi-agent-core";
|
|
6
6
|
/** A conversation record, as the tool runtime and the control plane hold it. */
|
|
7
7
|
export type { SessionManager, SessionEntry as PiSessionEntry } from "@earendil-works/pi-coding-agent";
|
|
8
|
-
export { availableModelsFromDir, createPiAgentFromDir, type CreatePiAgentFromDirOptions, } from "./engines/pi/open.ts";
|
|
8
|
+
export { availableModelsFromDir, createPiAgentFromDir, type CreatePiAgentFromDirOptions, refreshModelCatalog, } from "./engines/pi/open.ts";
|
|
9
9
|
export type { LoadedDefinition, SkillCollision } from "./engines/pi/definition.ts";
|
|
10
10
|
export { defineConfig, listModels, resolveModel, type FastagentConfig, } from "./engines/pi/config.ts";
|
|
11
11
|
export type { SessionObserver } from "./engines/pi/turn-kit.ts";
|
|
@@ -14,9 +14,11 @@ export { createPiSessionControl, type CreatePiSessionControlOptions, } from "./e
|
|
|
14
14
|
export { piInMemorySessionRecordStore, piSessionRecordStore, type PiSessionRecordStore, } from "./engines/pi/session-store.ts";
|
|
15
15
|
export type { SessionInheritance } from "./engines/pi/session-inheritance.ts";
|
|
16
16
|
export { GLOBAL_AUTH_PATH, fastagentCredentialStore, type FastagentAuthOptions } from "./engines/pi/auth.ts";
|
|
17
|
-
export { createPiModels,
|
|
17
|
+
export { createPiModels, type CreatePiModelsOptions } from "./engines/pi/agent-models.ts";
|
|
18
|
+
export { probeAuthSource } from "./engines/pi/models.ts";
|
|
18
19
|
export { login, loginOptions, LoginCancelled, type LoginMethod, type LoginOption, type LoginRequest, type LoginResult, } from "./engines/pi/login.ts";
|
|
19
|
-
|
|
20
|
+
/** pi-ai's sign-in types, and the `CredentialStore` a caller supplies as `credentialStore` (with what it holds). */
|
|
21
|
+
export type { AuthEvent, AuthInteraction, AuthPrompt, Credential, CredentialStore } from "@earendil-works/pi-ai";
|
|
20
22
|
export type { Models } from "@earendil-works/pi-ai";
|
|
21
23
|
export type { Provider, ProviderAuth } from "@earendil-works/pi-ai";
|
|
22
24
|
export type { Model } from "@earendil-works/pi-ai";
|
package/dist/pi.js
CHANGED
|
@@ -2,13 +2,14 @@
|
|
|
2
2
|
export { createPiAgent, createPiAgentFromDefinition, } from "./engines/pi/create.js";
|
|
3
3
|
export { defineTool, } from "./engines/pi/tool.js";
|
|
4
4
|
export { z } from "zod";
|
|
5
|
-
export { availableModelsFromDir, createPiAgentFromDir, } from "./engines/pi/open.js";
|
|
5
|
+
export { availableModelsFromDir, createPiAgentFromDir, refreshModelCatalog, } from "./engines/pi/open.js";
|
|
6
6
|
export { defineConfig, listModels, resolveModel, } from "./engines/pi/config.js";
|
|
7
7
|
export { inProcessLease } from "./engines/pi/turn-kit.js";
|
|
8
8
|
export { createPiSessionControl, } from "./engines/pi/session-control.js";
|
|
9
9
|
export { piInMemorySessionRecordStore, piSessionRecordStore, } from "./engines/pi/session-store.js";
|
|
10
10
|
export { GLOBAL_AUTH_PATH, fastagentCredentialStore } from "./engines/pi/auth.js";
|
|
11
|
-
export { createPiModels
|
|
11
|
+
export { createPiModels } from "./engines/pi/agent-models.js";
|
|
12
|
+
export { probeAuthSource } from "./engines/pi/models.js";
|
|
12
13
|
export { login, loginOptions, LoginCancelled, } from "./engines/pi/login.js";
|
|
13
14
|
// The product's one-call assembly: a directory becomes a live service.
|
|
14
15
|
export { createAgentService } from "./engines/pi/service.js";
|
package/dist/service.d.ts
CHANGED
|
@@ -7,9 +7,9 @@ import type { LoadedRoutine } from "./schedule/routine.ts";
|
|
|
7
7
|
export interface ServingSurface {
|
|
8
8
|
/**
|
|
9
9
|
* The routes that AUTHENTICATE NOBODY — `GET /health`, `POST /invoke`, and `POST /run` where the definition
|
|
10
|
-
* declares routines, minus what a channel took over or `http.invoke: false` withheld. Kept APART from the
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* declares routines, minus what a channel took over or `http.invoke: false` withheld. Kept APART from the
|
|
11
|
+
* channels' table rather than derived from it, because three decisions read it (browser reachability, the JSON
|
|
12
|
+
* body gate, what the startup line calls unauthenticated) and must give the same answer.
|
|
13
13
|
*/
|
|
14
14
|
unverified: Routes;
|
|
15
15
|
/**
|
|
@@ -133,11 +133,8 @@ export interface AgentService {
|
|
|
133
133
|
longConnections: string[];
|
|
134
134
|
};
|
|
135
135
|
/**
|
|
136
|
-
* The
|
|
137
|
-
*
|
|
138
|
-
* `routes` instead answers a different question, since a channel may serve one of those paths with a protocol of
|
|
139
|
-
* its own. That mistake ran in both directions here — a try-it curl for a route that 404s, and a warning about an
|
|
140
|
-
* unauthenticated `/invoke` that was really a signature-checked channel.
|
|
136
|
+
* The keys of {@link ServingSurface.unverified}: what a caller reads to describe this surface. Not derivable from
|
|
137
|
+
* `routes`, where a channel may serve one of those paths behind its own signature check.
|
|
141
138
|
*/
|
|
142
139
|
unverifiedRoutes: readonly string[];
|
|
143
140
|
/**
|
package/dist/session-remote.js
CHANGED
|
@@ -271,6 +271,19 @@ export async function connectSessionControl(options) {
|
|
|
271
271
|
id: session,
|
|
272
272
|
state: () => get(`/control/sessions/${id(session)}`),
|
|
273
273
|
entries: (options) => get(`/control/sessions/${id(session)}/entries${options?.since !== undefined ? `?since=${encodeURIComponent(options.since)}` : ""}`, PAYLOAD_TIMEOUT_MS),
|
|
274
|
+
image: async (ref) => {
|
|
275
|
+
const res = await fetchFn(`${base}/control/sessions/${id(session)}/image?ref=${encodeURIComponent(ref)}`, {
|
|
276
|
+
signal: AbortSignal.timeout(PAYLOAD_TIMEOUT_MS),
|
|
277
|
+
});
|
|
278
|
+
if (!res.ok)
|
|
279
|
+
throw await controlError(res);
|
|
280
|
+
if (res.status === 204)
|
|
281
|
+
return undefined;
|
|
282
|
+
return {
|
|
283
|
+
data: toBase64(new Uint8Array(await res.arrayBuffer())),
|
|
284
|
+
mimeType: res.headers.get("content-type") ?? "application/octet-stream",
|
|
285
|
+
};
|
|
286
|
+
},
|
|
274
287
|
events: () => eventsOf(session),
|
|
275
288
|
update: (patch) => write(`/control/sessions/${id(session)}`, "PATCH", patch),
|
|
276
289
|
steer: (prompt) => write(`/control/sessions/${id(session)}/actions`, "POST", { type: "steer", prompt }),
|
|
@@ -404,6 +417,14 @@ export function connectAgent(options) {
|
|
|
404
417
|
},
|
|
405
418
|
};
|
|
406
419
|
}
|
|
420
|
+
/** Base64 without `Buffer` (this module runs in any Fetch runtime), in chunks: spreading megabytes of bytes into one
|
|
421
|
+
* `String.fromCharCode` call overflows the argument limit. */
|
|
422
|
+
function toBase64(bytes) {
|
|
423
|
+
let binary = "";
|
|
424
|
+
for (let i = 0; i < bytes.length; i += 0x8000)
|
|
425
|
+
binary += String.fromCharCode(...bytes.subarray(i, i + 0x8000));
|
|
426
|
+
return btoa(binary);
|
|
427
|
+
}
|
|
407
428
|
/** Minimal SSE reader: yields each `data:` payload; ignores comments (heartbeats) and other fields. */
|
|
408
429
|
async function* sseData(body, watch) {
|
|
409
430
|
const decoder = new TextDecoder();
|
package/dist/session.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Session control plane — the engine-neutral serving extension beside Agent Handler (docs/design/session-control.md).
|
|
3
3
|
*/
|
|
4
|
-
import type { Json, Prompt } from "./agent.ts";
|
|
4
|
+
import type { ImageRef, Json, Prompt } from "./agent.ts";
|
|
5
5
|
export interface SessionControl {
|
|
6
6
|
capabilities(): SessionCapabilities;
|
|
7
7
|
/** The names this agent exposes — what a composer's `/` completion LISTS. */
|
|
@@ -42,6 +42,17 @@ export interface Session {
|
|
|
42
42
|
entries(options?: {
|
|
43
43
|
since?: string;
|
|
44
44
|
}): Promise<SessionEntries>;
|
|
45
|
+
/**
|
|
46
|
+
* The bytes behind an {@link EntryImage.ref} this session published; `undefined` when this session holds no such
|
|
47
|
+
* image. Total, like every read but `list()`.
|
|
48
|
+
*
|
|
49
|
+
* Over the wire, a `mimeType` other than `image/png`, `image/jpeg`, `image/gif` or `image/webp` reads back as
|
|
50
|
+
* `application/octet-stream`: the transport will not serve a sender-declared type it cannot trust (the entry still
|
|
51
|
+
* lists the declared one). pi normalizes an invoke's opening prompt images to those four; a `steer`/`followUp`
|
|
52
|
+
* image and a tool result keep whatever type the sender declared, so `image/jpg`, a type with parameters, or
|
|
53
|
+
* `image/svg+xml` sent that way reads back as `application/octet-stream` remotely.
|
|
54
|
+
*/
|
|
55
|
+
image(ref: string): Promise<ImageRef | undefined>;
|
|
45
56
|
events(): SessionEventStream;
|
|
46
57
|
/** Set durable session properties. */
|
|
47
58
|
update(patch: SessionUpdate): Promise<SessionResult>;
|
|
@@ -232,6 +243,15 @@ export interface SessionEntry {
|
|
|
232
243
|
kind: string;
|
|
233
244
|
data: Json;
|
|
234
245
|
}
|
|
246
|
+
/**
|
|
247
|
+
* One image a message carried, as `data.images` lists it on a `user` or `tool` entry and on `user_message` (absent
|
|
248
|
+
* when there are none). Bytes stay out: `entries()` is read on every open and reconnect, and one screenshot is
|
|
249
|
+
* megabytes. `ref` is opaque; {@link Session.image} of the same session reads the bytes.
|
|
250
|
+
*/
|
|
251
|
+
export type EntryImage = {
|
|
252
|
+
ref: string;
|
|
253
|
+
mimeType: string;
|
|
254
|
+
};
|
|
235
255
|
/**
|
|
236
256
|
* A live event subscription, plus the ONE thing a reconnecting client cannot infer: when it started.
|
|
237
257
|
*
|
|
@@ -280,6 +300,19 @@ export type RunSettledEvent = SessionEvent<"run_settled", {
|
|
|
280
300
|
export type MessageStartedEvent = SessionEvent<"message_started", Record<never, never>> & {
|
|
281
301
|
runId: string;
|
|
282
302
|
};
|
|
303
|
+
/**
|
|
304
|
+
* A user message entered the conversation: the run's opening prompt, a steer or follow-up leaving `pending`, or one an
|
|
305
|
+
* extension sent. It reports after the message is recorded, so `entries()` already holds `entryId` with the same
|
|
306
|
+
* `text` (a slash command expanded) and `images`, and before the answer to it starts. The one live event a backfill
|
|
307
|
+
* can be deduplicated against exactly.
|
|
308
|
+
*/
|
|
309
|
+
export type UserMessageEvent = SessionEvent<"user_message", {
|
|
310
|
+
entryId: string;
|
|
311
|
+
text: string;
|
|
312
|
+
images?: EntryImage[];
|
|
313
|
+
}> & {
|
|
314
|
+
runId: string;
|
|
315
|
+
};
|
|
283
316
|
export type MessageDeltaEvent = SessionEvent<"message_delta", {
|
|
284
317
|
channel: "text" | "thinking";
|
|
285
318
|
delta: string;
|
|
@@ -313,10 +346,10 @@ export type ToolFinishedEvent = SessionEvent<"tool_finished", {
|
|
|
313
346
|
};
|
|
314
347
|
/**
|
|
315
348
|
* The prompts queued on the active run, oldest first, as the engine queued them: plain text as sent, a slash command
|
|
316
|
-
* already expanded (a `/skill:…` becomes the skill's text),
|
|
317
|
-
* enters the conversation as a user message: from then on it is in the record and in
|
|
318
|
-
* can still end the run before the model answers it). Whatever is still listed when
|
|
319
|
-
* the conversation and is dropped with the run.
|
|
349
|
+
* already expanded (a `/skill:…` becomes the skill's text), without its images (its `user_message` carries them). A
|
|
350
|
+
* prompt LEAVES its list when it enters the conversation as a user message: from then on it is in the record and in
|
|
351
|
+
* every later model call (an abort can still end the run before the model answers it). Whatever is still listed when
|
|
352
|
+
* the run settles never entered the conversation and is dropped with the run.
|
|
320
353
|
*/
|
|
321
354
|
export type PendingPrompts = {
|
|
322
355
|
steering: string[];
|
|
@@ -368,4 +401,4 @@ export type ServingErrorEvent = SessionEvent<"serving_error", {
|
|
|
368
401
|
* Every event the in-process observation plane emits today: L0, L1 `queue_changed`, and the L2 events
|
|
369
402
|
* (`state_changed`, `compaction_*`, `retry_scheduled`).
|
|
370
403
|
*/
|
|
371
|
-
export type KnownSessionEvent = RunStartedEvent | RunSettledEvent | MessageStartedEvent | MessageDeltaEvent | MessageFinishedEvent | ToolStartedEvent | ToolProgressEvent | ToolFinishedEvent | QueueChangedEvent | StateChangedEvent | CompactionStartedEvent | CompactionFinishedEvent | RetryScheduledEvent;
|
|
404
|
+
export type KnownSessionEvent = RunStartedEvent | RunSettledEvent | MessageStartedEvent | UserMessageEvent | MessageDeltaEvent | MessageFinishedEvent | ToolStartedEvent | ToolProgressEvent | ToolFinishedEvent | QueueChangedEvent | StateChangedEvent | CompactionStartedEvent | CompactionFinishedEvent | RetryScheduledEvent;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fastagent-sh/fastagent",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.0",
|
|
4
4
|
"description": "Vibe first. Then FastAgent: turn a local agent directory into a live service in your app, on Telegram, Slack, Feishu, or behind any channel.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agent",
|
|
@@ -106,6 +106,7 @@
|
|
|
106
106
|
"@earendil-works/pi-coding-agent": "^0.87.1",
|
|
107
107
|
"@hono/node-server": "^2.1.1",
|
|
108
108
|
"@larksuiteoapi/node-sdk": "^1.71.1",
|
|
109
|
+
"@smithy/signature-v4": "^5.7.4",
|
|
109
110
|
"chokidar": "^5.0.0",
|
|
110
111
|
"commander": "^15.0.0",
|
|
111
112
|
"croner": "^10.0.1",
|