@aigne/afs 1.4.0-beta.1 → 1.4.0-beta.11

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/CHANGELOG.md CHANGED
@@ -1,5 +1,127 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.4.0-beta.11](https://github.com/AIGNE-io/aigne-framework/compare/afs-v1.4.0-beta.10...afs-v1.4.0-beta.11) (2026-01-16)
4
+
5
+
6
+ ### Features
7
+
8
+ * add Agent Skill support ([#787](https://github.com/AIGNE-io/aigne-framework/issues/787)) ([f04fbe7](https://github.com/AIGNE-io/aigne-framework/commit/f04fbe76ec24cf3c59c74adf92d87b0c3784a8f7))
9
+ * add session compact support for AIAgent ([#863](https://github.com/AIGNE-io/aigne-framework/issues/863)) ([9010918](https://github.com/AIGNE-io/aigne-framework/commit/9010918cd3f18b02b5c60ddc9ed5c34b568d0b28))
10
+ * **afs,bash:** add physical path mapping for AFS modules in bash execution ([#881](https://github.com/AIGNE-io/aigne-framework/issues/881)) ([50dbda2](https://github.com/AIGNE-io/aigne-framework/commit/50dbda224bd666d951494d2449779830d8db57fc))
11
+ * **afs:** add basic AFS(AIGNE File System) support ([#505](https://github.com/AIGNE-io/aigne-framework/issues/505)) ([ac2a18a](https://github.com/AIGNE-io/aigne-framework/commit/ac2a18a82470a2f31c466f329386525eb1cdab6d))
12
+ * **afs:** add edit/delete/rename methods for AFS ([#820](https://github.com/AIGNE-io/aigne-framework/issues/820)) ([68cb508](https://github.com/AIGNE-io/aigne-framework/commit/68cb508d1cfc9c516d56303018139f1e567f897e))
13
+ * **afs:** add module access control and schema validation support ([#904](https://github.com/AIGNE-io/aigne-framework/issues/904)) ([d0b279a](https://github.com/AIGNE-io/aigne-framework/commit/d0b279aac07ebe2bcc1fd4148498fc3f6bbcd561))
14
+ * **afs:** add module system fs for afs ([#594](https://github.com/AIGNE-io/aigne-framework/issues/594)) ([83c7b65](https://github.com/AIGNE-io/aigne-framework/commit/83c7b6555d21c606a5005eb05f6686882fb8ffa3))
15
+ * **afs:** improve list behavior to always include current path ([cb91f80](https://github.com/AIGNE-io/aigne-framework/commit/cb91f80c6ea3aa6e93dde26b6feeea8689fceb48))
16
+ * **afs:** support expand context into prompt template by call `$afs.xxx` ([#830](https://github.com/AIGNE-io/aigne-framework/issues/830)) ([5616acd](https://github.com/AIGNE-io/aigne-framework/commit/5616acd6ea257c91aa0b766608f45c5ce17f0345))
17
+ * **core:** add session history support ([#858](https://github.com/AIGNE-io/aigne-framework/issues/858)) ([28a070e](https://github.com/AIGNE-io/aigne-framework/commit/28a070ed33b821d1fd344b899706d817ca992b9f))
18
+ * support define agent by third library & orchestrator agent refactor ([#799](https://github.com/AIGNE-io/aigne-framework/issues/799)) ([7264b11](https://github.com/AIGNE-io/aigne-framework/commit/7264b11ab6eed787e928367f09aa08d254968d40))
19
+ * support mount mcp agent into AFS ([#740](https://github.com/AIGNE-io/aigne-framework/issues/740)) ([6d474fc](https://github.com/AIGNE-io/aigne-framework/commit/6d474fc05845a15e2c3e8fa97727b409bdd70945))
20
+
21
+
22
+ ### Bug Fixes
23
+
24
+ * **afs:** add case-sensitive option for search with case-insensitive default ([#814](https://github.com/AIGNE-io/aigne-framework/issues/814)) ([9dc9446](https://github.com/AIGNE-io/aigne-framework/commit/9dc944635104fc311e7756b4bde0a20275cfe8ec))
25
+ * **afs:** check module existence on normalized path ([#793](https://github.com/AIGNE-io/aigne-framework/issues/793)) ([0c991bf](https://github.com/AIGNE-io/aigne-framework/commit/0c991bf0caa948ce62948986ce885b5a98437689))
26
+ * **afs:** improve module path resolution and depth handling ([#659](https://github.com/AIGNE-io/aigne-framework/issues/659)) ([c609d4f](https://github.com/AIGNE-io/aigne-framework/commit/c609d4fc9614123afcf4b8f86b3382a613ace417))
27
+ * **afs:** read method should not throw not found error ([#835](https://github.com/AIGNE-io/aigne-framework/issues/835)) ([ebfdfc1](https://github.com/AIGNE-io/aigne-framework/commit/ebfdfc1cdba23efd23ac2ad4621e3f046990fd8b))
28
+ * **afs:** show gitignored files with marker instead of filtering ([c2bdea1](https://github.com/AIGNE-io/aigne-framework/commit/c2bdea155f47c9420f2fe810cdfed79ef70ef899))
29
+ * **afs:** throw errors instead of logging in AFS module operations ([#874](https://github.com/AIGNE-io/aigne-framework/issues/874)) ([f0cc1c4](https://github.com/AIGNE-io/aigne-framework/commit/f0cc1c4056f8b95b631d595892bb12eb75da4b9f))
30
+ * **afs:** use simple-list instead of tree as default type ([#839](https://github.com/AIGNE-io/aigne-framework/issues/839)) ([65a9a40](https://github.com/AIGNE-io/aigne-framework/commit/65a9a4054b3bdad6f7e40357299ef3dc48f7c3e4))
31
+ * bump version ([696560f](https://github.com/AIGNE-io/aigne-framework/commit/696560fa2673eddcb4d00ac0523fbbbde7273cb3))
32
+ * bump version ([70d217c](https://github.com/AIGNE-io/aigne-framework/commit/70d217c8360dd0dda7f5f17011c4e92ec836e801))
33
+ * bump version ([af04b69](https://github.com/AIGNE-io/aigne-framework/commit/af04b6931951afa35d52065430acc7fef4b10087))
34
+ * bump version ([ba7ad18](https://github.com/AIGNE-io/aigne-framework/commit/ba7ad184fcf32b49bf0507a3cb638d20fb00690d))
35
+ * bump version ([93a1c10](https://github.com/AIGNE-io/aigne-framework/commit/93a1c10cf35f88eaafe91092481f5d087bd5b3a9))
36
+ * improve test coverage tracking and reporting ([#903](https://github.com/AIGNE-io/aigne-framework/issues/903)) ([031144e](https://github.com/AIGNE-io/aigne-framework/commit/031144e74f29e882cffe52ffda8f7a18c76ace7f))
37
+
38
+ ## [1.4.0-beta.10](https://github.com/AIGNE-io/aigne-framework/compare/afs-v1.4.0-beta.9...afs-v1.4.0-beta.10) (2026-01-16)
39
+
40
+
41
+ ### Features
42
+
43
+ * **afs:** improve list behavior to always include current path ([cb91f80](https://github.com/AIGNE-io/aigne-framework/commit/cb91f80c6ea3aa6e93dde26b6feeea8689fceb48))
44
+
45
+ ## [1.4.0-beta.9](https://github.com/AIGNE-io/aigne-framework/compare/afs-v1.4.0-beta.8...afs-v1.4.0-beta.9) (2026-01-14)
46
+
47
+
48
+ ### Features
49
+
50
+ * **afs:** add module access control and schema validation support ([#904](https://github.com/AIGNE-io/aigne-framework/issues/904)) ([d0b279a](https://github.com/AIGNE-io/aigne-framework/commit/d0b279aac07ebe2bcc1fd4148498fc3f6bbcd561))
51
+
52
+
53
+ ### Bug Fixes
54
+
55
+ * improve test coverage tracking and reporting ([#903](https://github.com/AIGNE-io/aigne-framework/issues/903)) ([031144e](https://github.com/AIGNE-io/aigne-framework/commit/031144e74f29e882cffe52ffda8f7a18c76ace7f))
56
+
57
+
58
+ ### Dependencies
59
+
60
+ * The following workspace dependencies were updated
61
+ * dependencies
62
+ * @aigne/platform-helpers bumped to 0.6.7-beta.2
63
+
64
+ ## [1.4.0-beta.8](https://github.com/AIGNE-io/aigne-framework/compare/afs-v1.4.0-beta.7...afs-v1.4.0-beta.8) (2026-01-12)
65
+
66
+
67
+ ### Bug Fixes
68
+
69
+ * **afs:** show gitignored files with marker instead of filtering ([c2bdea1](https://github.com/AIGNE-io/aigne-framework/commit/c2bdea155f47c9420f2fe810cdfed79ef70ef899))
70
+
71
+ ## [1.4.0-beta.7](https://github.com/AIGNE-io/aigne-framework/compare/afs-v1.4.0-beta.6...afs-v1.4.0-beta.7) (2026-01-08)
72
+
73
+
74
+ ### Features
75
+
76
+ * **afs,bash:** add physical path mapping for AFS modules in bash execution ([#881](https://github.com/AIGNE-io/aigne-framework/issues/881)) ([50dbda2](https://github.com/AIGNE-io/aigne-framework/commit/50dbda224bd666d951494d2449779830d8db57fc))
77
+
78
+
79
+ ### Bug Fixes
80
+
81
+ * bump version ([696560f](https://github.com/AIGNE-io/aigne-framework/commit/696560fa2673eddcb4d00ac0523fbbbde7273cb3))
82
+
83
+
84
+ ### Dependencies
85
+
86
+ * The following workspace dependencies were updated
87
+ * dependencies
88
+ * @aigne/platform-helpers bumped to 0.6.7-beta.1
89
+
90
+ ## [1.4.0-beta.6](https://github.com/AIGNE-io/aigne-framework/compare/afs-v1.4.0-beta.5...afs-v1.4.0-beta.6) (2026-01-06)
91
+
92
+
93
+ ### Bug Fixes
94
+
95
+ * **afs:** throw errors instead of logging in AFS module operations ([#874](https://github.com/AIGNE-io/aigne-framework/issues/874)) ([f0cc1c4](https://github.com/AIGNE-io/aigne-framework/commit/f0cc1c4056f8b95b631d595892bb12eb75da4b9f))
96
+
97
+ ## [1.4.0-beta.5](https://github.com/AIGNE-io/aigne-framework/compare/afs-v1.4.0-beta.4...afs-v1.4.0-beta.5) (2025-12-31)
98
+
99
+
100
+ ### Features
101
+
102
+ * add session compact support for AIAgent ([#863](https://github.com/AIGNE-io/aigne-framework/issues/863)) ([9010918](https://github.com/AIGNE-io/aigne-framework/commit/9010918cd3f18b02b5c60ddc9ed5c34b568d0b28))
103
+
104
+ ## [1.4.0-beta.4](https://github.com/AIGNE-io/aigne-framework/compare/afs-v1.4.0-beta.3...afs-v1.4.0-beta.4) (2025-12-26)
105
+
106
+
107
+ ### Features
108
+
109
+ * **core:** add session history support ([#858](https://github.com/AIGNE-io/aigne-framework/issues/858)) ([28a070e](https://github.com/AIGNE-io/aigne-framework/commit/28a070ed33b821d1fd344b899706d817ca992b9f))
110
+
111
+ ## [1.4.0-beta.3](https://github.com/AIGNE-io/aigne-framework/compare/afs-v1.4.0-beta.2...afs-v1.4.0-beta.3) (2025-12-24)
112
+
113
+
114
+ ### Features
115
+
116
+ * add Agent Skill support ([#787](https://github.com/AIGNE-io/aigne-framework/issues/787)) ([f04fbe7](https://github.com/AIGNE-io/aigne-framework/commit/f04fbe76ec24cf3c59c74adf92d87b0c3784a8f7))
117
+
118
+ ## [1.4.0-beta.2](https://github.com/AIGNE-io/aigne-framework/compare/afs-v1.4.0-beta.1...afs-v1.4.0-beta.2) (2025-12-19)
119
+
120
+
121
+ ### Bug Fixes
122
+
123
+ * **afs:** use simple-list instead of tree as default type ([#839](https://github.com/AIGNE-io/aigne-framework/issues/839)) ([65a9a40](https://github.com/AIGNE-io/aigne-framework/commit/65a9a4054b3bdad6f7e40357299ef3dc48f7c3e4))
124
+
3
125
  ## [1.4.0-beta.1](https://github.com/AIGNE-io/aigne-framework/compare/afs-v1.4.0-beta...afs-v1.4.0-beta.1) (2025-12-17)
4
126
 
5
127
 
package/lib/cjs/afs.d.ts CHANGED
@@ -9,6 +9,11 @@ export declare class AFS extends Emitter<AFSRootEvents> implements AFSRoot {
9
9
  name: string;
10
10
  constructor(options?: AFSOptions);
11
11
  private modules;
12
+ /**
13
+ * Check if write operations are allowed for the given module.
14
+ * Throws AFSReadonlyError if not allowed.
15
+ */
16
+ private checkWritePermission;
12
17
  mount(module: AFSModule): this;
13
18
  listModules(): Promise<{
14
19
  name: string;
@@ -28,5 +33,10 @@ export declare class AFS extends Emitter<AFSRootEvents> implements AFSRoot {
28
33
  private _search;
29
34
  private findModules;
30
35
  exec(path: string, args: Record<string, any>, options: AFSExecOptions): Promise<AFSExecResult>;
36
+ private buildSimpleListView;
31
37
  private buildTreeView;
38
+ private buildMetadataSuffix;
39
+ private physicalPath?;
40
+ initializePhysicalPath(): Promise<string>;
41
+ cleanupPhysicalPath(): Promise<void>;
32
42
  }
package/lib/cjs/afs.js CHANGED
@@ -1,9 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.AFS = void 0;
4
+ const index_js_1 = require("@aigne/platform-helpers/nodejs/index.js");
5
+ const uuid_1 = require("@aigne/uuid");
4
6
  const strict_event_emitter_1 = require("strict-event-emitter");
5
7
  const ufo_1 = require("ufo");
6
8
  const zod_1 = require("zod");
9
+ const error_js_1 = require("./error.js");
7
10
  const type_js_1 = require("./type.js");
8
11
  const DEFAULT_MAX_DEPTH = 1;
9
12
  const MODULES_ROOT_DIR = "/modules";
@@ -18,12 +21,22 @@ class AFS extends strict_event_emitter_1.Emitter {
18
21
  }
19
22
  }
20
23
  modules = new Map();
24
+ /**
25
+ * Check if write operations are allowed for the given module.
26
+ * Throws AFSReadonlyError if not allowed.
27
+ */
28
+ checkWritePermission(module, operation, path) {
29
+ // Module-level readonly (undefined means readonly by default)
30
+ if (module.accessMode !== "readwrite") {
31
+ throw new error_js_1.AFSReadonlyError(`Module '${module.name}' is readonly, cannot perform ${operation} to ${path}`);
32
+ }
33
+ }
21
34
  mount(module) {
22
- let path = (0, ufo_1.joinURL)("/", module.name);
23
- if (!/^\/[^/]+$/.test(path)) {
24
- throw new Error(`Invalid mount path: ${path}. Must start with '/' and contain no other '/'`);
35
+ // Validate module name (should not contain '/')
36
+ if (module.name.includes("/")) {
37
+ throw new Error(`Invalid module name: ${module.name}. Module name must not contain '/'`);
25
38
  }
26
- path = (0, ufo_1.joinURL)(MODULES_ROOT_DIR, path);
39
+ const path = (0, ufo_1.joinURL)(MODULES_ROOT_DIR, module.name);
27
40
  if (this.modules.has(path)) {
28
41
  throw new Error(`Module already mounted at path: ${path}`);
29
42
  }
@@ -53,14 +66,36 @@ class AFS extends strict_event_emitter_1.Emitter {
53
66
  }
54
67
  async _list(path, options = {}) {
55
68
  const results = [];
69
+ // Special case: listing root "/" should return /modules directory
70
+ if (path === "/" && this.modules.size > 0) {
71
+ const maxDepth = options?.maxDepth ?? DEFAULT_MAX_DEPTH;
72
+ // Always include /modules directory first
73
+ results.push({
74
+ id: "modules",
75
+ path: MODULES_ROOT_DIR,
76
+ summary: "All mounted modules",
77
+ });
78
+ if (maxDepth === 1) {
79
+ // Only show /modules directory
80
+ return { data: results };
81
+ }
82
+ // For maxDepth > 1, also get children of /modules with reduced depth
83
+ const childrenResult = await this._list(MODULES_ROOT_DIR, {
84
+ ...options,
85
+ maxDepth: maxDepth - 1,
86
+ });
87
+ results.push(...childrenResult.data);
88
+ return { data: results };
89
+ }
56
90
  const matches = this.findModules(path, options);
57
91
  for (const matched of matches) {
58
- const moduleEntry = {
59
- id: matched.module.name,
60
- path: matched.remainedModulePath,
61
- summary: matched.module.description,
62
- };
63
92
  if (matched.maxDepth === 0) {
93
+ // When maxDepth is 0, show the module entry
94
+ const moduleEntry = {
95
+ id: matched.module.name,
96
+ path: matched.modulePath,
97
+ summary: matched.module.description,
98
+ };
64
99
  results.push(moduleEntry);
65
100
  continue;
66
101
  }
@@ -71,18 +106,16 @@ class AFS extends strict_event_emitter_1.Emitter {
71
106
  ...options,
72
107
  maxDepth: matched.maxDepth,
73
108
  });
74
- if (data.length) {
75
- results.push(...data.map((entry) => ({
76
- ...entry,
77
- path: (0, ufo_1.joinURL)(matched.modulePath, entry.path),
78
- })));
79
- }
80
- else {
81
- results.push(moduleEntry);
82
- }
109
+ const children = data.map((entry) => ({
110
+ ...entry,
111
+ path: (0, ufo_1.joinURL)(matched.modulePath, entry.path),
112
+ }));
113
+ // Always include all nodes (including the current path itself)
114
+ // This ensures consistent behavior across all listing scenarios
115
+ results.push(...children);
83
116
  }
84
117
  catch (error) {
85
- console.error(`Error listing from module at ${matched.modulePath}`, error);
118
+ throw new Error(`Error listing from module at ${matched.modulePath}: ${error.message}`);
86
119
  }
87
120
  }
88
121
  return { data: results };
@@ -107,6 +140,7 @@ class AFS extends strict_event_emitter_1.Emitter {
107
140
  const module = this.findModules(path, { exactMatch: true })[0];
108
141
  if (!module?.module.write)
109
142
  throw new Error(`No module found for path: ${path}`);
143
+ this.checkWritePermission(module.module, "write", path);
110
144
  const res = await module.module.write(module.subpath, content, options);
111
145
  return {
112
146
  ...res,
@@ -120,6 +154,7 @@ class AFS extends strict_event_emitter_1.Emitter {
120
154
  const module = this.findModules(path, { exactMatch: true })[0];
121
155
  if (!module?.module.delete)
122
156
  throw new Error(`No module found for path: ${path}`);
157
+ this.checkWritePermission(module.module, "delete", path);
123
158
  return await module.module.delete(module.subpath, options);
124
159
  }
125
160
  async rename(oldPath, newPath, options) {
@@ -132,6 +167,7 @@ class AFS extends strict_event_emitter_1.Emitter {
132
167
  if (!oldModule.module.rename) {
133
168
  throw new Error(`Module does not support rename operation: ${oldModule.modulePath}`);
134
169
  }
170
+ this.checkWritePermission(oldModule.module, "rename", oldPath);
135
171
  return await oldModule.module.rename(oldModule.subpath, newModule.subpath, options);
136
172
  }
137
173
  async search(path, query, options = {}) {
@@ -161,12 +197,14 @@ class AFS extends strict_event_emitter_1.Emitter {
161
197
  ? await dedupe.invoke({ data: mapped }, options).then((res) => res.data)
162
198
  : mapped;
163
199
  let formatted = deduped;
164
- if (format === "tree") {
200
+ if (format === "simple-list" || format === "tree") {
165
201
  const valid = zod_1.z.array(type_js_1.afsEntrySchema).safeParse(deduped);
166
- if (valid.data)
167
- formatted = this.buildTreeView(valid.data);
168
- else
202
+ if (!valid.data)
169
203
  throw new Error("Tree format requires entries to be AFSEntry objects");
204
+ if (format === "tree")
205
+ formatted = this.buildTreeView(valid.data);
206
+ else if (format === "simple-list")
207
+ formatted = this.buildSimpleListView(valid.data);
170
208
  }
171
209
  else if (typeof format === "object" && typeof format.invoke === "function") {
172
210
  formatted = await format.invoke({ data: deduped }, options).then((res) => res.data);
@@ -194,7 +232,7 @@ class AFS extends strict_event_emitter_1.Emitter {
194
232
  messages.push(message);
195
233
  }
196
234
  catch (error) {
197
- console.error(`Error searching in module at ${modulePath}`, error);
235
+ throw new Error(`Error searching in module at ${modulePath}: ${error.message}`);
198
236
  }
199
237
  }
200
238
  return { data: results, message: messages.join("; ") };
@@ -233,6 +271,9 @@ class AFS extends strict_event_emitter_1.Emitter {
233
271
  throw new Error(`No module found for path: ${path}`);
234
272
  return await module.module.exec(module.subpath, args, options);
235
273
  }
274
+ buildSimpleListView(entries) {
275
+ return entries.map((entry) => `${entry.path}${this.buildMetadataSuffix(entry)}`);
276
+ }
236
277
  buildTreeView(entries) {
237
278
  const tree = {};
238
279
  const entryMap = new Map();
@@ -254,23 +295,7 @@ class AFS extends strict_event_emitter_1.Emitter {
254
295
  const isLast = index === keys.length - 1;
255
296
  const fullPath = currentPath ? `${currentPath}/${key}` : `/${key}`;
256
297
  const entry = entryMap.get(fullPath);
257
- // Build metadata suffix
258
- const metadataParts = [];
259
- // Children count
260
- const childrenCount = entry?.metadata?.childrenCount;
261
- if (typeof childrenCount === "number") {
262
- metadataParts.push(`${childrenCount} items`);
263
- }
264
- // Children truncated
265
- if (entry?.metadata?.childrenTruncated) {
266
- metadataParts.push("truncated");
267
- }
268
- // Executable
269
- if (entry?.metadata?.execute) {
270
- metadataParts.push("executable");
271
- }
272
- const metadataSuffix = metadataParts.length > 0 ? ` [${metadataParts.join(", ")}]` : "";
273
- result += `${prefix}${isLast ? "└── " : "├── "}${key}${metadataSuffix}`;
298
+ result += `${prefix}${isLast ? "└── " : "├── "}${key}${entry ? this.buildMetadataSuffix(entry) : ""}`;
274
299
  result += `\n`;
275
300
  result += renderTree(node[key], `${prefix}${isLast ? " " : "│ "}`, fullPath);
276
301
  });
@@ -278,5 +303,48 @@ class AFS extends strict_event_emitter_1.Emitter {
278
303
  };
279
304
  return renderTree(tree);
280
305
  }
306
+ buildMetadataSuffix(entry) {
307
+ // Build metadata suffix
308
+ const metadataParts = [];
309
+ // Children count
310
+ const childrenCount = entry?.metadata?.childrenCount;
311
+ if (typeof childrenCount === "number") {
312
+ metadataParts.push(`${childrenCount} items`);
313
+ }
314
+ // Children truncated
315
+ if (entry?.metadata?.childrenTruncated) {
316
+ metadataParts.push("truncated");
317
+ }
318
+ // Gitignored
319
+ if (entry?.metadata?.gitignored) {
320
+ metadataParts.push("gitignored");
321
+ }
322
+ // Executable
323
+ if (entry?.metadata?.execute) {
324
+ metadataParts.push("executable");
325
+ }
326
+ const metadataSuffix = metadataParts.length > 0 ? ` [${metadataParts.join(", ")}]` : "";
327
+ return metadataSuffix;
328
+ }
329
+ physicalPath;
330
+ async initializePhysicalPath() {
331
+ this.physicalPath ??= (async () => {
332
+ const rootDir = index_js_1.nodejs.path.join(index_js_1.nodejs.os.tmpdir(), (0, uuid_1.v7)());
333
+ await index_js_1.nodejs.fs.mkdir(rootDir, { recursive: true });
334
+ for (const [modulePath, module] of this.modules) {
335
+ const physicalModulePath = index_js_1.nodejs.path.join(rootDir, modulePath);
336
+ await index_js_1.nodejs.fs.mkdir(index_js_1.nodejs.path.dirname(physicalModulePath), { recursive: true });
337
+ await module.symlinkToPhysical?.(physicalModulePath);
338
+ }
339
+ return rootDir;
340
+ })();
341
+ return this.physicalPath;
342
+ }
343
+ async cleanupPhysicalPath() {
344
+ if (this.physicalPath) {
345
+ await index_js_1.nodejs.fs.rm(await this.physicalPath, { recursive: true, force: true });
346
+ this.physicalPath = undefined;
347
+ }
348
+ }
281
349
  }
282
350
  exports.AFS = AFS;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Base error class for all AFS errors.
3
+ */
4
+ export declare class AFSError extends Error {
5
+ readonly code: string;
6
+ constructor(message: string, code: string);
7
+ }
8
+ /**
9
+ * Error thrown when attempting write operations on a readonly AFS or module.
10
+ */
11
+ export declare class AFSReadonlyError extends AFSError {
12
+ constructor(message: string);
13
+ }
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AFSReadonlyError = exports.AFSError = void 0;
4
+ /**
5
+ * Base error class for all AFS errors.
6
+ */
7
+ class AFSError extends Error {
8
+ code;
9
+ constructor(message, code) {
10
+ super(message);
11
+ this.name = "AFSError";
12
+ this.code = code;
13
+ }
14
+ }
15
+ exports.AFSError = AFSError;
16
+ /**
17
+ * Error thrown when attempting write operations on a readonly AFS or module.
18
+ */
19
+ class AFSReadonlyError extends AFSError {
20
+ constructor(message) {
21
+ super(message, "AFS_READONLY");
22
+ this.name = "AFSReadonlyError";
23
+ }
24
+ }
25
+ exports.AFSReadonlyError = AFSReadonlyError;
@@ -1,2 +1,3 @@
1
1
  export * from "./afs.js";
2
+ export * from "./error.js";
2
3
  export * from "./type.js";
package/lib/cjs/index.js CHANGED
@@ -15,4 +15,5 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./afs.js"), exports);
18
+ __exportStar(require("./error.js"), exports);
18
19
  __exportStar(require("./type.js"), exports);
package/lib/cjs/type.d.ts CHANGED
@@ -1,9 +1,26 @@
1
1
  import type { Emitter } from "strict-event-emitter";
2
- import { type ZodType } from "zod";
3
- export interface AFSListOptions {
2
+ import { type ZodType, z } from "zod";
3
+ /**
4
+ * Access mode for AFS modules and root.
5
+ * - "readonly": Only read operations are allowed (list, read, search)
6
+ * - "readwrite": All operations are allowed
7
+ */
8
+ export type AFSAccessMode = "readonly" | "readwrite";
9
+ /**
10
+ * Zod schema for access mode validation.
11
+ * Can be reused across modules that support access mode configuration.
12
+ */
13
+ export declare const accessModeSchema: z.ZodOptional<z.ZodEnum<["readonly", "readwrite"]>>;
14
+ export interface AFSOperationOptions {
15
+ context?: any;
16
+ }
17
+ export interface AFSListOptions extends AFSOperationOptions {
4
18
  filter?: {
19
+ agentId?: string;
5
20
  userId?: string;
6
21
  sessionId?: string;
22
+ before?: string;
23
+ after?: string;
7
24
  };
8
25
  maxDepth?: number;
9
26
  limit?: number;
@@ -15,46 +32,45 @@ export interface AFSListOptions {
15
32
  * @default false
16
33
  */
17
34
  disableGitignore?: boolean;
18
- context?: any;
35
+ /**
36
+ * Glob pattern to filter entries by path.
37
+ * Examples: "*.ts", "**\/*.js", "src/**\/*.{ts,tsx}"
38
+ */
39
+ pattern?: string;
19
40
  }
20
41
  export interface AFSListResult {
21
42
  data: AFSEntry[];
22
43
  message?: string;
23
- context?: any;
24
44
  }
25
- export interface AFSSearchOptions {
45
+ export interface AFSSearchOptions extends AFSOperationOptions {
26
46
  limit?: number;
27
47
  caseSensitive?: boolean;
28
- context?: any;
29
48
  }
30
49
  export interface AFSSearchResult {
31
50
  data: AFSEntry[];
32
51
  message?: string;
33
52
  }
34
- export interface AFSReadOptions {
35
- context?: any;
53
+ export interface AFSReadOptions extends AFSOperationOptions {
54
+ filter?: AFSListOptions["filter"];
36
55
  }
37
56
  export interface AFSReadResult {
38
57
  data?: AFSEntry;
39
58
  message?: string;
40
59
  }
41
- export interface AFSDeleteOptions {
60
+ export interface AFSDeleteOptions extends AFSOperationOptions {
42
61
  recursive?: boolean;
43
- context?: any;
44
62
  }
45
63
  export interface AFSDeleteResult {
46
64
  message?: string;
47
65
  }
48
- export interface AFSRenameOptions {
66
+ export interface AFSRenameOptions extends AFSOperationOptions {
49
67
  overwrite?: boolean;
50
- context?: any;
51
68
  }
52
69
  export interface AFSRenameResult {
53
70
  message?: string;
54
71
  }
55
- export interface AFSWriteOptions {
72
+ export interface AFSWriteOptions extends AFSOperationOptions {
56
73
  append?: boolean;
57
- context?: any;
58
74
  }
59
75
  export interface AFSWriteResult {
60
76
  data: AFSEntry;
@@ -63,8 +79,7 @@ export interface AFSWriteResult {
63
79
  }
64
80
  export interface AFSWriteEntryPayload extends Omit<AFSEntry, "id" | "path"> {
65
81
  }
66
- export interface AFSExecOptions {
67
- context: any;
82
+ export interface AFSExecOptions extends AFSOperationOptions {
68
83
  }
69
84
  export interface AFSExecResult {
70
85
  data: Record<string, any>;
@@ -72,7 +87,21 @@ export interface AFSExecResult {
72
87
  export interface AFSModule {
73
88
  readonly name: string;
74
89
  readonly description?: string;
90
+ /**
91
+ * Access mode for this module.
92
+ * - "readonly": Only read operations are allowed
93
+ * - "readwrite": All operations are allowed
94
+ * Default behavior is implementation-specific.
95
+ */
96
+ readonly accessMode?: AFSAccessMode;
97
+ /**
98
+ * Enable automatic agent skill scanning for this module.
99
+ * When set to true, the system will scan this module for agent skills.
100
+ * @default false
101
+ */
102
+ readonly agentSkills?: boolean;
75
103
  onMount?(root: AFSRoot): void;
104
+ symlinkToPhysical?(path: string): Promise<void>;
76
105
  list?(path: string, options?: AFSListOptions): Promise<AFSListResult>;
77
106
  read?(path: string, options?: AFSReadOptions): Promise<AFSReadResult>;
78
107
  write?(path: string, content: AFSWriteEntryPayload, options?: AFSWriteOptions): Promise<AFSWriteResult>;
@@ -81,14 +110,53 @@ export interface AFSModule {
81
110
  search?(path: string, query: string, options?: AFSSearchOptions): Promise<AFSSearchResult>;
82
111
  exec?(path: string, args: Record<string, any>, options: AFSExecOptions): Promise<AFSExecResult>;
83
112
  }
113
+ /**
114
+ * Parameters for loading a module from configuration.
115
+ */
116
+ export interface AFSModuleLoadParams {
117
+ /** Path to the configuration file */
118
+ filepath: string;
119
+ /** Parsed configuration object */
120
+ parsed?: object;
121
+ }
122
+ /**
123
+ * Interface for module classes that support schema validation and loading from configuration.
124
+ * This describes the static part of a module class.
125
+ *
126
+ * @example
127
+ * ```typescript
128
+ * class MyModule implements AFSModule {
129
+ * static schema() { return mySchema; }
130
+ * static async load(params: AFSModuleLoadParams) { ... }
131
+ * // ...
132
+ * }
133
+ *
134
+ * // Type check
135
+ * const _check: AFSModuleClass<MyModule, MyModuleOptions> = MyModule;
136
+ * ```
137
+ */
138
+ export interface AFSModuleClass<T extends AFSModule = AFSModule, O extends object = object> {
139
+ /** Returns the Zod schema for validating module configuration */
140
+ schema(): ZodType<O>;
141
+ /** Loads a module instance from configuration file path and parsed config */
142
+ load(params: AFSModuleLoadParams): Promise<T>;
143
+ /** Constructor */
144
+ new (options: O): T;
145
+ }
84
146
  export type AFSRootEvents = {
85
- agentSucceed: [{
86
- input: object;
87
- output: object;
88
- }];
147
+ agentSucceed: [
148
+ {
149
+ agentId?: string;
150
+ userId?: string;
151
+ sessionId?: string;
152
+ input: object;
153
+ output: object;
154
+ messages?: object[];
155
+ }
156
+ ];
89
157
  historyCreated: [{
90
158
  entry: AFSEntry;
91
- }];
159
+ }, options: AFSOperationOptions];
92
160
  };
93
161
  export interface AFSRootListOptions extends AFSListOptions, AFSContextPreset {
94
162
  preset?: string;
@@ -105,6 +173,8 @@ export interface AFSRootSearchResult extends Omit<AFSSearchResult, "data"> {
105
173
  export interface AFSRoot extends Emitter<AFSRootEvents>, AFSModule {
106
174
  list(path: string, options?: AFSRootListOptions): Promise<AFSRootListResult>;
107
175
  search(path: string, query: string, options: AFSRootSearchOptions): Promise<AFSRootSearchResult>;
176
+ initializePhysicalPath(): Promise<string>;
177
+ cleanupPhysicalPath(): Promise<void>;
108
178
  }
109
179
  export interface AFSEntryMetadata extends Record<string, any> {
110
180
  execute?: {
@@ -115,12 +185,14 @@ export interface AFSEntryMetadata extends Record<string, any> {
115
185
  };
116
186
  childrenCount?: number;
117
187
  childrenTruncated?: boolean;
188
+ gitignored?: boolean;
118
189
  }
119
190
  export interface AFSEntry<T = any> {
120
191
  id: string;
121
192
  createdAt?: Date;
122
193
  updatedAt?: Date;
123
194
  path: string;
195
+ agentId?: string | null;
124
196
  userId?: string | null;
125
197
  sessionId?: string | null;
126
198
  summary?: string | null;
@@ -151,7 +223,7 @@ export interface AFSContextPreset {
151
223
  }, {
152
224
  data: unknown;
153
225
  }>;
154
- format?: "default" | "tree" | AFSContextPresetOptionAgent<{
226
+ format?: "default" | "simple-list" | "tree" | AFSContextPresetOptionAgent<{
155
227
  data: unknown;
156
228
  }, {
157
229
  data: unknown;