@agentvault/agentvault 0.14.4 → 0.14.6

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.
@@ -1 +1 @@
1
- {"version":3,"file":"create-agent.d.ts","sourceRoot":"","sources":["../src/create-agent.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAOH,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAID,wDAAwD;AACxD,wBAAgB,YAAY,IAAI,MAAM,CAGrC;AAED,mDAAmD;AACnD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,GAAG,CAIpD;AA4BD;;;GAGG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,GAAG,EAAE,SAAS,SAAQ,GAAG,MAAM,CAInE;AAOD,yDAAyD;AACzD,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CA8C3E;AAID,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAsKjF"}
1
+ {"version":3,"file":"create-agent.d.ts","sourceRoot":"","sources":["../src/create-agent.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAOH,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAID,wDAAwD;AACxD,wBAAgB,YAAY,IAAI,MAAM,CAGrC;AAED,mDAAmD;AACnD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,GAAG,CAIpD;AA4BD;;;GAGG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,GAAG,EAAE,SAAS,SAAQ,GAAG,MAAM,CAInE;AAOD,yDAAyD;AACzD,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CA8C3E;AAID,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CA+KjF"}
package/dist/index.js CHANGED
@@ -1,5 +1,11 @@
1
1
  var __defProp = Object.defineProperty;
2
2
  var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __require = /* @__PURE__ */ ((x2) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x2, {
4
+ get: (a2, b2) => (typeof require !== "undefined" ? require : a2)[b2]
5
+ }) : x2)(function(x2) {
6
+ if (typeof require !== "undefined") return require.apply(this, arguments);
7
+ throw Error('Dynamic require of "' + x2 + '" is not supported');
8
+ });
3
9
  var __esm = (fn, res) => function __init() {
4
10
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
5
11
  };
@@ -45022,9 +45028,53 @@ var init_file_crypto = __esm({
45022
45028
  });
45023
45029
 
45024
45030
  // ../crypto/dist/did.js
45031
+ function canonicalize(obj) {
45032
+ const json = jsonCanonical(obj) ?? "";
45033
+ return libsodium_wrappers_default.from_string(json);
45034
+ }
45035
+ function jsonCanonical(value) {
45036
+ if (value === void 0)
45037
+ return void 0;
45038
+ if (value === null)
45039
+ return "null";
45040
+ if (typeof value === "boolean" || typeof value === "number")
45041
+ return JSON.stringify(value);
45042
+ if (typeof value === "string")
45043
+ return JSON.stringify(value);
45044
+ if (Array.isArray(value)) {
45045
+ return "[" + value.map(jsonCanonical).join(",") + "]";
45046
+ }
45047
+ if (typeof value === "object") {
45048
+ const keys = Object.keys(value).sort();
45049
+ const entries = keys.map((k2) => {
45050
+ const v2 = jsonCanonical(value[k2]);
45051
+ if (v2 === void 0)
45052
+ return void 0;
45053
+ return JSON.stringify(k2) + ":" + v2;
45054
+ }).filter((entry) => entry !== void 0);
45055
+ return "{" + entries.join(",") + "}";
45056
+ }
45057
+ return JSON.stringify(value);
45058
+ }
45059
+ async function verifyDocumentSignature(document, signature, publicKey) {
45060
+ await libsodium_wrappers_default.ready;
45061
+ const canonical = canonicalize(document);
45062
+ const domainPrefix = libsodium_wrappers_default.from_string(DOMAIN_DID_DOCUMENT);
45063
+ const message = new Uint8Array(domainPrefix.length + canonical.length);
45064
+ message.set(domainPrefix);
45065
+ message.set(canonical, domainPrefix.length);
45066
+ try {
45067
+ return libsodium_wrappers_default.crypto_sign_verify_detached(signature, message, publicKey);
45068
+ } catch {
45069
+ return false;
45070
+ }
45071
+ }
45072
+ var DOMAIN_DID_DOCUMENT;
45025
45073
  var init_did = __esm({
45026
45074
  async "../crypto/dist/did.js"() {
45027
45075
  "use strict";
45076
+ await init_libsodium_wrappers();
45077
+ DOMAIN_DID_DOCUMENT = "DID-DOCUMENT:";
45028
45078
  }
45029
45079
  });
45030
45080
 
@@ -45979,13 +46029,152 @@ var init_http_handlers = __esm({
45979
46029
  }
45980
46030
  });
45981
46031
 
