@ganglion/xacpx 0.17.0-beta.2 → 0.17.0-beta.3

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.js CHANGED
@@ -33889,6 +33889,7 @@ class WorkspaceFs {
33889
33889
  path: rel,
33890
33890
  content: binary ? "" : slice.toString("utf8"),
33891
33891
  size: info.size,
33892
+ mtimeMs: info.mtimeMs,
33892
33893
  truncated: info.size > FILE_READ_CAP,
33893
33894
  binary
33894
33895
  };
@@ -33896,6 +33897,23 @@ class WorkspaceFs {
33896
33897
  await fh.close();
33897
33898
  }
33898
33899
  }
33900
+ async writeFile(workspace3, relPath, content, expected) {
33901
+ const { abs, rel } = await this.resolve(workspace3, relPath);
33902
+ const info = await stat3(abs);
33903
+ if (!info.isFile())
33904
+ throw new Error("not-a-file");
33905
+ if (info.size > FILE_READ_CAP)
33906
+ throw new Error("file-too-large");
33907
+ if (content.includes("\x00"))
33908
+ throw new Error("is-binary");
33909
+ if (Buffer.byteLength(content, "utf8") > FILE_READ_CAP)
33910
+ throw new Error("file-too-large");
33911
+ if (info.mtimeMs !== expected.mtimeMs || info.size !== expected.size)
33912
+ throw new Error("stale-write");
33913
+ await import_write_file_atomic3.default(abs, content);
33914
+ const after = await stat3(abs);
33915
+ return { path: rel, mtimeMs: after.mtimeMs, size: after.size };
33916
+ }
33899
33917
  async search(workspace3, opts) {
33900
33918
  const { root, abs: base, rel: scopeRel } = await this.resolve(workspace3, opts.path);
33901
33919
  const query = opts.query.trim();
@@ -34276,8 +34294,9 @@ function globToRegExp(glob) {
34276
34294
  return /$^/;
34277
34295
  }
34278
34296
  }
34279
- var execFileAsync, MAX_ENTRIES = 2000, FILE_READ_CAP, DIFF_CAP, GIT_MAX_BUFFER, SEARCH_MAX_RESULTS = 200, SEARCH_MAX_SCAN = 20000, SEARCH_SKIP_DIRS, SEARCH_CONTENT_MAX_HITS = 500, SEARCH_CONTENT_MAX_FILE, LINE_MATCH_CAP = 2000, DOWNLOAD_MAX, GIT_TIMEOUT_MS = 1e4, SEARCH_DEADLINE_MS = 4000, MIME_BY_EXT;
34297
+ var import_write_file_atomic3, execFileAsync, MAX_ENTRIES = 2000, FILE_READ_CAP, DIFF_CAP, GIT_MAX_BUFFER, SEARCH_MAX_RESULTS = 200, SEARCH_MAX_SCAN = 20000, SEARCH_SKIP_DIRS, SEARCH_CONTENT_MAX_HITS = 500, SEARCH_CONTENT_MAX_FILE, LINE_MATCH_CAP = 2000, DOWNLOAD_MAX, GIT_TIMEOUT_MS = 1e4, SEARCH_DEADLINE_MS = 4000, MIME_BY_EXT;
34280
34298
  var init_workspace_fs = __esm(() => {
34299
+ import_write_file_atomic3 = __toESM(require_lib(), 1);
34281
34300
  execFileAsync = promisify(execFile);
34282
34301
  FILE_READ_CAP = 256 * 1024;
34283
34302
  DIFF_CAP = 512 * 1024;
@@ -34348,6 +34367,11 @@ class ControlService {
34348
34367
  throw new Error("files-write-disabled");
34349
34368
  return this.workspaceFs.duplicate(workspace3, path16);
34350
34369
  }
34370
+ async fsWrite(workspace3, path16, content, expected) {
34371
+ if (!this.deps.filesWriteEnabled())
34372
+ throw new Error("files-write-disabled");
34373
+ return this.workspaceFs.writeFile(workspace3, path16, content, expected);
34374
+ }
34351
34375
  async fsDownload(workspace3, path16) {
34352
34376
  return this.workspaceFs.readFileBytes(workspace3, path16);
34353
34377
  }
@@ -152,6 +152,14 @@ export declare class ControlService {
152
152
  fsCopy(workspace: string, path: string): Promise<{
153
153
  path: string;
154
154
  }>;
155
+ fsWrite(workspace: string, path: string, content: string, expected: {
156
+ mtimeMs: number;
157
+ size: number;
158
+ }): Promise<{
159
+ path: string;
160
+ mtimeMs: number;
161
+ size: number;
162
+ }>;
155
163
  fsDownload(workspace: string, path: string): Promise<{
156
164
  path: string;
157
165
  base64: string;
@@ -19,6 +19,7 @@ export interface FileContent {
19
19
  path: string;
20
20
  content: string;
21
21
  size: number;
22
+ mtimeMs: number;
22
23
  truncated: boolean;
23
24
  binary: boolean;
24
25
  }
@@ -109,6 +110,18 @@ export declare class WorkspaceFs {
109
110
  * "nothing ignored". Never throws. */
110
111
  private gitCheckIgnore;
111
112
  readFile(workspace: string, relPath: string): Promise<FileContent>;
113
+ /** Overwrite an EXISTING file's content, guarded by a stale-write token. The target must
114
+ * resolve inside the workspace (realpath containment), be a regular file, and match the
115
+ * caller's `{mtimeMs,size}` token — otherwise `stale-write`. Content must be UTF-8 text
116
+ * (no NUL) and within the read cap. Writes atomically. Returns a fresh token. */
117
+ writeFile(workspace: string, relPath: string, content: string, expected: {
118
+ mtimeMs: number;
119
+ size: number;
120
+ }): Promise<{
121
+ path: string;
122
+ mtimeMs: number;
123
+ size: number;
124
+ }>;
112
125
  /** Find files by name (default) or content. Name mode walks relative paths
113
126
  * breadth-first, skipping `.git`/`node_modules` and never following symlinks (so it
114
127
  * stays contained), applying the query plus any include/exclude globs. Content mode
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ganglion/xacpx",
3
- "version": "0.17.0-beta.2",
3
+ "version": "0.17.0-beta.3",
4
4
  "description": "随时随地通过聊天频道(微信 / 飞书 / 元宝等)远程控制 `acpx` 上的 Claude Code、Codex 等 Agents。",
5
5
  "keywords": [
6
6
  "acpx",