@fastagent-sh/fastagent 0.15.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 +13 -3
- 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 +5 -0
- package/dist/channels/context-buffer.js +5 -0
- package/dist/channels/feishu/context-buffer.d.ts +13 -8
- package/dist/channels/feishu/context-buffer.js +60 -6
- package/dist/channels/feishu/feishu-api.js +4 -1
- package/dist/channels/feishu/feishu.d.ts +0 -12
- package/dist/channels/feishu/feishu.js +143 -54
- package/dist/channels/feishu/invoke-turn.js +35 -18
- package/dist/channels/feishu/model.d.ts +0 -1
- package/dist/channels/feishu/normalize.js +0 -1
- package/dist/channels/feishu/parse.d.ts +21 -7
- package/dist/channels/feishu/parse.js +24 -7
- package/dist/channels/feishu/preview.js +3 -2
- 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 +30 -1
- package/dist/channels/feishu/setup-mode.js +27 -1
- package/dist/channels/github/github.js +8 -1
- package/dist/channels/http.js +1 -1
- 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 +7 -1
- package/dist/channels/preview-kit.js +3 -2
- package/dist/channels/slack/parse.d.ts +16 -1
- package/dist/channels/slack/parse.js +46 -3
- package/dist/channels/slack/preview.d.ts +1 -2
- package/dist/channels/slack/preview.js +68 -24
- package/dist/channels/slack/scaffold/channel.ts +5 -5
- package/dist/channels/slack/slack-api.d.ts +3 -23
- package/dist/channels/slack/slack-api.js +6 -22
- package/dist/channels/slack/slack.d.ts +13 -20
- package/dist/channels/slack/slack.js +95 -50
- package/dist/channels/state.d.ts +11 -4
- package/dist/channels/state.js +19 -12
- package/dist/channels/tasks.d.ts +0 -6
- package/dist/channels/tasks.js +16 -1
- package/dist/channels/telegram/parse.d.ts +0 -7
- package/dist/channels/telegram/parse.js +4 -2
- package/dist/channels/telegram/scaffold/telegram-send.ts +6 -3
- package/dist/channels/telegram/telegram.js +1 -1
- package/dist/channels/text.d.ts +14 -0
- package/dist/channels/text.js +14 -0
- package/dist/channels/thread-participants.d.ts +21 -0
- package/dist/channels/thread-participants.js +132 -0
- package/dist/channels/turn-queue.js +7 -0
- package/dist/cli/add-feishu.d.ts +7 -4
- package/dist/cli/add-feishu.js +57 -37
- package/dist/cli/add-slack.d.ts +2 -1
- package/dist/cli/add-slack.js +6 -11
- package/dist/cli/commands/add.js +50 -51
- package/dist/cli/commands/attach.js +8 -4
- package/dist/cli/commands/chat.js +8 -8
- package/dist/cli/commands/deploy.d.ts +1 -1
- package/dist/cli/commands/deploy.js +323 -77
- package/dist/cli/commands/dev.js +24 -22
- package/dist/cli/commands/fire.js +15 -16
- 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 +9 -6
- package/dist/cli/commands/login.js +35 -21
- package/dist/cli/commands/schedule.js +6 -8
- package/dist/cli/commands/start.js +93 -37
- package/dist/cli/commands/tool.js +22 -18
- package/dist/cli/fail.d.ts +17 -0
- package/dist/cli/fail.js +24 -0
- package/dist/cli/program.js +57 -36
- package/dist/cli/serve.d.ts +26 -6
- package/dist/cli/serve.js +62 -15
- package/dist/cli/shared.d.ts +15 -2
- package/dist/cli/shared.js +30 -18
- 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 +1 -1
- package/dist/deploy/docker/plan.js +12 -17
- package/dist/deploy/fly/plan.d.ts +2 -0
- package/dist/deploy/fly/plan.js +27 -19
- package/dist/deploy/fly/run.d.ts +12 -1
- package/dist/deploy/fly/run.js +36 -2
- package/dist/deploy/preflight.d.ts +11 -5
- package/dist/deploy/preflight.js +217 -65
- package/dist/deploy/railway/plan.d.ts +7 -0
- package/dist/deploy/railway/plan.js +41 -16
- package/dist/deploy/railway/run.d.ts +8 -1
- package/dist/deploy/railway/run.js +7 -2
- package/dist/deploy/runner.d.ts +5 -2
- package/dist/deploy/runner.js +9 -3
- package/dist/dev-supervisor.d.ts +11 -8
- package/dist/dev-supervisor.js +53 -51
- package/dist/engines/pi/auth.d.ts +8 -7
- package/dist/engines/pi/auth.js +12 -10
- package/dist/engines/pi/channel.d.ts +1 -1
- package/dist/engines/pi/channel.js +5 -5
- package/dist/engines/pi/chat.js +2 -2
- package/dist/engines/pi/config.d.ts +6 -46
- package/dist/engines/pi/config.js +21 -108
- package/dist/engines/pi/create.d.ts +8 -8
- package/dist/engines/pi/create.js +13 -12
- package/dist/engines/pi/definition.d.ts +7 -26
- package/dist/engines/pi/definition.js +8 -54
- package/dist/engines/pi/login.d.ts +1 -1
- package/dist/engines/pi/models.d.ts +3 -3
- package/dist/engines/pi/models.js +1 -1
- package/dist/engines/pi/{workspace.d.ts → open.d.ts} +29 -23
- package/dist/engines/pi/{workspace.js → open.js} +27 -29
- package/dist/engines/pi/session-builder.d.ts +2 -2
- package/dist/engines/pi/session-builder.js +11 -11
- package/dist/engines/pi/tool.js +4 -0
- package/dist/env.d.ts +16 -4
- package/dist/env.js +43 -5
- package/dist/host/node.d.ts +2 -2
- 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/paths.d.ts +138 -0
- package/dist/paths.js +326 -0
- package/dist/pi.d.ts +1 -1
- package/dist/pi.js +2 -2
- package/dist/runtime.d.ts +7 -5
- package/dist/runtime.js +2 -2
- package/dist/scaffold/add-channel.d.ts +7 -3
- package/dist/scaffold/add-channel.js +55 -29
- 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 +1 -1
- 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 -0
- package/dist/schedule/wakeups.js +18 -0
- package/dist/tunnel.d.ts +3 -3
- package/dist/tunnel.js +7 -7
- package/package.json +4 -1
- package/dist/channels/feishu/owned-threads.d.ts +0 -7
- package/dist/channels/feishu/owned-threads.js +0 -47
- package/dist/channels/slack/owned-threads.d.ts +0 -6
- package/dist/channels/slack/owned-threads.js +0 -43
- 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
package/dist/schedule/wakeups.js
CHANGED
|
@@ -47,6 +47,18 @@ function isWakeup(e) {
|
|
|
47
47
|
(w.cron === undefined ||
|
|
48
48
|
(typeof w.cron === "string" && cronError(w.cron, typeof w.tz === "string" ? w.tz : undefined) === undefined)));
|
|
49
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* The wake-ALARM sink: notified after EVERY wakeups-store mutation with the new pending set. The
|
|
52
|
+
* AgentCore deployment registers one (schedule/wake-alarm.ts) that mirrors pending wake-ups into
|
|
53
|
+
* one-shot EventBridge schedules — the external clock that makes `wake` reliable on a host with no
|
|
54
|
+
* resident process. Neutral seam: this module knows only "someone wants to observe changes"; a
|
|
55
|
+
* resident host registers nothing and behaves exactly as before. The sink is fire-and-forget and
|
|
56
|
+
* must own its errors; a throw is caught here so a broken alarm never corrupts a store write.
|
|
57
|
+
*/
|
|
58
|
+
let wakeupsSink;
|
|
59
|
+
export function setWakeupsSink(sink) {
|
|
60
|
+
wakeupsSink = sink;
|
|
61
|
+
}
|
|
50
62
|
function load(stateRoot) {
|
|
51
63
|
const v = readScheduleFile(scheduleFile(stateRoot, "wakeups"));
|
|
52
64
|
if (v === undefined)
|
|
@@ -68,6 +80,12 @@ function load(stateRoot) {
|
|
|
68
80
|
}
|
|
69
81
|
function save(stateRoot, wakeups) {
|
|
70
82
|
writeScheduleFile(scheduleFile(stateRoot, "wakeups"), wakeups);
|
|
83
|
+
try {
|
|
84
|
+
wakeupsSink?.(stateRoot, wakeups);
|
|
85
|
+
}
|
|
86
|
+
catch (e) {
|
|
87
|
+
log.error(`[schedule] wake-alarm sink failed (store write is unaffected): ${String(e)}`);
|
|
88
|
+
}
|
|
71
89
|
}
|
|
72
90
|
/** The current pending wake-ups (`fastagent schedule list` uses this). */
|
|
73
91
|
export function listWakeups(stateRoot) {
|
package/dist/tunnel.d.ts
CHANGED
|
@@ -25,9 +25,9 @@ type SpawnCloudflared = (port: number) => ChildProcess;
|
|
|
25
25
|
*/
|
|
26
26
|
export declare function startCloudflareTunnel(port: number, spawnFn?: SpawnCloudflared, attemptTimeoutMs?: number): Promise<Tunnel | undefined>;
|
|
27
27
|
/**
|
|
28
|
-
* Print the public URL and wire up first-party webhook channels found under `dir
|
|
29
|
-
* Feishu/Lark use runtime credentials; onboarded Slack uses its owner-local config
|
|
30
|
-
* manually scaffolded Slack app receive explicit console URLs.
|
|
28
|
+
* Print the public URL and wire up first-party webhook channels found under `dir` (the agent
|
|
29
|
+
* ROOT): Telegram and Feishu/Lark use runtime credentials; onboarded Slack uses its owner-local config
|
|
30
|
+
* token; GitHub and a manually scaffolded Slack app receive explicit console URLs.
|
|
31
31
|
*/
|
|
32
32
|
export declare function announceWebhooks(dir: string, baseUrl: string, opts?: {
|
|
33
33
|
openUrl?: (url: string) => void;
|
package/dist/tunnel.js
CHANGED
|
@@ -12,8 +12,8 @@ import { join } from "node:path";
|
|
|
12
12
|
import { registerFeishuWebhook } from "./channels/feishu/register-webhook.js";
|
|
13
13
|
import { registerSlackWebhook } from "./channels/slack/register-webhook.js";
|
|
14
14
|
import { registerTelegramWebhook } from "./channels/telegram/register-webhook.js";
|
|
15
|
-
import { loadDotEnv } from "./env.js";
|
|
16
|
-
import { resolveStateRoot } from "./
|
|
15
|
+
import { dotEnvPath, loadDotEnv } from "./env.js";
|
|
16
|
+
import { resolveStateRoot } from "./paths.js";
|
|
17
17
|
import { log } from "./log.js";
|
|
18
18
|
// `(?!api\.)`: cloudflared's ERROR lines mention its request endpoint (`https://api.trycloudflare.com/tunnel`,
|
|
19
19
|
// e.g. "failed to request quick Tunnel: Post ... timeout" under a flaky proxy) — without the exclusion a
|
|
@@ -118,9 +118,9 @@ function channelBasenames(dir) {
|
|
|
118
118
|
}
|
|
119
119
|
}
|
|
120
120
|
/**
|
|
121
|
-
* Print the public URL and wire up first-party webhook channels found under `dir
|
|
122
|
-
* Feishu/Lark use runtime credentials; onboarded Slack uses its owner-local config
|
|
123
|
-
* manually scaffolded Slack app receive explicit console URLs.
|
|
121
|
+
* Print the public URL and wire up first-party webhook channels found under `dir` (the agent
|
|
122
|
+
* ROOT): Telegram and Feishu/Lark use runtime credentials; onboarded Slack uses its owner-local config
|
|
123
|
+
* token; GitHub and a manually scaffolded Slack app receive explicit console URLs.
|
|
124
124
|
*/
|
|
125
125
|
export async function announceWebhooks(dir, baseUrl, opts = {}) {
|
|
126
126
|
log.info(`[fastagent] public URL: ${baseUrl}`);
|
|
@@ -133,10 +133,10 @@ export async function announceWebhooks(dir, baseUrl, opts = {}) {
|
|
|
133
133
|
// void-called with no unhandledRejection handler, so a throw here would terminate the process. Warn
|
|
134
134
|
// (surface it, rule 8) and continue best-effort; each registrar then surfaces its own
|
|
135
135
|
// missing-credential guidance. loadDotEnv keeps throwing for the synchronous command callers.
|
|
136
|
-
log.warn(`[fastagent] could not read ${
|
|
136
|
+
log.warn(`[fastagent] could not read ${dotEnvPath(dir)}: ${error.message} — continuing without it`);
|
|
137
137
|
}
|
|
138
138
|
// Serving passes the validated route-channel subset. The basename fallback preserves the public
|
|
139
|
-
// helper's
|
|
139
|
+
// helper's behavior for callers that did not assemble channels first.
|
|
140
140
|
const routeChannels = opts.routeChannels ?? channelBasenames(dir);
|
|
141
141
|
if (routeChannels.length === 0)
|
|
142
142
|
return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fastagent-sh/fastagent",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"description": "Vibe first. Then FastAgent: turn a local agent directory into a live service in your app, on GitHub, Telegram, Slack, or behind any channel.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agent",
|
|
@@ -94,6 +94,9 @@
|
|
|
94
94
|
"lint": "biome check src test && node scripts/check-doc-links.mjs && knip",
|
|
95
95
|
"format": "biome check --write src test",
|
|
96
96
|
"postbuild": "node scripts/copy-scaffold-assets.mjs",
|
|
97
|
+
"release:patch": "node scripts/release.mjs patch",
|
|
98
|
+
"release:minor": "node scripts/release.mjs minor",
|
|
99
|
+
"release:major": "node scripts/release.mjs major",
|
|
97
100
|
"knip": "knip"
|
|
98
101
|
},
|
|
99
102
|
"dependencies": {
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
interface OwnedFeishuThreads {
|
|
2
|
-
has(chatId: string, rootId: string): boolean;
|
|
3
|
-
/** Idempotent, synchronous, pre-ACK persistence. A failed write throws so the platform redelivers. */
|
|
4
|
-
add(chatId: string, rootId: string): void;
|
|
5
|
-
}
|
|
6
|
-
export declare function createOwnedFeishuThreads(path: string, label: string, now?: () => number): OwnedFeishuThreads;
|
|
7
|
-
export {};
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Durable ownership index for group threads the channel created from a top-level summon. Feishu/Lark
|
|
3
|
-
* deliver an ordinary thread continuation as `thread_id + root_id`; the root is the original user
|
|
4
|
-
* message the channel reply-threaded under. Keeping that root across restarts lets the default route
|
|
5
|
-
* admit unmentioned continuations only inside Agent-managed threads, never across the whole group.
|
|
6
|
-
*/
|
|
7
|
-
import { log } from "../../log.js";
|
|
8
|
-
import { loadStateFile, saveStateFile } from "../state.js";
|
|
9
|
-
function isRecord(value) {
|
|
10
|
-
const record = value;
|
|
11
|
-
return (typeof record?.rootId === "string" &&
|
|
12
|
-
typeof record.chatId === "string" &&
|
|
13
|
-
typeof record.createdAt === "number" &&
|
|
14
|
-
Number.isFinite(record.createdAt));
|
|
15
|
-
}
|
|
16
|
-
export function createOwnedFeishuThreads(path, label, now = Date.now) {
|
|
17
|
-
const raw = loadStateFile(path);
|
|
18
|
-
let records = new Map();
|
|
19
|
-
if (raw !== undefined) {
|
|
20
|
-
if (typeof raw === "object" &&
|
|
21
|
-
raw !== null &&
|
|
22
|
-
!Array.isArray(raw) &&
|
|
23
|
-
Object.entries(raw).every(([rootId, record]) => isRecord(record) && record.rootId === rootId)) {
|
|
24
|
-
records = new Map(Object.entries(raw));
|
|
25
|
-
}
|
|
26
|
-
else {
|
|
27
|
-
log.warn(`${label} unexpected shape in ${path} — starting with no managed group threads`);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
return {
|
|
31
|
-
has(chatId, rootId) {
|
|
32
|
-
const record = records.get(rootId);
|
|
33
|
-
return record?.chatId === chatId;
|
|
34
|
-
},
|
|
35
|
-
add(chatId, rootId) {
|
|
36
|
-
const existing = records.get(rootId);
|
|
37
|
-
if (existing?.chatId === chatId)
|
|
38
|
-
return;
|
|
39
|
-
const next = new Map(records);
|
|
40
|
-
next.set(rootId, { rootId, chatId, createdAt: now() });
|
|
41
|
-
// Persist the staged copy first: an IO failure must not leave memory claiming durability the file
|
|
42
|
-
// does not have. The webhook remains un-ACKed and the platform can redeliver after recovery.
|
|
43
|
-
saveStateFile(path, Object.fromEntries(next));
|
|
44
|
-
records = next;
|
|
45
|
-
},
|
|
46
|
-
};
|
|
47
|
-
}
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
interface OwnedSlackThreads {
|
|
2
|
-
has(teamId: string, channelId: string, rootTs: string): boolean;
|
|
3
|
-
add(teamId: string, channelId: string, rootTs: string): void;
|
|
4
|
-
}
|
|
5
|
-
export declare function createOwnedSlackThreads(path: string, label?: string, now?: () => number): OwnedSlackThreads;
|
|
6
|
-
export {};
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
/** Durable roots of Slack group threads entered by an explicit summon. */
|
|
2
|
-
import { log } from "../../log.js";
|
|
3
|
-
import { loadStateFile, saveStateFile } from "../state.js";
|
|
4
|
-
function key(teamId, channelId, rootTs) {
|
|
5
|
-
return `${teamId}:${channelId}:${rootTs}`;
|
|
6
|
-
}
|
|
7
|
-
function isRecord(value) {
|
|
8
|
-
const record = value;
|
|
9
|
-
return (typeof record?.teamId === "string" &&
|
|
10
|
-
typeof record.channelId === "string" &&
|
|
11
|
-
typeof record.rootTs === "string" &&
|
|
12
|
-
typeof record.createdAt === "number" &&
|
|
13
|
-
Number.isFinite(record.createdAt));
|
|
14
|
-
}
|
|
15
|
-
export function createOwnedSlackThreads(path, label = "[slack]", now = Date.now) {
|
|
16
|
-
const raw = loadStateFile(path);
|
|
17
|
-
let records = new Map();
|
|
18
|
-
if (raw !== undefined) {
|
|
19
|
-
if (typeof raw === "object" &&
|
|
20
|
-
raw !== null &&
|
|
21
|
-
!Array.isArray(raw) &&
|
|
22
|
-
Object.entries(raw).every(([id, record]) => isRecord(record) && key(record.teamId, record.channelId, record.rootTs) === id)) {
|
|
23
|
-
records = new Map(Object.entries(raw));
|
|
24
|
-
}
|
|
25
|
-
else {
|
|
26
|
-
log.warn(`${label} unexpected shape in ${path} — starting with no managed Slack threads`);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
return {
|
|
30
|
-
has(teamId, channelId, rootTs) {
|
|
31
|
-
return records.has(key(teamId, channelId, rootTs));
|
|
32
|
-
},
|
|
33
|
-
add(teamId, channelId, rootTs) {
|
|
34
|
-
const id = key(teamId, channelId, rootTs);
|
|
35
|
-
if (records.has(id))
|
|
36
|
-
return;
|
|
37
|
-
const next = new Map(records);
|
|
38
|
-
next.set(id, { teamId, channelId, rootTs, createdAt: now() });
|
|
39
|
-
saveStateFile(path, Object.fromEntries(next));
|
|
40
|
-
records = next;
|
|
41
|
-
},
|
|
42
|
-
};
|
|
43
|
-
}
|
package/dist/workspace.d.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { type Ignore } from "ignore";
|
|
2
|
-
/**
|
|
3
|
-
* Guard that `<workspaceDir>/<name>` resolves INSIDE the workspace — a symlink that escapes (or an
|
|
4
|
-
* absolute target) is rejected, so discovery/scaffolding never reaches out of the definition directory.
|
|
5
|
-
* A missing target is fine (nothing to guard yet).
|
|
6
|
-
*/
|
|
7
|
-
export declare function assertInsideWorkspace(workspaceDir: string, name: string): Promise<void>;
|
|
8
|
-
/** Load `.gitignore` + `.fastagentignore` from `dir` into one matcher (case-sensitive), or undefined if none. */
|
|
9
|
-
export declare function loadRootIgnore(dir: string): Promise<Ignore | undefined>;
|
package/dist/workspace.js
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Workspace path/ignore utilities — engine-neutral (pure fs/path + the `ignore` matcher). Shared by the
|
|
3
|
-
* scaffold (init/add) and the engine's channel discovery, so they live here, not under engines/pi.
|
|
4
|
-
*/
|
|
5
|
-
import { readFile, realpath } from "node:fs/promises";
|
|
6
|
-
import { isAbsolute, join, relative, resolve } from "node:path";
|
|
7
|
-
import ignore, {} from "ignore";
|
|
8
|
-
/**
|
|
9
|
-
* Guard that `<workspaceDir>/<name>` resolves INSIDE the workspace — a symlink that escapes (or an
|
|
10
|
-
* absolute target) is rejected, so discovery/scaffolding never reaches out of the definition directory.
|
|
11
|
-
* A missing target is fine (nothing to guard yet).
|
|
12
|
-
*/
|
|
13
|
-
export async function assertInsideWorkspace(workspaceDir, name) {
|
|
14
|
-
const target = join(workspaceDir, name);
|
|
15
|
-
const real = await realpath(target).catch((e) => {
|
|
16
|
-
if (e.code === "ENOENT" || e.code === "not_found")
|
|
17
|
-
return undefined;
|
|
18
|
-
throw e;
|
|
19
|
-
});
|
|
20
|
-
if (real === undefined)
|
|
21
|
-
return;
|
|
22
|
-
const root = await realpath(workspaceDir).catch(() => resolve(workspaceDir));
|
|
23
|
-
const rel = relative(root, real);
|
|
24
|
-
if (rel === "" || rel.startsWith("..") || isAbsolute(rel)) {
|
|
25
|
-
throw new Error(`${target} resolves outside the workspace (${real}) — it must live inside the definition directory; ` +
|
|
26
|
-
`use a real directory or a symlink that stays within it`);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
/** Load `.gitignore` + `.fastagentignore` from `dir` into one matcher (case-sensitive), or undefined if none. */
|
|
30
|
-
export async function loadRootIgnore(dir) {
|
|
31
|
-
let rules = "";
|
|
32
|
-
for (const name of [".gitignore", ".fastagentignore"]) {
|
|
33
|
-
try {
|
|
34
|
-
rules += `\n${await readFile(join(dir, name), "utf8")}`;
|
|
35
|
-
}
|
|
36
|
-
catch (error) {
|
|
37
|
-
if (error.code !== "ENOENT") {
|
|
38
|
-
throw new Error(`cannot read ${join(dir, name)}: ${error.message}`);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
// ignorecase:false — the library defaults to case-INSENSITIVE, which would make a rule `README.md`
|
|
43
|
-
// also drop an authored `readme.md`. Match git on a case-sensitive filesystem, reproducibly.
|
|
44
|
-
return rules.trim() === "" ? undefined : ignore({ ignorecase: false }).add(rules);
|
|
45
|
-
}
|