46032
+ // src/workspace-handlers.ts
46033
+ var workspace_handlers_exports = {};
46034
+ __export(workspace_handlers_exports, {
46035
+ handleWorkspaceList: () => handleWorkspaceList,
46036
+ handleWorkspaceRead: () => handleWorkspaceRead,
46037
+ handleWorkspaceUpload: () => handleWorkspaceUpload,
46038
+ validateWorkspaceFilename: () => validateWorkspaceFilename
46039
+ });
46040
+ import { readdir, readFile as readFile2, writeFile as writeFile2, rename as rename2, stat, unlink } from "node:fs/promises";
46041
+ import { join as join2 } from "node:path";
46042
+ import { randomUUID } from "node:crypto";
46043
+ function validateWorkspaceFilename(filename) {
46044
+ if (!filename || typeof filename !== "string") {
46045
+ return "Filename is required";
46046
+ }
46047
+ if (filename.includes("\0")) {
46048
+ return "Filename contains null bytes";
46049
+ }
46050
+ if (filename.includes("..")) {
46051
+ return "Path traversal not allowed";
46052
+ }
46053
+ if (filename.includes("/") || filename.includes("\\")) {
46054
+ return "Path separators not allowed in filename";
46055
+ }
46056
+ if (!filename.endsWith(".md")) {
46057
+ return "Only .md files are allowed";
46058
+ }
46059
+ if (!/^[a-zA-Z0-9._-]+$/.test(filename)) {
46060
+ return "Filename may only contain letters, numbers, hyphens, underscores, and dots";
46061
+ }
46062
+ return null;
46063
+ }
46064
+ async function handleWorkspaceUpload(data, workspaceDir) {
46065
+ const filenameError = validateWorkspaceFilename(data.filename);
46066
+ if (filenameError) {
46067
+ return { status: "error", error: filenameError };
46068
+ }
46069
+ if (!data.content || typeof data.content !== "string") {
46070
+ return { status: "error", error: "File content is required" };
46071
+ }
46072
+ const contentBytes = Buffer.byteLength(data.content, "utf-8");
46073
+ if (contentBytes > MAX_FILE_SIZE) {
46074
+ return {
46075
+ status: "error",
46076
+ error: `File exceeds 100KB limit (${Math.round(contentBytes / 1024)}KB)`
46077
+ };
46078
+ }
46079
+ let verified = false;
46080
+ try {
46081
+ const signatureBytes = base64ToBytes(data.signature);
46082
+ const publicKeyBytes = base64ToBytes(data.signer_public_key);
46083
+ const signedPayload = {
46084
+ filename: data.filename,
46085
+ content: data.content,
46086
+ timestamp: data.timestamp
46087
+ };
46088
+ verified = await verifyDocumentSignature(signedPayload, signatureBytes, publicKeyBytes);
46089
+ } catch (err) {
46090
+ return { status: "error", error: "Signature verification failed: invalid format" };
46091
+ }
46092
+ if (!verified) {
46093
+ return { status: "error", error: "Invalid signature \u2014 file may have been tampered with" };
46094
+ }
46095
+ const targetPath = join2(workspaceDir, data.filename);
46096
+ const tempPath = join2(workspaceDir, `.tmp_${randomUUID()}_${data.filename}`);
46097
+ try {
46098
+ await writeFile2(tempPath, data.content, "utf-8");
46099
+ await rename2(tempPath, targetPath);
46100
+ } catch (err) {
46101
+ try {
46102
+ await unlink(tempPath);
46103
+ } catch {
46104
+ }
46105
+ return { status: "error", error: `Failed to write file: ${err.message}` };
46106
+ }
46107
+ console.log(`[Workspace] Wrote ${data.filename} (${contentBytes} bytes, sig verified) \u2192 ${targetPath}`);
46108
+ return {
46109
+ status: "success",
46110
+ written_path: targetPath,
46111
+ verified_signature: true
46112
+ };
46113
+ }
46114
+ async function handleWorkspaceList(workspaceDir) {
46115
+ try {
46116
+ const entries = await readdir(workspaceDir);
46117
+ const files = [];
46118
+ for (const entry of entries) {
46119
+ if (!entry.endsWith(".md")) continue;
46120
+ try {
46121
+ const filePath = join2(workspaceDir, entry);
46122
+ const fileStat = await stat(filePath);
46123
+ if (!fileStat.isFile()) continue;
46124
+ files.push({
46125
+ filename: entry,
46126
+ size: fileStat.size,
46127
+ modified: fileStat.mtime.toISOString()
46128
+ });
46129
+ } catch {
46130
+ }
46131
+ }
46132
+ files.sort((a2, b2) => a2.filename.localeCompare(b2.filename));
46133
+ return { files };
46134
+ } catch (err) {
46135
+ if (err.code === "ENOENT") {
46136
+ return { files: [] };
46137
+ }
46138
+ throw err;
46139
+ }
46140
+ }
46141
+ async function handleWorkspaceRead(data, workspaceDir) {
46142
+ const filenameError = validateWorkspaceFilename(data.filename);
46143
+ if (filenameError) {
46144
+ return { error: filenameError };
46145
+ }
46146
+ const filePath = join2(workspaceDir, data.filename);
46147
+ try {
46148
+ const content = await readFile2(filePath, "utf-8");
46149
+ const fileStat = await stat(filePath);
46150
+ return {
46151
+ filename: data.filename,
46152
+ content,
46153
+ size: fileStat.size
46154
+ };
46155
+ } catch (err) {
46156
+ if (err.code === "ENOENT") {
46157
+ return { error: `File not found: ${data.filename}` };
46158
+ }
46159
+ return { error: `Failed to read file: ${err.message}` };
46160
+ }
46161
+ }
46162
+ var MAX_FILE_SIZE;
46163
+ var init_workspace_handlers = __esm({
46164
+ async "src/workspace-handlers.ts"() {
46165
+ "use strict";
46166
+ await init_dist();
46167
+ MAX_FILE_SIZE = 100 * 1024;
46168
+ }
46169
+ });
46170
+
45982
46171
  // src/channel.ts
