@aigne/afs-cli 1.11.0-beta.10 → 1.11.0-beta.12
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/cli.cjs +3 -2
- package/dist/cli.mjs +3 -2
- package/dist/cli.mjs.map +1 -1
- package/dist/config/afs-loader.cjs +64 -315
- package/dist/config/afs-loader.d.cts.map +1 -1
- package/dist/config/afs-loader.d.mts +2 -1
- package/dist/config/afs-loader.d.mts.map +1 -1
- package/dist/config/afs-loader.mjs +59 -310
- package/dist/config/afs-loader.mjs.map +1 -1
- package/dist/config/credential-helpers.cjs +291 -0
- package/dist/config/credential-helpers.d.mts +2 -0
- package/dist/config/credential-helpers.mjs +288 -0
- package/dist/config/credential-helpers.mjs.map +1 -0
- package/dist/config/loader.cjs +3 -1
- package/dist/config/loader.mjs +3 -2
- package/dist/config/loader.mjs.map +1 -1
- package/dist/config/program-install.cjs +276 -0
- package/dist/config/program-install.d.mts +1 -0
- package/dist/config/program-install.mjs +273 -0
- package/dist/config/program-install.mjs.map +1 -0
- package/dist/core/commands/connect.cjs +53 -0
- package/dist/core/commands/connect.d.mts +2 -0
- package/dist/core/commands/connect.mjs +55 -0
- package/dist/core/commands/connect.mjs.map +1 -0
- package/dist/core/commands/daemon.cjs +207 -0
- package/dist/core/commands/daemon.d.mts +2 -0
- package/dist/core/commands/daemon.mjs +208 -0
- package/dist/core/commands/daemon.mjs.map +1 -0
- package/dist/core/commands/explain.cjs +3 -1
- package/dist/core/commands/explain.mjs +3 -1
- package/dist/core/commands/explain.mjs.map +1 -1
- package/dist/core/commands/explore.cjs +47 -12
- package/dist/core/commands/explore.mjs +47 -12
- package/dist/core/commands/explore.mjs.map +1 -1
- package/dist/core/commands/gen-agent-md.cjs +126 -0
- package/dist/core/commands/gen-agent-md.d.mts +2 -0
- package/dist/core/commands/gen-agent-md.mjs +125 -0
- package/dist/core/commands/gen-agent-md.mjs.map +1 -0
- package/dist/core/commands/index.cjs +13 -1
- package/dist/core/commands/index.d.cts.map +1 -1
- package/dist/core/commands/index.d.mts +6 -0
- package/dist/core/commands/index.d.mts.map +1 -1
- package/dist/core/commands/index.mjs +13 -1
- package/dist/core/commands/index.mjs.map +1 -1
- package/dist/core/commands/install.cjs +91 -0
- package/dist/core/commands/install.d.mts +2 -0
- package/dist/core/commands/install.mjs +92 -0
- package/dist/core/commands/install.mjs.map +1 -0
- package/dist/core/commands/ls.cjs +14 -2
- package/dist/core/commands/ls.d.cts +2 -0
- package/dist/core/commands/ls.d.cts.map +1 -1
- package/dist/core/commands/ls.d.mts +2 -0
- package/dist/core/commands/ls.d.mts.map +1 -1
- package/dist/core/commands/ls.mjs +14 -2
- package/dist/core/commands/ls.mjs.map +1 -1
- package/dist/core/commands/mcp-bridge.cjs +201 -0
- package/dist/core/commands/mcp-bridge.d.mts +2 -0
- package/dist/core/commands/mcp-bridge.mjs +201 -0
- package/dist/core/commands/mcp-bridge.mjs.map +1 -0
- package/dist/core/commands/read.cjs +20 -7
- package/dist/core/commands/read.d.cts +2 -0
- package/dist/core/commands/read.d.cts.map +1 -1
- package/dist/core/commands/read.d.mts +2 -0
- package/dist/core/commands/read.d.mts.map +1 -1
- package/dist/core/commands/read.mjs +20 -7
- package/dist/core/commands/read.mjs.map +1 -1
- package/dist/core/commands/search.cjs +5 -1
- package/dist/core/commands/search.mjs +5 -1
- package/dist/core/commands/search.mjs.map +1 -1
- package/dist/core/commands/stat.mjs.map +1 -1
- package/dist/core/commands/types.d.cts +2 -0
- package/dist/core/commands/types.d.cts.map +1 -1
- package/dist/core/commands/types.d.mts +2 -0
- package/dist/core/commands/types.d.mts.map +1 -1
- package/dist/core/commands/types.mjs.map +1 -1
- package/dist/core/commands/vault.cjs +289 -0
- package/dist/core/commands/vault.d.mts +2 -0
- package/dist/core/commands/vault.mjs +289 -0
- package/dist/core/commands/vault.mjs.map +1 -0
- package/dist/core/commands/write.cjs +19 -6
- package/dist/core/commands/write.d.cts +2 -1
- package/dist/core/commands/write.d.cts.map +1 -1
- package/dist/core/commands/write.d.mts +2 -1
- package/dist/core/commands/write.d.mts.map +1 -1
- package/dist/core/commands/write.mjs +19 -6
- package/dist/core/commands/write.mjs.map +1 -1
- package/dist/core/executor/index.cjs +95 -19
- package/dist/core/executor/index.d.cts +4 -0
- package/dist/core/executor/index.d.cts.map +1 -1
- package/dist/core/executor/index.d.mts +4 -0
- package/dist/core/executor/index.d.mts.map +1 -1
- package/dist/core/executor/index.mjs +95 -19
- package/dist/core/executor/index.mjs.map +1 -1
- package/dist/core/formatters/index.d.mts +1 -0
- package/dist/core/formatters/install.cjs +21 -0
- package/dist/core/formatters/install.d.mts +1 -0
- package/dist/core/formatters/install.mjs +19 -0
- package/dist/core/formatters/install.mjs.map +1 -0
- package/dist/core/formatters/vault.cjs +36 -0
- package/dist/core/formatters/vault.mjs +32 -0
- package/dist/core/formatters/vault.mjs.map +1 -0
- package/dist/credential/index.d.mts +2 -1
- package/dist/credential/mcp-auth-context.cjs +27 -5
- package/dist/credential/mcp-auth-context.mjs +27 -5
- package/dist/credential/mcp-auth-context.mjs.map +1 -1
- package/dist/credential/resolver.cjs +7 -2
- package/dist/credential/resolver.mjs +7 -2
- package/dist/credential/resolver.mjs.map +1 -1
- package/dist/credential/vault-store.d.mts +1 -0
- package/dist/daemon/config-manager.cjs +279 -0
- package/dist/daemon/config-manager.mjs +279 -0
- package/dist/daemon/config-manager.mjs.map +1 -0
- package/dist/daemon/manager.cjs +164 -0
- package/dist/daemon/manager.mjs +157 -0
- package/dist/daemon/manager.mjs.map +1 -0
- package/dist/daemon/server.cjs +220 -0
- package/dist/daemon/server.mjs +220 -0
- package/dist/daemon/server.mjs.map +1 -0
- package/dist/mcp/http-transport.cjs +14 -1
- package/dist/mcp/http-transport.mjs +14 -1
- package/dist/mcp/http-transport.mjs.map +1 -1
- package/dist/mcp/server.cjs +4 -2
- package/dist/mcp/server.mjs +4 -2
- package/dist/mcp/server.mjs.map +1 -1
- package/dist/mcp/tools.cjs +62 -12
- package/dist/mcp/tools.mjs +62 -12
- package/dist/mcp/tools.mjs.map +1 -1
- package/dist/program/daemon-integration.cjs +46 -0
- package/dist/program/daemon-integration.mjs +45 -0
- package/dist/program/daemon-integration.mjs.map +1 -0
- package/dist/program/program-manager.cjs +162 -0
- package/dist/program/program-manager.mjs +162 -0
- package/dist/program/program-manager.mjs.map +1 -0
- package/dist/program/trigger-scanner.cjs +148 -0
- package/dist/program/trigger-scanner.mjs +148 -0
- package/dist/program/trigger-scanner.mjs.map +1 -0
- package/dist/providers/vault/dist/_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.cjs +11 -0
- package/dist/providers/vault/dist/_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.mjs +11 -0
- package/dist/providers/vault/dist/_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.mjs.map +1 -0
- package/dist/providers/vault/dist/encrypted-file.cjs +158 -0
- package/dist/providers/vault/dist/encrypted-file.mjs +153 -0
- package/dist/providers/vault/dist/encrypted-file.mjs.map +1 -0
- package/dist/providers/vault/dist/index.cjs +405 -0
- package/dist/providers/vault/dist/index.mjs +400 -0
- package/dist/providers/vault/dist/index.mjs.map +1 -0
- package/dist/providers/vault/dist/key-resolver.cjs +181 -0
- package/dist/providers/vault/dist/key-resolver.mjs +180 -0
- package/dist/providers/vault/dist/key-resolver.mjs.map +1 -0
- package/dist/repl.cjs +105 -14
- package/dist/repl.d.cts.map +1 -1
- package/dist/repl.d.mts.map +1 -1
- package/dist/repl.mjs +105 -14
- package/dist/repl.mjs.map +1 -1
- package/package.json +29 -22
package/dist/mcp/tools.cjs
CHANGED
|
@@ -9,6 +9,28 @@ const require_path_utils = require('../path-utils.cjs');
|
|
|
9
9
|
let zod = require("zod");
|
|
10
10
|
|
|
11
11
|
//#region src/mcp/tools.ts
|
|
12
|
+
/** Default timeout for MCP tool calls (30 seconds) */
|
|
13
|
+
const TOOL_TIMEOUT_MS = 3e4;
|
|
14
|
+
/** Extended timeout for exec operations (5 minutes) — covers agent-run and other long-running actions */
|
|
15
|
+
const EXEC_TIMEOUT_MS = 3e5;
|
|
16
|
+
/**
|
|
17
|
+
* Wrap an async operation with a timeout.
|
|
18
|
+
* Returns an error result instead of blocking the MCP connection indefinitely.
|
|
19
|
+
*/
|
|
20
|
+
function withTimeout(fn, timeoutMs = TOOL_TIMEOUT_MS) {
|
|
21
|
+
return new Promise((resolve, reject) => {
|
|
22
|
+
const timer = setTimeout(() => {
|
|
23
|
+
reject(/* @__PURE__ */ new Error(`Operation timed out after ${timeoutMs / 1e3}s`));
|
|
24
|
+
}, timeoutMs);
|
|
25
|
+
fn().then((result) => {
|
|
26
|
+
clearTimeout(timer);
|
|
27
|
+
resolve(result);
|
|
28
|
+
}, (error) => {
|
|
29
|
+
clearTimeout(timer);
|
|
30
|
+
reject(error);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
}
|
|
12
34
|
/**
|
|
13
35
|
* Create a safe error result for MCP tools.
|
|
14
36
|
* Strips stack traces and internal details from error messages.
|
|
@@ -35,10 +57,17 @@ function textResult(text) {
|
|
|
35
57
|
* Register all AFS tools on the MCP server.
|
|
36
58
|
*/
|
|
37
59
|
function registerTools(server, afs) {
|
|
38
|
-
server.tool("afs_read", "Read content at an AFS path", {
|
|
60
|
+
server.tool("afs_read", "Read content at an AFS path", {
|
|
61
|
+
path: zod.z.string().describe("AFS path to read, e.g. /modules/fs/README.md or $afs:ns/path"),
|
|
62
|
+
startLine: zod.z.number().int().optional().describe("Start line (1-indexed, inclusive)"),
|
|
63
|
+
endLine: zod.z.number().int().optional().describe("End line (1-indexed, inclusive). -1 for end of file")
|
|
64
|
+
}, async ({ path, startLine, endLine }) => {
|
|
39
65
|
try {
|
|
40
66
|
const canonicalPath = require_path_utils.cliPathToCanonical(path);
|
|
41
|
-
const result = await afs.read(canonicalPath
|
|
67
|
+
const result = await withTimeout(() => afs.read(canonicalPath, {
|
|
68
|
+
startLine,
|
|
69
|
+
endLine
|
|
70
|
+
}));
|
|
42
71
|
if (!result.data) return {
|
|
43
72
|
isError: true,
|
|
44
73
|
content: [{
|
|
@@ -59,11 +88,11 @@ function registerTools(server, afs) {
|
|
|
59
88
|
}, async ({ path, depth, pattern, limit }) => {
|
|
60
89
|
try {
|
|
61
90
|
const canonicalPath = require_path_utils.cliPathToCanonical(path);
|
|
62
|
-
const result = await afs.list(canonicalPath, {
|
|
91
|
+
const result = await withTimeout(() => afs.list(canonicalPath, {
|
|
63
92
|
maxDepth: depth ?? 1,
|
|
64
93
|
pattern,
|
|
65
94
|
limit
|
|
66
|
-
});
|
|
95
|
+
}));
|
|
67
96
|
if (result.data.length === 0 && result.message) return {
|
|
68
97
|
isError: true,
|
|
69
98
|
content: [{
|
|
@@ -78,11 +107,32 @@ function registerTools(server, afs) {
|
|
|
78
107
|
});
|
|
79
108
|
server.tool("afs_write", "Write content to an AFS path", {
|
|
80
109
|
path: zod.z.string().describe("AFS path to write to"),
|
|
81
|
-
content: zod.z.union([zod.z.string(), zod.z.record(zod.z.string(), zod.z.unknown())]).describe("Content to write (string or JSON object)")
|
|
82
|
-
|
|
110
|
+
content: zod.z.union([zod.z.string(), zod.z.record(zod.z.string(), zod.z.unknown())]).optional().describe("Content to write (string or JSON object)"),
|
|
111
|
+
mode: zod.z.enum([
|
|
112
|
+
"replace",
|
|
113
|
+
"append",
|
|
114
|
+
"prepend",
|
|
115
|
+
"patch",
|
|
116
|
+
"create",
|
|
117
|
+
"update"
|
|
118
|
+
]).optional().default("replace").describe("Write mode: replace (default), append, prepend, or patch"),
|
|
119
|
+
patches: zod.z.array(zod.z.object({
|
|
120
|
+
op: zod.z.enum([
|
|
121
|
+
"str_replace",
|
|
122
|
+
"insert_before",
|
|
123
|
+
"insert_after",
|
|
124
|
+
"delete"
|
|
125
|
+
]),
|
|
126
|
+
target: zod.z.string().describe("Unique string to locate in the file"),
|
|
127
|
+
content: zod.z.string().optional().default("").describe("Replacement/insertion content")
|
|
128
|
+
})).optional().describe("Patch operations (only used with mode=patch)")
|
|
129
|
+
}, async ({ path, content, mode, patches }) => {
|
|
83
130
|
try {
|
|
84
131
|
const canonicalPath = require_path_utils.cliPathToCanonical(path);
|
|
85
|
-
|
|
132
|
+
const payload = {};
|
|
133
|
+
if (content !== void 0) payload.content = content;
|
|
134
|
+
if (patches) payload.patches = patches;
|
|
135
|
+
return textResult(require_write.formatWriteOutput(await withTimeout(() => afs.write(canonicalPath, payload, { mode })), "llm"));
|
|
86
136
|
} catch (error) {
|
|
87
137
|
return errorResult(error);
|
|
88
138
|
}
|
|
@@ -93,7 +143,7 @@ function registerTools(server, afs) {
|
|
|
93
143
|
}, async ({ path, recursive }) => {
|
|
94
144
|
try {
|
|
95
145
|
const canonicalPath = require_path_utils.cliPathToCanonical(path);
|
|
96
|
-
return textResult(require_delete.formatDeleteOutput(await afs.delete(canonicalPath, { recursive }), "llm", { path }));
|
|
146
|
+
return textResult(require_delete.formatDeleteOutput(await withTimeout(() => afs.delete(canonicalPath, { recursive })), "llm", { path }));
|
|
97
147
|
} catch (error) {
|
|
98
148
|
return errorResult(error);
|
|
99
149
|
}
|
|
@@ -106,7 +156,7 @@ function registerTools(server, afs) {
|
|
|
106
156
|
}, async ({ path, query, limit }) => {
|
|
107
157
|
try {
|
|
108
158
|
const canonicalPath = require_path_utils.cliPathToCanonical(path);
|
|
109
|
-
return textResult(require_ls.formatLsOutput(await afs.search(canonicalPath, query, { limit }), "llm", { path }));
|
|
159
|
+
return textResult(require_ls.formatLsOutput(await withTimeout(() => afs.search(canonicalPath, query, { limit })), "llm", { path }));
|
|
110
160
|
} catch (error) {
|
|
111
161
|
return errorResult(error);
|
|
112
162
|
}
|
|
@@ -117,7 +167,7 @@ function registerTools(server, afs) {
|
|
|
117
167
|
}, async ({ path, args }) => {
|
|
118
168
|
try {
|
|
119
169
|
const canonicalPath = require_path_utils.cliPathToCanonical(path);
|
|
120
|
-
return textResult(require_exec.formatExecOutput(await afs.exec(canonicalPath, args ?? {}), "llm", { path }));
|
|
170
|
+
return textResult(require_exec.formatExecOutput(await withTimeout(() => afs.exec(canonicalPath, args ?? {}), EXEC_TIMEOUT_MS), "llm", { path }));
|
|
121
171
|
} catch (error) {
|
|
122
172
|
return errorResult(error);
|
|
123
173
|
}
|
|
@@ -125,7 +175,7 @@ function registerTools(server, afs) {
|
|
|
125
175
|
server.tool("afs_stat", "Get metadata for an AFS path", { path: zod.z.string().describe("AFS path to stat") }, async ({ path }) => {
|
|
126
176
|
try {
|
|
127
177
|
const canonicalPath = require_path_utils.cliPathToCanonical(path);
|
|
128
|
-
const result = await afs.stat(canonicalPath);
|
|
178
|
+
const result = await withTimeout(() => afs.stat(canonicalPath));
|
|
129
179
|
if (!result.data) return {
|
|
130
180
|
isError: true,
|
|
131
181
|
content: [{
|
|
@@ -141,7 +191,7 @@ function registerTools(server, afs) {
|
|
|
141
191
|
server.tool("afs_explain", "Get a human-readable explanation for an AFS path or topic", { path: zod.z.string().describe("AFS path or topic to explain") }, async ({ path }) => {
|
|
142
192
|
try {
|
|
143
193
|
const canonicalPath = require_path_utils.cliPathToCanonical(path);
|
|
144
|
-
return textResult((await afs.explain(canonicalPath)).content);
|
|
194
|
+
return textResult((await withTimeout(() => afs.explain(canonicalPath))).content);
|
|
145
195
|
} catch (error) {
|
|
146
196
|
return errorResult(error);
|
|
147
197
|
}
|
package/dist/mcp/tools.mjs
CHANGED
|
@@ -8,6 +8,28 @@ import { cliPathToCanonical } from "../path-utils.mjs";
|
|
|
8
8
|
import { z } from "zod";
|
|
9
9
|
|
|
10
10
|
//#region src/mcp/tools.ts
|
|
11
|
+
/** Default timeout for MCP tool calls (30 seconds) */
|
|
12
|
+
const TOOL_TIMEOUT_MS = 3e4;
|
|
13
|
+
/** Extended timeout for exec operations (5 minutes) — covers agent-run and other long-running actions */
|
|
14
|
+
const EXEC_TIMEOUT_MS = 3e5;
|
|
15
|
+
/**
|
|
16
|
+
* Wrap an async operation with a timeout.
|
|
17
|
+
* Returns an error result instead of blocking the MCP connection indefinitely.
|
|
18
|
+
*/
|
|
19
|
+
function withTimeout(fn, timeoutMs = TOOL_TIMEOUT_MS) {
|
|
20
|
+
return new Promise((resolve, reject) => {
|
|
21
|
+
const timer = setTimeout(() => {
|
|
22
|
+
reject(/* @__PURE__ */ new Error(`Operation timed out after ${timeoutMs / 1e3}s`));
|
|
23
|
+
}, timeoutMs);
|
|
24
|
+
fn().then((result) => {
|
|
25
|
+
clearTimeout(timer);
|
|
26
|
+
resolve(result);
|
|
27
|
+
}, (error) => {
|
|
28
|
+
clearTimeout(timer);
|
|
29
|
+
reject(error);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
}
|
|
11
33
|
/**
|
|
12
34
|
* Create a safe error result for MCP tools.
|
|
13
35
|
* Strips stack traces and internal details from error messages.
|
|
@@ -34,10 +56,17 @@ function textResult(text) {
|
|
|
34
56
|
* Register all AFS tools on the MCP server.
|
|
35
57
|
*/
|
|
36
58
|
function registerTools(server, afs) {
|
|
37
|
-
server.tool("afs_read", "Read content at an AFS path", {
|
|
59
|
+
server.tool("afs_read", "Read content at an AFS path", {
|
|
60
|
+
path: z.string().describe("AFS path to read, e.g. /modules/fs/README.md or $afs:ns/path"),
|
|
61
|
+
startLine: z.number().int().optional().describe("Start line (1-indexed, inclusive)"),
|
|
62
|
+
endLine: z.number().int().optional().describe("End line (1-indexed, inclusive). -1 for end of file")
|
|
63
|
+
}, async ({ path, startLine, endLine }) => {
|
|
38
64
|
try {
|
|
39
65
|
const canonicalPath = cliPathToCanonical(path);
|
|
40
|
-
const result = await afs.read(canonicalPath
|
|
66
|
+
const result = await withTimeout(() => afs.read(canonicalPath, {
|
|
67
|
+
startLine,
|
|
68
|
+
endLine
|
|
69
|
+
}));
|
|
41
70
|
if (!result.data) return {
|
|
42
71
|
isError: true,
|
|
43
72
|
content: [{
|
|
@@ -58,11 +87,11 @@ function registerTools(server, afs) {
|
|
|
58
87
|
}, async ({ path, depth, pattern, limit }) => {
|
|
59
88
|
try {
|
|
60
89
|
const canonicalPath = cliPathToCanonical(path);
|
|
61
|
-
const result = await afs.list(canonicalPath, {
|
|
90
|
+
const result = await withTimeout(() => afs.list(canonicalPath, {
|
|
62
91
|
maxDepth: depth ?? 1,
|
|
63
92
|
pattern,
|
|
64
93
|
limit
|
|
65
|
-
});
|
|
94
|
+
}));
|
|
66
95
|
if (result.data.length === 0 && result.message) return {
|
|
67
96
|
isError: true,
|
|
68
97
|
content: [{
|
|
@@ -77,11 +106,32 @@ function registerTools(server, afs) {
|
|
|
77
106
|
});
|
|
78
107
|
server.tool("afs_write", "Write content to an AFS path", {
|
|
79
108
|
path: z.string().describe("AFS path to write to"),
|
|
80
|
-
content: z.union([z.string(), z.record(z.string(), z.unknown())]).describe("Content to write (string or JSON object)")
|
|
81
|
-
|
|
109
|
+
content: z.union([z.string(), z.record(z.string(), z.unknown())]).optional().describe("Content to write (string or JSON object)"),
|
|
110
|
+
mode: z.enum([
|
|
111
|
+
"replace",
|
|
112
|
+
"append",
|
|
113
|
+
"prepend",
|
|
114
|
+
"patch",
|
|
115
|
+
"create",
|
|
116
|
+
"update"
|
|
117
|
+
]).optional().default("replace").describe("Write mode: replace (default), append, prepend, or patch"),
|
|
118
|
+
patches: z.array(z.object({
|
|
119
|
+
op: z.enum([
|
|
120
|
+
"str_replace",
|
|
121
|
+
"insert_before",
|
|
122
|
+
"insert_after",
|
|
123
|
+
"delete"
|
|
124
|
+
]),
|
|
125
|
+
target: z.string().describe("Unique string to locate in the file"),
|
|
126
|
+
content: z.string().optional().default("").describe("Replacement/insertion content")
|
|
127
|
+
})).optional().describe("Patch operations (only used with mode=patch)")
|
|
128
|
+
}, async ({ path, content, mode, patches }) => {
|
|
82
129
|
try {
|
|
83
130
|
const canonicalPath = cliPathToCanonical(path);
|
|
84
|
-
|
|
131
|
+
const payload = {};
|
|
132
|
+
if (content !== void 0) payload.content = content;
|
|
133
|
+
if (patches) payload.patches = patches;
|
|
134
|
+
return textResult(formatWriteOutput(await withTimeout(() => afs.write(canonicalPath, payload, { mode })), "llm"));
|
|
85
135
|
} catch (error) {
|
|
86
136
|
return errorResult(error);
|
|
87
137
|
}
|
|
@@ -92,7 +142,7 @@ function registerTools(server, afs) {
|
|
|
92
142
|
}, async ({ path, recursive }) => {
|
|
93
143
|
try {
|
|
94
144
|
const canonicalPath = cliPathToCanonical(path);
|
|
95
|
-
return textResult(formatDeleteOutput(await afs.delete(canonicalPath, { recursive }), "llm", { path }));
|
|
145
|
+
return textResult(formatDeleteOutput(await withTimeout(() => afs.delete(canonicalPath, { recursive })), "llm", { path }));
|
|
96
146
|
} catch (error) {
|
|
97
147
|
return errorResult(error);
|
|
98
148
|
}
|
|
@@ -105,7 +155,7 @@ function registerTools(server, afs) {
|
|
|
105
155
|
}, async ({ path, query, limit }) => {
|
|
106
156
|
try {
|
|
107
157
|
const canonicalPath = cliPathToCanonical(path);
|
|
108
|
-
return textResult(formatLsOutput(await afs.search(canonicalPath, query, { limit }), "llm", { path }));
|
|
158
|
+
return textResult(formatLsOutput(await withTimeout(() => afs.search(canonicalPath, query, { limit })), "llm", { path }));
|
|
109
159
|
} catch (error) {
|
|
110
160
|
return errorResult(error);
|
|
111
161
|
}
|
|
@@ -116,7 +166,7 @@ function registerTools(server, afs) {
|
|
|
116
166
|
}, async ({ path, args }) => {
|
|
117
167
|
try {
|
|
118
168
|
const canonicalPath = cliPathToCanonical(path);
|
|
119
|
-
return textResult(formatExecOutput(await afs.exec(canonicalPath, args ?? {}), "llm", { path }));
|
|
169
|
+
return textResult(formatExecOutput(await withTimeout(() => afs.exec(canonicalPath, args ?? {}), EXEC_TIMEOUT_MS), "llm", { path }));
|
|
120
170
|
} catch (error) {
|
|
121
171
|
return errorResult(error);
|
|
122
172
|
}
|
|
@@ -124,7 +174,7 @@ function registerTools(server, afs) {
|
|
|
124
174
|
server.tool("afs_stat", "Get metadata for an AFS path", { path: z.string().describe("AFS path to stat") }, async ({ path }) => {
|
|
125
175
|
try {
|
|
126
176
|
const canonicalPath = cliPathToCanonical(path);
|
|
127
|
-
const result = await afs.stat(canonicalPath);
|
|
177
|
+
const result = await withTimeout(() => afs.stat(canonicalPath));
|
|
128
178
|
if (!result.data) return {
|
|
129
179
|
isError: true,
|
|
130
180
|
content: [{
|
|
@@ -140,7 +190,7 @@ function registerTools(server, afs) {
|
|
|
140
190
|
server.tool("afs_explain", "Get a human-readable explanation for an AFS path or topic", { path: z.string().describe("AFS path or topic to explain") }, async ({ path }) => {
|
|
141
191
|
try {
|
|
142
192
|
const canonicalPath = cliPathToCanonical(path);
|
|
143
|
-
return textResult((await afs.explain(canonicalPath)).content);
|
|
193
|
+
return textResult((await withTimeout(() => afs.explain(canonicalPath))).content);
|
|
144
194
|
} catch (error) {
|
|
145
195
|
return errorResult(error);
|
|
146
196
|
}
|
package/dist/mcp/tools.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tools.mjs","names":[],"sources":["../../src/mcp/tools.ts"],"sourcesContent":["/**\n * AFS MCP Tools Registration\n *\n * Registers all 10 AFS operations as MCP tools.\n */\n\nimport type { AFS } from \"@aigne/afs\";\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { z } from \"zod\";\nimport { formatDeleteOutput } from \"../core/formatters/delete.js\";\nimport { formatExecOutput } from \"../core/formatters/exec.js\";\nimport { formatLsOutput } from \"../core/formatters/ls.js\";\nimport { formatReadOutput } from \"../core/formatters/read.js\";\nimport { formatStatOutput } from \"../core/formatters/stat.js\";\nimport { formatWriteOutput } from \"../core/formatters/write.js\";\nimport { cliPathToCanonical } from \"../path-utils.js\";\n\n/**\n * Create a safe error result for MCP tools.\n * Strips stack traces and internal details from error messages.\n */\nfunction errorResult(error: unknown) {\n const message = error instanceof Error ? error.message : String(error);\n const safeMessage = message.split(\"\\n\")[0] || \"Operation failed\";\n return {\n isError: true as const,\n content: [{ type: \"text\" as const, text: safeMessage }],\n };\n}\n\n/**\n * Create a text result for MCP tools.\n */\nfunction textResult(text: string) {\n return {\n content: [{ type: \"text\" as const, text }],\n };\n}\n\n/**\n * Register all AFS tools on the MCP server.\n */\nexport function registerTools(server: McpServer, afs: AFS): void {\n // afs_read - Read content at a path\n server.tool(\n \"afs_read\",\n \"Read content at an AFS path\",\n { path: z.string().describe(\"AFS path to read, e.g. /modules/fs/README.md or $afs:ns/path\") },\n async ({ path }) => {\n try {\n const canonicalPath = cliPathToCanonical(path);\n const result = await afs.read(canonicalPath);\n if (!result.data) {\n return {\n isError: true as const,\n content: [{ type: \"text\" as const, text: result.message || \"Not found\" }],\n };\n }\n const formatted = formatReadOutput(result, \"llm\", { path });\n return textResult(formatted);\n } catch (error) {\n return errorResult(error);\n }\n },\n );\n\n // afs_list - List directory contents\n server.tool(\n \"afs_list\",\n \"List directory contents at an AFS path\",\n {\n path: z.string().describe(\"AFS path to list\"),\n depth: z.number().optional().describe(\"Recursion depth, default 1\"),\n pattern: z.string().optional().describe(\"Filter pattern, e.g. *.md\"),\n limit: z.number().optional().describe(\"Max entries to return\"),\n },\n async ({ path, depth, pattern, limit }) => {\n try {\n const canonicalPath = cliPathToCanonical(path);\n const result = await afs.list(canonicalPath, {\n maxDepth: depth ?? 1,\n pattern,\n limit,\n });\n if (result.data.length === 0 && result.message) {\n return {\n isError: true as const,\n content: [{ type: \"text\" as const, text: result.message }],\n };\n }\n const formatted = formatLsOutput(result, \"llm\", { path });\n return textResult(formatted);\n } catch (error) {\n return errorResult(error);\n }\n },\n );\n\n // afs_write - Write content to a path\n server.tool(\n \"afs_write\",\n \"Write content to an AFS path\",\n {\n path: z.string().describe(\"AFS path to write to\"),\n content: z\n .union([z.string(), z.record(z.string(), z.unknown())])\n .describe(\"Content to write (string or JSON object)\"),\n },\n async ({ path, content }) => {\n try {\n const canonicalPath = cliPathToCanonical(path);\n const result = await afs.write(canonicalPath, { content });\n const formatted = formatWriteOutput(result, \"llm\");\n return textResult(formatted);\n } catch (error) {\n return errorResult(error);\n }\n },\n );\n\n // afs_delete - Delete a path\n server.tool(\n \"afs_delete\",\n \"Delete a file or directory at an AFS path\",\n {\n path: z.string().describe(\"AFS path to delete\"),\n recursive: z.boolean().optional().describe(\"Delete recursively\"),\n },\n async ({ path, recursive }) => {\n try {\n const canonicalPath = cliPathToCanonical(path);\n const result = await afs.delete(canonicalPath, { recursive });\n const formatted = formatDeleteOutput(result, \"llm\", { path });\n return textResult(formatted);\n } catch (error) {\n return errorResult(error);\n }\n },\n );\n\n // afs_search - Search for content\n server.tool(\n \"afs_search\",\n \"Search for content within an AFS path\",\n {\n path: z.string().describe(\"Base path to search from\"),\n query: z.string().describe(\"Search query\"),\n pattern: z.string().optional().describe(\"File pattern filter\"),\n limit: z.number().optional().describe(\"Max results\"),\n },\n async ({ path, query, limit }) => {\n try {\n const canonicalPath = cliPathToCanonical(path);\n const result = await afs.search(canonicalPath, query, { limit });\n // Search results use list formatter\n const formatted = formatLsOutput(result as any, \"llm\", { path });\n return textResult(formatted);\n } catch (error) {\n return errorResult(error);\n }\n },\n );\n\n // afs_exec - Execute an action\n server.tool(\n \"afs_exec\",\n \"Execute an action. Use afs_list on {path}/.actions to discover available actions and their inputSchema.\",\n {\n path: z.string().describe(\"Action path, e.g. /modules/sqlite/users/.actions/insert\"),\n args: z\n .record(z.string(), z.unknown())\n .optional()\n .describe(\"Action arguments matching the action's inputSchema\"),\n },\n async ({ path, args }) => {\n try {\n const canonicalPath = cliPathToCanonical(path);\n const result = await afs.exec(canonicalPath, args ?? {});\n const formatted = formatExecOutput(result, \"llm\", { path });\n return textResult(formatted);\n } catch (error) {\n return errorResult(error);\n }\n },\n );\n\n // afs_stat - Get path metadata\n server.tool(\n \"afs_stat\",\n \"Get metadata for an AFS path\",\n { path: z.string().describe(\"AFS path to stat\") },\n async ({ path }) => {\n try {\n const canonicalPath = cliPathToCanonical(path);\n const result = await afs.stat(canonicalPath);\n if (!result.data) {\n return {\n isError: true as const,\n content: [{ type: \"text\" as const, text: result.message || \"Not found\" }],\n };\n }\n const formatted = formatStatOutput(result, \"llm\");\n return textResult(formatted);\n } catch (error) {\n return errorResult(error);\n }\n },\n );\n\n // afs_explain - Get human-readable explanation\n server.tool(\n \"afs_explain\",\n \"Get a human-readable explanation for an AFS path or topic\",\n { path: z.string().describe(\"AFS path or topic to explain\") },\n async ({ path }) => {\n try {\n const canonicalPath = cliPathToCanonical(path);\n const result = await afs.explain(canonicalPath);\n // explain returns { format, content } directly\n return textResult(result.content);\n } catch (error) {\n return errorResult(error);\n }\n },\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"tools.mjs","names":[],"sources":["../../src/mcp/tools.ts"],"sourcesContent":["/**\n * AFS MCP Tools Registration\n *\n * Registers AFS operations as MCP tools.\n */\n\nimport type { AFS } from \"@aigne/afs\";\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { z } from \"zod\";\nimport { formatDeleteOutput } from \"../core/formatters/delete.js\";\nimport { formatExecOutput } from \"../core/formatters/exec.js\";\nimport { formatLsOutput } from \"../core/formatters/ls.js\";\nimport { formatReadOutput } from \"../core/formatters/read.js\";\nimport { formatStatOutput } from \"../core/formatters/stat.js\";\nimport { formatWriteOutput } from \"../core/formatters/write.js\";\nimport { cliPathToCanonical } from \"../path-utils.js\";\n\n/** Default timeout for MCP tool calls (30 seconds) */\nconst TOOL_TIMEOUT_MS = 30_000;\n\n/** Extended timeout for exec operations (5 minutes) — covers agent-run and other long-running actions */\nconst EXEC_TIMEOUT_MS = 300_000;\n\n/**\n * Wrap an async operation with a timeout.\n * Returns an error result instead of blocking the MCP connection indefinitely.\n */\nfunction withTimeout<T>(fn: () => Promise<T>, timeoutMs = TOOL_TIMEOUT_MS): Promise<T> {\n return new Promise<T>((resolve, reject) => {\n const timer = setTimeout(() => {\n reject(new Error(`Operation timed out after ${timeoutMs / 1000}s`));\n }, timeoutMs);\n\n fn().then(\n (result) => {\n clearTimeout(timer);\n resolve(result);\n },\n (error) => {\n clearTimeout(timer);\n reject(error);\n },\n );\n });\n}\n\n/**\n * Create a safe error result for MCP tools.\n * Strips stack traces and internal details from error messages.\n */\nfunction errorResult(error: unknown) {\n const message = error instanceof Error ? error.message : String(error);\n const safeMessage = message.split(\"\\n\")[0] || \"Operation failed\";\n return {\n isError: true as const,\n content: [{ type: \"text\" as const, text: safeMessage }],\n };\n}\n\n/**\n * Create a text result for MCP tools.\n */\nfunction textResult(text: string) {\n return {\n content: [{ type: \"text\" as const, text }],\n };\n}\n\n/**\n * Register all AFS tools on the MCP server.\n */\nexport function registerTools(server: McpServer, afs: AFS): void {\n // afs_read - Read content at a path\n server.tool(\n \"afs_read\",\n \"Read content at an AFS path\",\n {\n path: z.string().describe(\"AFS path to read, e.g. /modules/fs/README.md or $afs:ns/path\"),\n startLine: z.number().int().optional().describe(\"Start line (1-indexed, inclusive)\"),\n endLine: z\n .number()\n .int()\n .optional()\n .describe(\"End line (1-indexed, inclusive). -1 for end of file\"),\n },\n async ({ path, startLine, endLine }) => {\n try {\n const canonicalPath = cliPathToCanonical(path);\n const result = await withTimeout(() => afs.read(canonicalPath, { startLine, endLine }));\n if (!result.data) {\n return {\n isError: true as const,\n content: [{ type: \"text\" as const, text: result.message || \"Not found\" }],\n };\n }\n const formatted = formatReadOutput(result, \"llm\", { path });\n return textResult(formatted);\n } catch (error) {\n return errorResult(error);\n }\n },\n );\n\n // afs_list - List directory contents\n server.tool(\n \"afs_list\",\n \"List directory contents at an AFS path\",\n {\n path: z.string().describe(\"AFS path to list\"),\n depth: z.number().optional().describe(\"Recursion depth, default 1\"),\n pattern: z.string().optional().describe(\"Filter pattern, e.g. *.md\"),\n limit: z.number().optional().describe(\"Max entries to return\"),\n },\n async ({ path, depth, pattern, limit }) => {\n try {\n const canonicalPath = cliPathToCanonical(path);\n const result = await withTimeout(() =>\n afs.list(canonicalPath, {\n maxDepth: depth ?? 1,\n pattern,\n limit,\n }),\n );\n if (result.data.length === 0 && result.message) {\n return {\n isError: true as const,\n content: [{ type: \"text\" as const, text: result.message }],\n };\n }\n const formatted = formatLsOutput(result, \"llm\", { path });\n return textResult(formatted);\n } catch (error) {\n return errorResult(error);\n }\n },\n );\n\n // afs_write - Write content to a path\n server.tool(\n \"afs_write\",\n \"Write content to an AFS path\",\n {\n path: z.string().describe(\"AFS path to write to\"),\n content: z\n .union([z.string(), z.record(z.string(), z.unknown())])\n .optional()\n .describe(\"Content to write (string or JSON object)\"),\n mode: z\n .enum([\"replace\", \"append\", \"prepend\", \"patch\", \"create\", \"update\"])\n .optional()\n .default(\"replace\")\n .describe(\"Write mode: replace (default), append, prepend, or patch\"),\n patches: z\n .array(\n z.object({\n op: z.enum([\"str_replace\", \"insert_before\", \"insert_after\", \"delete\"]),\n target: z.string().describe(\"Unique string to locate in the file\"),\n content: z.string().optional().default(\"\").describe(\"Replacement/insertion content\"),\n }),\n )\n .optional()\n .describe(\"Patch operations (only used with mode=patch)\"),\n },\n async ({ path, content, mode, patches }) => {\n try {\n const canonicalPath = cliPathToCanonical(path);\n const payload: Record<string, unknown> = {};\n if (content !== undefined) payload.content = content;\n if (patches) payload.patches = patches;\n const result = await withTimeout(() => afs.write(canonicalPath, payload, { mode }));\n const formatted = formatWriteOutput(result, \"llm\");\n return textResult(formatted);\n } catch (error) {\n return errorResult(error);\n }\n },\n );\n\n // afs_delete - Delete a path\n server.tool(\n \"afs_delete\",\n \"Delete a file or directory at an AFS path\",\n {\n path: z.string().describe(\"AFS path to delete\"),\n recursive: z.boolean().optional().describe(\"Delete recursively\"),\n },\n async ({ path, recursive }) => {\n try {\n const canonicalPath = cliPathToCanonical(path);\n const result = await withTimeout(() => afs.delete(canonicalPath, { recursive }));\n const formatted = formatDeleteOutput(result, \"llm\", { path });\n return textResult(formatted);\n } catch (error) {\n return errorResult(error);\n }\n },\n );\n\n // afs_search - Search for content\n server.tool(\n \"afs_search\",\n \"Search for content within an AFS path\",\n {\n path: z.string().describe(\"Base path to search from\"),\n query: z.string().describe(\"Search query\"),\n pattern: z.string().optional().describe(\"File pattern filter\"),\n limit: z.number().optional().describe(\"Max results\"),\n },\n async ({ path, query, limit }) => {\n try {\n const canonicalPath = cliPathToCanonical(path);\n const result = await withTimeout(() => afs.search(canonicalPath, query, { limit }));\n // Search results use list formatter\n const formatted = formatLsOutput(result as any, \"llm\", { path });\n return textResult(formatted);\n } catch (error) {\n return errorResult(error);\n }\n },\n );\n\n // afs_exec - Execute an action\n server.tool(\n \"afs_exec\",\n \"Execute an action. Use afs_list on {path}/.actions to discover available actions and their inputSchema.\",\n {\n path: z.string().describe(\"Action path, e.g. /modules/sqlite/users/.actions/insert\"),\n args: z\n .record(z.string(), z.unknown())\n .optional()\n .describe(\"Action arguments matching the action's inputSchema\"),\n },\n async ({ path, args }) => {\n try {\n const canonicalPath = cliPathToCanonical(path);\n const result = await withTimeout(\n () => afs.exec(canonicalPath, args ?? {}),\n EXEC_TIMEOUT_MS,\n );\n const formatted = formatExecOutput(result, \"llm\", { path });\n return textResult(formatted);\n } catch (error) {\n return errorResult(error);\n }\n },\n );\n\n // afs_stat - Get path metadata\n server.tool(\n \"afs_stat\",\n \"Get metadata for an AFS path\",\n { path: z.string().describe(\"AFS path to stat\") },\n async ({ path }) => {\n try {\n const canonicalPath = cliPathToCanonical(path);\n const result = await withTimeout(() => afs.stat(canonicalPath));\n if (!result.data) {\n return {\n isError: true as const,\n content: [{ type: \"text\" as const, text: result.message || \"Not found\" }],\n };\n }\n const formatted = formatStatOutput(result, \"llm\");\n return textResult(formatted);\n } catch (error) {\n return errorResult(error);\n }\n },\n );\n\n // afs_explain - Get human-readable explanation\n server.tool(\n \"afs_explain\",\n \"Get a human-readable explanation for an AFS path or topic\",\n { path: z.string().describe(\"AFS path or topic to explain\") },\n async ({ path }) => {\n try {\n const canonicalPath = cliPathToCanonical(path);\n const result = await withTimeout(() => afs.explain(canonicalPath));\n // explain returns { format, content } directly\n return textResult(result.content);\n } catch (error) {\n return errorResult(error);\n }\n },\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
|
|
2
|
+
//#region src/program/daemon-integration.ts
|
|
3
|
+
/**
|
|
4
|
+
* Handle /api/programs/* HTTP requests.
|
|
5
|
+
*
|
|
6
|
+
* Routes:
|
|
7
|
+
* - GET /api/programs → list activated programs
|
|
8
|
+
* - POST /api/programs/reload → reload all programs
|
|
9
|
+
*/
|
|
10
|
+
async function handleProgramsAPI(request, programManager) {
|
|
11
|
+
const url = new URL(request.url);
|
|
12
|
+
const method = request.method;
|
|
13
|
+
const path = url.pathname;
|
|
14
|
+
if (method === "GET" && path === "/api/programs") {
|
|
15
|
+
const programs = programManager.getActivatedPrograms();
|
|
16
|
+
return Response.json({ programs });
|
|
17
|
+
}
|
|
18
|
+
if (method === "POST" && path === "/api/programs/reload") try {
|
|
19
|
+
await programManager.reload();
|
|
20
|
+
return Response.json({ ok: true });
|
|
21
|
+
} catch (err) {
|
|
22
|
+
const safeMessage = (err instanceof Error ? err.message : String(err)).replace(/\/[^\s"']+/g, "[path]");
|
|
23
|
+
return Response.json({ error: safeMessage }, { status: 500 });
|
|
24
|
+
}
|
|
25
|
+
return Response.json({ error: "Not found" }, { status: 404 });
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Notify the running daemon to reload programs.
|
|
29
|
+
* Silently skips if daemon is not running.
|
|
30
|
+
* Silently swallows errors (best-effort notification).
|
|
31
|
+
*/
|
|
32
|
+
async function notifyDaemonReload(options) {
|
|
33
|
+
const fetchFn = options.fetch ?? globalThis.fetch;
|
|
34
|
+
try {
|
|
35
|
+
const status = await options.getDaemonStatus();
|
|
36
|
+
if (!status) return;
|
|
37
|
+
await fetchFn(`${status.url}/api/programs/reload`, {
|
|
38
|
+
method: "POST",
|
|
39
|
+
headers: { "Content-Type": "application/json" }
|
|
40
|
+
});
|
|
41
|
+
} catch {}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
//#endregion
|
|
45
|
+
exports.handleProgramsAPI = handleProgramsAPI;
|
|
46
|
+
exports.notifyDaemonReload = notifyDaemonReload;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
//#region src/program/daemon-integration.ts
|
|
2
|
+
/**
|
|
3
|
+
* Handle /api/programs/* HTTP requests.
|
|
4
|
+
*
|
|
5
|
+
* Routes:
|
|
6
|
+
* - GET /api/programs → list activated programs
|
|
7
|
+
* - POST /api/programs/reload → reload all programs
|
|
8
|
+
*/
|
|
9
|
+
async function handleProgramsAPI(request, programManager) {
|
|
10
|
+
const url = new URL(request.url);
|
|
11
|
+
const method = request.method;
|
|
12
|
+
const path = url.pathname;
|
|
13
|
+
if (method === "GET" && path === "/api/programs") {
|
|
14
|
+
const programs = programManager.getActivatedPrograms();
|
|
15
|
+
return Response.json({ programs });
|
|
16
|
+
}
|
|
17
|
+
if (method === "POST" && path === "/api/programs/reload") try {
|
|
18
|
+
await programManager.reload();
|
|
19
|
+
return Response.json({ ok: true });
|
|
20
|
+
} catch (err) {
|
|
21
|
+
const safeMessage = (err instanceof Error ? err.message : String(err)).replace(/\/[^\s"']+/g, "[path]");
|
|
22
|
+
return Response.json({ error: safeMessage }, { status: 500 });
|
|
23
|
+
}
|
|
24
|
+
return Response.json({ error: "Not found" }, { status: 404 });
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Notify the running daemon to reload programs.
|
|
28
|
+
* Silently skips if daemon is not running.
|
|
29
|
+
* Silently swallows errors (best-effort notification).
|
|
30
|
+
*/
|
|
31
|
+
async function notifyDaemonReload(options) {
|
|
32
|
+
const fetchFn = options.fetch ?? globalThis.fetch;
|
|
33
|
+
try {
|
|
34
|
+
const status = await options.getDaemonStatus();
|
|
35
|
+
if (!status) return;
|
|
36
|
+
await fetchFn(`${status.url}/api/programs/reload`, {
|
|
37
|
+
method: "POST",
|
|
38
|
+
headers: { "Content-Type": "application/json" }
|
|
39
|
+
});
|
|
40
|
+
} catch {}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
//#endregion
|
|
44
|
+
export { handleProgramsAPI, notifyDaemonReload };
|
|
45
|
+
//# sourceMappingURL=daemon-integration.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"daemon-integration.mjs","names":[],"sources":["../../src/program/daemon-integration.ts"],"sourcesContent":["/**\n * Daemon Integration for Program Activation\n *\n * Provides:\n * - HTTP API handler for /api/programs/* routes\n * - Daemon reload notification for install/uninstall hooks\n */\n\nimport type { ProgramManager } from \"./program-manager.js\";\n\n// ─── HTTP API Handler ───────────────────────────────────────────────────────\n\n/**\n * Handle /api/programs/* HTTP requests.\n *\n * Routes:\n * - GET /api/programs → list activated programs\n * - POST /api/programs/reload → reload all programs\n */\nexport async function handleProgramsAPI(\n request: Request,\n programManager: ProgramManager,\n): Promise<Response> {\n const url = new URL(request.url);\n const method = request.method;\n const path = url.pathname;\n\n // GET /api/programs — list activated programs\n if (method === \"GET\" && path === \"/api/programs\") {\n const programs = programManager.getActivatedPrograms();\n return Response.json({ programs });\n }\n\n // POST /api/programs/reload — trigger reload\n if (method === \"POST\" && path === \"/api/programs/reload\") {\n try {\n await programManager.reload();\n return Response.json({ ok: true });\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n // Sanitize error: strip filesystem paths\n const safeMessage = message.replace(/\\/[^\\s\"']+/g, \"[path]\");\n return Response.json({ error: safeMessage }, { status: 500 });\n }\n }\n\n // Unknown route\n return Response.json({ error: \"Not found\" }, { status: 404 });\n}\n\n// ─── Daemon Reload Notification ─────────────────────────────────────────────\n\nexport interface NotifyDaemonReloadOptions {\n /** Check if daemon is running. Returns null if not running. */\n getDaemonStatus: () => Promise<{ pid: number; port: number; url: string } | null>;\n /** HTTP fetch function (injectable for testing). Default: global fetch. */\n fetch?: (url: string, init?: RequestInit) => Promise<Response>;\n}\n\n/**\n * Notify the running daemon to reload programs.\n * Silently skips if daemon is not running.\n * Silently swallows errors (best-effort notification).\n */\nexport async function notifyDaemonReload(options: NotifyDaemonReloadOptions): Promise<void> {\n const fetchFn = options.fetch ?? globalThis.fetch;\n\n try {\n const status = await options.getDaemonStatus();\n if (!status) return; // Daemon not running\n\n await fetchFn(`${status.url}/api/programs/reload`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n });\n } catch {\n // Best-effort — swallow errors\n }\n}\n"],"mappings":";;;;;;;;AAmBA,eAAsB,kBACpB,SACA,gBACmB;CACnB,MAAM,MAAM,IAAI,IAAI,QAAQ,IAAI;CAChC,MAAM,SAAS,QAAQ;CACvB,MAAM,OAAO,IAAI;AAGjB,KAAI,WAAW,SAAS,SAAS,iBAAiB;EAChD,MAAM,WAAW,eAAe,sBAAsB;AACtD,SAAO,SAAS,KAAK,EAAE,UAAU,CAAC;;AAIpC,KAAI,WAAW,UAAU,SAAS,uBAChC,KAAI;AACF,QAAM,eAAe,QAAQ;AAC7B,SAAO,SAAS,KAAK,EAAE,IAAI,MAAM,CAAC;UAC3B,KAAK;EAGZ,MAAM,eAFU,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,EAEpC,QAAQ,eAAe,SAAS;AAC5D,SAAO,SAAS,KAAK,EAAE,OAAO,aAAa,EAAE,EAAE,QAAQ,KAAK,CAAC;;AAKjE,QAAO,SAAS,KAAK,EAAE,OAAO,aAAa,EAAE,EAAE,QAAQ,KAAK,CAAC;;;;;;;AAiB/D,eAAsB,mBAAmB,SAAmD;CAC1F,MAAM,UAAU,QAAQ,SAAS,WAAW;AAE5C,KAAI;EACF,MAAM,SAAS,MAAM,QAAQ,iBAAiB;AAC9C,MAAI,CAAC,OAAQ;AAEb,QAAM,QAAQ,GAAG,OAAO,IAAI,uBAAuB;GACjD,QAAQ;GACR,SAAS,EAAE,gBAAgB,oBAAoB;GAChD,CAAC;SACI"}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
const require_rolldown_runtime = require('../_virtual/rolldown_runtime.cjs');
|
|
2
|
+
let ufo = require("ufo");
|
|
3
|
+
let _aigne_afs = require("@aigne/afs");
|
|
4
|
+
|
|
5
|
+
//#region src/program/program-manager.ts
|
|
6
|
+
/**
|
|
7
|
+
* ProgramManager — manages activation and deactivation of installed programs.
|
|
8
|
+
*
|
|
9
|
+
* Activation creates a persistent Runtime AFS, registers EventBus subscriptions
|
|
10
|
+
* and cron jobs for trigger-bearing scripts. Deactivation cleans up everything.
|
|
11
|
+
*/
|
|
12
|
+
var ProgramManager = class {
|
|
13
|
+
activated = /* @__PURE__ */ new Map();
|
|
14
|
+
reloadLock = null;
|
|
15
|
+
deps;
|
|
16
|
+
constructor(deps) {
|
|
17
|
+
this.deps = deps;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Activate a single program by ID.
|
|
21
|
+
* Creates Runtime AFS, registers event/cron subscriptions.
|
|
22
|
+
* If already activated, deactivates first then re-activates.
|
|
23
|
+
*/
|
|
24
|
+
async activate(programId) {
|
|
25
|
+
const program = (await this.deps.listPrograms()).find((p) => p.id === programId);
|
|
26
|
+
if (!program) throw new Error(`Program "${programId}" not found in installed programs`);
|
|
27
|
+
if (this.activated.has(programId)) await this.deactivate(programId);
|
|
28
|
+
const triggerInfo = await this.deps.scanTriggers(program.installPath);
|
|
29
|
+
if (!triggerInfo || triggerInfo.triggers.length === 0) return;
|
|
30
|
+
const createAFS = this.deps.createProgramAFS ?? _aigne_afs.createProgramAFS;
|
|
31
|
+
const dataPath = this.deps.dataDir(programId);
|
|
32
|
+
const { afs, manifest, ownedProviders } = await createAFS(program.mountPath, dataPath, this.deps.globalAFS, this.deps.createProvider ? { createProvider: this.deps.createProvider } : void 0);
|
|
33
|
+
const eventUnsubs = [];
|
|
34
|
+
const cronHandles = [];
|
|
35
|
+
for (const trigger of triggerInfo.triggers) if (trigger.trigger.kind === "event" && trigger.trigger.path) {
|
|
36
|
+
if (afs.subscribe) {
|
|
37
|
+
const unsub = afs.subscribe({ path: trigger.trigger.path }, (event) => {
|
|
38
|
+
this.deps.onTrigger?.(programId, trigger.scriptPath, trigger.jobName, event);
|
|
39
|
+
this.executeTriggerJob(programId, afs, trigger, event);
|
|
40
|
+
});
|
|
41
|
+
eventUnsubs.push(unsub);
|
|
42
|
+
}
|
|
43
|
+
} else if (trigger.trigger.kind === "cron" && trigger.trigger.expression) {
|
|
44
|
+
if (this.deps.createCron) {
|
|
45
|
+
const handle = this.deps.createCron(trigger.trigger.expression, () => {
|
|
46
|
+
const cronEvent = {
|
|
47
|
+
type: "cron",
|
|
48
|
+
path: `/cron/${programId}/${trigger.jobName}`,
|
|
49
|
+
source: "program-manager",
|
|
50
|
+
timestamp: Date.now()
|
|
51
|
+
};
|
|
52
|
+
this.deps.onTrigger?.(programId, trigger.scriptPath, trigger.jobName, cronEvent);
|
|
53
|
+
this.executeTriggerJob(programId, afs, trigger, cronEvent);
|
|
54
|
+
});
|
|
55
|
+
cronHandles.push(handle);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
this.activated.set(programId, {
|
|
59
|
+
manifest,
|
|
60
|
+
runtimeAFS: afs,
|
|
61
|
+
ownedProviders,
|
|
62
|
+
eventUnsubs,
|
|
63
|
+
cronHandles
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Deactivate a program by ID.
|
|
68
|
+
* Cancels subscriptions, stops cron jobs, closes owned providers.
|
|
69
|
+
* No-op if program is not activated.
|
|
70
|
+
*/
|
|
71
|
+
async deactivate(programId) {
|
|
72
|
+
const state = this.activated.get(programId);
|
|
73
|
+
if (!state) return;
|
|
74
|
+
this.activated.delete(programId);
|
|
75
|
+
for (const unsub of state.eventUnsubs) try {
|
|
76
|
+
unsub();
|
|
77
|
+
} catch {}
|
|
78
|
+
for (const cron of state.cronHandles) try {
|
|
79
|
+
cron.stop();
|
|
80
|
+
} catch {}
|
|
81
|
+
for (const provider of state.ownedProviders) try {
|
|
82
|
+
await provider.close?.();
|
|
83
|
+
} catch {}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Activate all installed programs that have triggers.
|
|
87
|
+
* Failures on individual programs are skipped.
|
|
88
|
+
*/
|
|
89
|
+
async activateAll() {
|
|
90
|
+
const programs = await this.deps.listPrograms();
|
|
91
|
+
for (const program of programs) try {
|
|
92
|
+
await this.activate(program.id);
|
|
93
|
+
} catch (err) {
|
|
94
|
+
console.error(`[PM] Failed to activate "${program.id}":`, err instanceof Error ? err.message : err);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Deactivate all currently activated programs.
|
|
99
|
+
*/
|
|
100
|
+
async deactivateAll() {
|
|
101
|
+
const ids = [...this.activated.keys()];
|
|
102
|
+
for (const id of ids) await this.deactivate(id);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Reload all programs: deactivateAll + activateAll.
|
|
106
|
+
* Serialized via lock to prevent concurrent reloads.
|
|
107
|
+
*/
|
|
108
|
+
async reload() {
|
|
109
|
+
if (this.reloadLock) await this.reloadLock;
|
|
110
|
+
this.reloadLock = (async () => {
|
|
111
|
+
try {
|
|
112
|
+
await this.deactivateAll();
|
|
113
|
+
await this.activateAll();
|
|
114
|
+
} finally {
|
|
115
|
+
this.reloadLock = null;
|
|
116
|
+
}
|
|
117
|
+
})();
|
|
118
|
+
await this.reloadLock;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Get list of activated program IDs.
|
|
122
|
+
*/
|
|
123
|
+
getActivatedPrograms() {
|
|
124
|
+
return [...this.activated.keys()];
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Execute a trigger job via the runtime AFS's ASH provider.
|
|
128
|
+
* Reads script source from /program/scripts/..., then calls /ash/.actions/run
|
|
129
|
+
* with job name and event data.
|
|
130
|
+
*/
|
|
131
|
+
async executeTriggerJob(programId, runtimeAFS, trigger, event) {
|
|
132
|
+
try {
|
|
133
|
+
const scriptPath = (0, ufo.joinURL)("/program", trigger.scriptPath);
|
|
134
|
+
if (!runtimeAFS.read) {
|
|
135
|
+
console.error(`[PM] Cannot execute trigger: runtime AFS has no read()`);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
const readResult = await runtimeAFS.read(scriptPath, {});
|
|
139
|
+
const source = String(readResult.data?.content ?? "");
|
|
140
|
+
if (!source.trim()) {
|
|
141
|
+
console.error(`[PM] Script source empty: ${scriptPath}`);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
if (!runtimeAFS.exec) {
|
|
145
|
+
console.error(`[PM] Cannot execute trigger: runtime AFS has no exec()`);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
const result = await runtimeAFS.exec("/ash/.actions/run", {
|
|
149
|
+
source,
|
|
150
|
+
job: trigger.jobName,
|
|
151
|
+
event,
|
|
152
|
+
_runtime_afs: runtimeAFS
|
|
153
|
+
}, {});
|
|
154
|
+
if (!result.success) console.error(`[PM] Job "${programId}/${trigger.jobName}" failed:`, result.data ?? result.error);
|
|
155
|
+
} catch (err) {
|
|
156
|
+
console.error(`[PM] Job "${programId}/${trigger.jobName}" threw:`, err instanceof Error ? err.message : err);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
//#endregion
|
|
162
|
+
exports.ProgramManager = ProgramManager;
|