45983
46172
  import { EventEmitter } from "node:events";
45984
46173
  import { createServer } from "node:http";
45985
- import { randomUUID } from "node:crypto";
45986
- import { writeFile as writeFile2, mkdir as mkdir2 } from "node:fs/promises";
45987
- import { join as join2 } from "node:path";
45988
- import { readFile as readFile2 } from "node:fs/promises";
46174
+ import { randomUUID as randomUUID2 } from "node:crypto";
46175
+ import { writeFile as writeFile3, mkdir as mkdir2 } from "node:fs/promises";
46176
+ import { join as join3 } from "node:path";
46177
+ import { readFile as readFile3 } from "node:fs/promises";
45989
46178
  import WebSocket from "ws";
45990
46179
  function migratePersistedState(raw) {
45991
46180
  if (raw.sessions && raw.primaryConversationId) {
@@ -46038,6 +46227,8 @@ var init_channel = __esm({
46038
46227
  _pollTimer = null;
46039
46228
  _reconnectAttempt = 0;
46040
46229
  _reconnectTimer = null;
46230
+ _rapidDisconnects = 0;
46231
+ _lastWsOpenTime = 0;
46041
46232
  _pingTimer = null;
46042
46233
  _lastServerMessage = 0;
46043
46234
  _pendingAcks = [];
@@ -46227,7 +46418,7 @@ var init_channel = __esm({
46227
46418
  }
46228
46419
  }
46229
46420
  }
46230
- const messageGroupId = randomUUID();
46421
+ const messageGroupId = randomUUID2();
46231
46422
  let sentCount = 0;
46232
46423
  for (const [convId, session] of this._sessions) {
46233
46424
  if (!session.activated) continue;
@@ -46322,7 +46513,7 @@ var init_channel = __esm({
46322
46513
  * as a high-priority message. Returns the generated decision_id.
46323
46514
  */
46324
46515
  async sendDecisionRequest(request) {
46325
- const decision_id = `dec_${randomUUID().replace(/-/g, "").slice(0, 16)}`;
46516
+ const decision_id = `dec_${randomUUID2().replace(/-/g, "").slice(0, 16)}`;
46326
46517
  const payload = JSON.stringify({
46327
46518
  type: "message",
46328
46519
  text: `\u{1F4CB} ${request.title}`,
@@ -46627,7 +46818,7 @@ var init_channel = __esm({
46627
46818
  description: artifact.description,
46628
46819
  attachment: attachMeta
46629
46820
  });
46630
- const messageGroupId = randomUUID();
46821
+ const messageGroupId = randomUUID2();
46631
46822
  for (const [convId, session] of this._sessions) {
46632
46823
  if (!session.activated) continue;
46633
46824
  const encrypted = session.ratchet.encrypt(envelope);
@@ -47264,6 +47455,7 @@ var init_channel = __esm({
47264
47455
  ws.on("open", async () => {
47265
47456
  try {
47266
47457
  this._reconnectAttempt = 0;
47458
+ this._lastWsOpenTime = Date.now();
47267
47459
  this._startPing(ws);
47268
47460
  this._startWakeDetector();
47269
47461
  this._startPendingPoll();
@@ -47789,6 +47981,41 @@ var init_channel = __esm({
47789
47981
  await this._persistState();
47790
47982
  return;
47791
47983
  }
47984
+ if (messageType === "workspace_file_upload") {
47985
+ try {
47986
+ const parsed = JSON.parse(plaintext);
47987
+ const workspaceDir = this._resolveWorkspaceDir();
47988
+ const { handleWorkspaceUpload: handleWorkspaceUpload2 } = await init_workspace_handlers().then(() => workspace_handlers_exports);
47989
+ const result = await handleWorkspaceUpload2(parsed, workspaceDir);
47990
+ await this._sendStructuredReply(convId, { type: "workspace_file_ack", filename: parsed.filename, ...result });
47991
+ } catch (err) {
47992
+ await this._sendStructuredReply(convId, { type: "workspace_file_ack", status: "error", error: err.message });
47993
+ }
47994
+ return;
47995
+ }
47996
+ if (messageType === "workspace_file_list") {
47997
+ try {
47998
+ const workspaceDir = this._resolveWorkspaceDir();
47999
+ const { handleWorkspaceList: handleWorkspaceList2 } = await init_workspace_handlers().then(() => workspace_handlers_exports);
48000
+ const result = await handleWorkspaceList2(workspaceDir);
48001
+ await this._sendStructuredReply(convId, { type: "workspace_file_list_response", ...result });
48002
+ } catch (err) {
48003
+ await this._sendStructuredReply(convId, { type: "workspace_file_list_response", files: [], error: err.message });
48004
+ }
48005
+ return;
48006
+ }
48007
+ if (messageType === "workspace_file_read") {
48008
+ try {
48009
+ const parsed = JSON.parse(plaintext);
48010
+ const workspaceDir = this._resolveWorkspaceDir();
48011
+ const { handleWorkspaceRead: handleWorkspaceRead2 } = await init_workspace_handlers().then(() => workspace_handlers_exports);
48012
+ const result = await handleWorkspaceRead2(parsed, workspaceDir);
48013
+ await this._sendStructuredReply(convId, { type: "workspace_file_read_response", ...result });
48014
+ } catch (err) {
48015
+ await this._sendStructuredReply(convId, { type: "workspace_file_read_response", error: err.message });
48016
+ }
48017
+ return;
48018
+ }
47792
48019
  if (this._scanEngine) {
47793
48020
  const scanResult = this._scanEngine.scanInbound(messageText);
47794
48021
  if (scanResult.status === "blocked") {
@@ -47869,7 +48096,7 @@ ${messageText}`;
47869
48096
  * and save the plaintext file to disk.
47870
48097
  */
47871
48098
  async _downloadAndDecryptAttachment(info) {
47872
- const attachDir = join2(this.config.dataDir, "attachments");
48099
+ const attachDir = join3(this.config.dataDir, "attachments");
47873
48100
  await mkdir2(attachDir, { recursive: true });
47874
48101
  const url = `${this.config.apiUrl}${info.blobUrl}`;
47875
48102
  const res = await fetch(url, {
@@ -47887,8 +48114,8 @@ ${messageText}`;
47887
48114
  const fileKey = base64ToBytes(info.fileKey);
47888
48115
  const fileNonce = base64ToBytes(info.fileNonce);
47889
48116
  const decrypted = decryptFile(encryptedData, fileKey, fileNonce);
47890
- const filePath = join2(attachDir, info.filename);
47891
- await writeFile2(filePath, decrypted);
48117
+ const filePath = join3(attachDir, info.filename);
48118
+ await writeFile3(filePath, decrypted);
47892
48119
  console.log(`[SecureChannel] Attachment saved: ${filePath} (${decrypted.length} bytes)`);
47893
48120
  return { filePath, decrypted };
47894
48121
  }
@@ -47897,7 +48124,7 @@ ${messageText}`;
47897
48124
  * for inclusion in the message envelope.
47898
48125
  */
47899
48126
  async _uploadAttachment(filePath, conversationId) {
47900
- const data = await readFile2(filePath);
48127
+ const data = await readFile3(filePath);
47901
48128
  const plainData = new Uint8Array(data);
47902
48129
  const result = encryptFile(plainData);
47903
48130
  const { Blob: NodeBlob, FormData: NodeFormData } = await import("node:buffer").then(
@@ -47949,7 +48176,7 @@ ${messageText}`;
47949
48176
  attachment: attachMeta
47950
48177
  });
47951
48178
  this._appendHistory("agent", plaintext, topicId);
47952
- const messageGroupId = randomUUID();
48179
+ const messageGroupId = randomUUID2();
47953
48180
  for (const [convId, session] of this._sessions) {
47954
48181
  if (!session.activated) continue;
47955
48182
  const encrypted = session.ratchet.encrypt(envelope);
@@ -47999,6 +48226,56 @@ ${messageText}`;
47999
48226
  );
48000
48227
  }
48001
48228
  }
48229
+ /**
48230
+ * Resolve the agent's workspace directory.
48231
+ * Looks for OpenClaw workspace config, falls back to default path.
48232
+ */
48233
+ _resolveWorkspaceDir() {
48234
+ const homedir = process.env.HOME ?? process.env.USERPROFILE ?? "/tmp";
48235
+ try {
48236
+ const configPath = join3(homedir, ".openclaw", "openclaw.json");
48237
+ const raw = __require("node:fs").readFileSync(configPath, "utf-8");
48238
+ const config = JSON.parse(raw);
48239
+ const agents = config?.agents?.list;
48240
+ if (Array.isArray(agents)) {
48241
+ for (const agent of agents) {
48242
+ if (agent.workspace && typeof agent.workspace === "string") {
48243
+ return agent.workspace;
48244
+ }
48245
+ }
48246
+ }
48247
+ } catch {
48248
+ }
48249
+ if (this.config.dataDir) {
48250
+ return join3(this.config.dataDir, "..", "workspace");
48251
+ }
48252
+ return join3(homedir, ".openclaw", "workspace");
48253
+ }
48254
+ /**
48255
+ * Send a structured JSON reply to a specific conversation.
48256
+ * Encrypts the payload via the conversation's ratchet and sends via WebSocket.
48257
+ */
48258
+ async _sendStructuredReply(convId, payload) {
48259
+ const session = this._sessions.get(convId);
48260
+ if (!session || !this._ws) {
48261
+ console.warn(`[SecureChannel] Cannot send structured reply \u2014 no session for ${convId.slice(0, 8)}...`);
48262
+ return;
48263
+ }
48264
+ const plaintext = JSON.stringify(payload);
48265
+ const encrypted = session.ratchet.encrypt(plaintext);
48266
+ const transport = encryptedMessageToTransport(encrypted);
48267
+ this._ws.send(
48268
+ JSON.stringify({
48269
+ event: "message",
48270
+ data: {
48271
+ conversation_id: convId,
48272
+ header_blob: transport.header_blob,
48273
+ ciphertext: transport.ciphertext
48274
+ }
48275
+ })
48276
+ );
48277
+ await this._persistState();
48278
+ }
48002
48279
  /**
48003
48280
  * Send stored message history to a newly-activated session.
48004
48281
  * Batches all history into a single encrypted message.
@@ -48559,6 +48836,26 @@ ${messageText}`;
48559
48836
  _scheduleReconnect() {
48560
48837
  if (this._stopped) return;
48561
48838
  if (this._reconnectTimer) return;
48839
+ const sinceOpen = Date.now() - this._lastWsOpenTime;
48840
+ if (this._lastWsOpenTime > 0 && sinceOpen < 1e4) {
48841
+ this._rapidDisconnects++;
48842
+ } else {
48843
+ this._rapidDisconnects = 0;
48844
+ }
48845
+ if (this._rapidDisconnects >= 5) {
48846
+ console.error(
48847
+ `[SecureChannel] Detected rapid connect/disconnect loop (${this._rapidDisconnects} times in <10s each).`
48848
+ );
48849
+ console.error(
48850
+ `[SecureChannel] This usually means another process is already connected as this device.`
48851
+ );
48852
+ console.error(
48853
+ `[SecureChannel] Stopping reconnection. Check for duplicate gateway processes or stale CLI sessions.`
48854
+ );
48855
+ this._setState("error");
48856
+ this.emit("error", new Error("Reconnect loop detected \u2014 another process may be connected as this device"));
48857
+ return;
48858
+ }
48562
48859
  const delay = Math.min(
48563
48860
  RECONNECT_BASE_MS * Math.pow(2, this._reconnectAttempt),
48564
48861
  RECONNECT_MAX_MS