@kyan-du/agent-wechat-cli 0.13.3 → 0.14.1

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
@@ -1199,8 +1199,8 @@ var require_command = __commonJS({
1199
1199
  "../../node_modules/.pnpm/commander@14.0.3/node_modules/commander/lib/command.js"(exports) {
1200
1200
  var EventEmitter = __require("node:events").EventEmitter;
1201
1201
  var childProcess = __require("node:child_process");
1202
- var path5 = __require("node:path");
1203
- var fs5 = __require("node:fs");
1202
+ var path6 = __require("node:path");
1203
+ var fs6 = __require("node:fs");
1204
1204
  var process2 = __require("node:process");
1205
1205
  var { Argument: Argument2, humanReadableArgName } = require_argument();
1206
1206
  var { CommanderError: CommanderError2 } = require_error();
@@ -2194,7 +2194,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2194
2194
  * @param {string} subcommandName
2195
2195
  */
2196
2196
  _checkForMissingExecutable(executableFile, executableDir, subcommandName) {
2197
- if (fs5.existsSync(executableFile)) return;
2197
+ if (fs6.existsSync(executableFile)) return;
2198
2198
  const executableDirMessage = executableDir ? `searched for local subcommand relative to directory '${executableDir}'` : "no directory for search for local subcommand, use .executableDir() to supply a custom directory";
2199
2199
  const executableMissing = `'${executableFile}' does not exist
2200
2200
  - if '${subcommandName}' is not meant to be an executable command, remove description parameter from '.command()' and use '.description()' instead
@@ -2212,11 +2212,11 @@ Expecting one of '${allowedValues.join("', '")}'`);
2212
2212
  let launchWithNode = false;
2213
2213
  const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
2214
2214
  function findFile(baseDir, baseName) {
2215
- const localBin = path5.resolve(baseDir, baseName);
2216
- if (fs5.existsSync(localBin)) return localBin;
2217
- if (sourceExt.includes(path5.extname(baseName))) return void 0;
2215
+ const localBin = path6.resolve(baseDir, baseName);
2216
+ if (fs6.existsSync(localBin)) return localBin;
2217
+ if (sourceExt.includes(path6.extname(baseName))) return void 0;
2218
2218
  const foundExt = sourceExt.find(
2219
- (ext) => fs5.existsSync(`${localBin}${ext}`)
2219
+ (ext) => fs6.existsSync(`${localBin}${ext}`)
2220
2220
  );
2221
2221
  if (foundExt) return `${localBin}${foundExt}`;
2222
2222
  return void 0;
@@ -2228,21 +2228,21 @@ Expecting one of '${allowedValues.join("', '")}'`);
2228
2228
  if (this._scriptPath) {
2229
2229
  let resolvedScriptPath;
2230
2230
  try {
2231
- resolvedScriptPath = fs5.realpathSync(this._scriptPath);
2231
+ resolvedScriptPath = fs6.realpathSync(this._scriptPath);
2232
2232
  } catch {
2233
2233
  resolvedScriptPath = this._scriptPath;
2234
2234
  }
2235
- executableDir = path5.resolve(
2236
- path5.dirname(resolvedScriptPath),
2235
+ executableDir = path6.resolve(
2236
+ path6.dirname(resolvedScriptPath),
2237
2237
  executableDir
2238
2238
  );
2239
2239
  }
2240
2240
  if (executableDir) {
2241
2241
  let localFile = findFile(executableDir, executableFile);
2242
2242
  if (!localFile && !subcommand._executableFile && this._scriptPath) {
2243
- const legacyName = path5.basename(
2243
+ const legacyName = path6.basename(
2244
2244
  this._scriptPath,
2245
- path5.extname(this._scriptPath)
2245
+ path6.extname(this._scriptPath)
2246
2246
  );
2247
2247
  if (legacyName !== this._name) {
2248
2248
  localFile = findFile(
@@ -2253,7 +2253,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2253
2253
  }
2254
2254
  executableFile = localFile || executableFile;
2255
2255
  }
2256
- launchWithNode = sourceExt.includes(path5.extname(executableFile));
2256
+ launchWithNode = sourceExt.includes(path6.extname(executableFile));
2257
2257
  let proc;
2258
2258
  if (process2.platform !== "win32") {
2259
2259
  if (launchWithNode) {
@@ -3055,9 +3055,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
3055
3055
  * @param {string[]} [aliases]
3056
3056
  * @return {(string[]|Command)}
3057
3057
  */
3058
- aliases(aliases) {
3059
- if (aliases === void 0) return this._aliases;
3060
- aliases.forEach((alias) => this.alias(alias));
3058
+ aliases(aliases2) {
3059
+ if (aliases2 === void 0) return this._aliases;
3060
+ aliases2.forEach((alias) => this.alias(alias));
3061
3061
  return this;
3062
3062
  }
3063
3063
  /**
@@ -3168,7 +3168,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
3168
3168
  * @return {Command}
3169
3169
  */
3170
3170
  nameFromFilename(filename) {
3171
- this._name = path5.basename(filename, path5.extname(filename));
3171
+ this._name = path6.basename(filename, path6.extname(filename));
3172
3172
  return this;
3173
3173
  }
3174
3174
  /**
@@ -3182,9 +3182,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
3182
3182
  * @param {string} [path]
3183
3183
  * @return {(string|null|Command)}
3184
3184
  */
3185
- executableDir(path6) {
3186
- if (path6 === void 0) return this._executableDir;
3187
- this._executableDir = path6;
3185
+ executableDir(path7) {
3186
+ if (path7 === void 0) return this._executableDir;
3187
+ this._executableDir = path7;
3188
3188
  return this;
3189
3189
  }
3190
3190
  /**
@@ -3482,9 +3482,9 @@ var {
3482
3482
  // src/cli.ts
3483
3483
  import { execFileSync as execFileSync3, spawn } from "node:child_process";
3484
3484
  import { createHash, randomBytes as randomBytes2 } from "node:crypto";
3485
- import fs4 from "node:fs";
3485
+ import fs5 from "node:fs";
3486
3486
  import os2 from "node:os";
3487
- import path4 from "node:path";
3487
+ import path5 from "node:path";
3488
3488
  import readline from "node:readline/promises";
3489
3489
  import { fileURLToPath } from "node:url";
3490
3490
  import qrTerminal from "qrcode-terminal";
@@ -3967,8 +3967,8 @@ function getErrorMap() {
3967
3967
 
3968
3968
  // ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/parseUtil.js
3969
3969
  var makeIssue = (params) => {
3970
- const { data, path: path5, errorMaps, issueData } = params;
3971
- const fullPath = [...path5, ...issueData.path || []];
3970
+ const { data, path: path6, errorMaps, issueData } = params;
3971
+ const fullPath = [...path6, ...issueData.path || []];
3972
3972
  const fullIssue = {
3973
3973
  ...issueData,
3974
3974
  path: fullPath
@@ -4084,11 +4084,11 @@ var errorUtil;
4084
4084
 
4085
4085
  // ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/types.js
4086
4086
  var ParseInputLazyPath = class {
4087
- constructor(parent, value, path5, key) {
4087
+ constructor(parent, value, path6, key) {
4088
4088
  this._cachedPath = [];
4089
4089
  this.parent = parent;
4090
4090
  this.data = value;
4091
- this._path = path5;
4091
+ this._path = path6;
4092
4092
  this._key = key;
4093
4093
  }
4094
4094
  get path() {
@@ -7585,6 +7585,23 @@ var chatSchema = external_exports.object({
7585
7585
  unreadCount: external_exports.number().int().nonnegative(),
7586
7586
  isGroup: external_exports.boolean()
7587
7587
  });
7588
+ var groupMemberSchema = external_exports.object({
7589
+ memberId: external_exports.string().min(1),
7590
+ displayName: external_exports.string().min(1),
7591
+ groupAlias: external_exports.string().optional(),
7592
+ nickName: external_exports.string().optional()
7593
+ });
7594
+ var groupMembersPageSchema = external_exports.object({
7595
+ schemaVersion: external_exports.literal(1),
7596
+ items: external_exports.array(groupMemberSchema),
7597
+ nextCursor: external_exports.string().nullable().optional(),
7598
+ errorCode: external_exports.string().optional()
7599
+ });
7600
+ var listGroupMembersParamsSchema = external_exports.object({
7601
+ groupId: external_exports.string().endsWith("@chatroom"),
7602
+ limit: external_exports.number().int().positive().max(100).optional().default(50),
7603
+ cursor: external_exports.string().optional()
7604
+ });
7588
7605
  var listChatsParamsSchema = external_exports.object({
7589
7606
  limit: external_exports.number().int().positive().max(100).optional().default(50),
7590
7607
  offset: external_exports.number().int().nonnegative().optional().default(0)
@@ -7611,6 +7628,22 @@ var openChatResultSchema = external_exports.object({
7611
7628
  error: external_exports.string().optional()
7612
7629
  });
7613
7630
  var idempotencyKeySchema = external_exports.string().min(1).max(128).regex(/^[A-Za-z0-9._:-]+$/);
7631
+ var forwardedMessageNodeSchema = external_exports.lazy(() => external_exports.object({
7632
+ sender: external_exports.string().optional(),
7633
+ senderId: external_exports.string().optional(),
7634
+ timestamp: external_exports.string().optional(),
7635
+ text: external_exports.string().optional(),
7636
+ messageType: external_exports.number().int().optional(),
7637
+ media: external_exports.string().optional(),
7638
+ children: external_exports.array(forwardedMessageNodeSchema),
7639
+ truncated: external_exports.boolean()
7640
+ }));
7641
+ var forwardedMessageTreeSchema = external_exports.object({
7642
+ schemaVersion: external_exports.number().int(),
7643
+ title: external_exports.string().optional(),
7644
+ nodes: external_exports.array(forwardedMessageNodeSchema),
7645
+ truncated: external_exports.boolean()
7646
+ });
7614
7647
  var messageSchema = external_exports.object({
7615
7648
  localId: external_exports.number().int(),
7616
7649
  serverId: external_exports.number(),
@@ -7619,7 +7652,26 @@ var messageSchema = external_exports.object({
7619
7652
  senderName: external_exports.string().optional(),
7620
7653
  type: external_exports.number().int(),
7621
7654
  content: external_exports.string(),
7622
- timestamp: external_exports.string()
7655
+ timestamp: external_exports.string(),
7656
+ forwarded: forwardedMessageTreeSchema.optional()
7657
+ });
7658
+ var mediaReferenceSchema = external_exports.object({
7659
+ localId: external_exports.number().int(),
7660
+ url: external_exports.string().min(1)
7661
+ });
7662
+ var syncReadStateSchema = external_exports.object({
7663
+ unreadCount: external_exports.number().int().nonnegative(),
7664
+ observedAt: external_exports.string().min(1)
7665
+ });
7666
+ var chatSyncPageSchema = external_exports.object({
7667
+ schemaVersion: external_exports.literal(1),
7668
+ chat: chatSchema.optional(),
7669
+ items: external_exports.array(messageSchema),
7670
+ nextCursor: external_exports.string().optional().nullable(),
7671
+ syncToken: external_exports.string(),
7672
+ readState: syncReadStateSchema,
7673
+ media: external_exports.array(mediaReferenceSchema),
7674
+ errorCode: external_exports.string().optional()
7623
7675
  });
7624
7676
  var listMessagesParamsSchema = external_exports.object({
7625
7677
  chatId: external_exports.string().min(1),
@@ -7645,6 +7697,31 @@ var sendParamsSchema = external_exports.object({
7645
7697
  source: external_exports.string().min(1).max(64).regex(/^[A-Za-z0-9._:-]+$/).optional(),
7646
7698
  similarityConfirmed: external_exports.boolean().optional()
7647
7699
  });
7700
+ var deliveryStateSchema = external_exports.enum(["queued", "composing", "submitted", "observed_in_chat", "confirmed", "uncertain", "failed"]);
7701
+ var deliveryCauseSchema = external_exports.enum(["send_accepted", "target_sender_and_payload_match", "observation_confirmed", "target_mismatch", "sender_unverified", "payload_mismatch", "pre_commit_failure", "post_commit_uncertain"]);
7702
+ var deliveryTransitionSchema = external_exports.object({ from: deliveryStateSchema, to: deliveryStateSchema, at: external_exports.string().datetime(), reason: deliveryCauseSchema });
7703
+ var deliveryObservationSchema = external_exports.object({
7704
+ chatId: external_exports.string().trim().min(1).max(512),
7705
+ localId: external_exports.number().int().nonnegative().safe(),
7706
+ serverId: external_exports.number().int().nonnegative().safe(),
7707
+ timestamp: external_exports.string().datetime(),
7708
+ type: external_exports.number().int().nonnegative().safe(),
7709
+ sender: external_exports.string().trim().min(1).max(512),
7710
+ content: external_exports.string().max(1e6)
7711
+ }).strict();
7712
+ var deliveryAttemptSchema = external_exports.object({
7713
+ schemaVersion: external_exports.literal(1),
7714
+ idempotencyKey: idempotencyKeySchema.optional(),
7715
+ senderId: external_exports.string().trim().min(1).max(512),
7716
+ targetChatId: external_exports.string().trim().min(1).max(512),
7717
+ payloadDigest: external_exports.string().regex(/^[a-f0-9]{64}$/),
7718
+ state: deliveryStateSchema,
7719
+ commitAttempted: external_exports.boolean(),
7720
+ createdAt: external_exports.string().datetime(),
7721
+ updatedAt: external_exports.string().datetime(),
7722
+ observedLocalId: external_exports.number().int().nonnegative().optional(),
7723
+ transitions: external_exports.array(deliveryTransitionSchema).max(32)
7724
+ });
7648
7725
  var sendResultSchema = external_exports.object({
7649
7726
  success: external_exports.boolean(),
7650
7727
  messageId: external_exports.string().optional(),
@@ -7657,11 +7734,13 @@ var getMediaParamsSchema = external_exports.object({
7657
7734
  localId: external_exports.number().int()
7658
7735
  });
7659
7736
  var mediaResultSchema = external_exports.object({
7660
- type: external_exports.enum(["image", "emoji", "voice", "video", "unsupported"]),
7737
+ type: external_exports.enum(["image", "emoji", "file", "voice", "video", "pending", "unsupported"]),
7661
7738
  data: external_exports.string().optional(),
7662
7739
  url: external_exports.string().optional(),
7663
7740
  format: external_exports.string(),
7664
- filename: external_exports.string()
7741
+ filename: external_exports.string(),
7742
+ source: external_exports.enum(["original", "thumbnail"]).optional(),
7743
+ errorCode: external_exports.string().optional()
7665
7744
  });
7666
7745
  var agentConfigSchema = external_exports.object({
7667
7746
  maxTurns: external_exports.number().int().positive().default(30),
@@ -7726,19 +7805,20 @@ var WeChatClient = class {
7726
7805
  return this.base;
7727
7806
  }
7728
7807
  // ---- internal helpers ----
7729
- async get(path5) {
7730
- const res = await fetch(`${this.base}${path5}`, {
7808
+ async get(path6) {
7809
+ const res = await fetch(`${this.base}${path6}`, {
7731
7810
  headers: this.headers
7732
7811
  });
7733
7812
  if (!res.ok)
7734
7813
  await throwHttpError(res);
7735
7814
  return res.json();
7736
7815
  }
7737
- async post(path5, body) {
7738
- const res = await fetch(`${this.base}${path5}`, {
7816
+ async post(path6, body, signal) {
7817
+ const res = await fetch(`${this.base}${path6}`, {
7739
7818
  method: "POST",
7740
7819
  headers: this.headers,
7741
- body: body != null ? JSON.stringify(body) : void 0
7820
+ body: body != null ? JSON.stringify(body) : void 0,
7821
+ signal
7742
7822
  });
7743
7823
  if (!res.ok)
7744
7824
  await throwHttpError(res);
@@ -7783,8 +7863,14 @@ var WeChatClient = class {
7783
7863
  async markChatRead(chatId) {
7784
7864
  return this.post(`/api/chats/${encodeURIComponent(chatId)}/mark-read`);
7785
7865
  }
7786
- async openChat(chatId, clearUnreads) {
7787
- return this.post(`/api/chats/${encodeURIComponent(chatId)}/open${qs({ clearUnreads })}`);
7866
+ async openChat(chatId, clearUnreads, signal, executionTimeoutMs) {
7867
+ return this.post(`/api/chats/${encodeURIComponent(chatId)}/open${qs({ clearUnreads, executionTimeoutMs })}`, void 0, signal);
7868
+ }
7869
+ async listGroupMembersPage(groupId, limit, cursor) {
7870
+ const page = groupMembersPageSchema.parse(await this.get(`/api/groups/${encodeURIComponent(groupId)}/members${qs({ limit, cursor })}`));
7871
+ if (page.errorCode)
7872
+ throw new WeChatHttpError(400, "Bad Request", page.errorCode, page.errorCode);
7873
+ return { ...page, nextCursor: page.nextCursor ?? void 0 };
7788
7874
  }
7789
7875
  // ---- Contacts ----
7790
7876
  async listContactsPage(limit, cursor) {
@@ -7809,6 +7895,17 @@ var WeChatClient = class {
7809
7895
  async listMessages(chatId, limit) {
7810
7896
  return (await this.listMessagesPage(chatId, limit)).items;
7811
7897
  }
7898
+ async syncChat(chatId, options = {}) {
7899
+ const raw = await this.get(`/api/sync/${encodeURIComponent(chatId)}${qs({ limit: options.limit, cursor: options.cursor, since: options.since, from: options.from, to: options.to })}`);
7900
+ if (raw && typeof raw === "object" && typeof raw.errorCode === "string") {
7901
+ const errorCode = raw.errorCode;
7902
+ throw new WeChatHttpError(400, "Bad Request", errorCode, errorCode);
7903
+ }
7904
+ const page = chatSyncPageSchema.parse(raw);
7905
+ if (page.errorCode)
7906
+ throw new WeChatHttpError(400, "Bad Request", page.errorCode, page.errorCode);
7907
+ return { ...page, nextCursor: page.nextCursor ?? void 0 };
7908
+ }
7812
7909
  async getMedia(chatId, localId) {
7813
7910
  return this.get(`/api/messages/${encodeURIComponent(chatId)}/media/${localId}`);
7814
7911
  }
@@ -8178,9 +8275,8 @@ function buildDockerRunArgs(identity, opts) {
8178
8275
  "-e",
8179
8276
  `AGENT_WECHAT_MAC=${identity.mac}`
8180
8277
  ];
8181
- if (opts.proxy) {
8182
- args.push("-e", `PROXY=${opts.proxy}`);
8183
- }
8278
+ if (opts.proxy) args.push("-e", `PROXY=${opts.proxy}`);
8279
+ for (const [key, value] of Object.entries(opts.outbound ?? {})) args.push("-e", `${key}=${value}`);
8184
8280
  args.push(opts.image);
8185
8281
  return args;
8186
8282
  }
@@ -8210,17 +8306,18 @@ function buildCliSendParams(options) {
8210
8306
  var FORK_IMAGE = "ghcr.io/kyan-du/agent-wechat";
8211
8307
  var VERSION_TAG = /^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-[0-9A-Za-z.-]+)?$/;
8212
8308
  var DIGEST = /^sha256:[0-9a-f]{64}$/;
8309
+ var COMMIT_TAG = /^[a-f0-9]{7}$/;
8213
8310
  function validatePublishedImageReference(reference) {
8214
8311
  const tagPrefix = `${FORK_IMAGE}:`;
8215
8312
  const digestPrefix = `${FORK_IMAGE}@`;
8216
- if (reference.startsWith(tagPrefix) && VERSION_TAG.test(reference.slice(tagPrefix.length))) {
8313
+ if (reference.startsWith(tagPrefix) && (VERSION_TAG.test(reference.slice(tagPrefix.length)) || COMMIT_TAG.test(reference.slice(tagPrefix.length)))) {
8217
8314
  return reference;
8218
8315
  }
8219
8316
  if (reference.startsWith(digestPrefix) && DIGEST.test(reference.slice(digestPrefix.length))) {
8220
8317
  return reference;
8221
8318
  }
8222
8319
  throw new Error(
8223
- `Invalid image reference. Use ${FORK_IMAGE}:<semver> or ${FORK_IMAGE}@sha256:<64 lowercase hex characters>.`
8320
+ `Invalid image reference. Use ${FORK_IMAGE}:<semver>, ${FORK_IMAGE}:<7-character commit SHA>, or ${FORK_IMAGE}@sha256:<64 lowercase hex characters>.`
8224
8321
  );
8225
8322
  }
8226
8323
  function localBuildImage(platform = process.platform, arch = process.arch) {
@@ -8326,8 +8423,8 @@ function assertSafePurgePath(target, root) {
8326
8423
 
8327
8424
  // src/lifecycle.ts
8328
8425
  import { execFileSync } from "node:child_process";
8329
- import fs3 from "node:fs";
8330
- import path3 from "node:path";
8426
+ import fs4 from "node:fs";
8427
+ import path4 from "node:path";
8331
8428
 
8332
8429
  // src/exit-contract.ts
8333
8430
  var EXIT = {
@@ -8369,47 +8466,910 @@ function failure(error) {
8369
8466
  };
8370
8467
  }
8371
8468
 
8372
- // src/lifecycle-policy.ts
8373
- var INSTANCE_LABEL = "dev.visionclaw.agent-wechat.instance";
8374
- var IMAGE_LABEL = "dev.visionclaw.agent-wechat.image";
8375
- var VOLUME_ROLE_LABEL = "dev.visionclaw.agent-wechat.volume-role";
8376
- function exactEnv(env, key) {
8377
- const values = (env ?? []).filter((entry) => entry.startsWith(`${key}=`)).map((entry) => entry.slice(key.length + 1));
8378
- return values.length === 1 ? values[0] : void 0;
8469
+ // src/outbound-config.ts
8470
+ import fs3 from "node:fs";
8471
+ import path3 from "node:path";
8472
+
8473
+ // ../../node_modules/.pnpm/smol-toml@1.6.0/node_modules/smol-toml/dist/error.js
8474
+ function getLineColFromPtr(string, ptr) {
8475
+ let lines = string.slice(0, ptr).split(/\r\n|\n|\r/g);
8476
+ return [lines.length, lines.pop().length + 1];
8477
+ }
8478
+ function makeCodeBlock(string, line, column) {
8479
+ let lines = string.split(/\r\n|\n|\r/g);
8480
+ let codeblock = "";
8481
+ let numberLen = (Math.log10(line + 1) | 0) + 1;
8482
+ for (let i = line - 1; i <= line + 1; i++) {
8483
+ let l = lines[i - 1];
8484
+ if (!l)
8485
+ continue;
8486
+ codeblock += i.toString().padEnd(numberLen, " ");
8487
+ codeblock += ": ";
8488
+ codeblock += l;
8489
+ codeblock += "\n";
8490
+ if (i === line) {
8491
+ codeblock += " ".repeat(numberLen + column + 2);
8492
+ codeblock += "^\n";
8493
+ }
8494
+ }
8495
+ return codeblock;
8496
+ }
8497
+ var TomlError = class extends Error {
8498
+ line;
8499
+ column;
8500
+ codeblock;
8501
+ constructor(message, options) {
8502
+ const [line, column] = getLineColFromPtr(options.toml, options.ptr);
8503
+ const codeblock = makeCodeBlock(options.toml, line, column);
8504
+ super(`Invalid TOML document: ${message}
8505
+
8506
+ ${codeblock}`, options);
8507
+ this.line = line;
8508
+ this.column = column;
8509
+ this.codeblock = codeblock;
8510
+ }
8511
+ };
8512
+
8513
+ // ../../node_modules/.pnpm/smol-toml@1.6.0/node_modules/smol-toml/dist/util.js
8514
+ function isEscaped(str, ptr) {
8515
+ let i = 0;
8516
+ while (str[ptr - ++i] === "\\")
8517
+ ;
8518
+ return --i && i % 2;
8519
+ }
8520
+ function indexOfNewline(str, start = 0, end = str.length) {
8521
+ let idx = str.indexOf("\n", start);
8522
+ if (str[idx - 1] === "\r")
8523
+ idx--;
8524
+ return idx <= end ? idx : -1;
8525
+ }
8526
+ function skipComment(str, ptr) {
8527
+ for (let i = ptr; i < str.length; i++) {
8528
+ let c = str[i];
8529
+ if (c === "\n")
8530
+ return i;
8531
+ if (c === "\r" && str[i + 1] === "\n")
8532
+ return i + 1;
8533
+ if (c < " " && c !== " " || c === "\x7F") {
8534
+ throw new TomlError("control characters are not allowed in comments", {
8535
+ toml: str,
8536
+ ptr
8537
+ });
8538
+ }
8539
+ }
8540
+ return str.length;
8541
+ }
8542
+ function skipVoid(str, ptr, banNewLines, banComments) {
8543
+ let c;
8544
+ while ((c = str[ptr]) === " " || c === " " || !banNewLines && (c === "\n" || c === "\r" && str[ptr + 1] === "\n"))
8545
+ ptr++;
8546
+ return banComments || c !== "#" ? ptr : skipVoid(str, skipComment(str, ptr), banNewLines);
8547
+ }
8548
+ function skipUntil(str, ptr, sep, end, banNewLines = false) {
8549
+ if (!end) {
8550
+ ptr = indexOfNewline(str, ptr);
8551
+ return ptr < 0 ? str.length : ptr;
8552
+ }
8553
+ for (let i = ptr; i < str.length; i++) {
8554
+ let c = str[i];
8555
+ if (c === "#") {
8556
+ i = indexOfNewline(str, i);
8557
+ } else if (c === sep) {
8558
+ return i + 1;
8559
+ } else if (c === end || banNewLines && (c === "\n" || c === "\r" && str[i + 1] === "\n")) {
8560
+ return i;
8561
+ }
8562
+ }
8563
+ throw new TomlError("cannot find end of structure", {
8564
+ toml: str,
8565
+ ptr
8566
+ });
8567
+ }
8568
+ function getStringEnd(str, seek) {
8569
+ let first = str[seek];
8570
+ let target = first === str[seek + 1] && str[seek + 1] === str[seek + 2] ? str.slice(seek, seek + 3) : first;
8571
+ seek += target.length - 1;
8572
+ do
8573
+ seek = str.indexOf(target, ++seek);
8574
+ while (seek > -1 && first !== "'" && isEscaped(str, seek));
8575
+ if (seek > -1) {
8576
+ seek += target.length;
8577
+ if (target.length > 1) {
8578
+ if (str[seek] === first)
8579
+ seek++;
8580
+ if (str[seek] === first)
8581
+ seek++;
8582
+ }
8583
+ }
8584
+ return seek;
8585
+ }
8586
+
8587
+ // ../../node_modules/.pnpm/smol-toml@1.6.0/node_modules/smol-toml/dist/date.js
8588
+ var DATE_TIME_RE = /^(\d{4}-\d{2}-\d{2})?[T ]?(?:(\d{2}):\d{2}(?::\d{2}(?:\.\d+)?)?)?(Z|[-+]\d{2}:\d{2})?$/i;
8589
+ var TomlDate = class _TomlDate extends Date {
8590
+ #hasDate = false;
8591
+ #hasTime = false;
8592
+ #offset = null;
8593
+ constructor(date) {
8594
+ let hasDate = true;
8595
+ let hasTime = true;
8596
+ let offset = "Z";
8597
+ if (typeof date === "string") {
8598
+ let match = date.match(DATE_TIME_RE);
8599
+ if (match) {
8600
+ if (!match[1]) {
8601
+ hasDate = false;
8602
+ date = `0000-01-01T${date}`;
8603
+ }
8604
+ hasTime = !!match[2];
8605
+ hasTime && date[10] === " " && (date = date.replace(" ", "T"));
8606
+ if (match[2] && +match[2] > 23) {
8607
+ date = "";
8608
+ } else {
8609
+ offset = match[3] || null;
8610
+ date = date.toUpperCase();
8611
+ if (!offset && hasTime)
8612
+ date += "Z";
8613
+ }
8614
+ } else {
8615
+ date = "";
8616
+ }
8617
+ }
8618
+ super(date);
8619
+ if (!isNaN(this.getTime())) {
8620
+ this.#hasDate = hasDate;
8621
+ this.#hasTime = hasTime;
8622
+ this.#offset = offset;
8623
+ }
8624
+ }
8625
+ isDateTime() {
8626
+ return this.#hasDate && this.#hasTime;
8627
+ }
8628
+ isLocal() {
8629
+ return !this.#hasDate || !this.#hasTime || !this.#offset;
8630
+ }
8631
+ isDate() {
8632
+ return this.#hasDate && !this.#hasTime;
8633
+ }
8634
+ isTime() {
8635
+ return this.#hasTime && !this.#hasDate;
8636
+ }
8637
+ isValid() {
8638
+ return this.#hasDate || this.#hasTime;
8639
+ }
8640
+ toISOString() {
8641
+ let iso = super.toISOString();
8642
+ if (this.isDate())
8643
+ return iso.slice(0, 10);
8644
+ if (this.isTime())
8645
+ return iso.slice(11, 23);
8646
+ if (this.#offset === null)
8647
+ return iso.slice(0, -1);
8648
+ if (this.#offset === "Z")
8649
+ return iso;
8650
+ let offset = +this.#offset.slice(1, 3) * 60 + +this.#offset.slice(4, 6);
8651
+ offset = this.#offset[0] === "-" ? offset : -offset;
8652
+ let offsetDate = new Date(this.getTime() - offset * 6e4);
8653
+ return offsetDate.toISOString().slice(0, -1) + this.#offset;
8654
+ }
8655
+ static wrapAsOffsetDateTime(jsDate, offset = "Z") {
8656
+ let date = new _TomlDate(jsDate);
8657
+ date.#offset = offset;
8658
+ return date;
8659
+ }
8660
+ static wrapAsLocalDateTime(jsDate) {
8661
+ let date = new _TomlDate(jsDate);
8662
+ date.#offset = null;
8663
+ return date;
8664
+ }
8665
+ static wrapAsLocalDate(jsDate) {
8666
+ let date = new _TomlDate(jsDate);
8667
+ date.#hasTime = false;
8668
+ date.#offset = null;
8669
+ return date;
8670
+ }
8671
+ static wrapAsLocalTime(jsDate) {
8672
+ let date = new _TomlDate(jsDate);
8673
+ date.#hasDate = false;
8674
+ date.#offset = null;
8675
+ return date;
8676
+ }
8677
+ };
8678
+
8679
+ // ../../node_modules/.pnpm/smol-toml@1.6.0/node_modules/smol-toml/dist/primitive.js
8680
+ var INT_REGEX = /^((0x[0-9a-fA-F](_?[0-9a-fA-F])*)|(([+-]|0[ob])?\d(_?\d)*))$/;
8681
+ var FLOAT_REGEX = /^[+-]?\d(_?\d)*(\.\d(_?\d)*)?([eE][+-]?\d(_?\d)*)?$/;
8682
+ var LEADING_ZERO = /^[+-]?0[0-9_]/;
8683
+ var ESCAPE_REGEX = /^[0-9a-f]{2,8}$/i;
8684
+ var ESC_MAP = {
8685
+ b: "\b",
8686
+ t: " ",
8687
+ n: "\n",
8688
+ f: "\f",
8689
+ r: "\r",
8690
+ e: "\x1B",
8691
+ '"': '"',
8692
+ "\\": "\\"
8693
+ };
8694
+ function parseString(str, ptr = 0, endPtr = str.length) {
8695
+ let isLiteral = str[ptr] === "'";
8696
+ let isMultiline = str[ptr++] === str[ptr] && str[ptr] === str[ptr + 1];
8697
+ if (isMultiline) {
8698
+ endPtr -= 2;
8699
+ if (str[ptr += 2] === "\r")
8700
+ ptr++;
8701
+ if (str[ptr] === "\n")
8702
+ ptr++;
8703
+ }
8704
+ let tmp = 0;
8705
+ let isEscape;
8706
+ let parsed = "";
8707
+ let sliceStart = ptr;
8708
+ while (ptr < endPtr - 1) {
8709
+ let c = str[ptr++];
8710
+ if (c === "\n" || c === "\r" && str[ptr] === "\n") {
8711
+ if (!isMultiline) {
8712
+ throw new TomlError("newlines are not allowed in strings", {
8713
+ toml: str,
8714
+ ptr: ptr - 1
8715
+ });
8716
+ }
8717
+ } else if (c < " " && c !== " " || c === "\x7F") {
8718
+ throw new TomlError("control characters are not allowed in strings", {
8719
+ toml: str,
8720
+ ptr: ptr - 1
8721
+ });
8722
+ }
8723
+ if (isEscape) {
8724
+ isEscape = false;
8725
+ if (c === "x" || c === "u" || c === "U") {
8726
+ let code = str.slice(ptr, ptr += c === "x" ? 2 : c === "u" ? 4 : 8);
8727
+ if (!ESCAPE_REGEX.test(code)) {
8728
+ throw new TomlError("invalid unicode escape", {
8729
+ toml: str,
8730
+ ptr: tmp
8731
+ });
8732
+ }
8733
+ try {
8734
+ parsed += String.fromCodePoint(parseInt(code, 16));
8735
+ } catch {
8736
+ throw new TomlError("invalid unicode escape", {
8737
+ toml: str,
8738
+ ptr: tmp
8739
+ });
8740
+ }
8741
+ } else if (isMultiline && (c === "\n" || c === " " || c === " " || c === "\r")) {
8742
+ ptr = skipVoid(str, ptr - 1, true);
8743
+ if (str[ptr] !== "\n" && str[ptr] !== "\r") {
8744
+ throw new TomlError("invalid escape: only line-ending whitespace may be escaped", {
8745
+ toml: str,
8746
+ ptr: tmp
8747
+ });
8748
+ }
8749
+ ptr = skipVoid(str, ptr);
8750
+ } else if (c in ESC_MAP) {
8751
+ parsed += ESC_MAP[c];
8752
+ } else {
8753
+ throw new TomlError("unrecognized escape sequence", {
8754
+ toml: str,
8755
+ ptr: tmp
8756
+ });
8757
+ }
8758
+ sliceStart = ptr;
8759
+ } else if (!isLiteral && c === "\\") {
8760
+ tmp = ptr - 1;
8761
+ isEscape = true;
8762
+ parsed += str.slice(sliceStart, tmp);
8763
+ }
8764
+ }
8765
+ return parsed + str.slice(sliceStart, endPtr - 1);
8766
+ }
8767
+ function parseValue(value, toml, ptr, integersAsBigInt) {
8768
+ if (value === "true")
8769
+ return true;
8770
+ if (value === "false")
8771
+ return false;
8772
+ if (value === "-inf")
8773
+ return -Infinity;
8774
+ if (value === "inf" || value === "+inf")
8775
+ return Infinity;
8776
+ if (value === "nan" || value === "+nan" || value === "-nan")
8777
+ return NaN;
8778
+ if (value === "-0")
8779
+ return integersAsBigInt ? 0n : 0;
8780
+ let isInt = INT_REGEX.test(value);
8781
+ if (isInt || FLOAT_REGEX.test(value)) {
8782
+ if (LEADING_ZERO.test(value)) {
8783
+ throw new TomlError("leading zeroes are not allowed", {
8784
+ toml,
8785
+ ptr
8786
+ });
8787
+ }
8788
+ value = value.replace(/_/g, "");
8789
+ let numeric = +value;
8790
+ if (isNaN(numeric)) {
8791
+ throw new TomlError("invalid number", {
8792
+ toml,
8793
+ ptr
8794
+ });
8795
+ }
8796
+ if (isInt) {
8797
+ if ((isInt = !Number.isSafeInteger(numeric)) && !integersAsBigInt) {
8798
+ throw new TomlError("integer value cannot be represented losslessly", {
8799
+ toml,
8800
+ ptr
8801
+ });
8802
+ }
8803
+ if (isInt || integersAsBigInt === true)
8804
+ numeric = BigInt(value);
8805
+ }
8806
+ return numeric;
8807
+ }
8808
+ const date = new TomlDate(value);
8809
+ if (!date.isValid()) {
8810
+ throw new TomlError("invalid value", {
8811
+ toml,
8812
+ ptr
8813
+ });
8814
+ }
8815
+ return date;
8816
+ }
8817
+
8818
+ // ../../node_modules/.pnpm/smol-toml@1.6.0/node_modules/smol-toml/dist/extract.js
8819
+ function sliceAndTrimEndOf(str, startPtr, endPtr) {
8820
+ let value = str.slice(startPtr, endPtr);
8821
+ let commentIdx = value.indexOf("#");
8822
+ if (commentIdx > -1) {
8823
+ skipComment(str, commentIdx);
8824
+ value = value.slice(0, commentIdx);
8825
+ }
8826
+ return [value.trimEnd(), commentIdx];
8827
+ }
8828
+ function extractValue(str, ptr, end, depth, integersAsBigInt) {
8829
+ if (depth === 0) {
8830
+ throw new TomlError("document contains excessively nested structures. aborting.", {
8831
+ toml: str,
8832
+ ptr
8833
+ });
8834
+ }
8835
+ let c = str[ptr];
8836
+ if (c === "[" || c === "{") {
8837
+ let [value, endPtr2] = c === "[" ? parseArray(str, ptr, depth, integersAsBigInt) : parseInlineTable(str, ptr, depth, integersAsBigInt);
8838
+ if (end) {
8839
+ endPtr2 = skipVoid(str, endPtr2);
8840
+ if (str[endPtr2] === ",")
8841
+ endPtr2++;
8842
+ else if (str[endPtr2] !== end) {
8843
+ throw new TomlError("expected comma or end of structure", {
8844
+ toml: str,
8845
+ ptr: endPtr2
8846
+ });
8847
+ }
8848
+ }
8849
+ return [value, endPtr2];
8850
+ }
8851
+ let endPtr;
8852
+ if (c === '"' || c === "'") {
8853
+ endPtr = getStringEnd(str, ptr);
8854
+ let parsed = parseString(str, ptr, endPtr);
8855
+ if (end) {
8856
+ endPtr = skipVoid(str, endPtr);
8857
+ if (str[endPtr] && str[endPtr] !== "," && str[endPtr] !== end && str[endPtr] !== "\n" && str[endPtr] !== "\r") {
8858
+ throw new TomlError("unexpected character encountered", {
8859
+ toml: str,
8860
+ ptr: endPtr
8861
+ });
8862
+ }
8863
+ endPtr += +(str[endPtr] === ",");
8864
+ }
8865
+ return [parsed, endPtr];
8866
+ }
8867
+ endPtr = skipUntil(str, ptr, ",", end);
8868
+ let slice = sliceAndTrimEndOf(str, ptr, endPtr - +(str[endPtr - 1] === ","));
8869
+ if (!slice[0]) {
8870
+ throw new TomlError("incomplete key-value declaration: no value specified", {
8871
+ toml: str,
8872
+ ptr
8873
+ });
8874
+ }
8875
+ if (end && slice[1] > -1) {
8876
+ endPtr = skipVoid(str, ptr + slice[1]);
8877
+ endPtr += +(str[endPtr] === ",");
8878
+ }
8879
+ return [
8880
+ parseValue(slice[0], str, ptr, integersAsBigInt),
8881
+ endPtr
8882
+ ];
8883
+ }
8884
+
8885
+ // ../../node_modules/.pnpm/smol-toml@1.6.0/node_modules/smol-toml/dist/struct.js
8886
+ var KEY_PART_RE = /^[a-zA-Z0-9-_]+[ \t]*$/;
8887
+ function parseKey(str, ptr, end = "=") {
8888
+ let dot = ptr - 1;
8889
+ let parsed = [];
8890
+ let endPtr = str.indexOf(end, ptr);
8891
+ if (endPtr < 0) {
8892
+ throw new TomlError("incomplete key-value: cannot find end of key", {
8893
+ toml: str,
8894
+ ptr
8895
+ });
8896
+ }
8897
+ do {
8898
+ let c = str[ptr = ++dot];
8899
+ if (c !== " " && c !== " ") {
8900
+ if (c === '"' || c === "'") {
8901
+ if (c === str[ptr + 1] && c === str[ptr + 2]) {
8902
+ throw new TomlError("multiline strings are not allowed in keys", {
8903
+ toml: str,
8904
+ ptr
8905
+ });
8906
+ }
8907
+ let eos = getStringEnd(str, ptr);
8908
+ if (eos < 0) {
8909
+ throw new TomlError("unfinished string encountered", {
8910
+ toml: str,
8911
+ ptr
8912
+ });
8913
+ }
8914
+ dot = str.indexOf(".", eos);
8915
+ let strEnd = str.slice(eos, dot < 0 || dot > endPtr ? endPtr : dot);
8916
+ let newLine = indexOfNewline(strEnd);
8917
+ if (newLine > -1) {
8918
+ throw new TomlError("newlines are not allowed in keys", {
8919
+ toml: str,
8920
+ ptr: ptr + dot + newLine
8921
+ });
8922
+ }
8923
+ if (strEnd.trimStart()) {
8924
+ throw new TomlError("found extra tokens after the string part", {
8925
+ toml: str,
8926
+ ptr: eos
8927
+ });
8928
+ }
8929
+ if (endPtr < eos) {
8930
+ endPtr = str.indexOf(end, eos);
8931
+ if (endPtr < 0) {
8932
+ throw new TomlError("incomplete key-value: cannot find end of key", {
8933
+ toml: str,
8934
+ ptr
8935
+ });
8936
+ }
8937
+ }
8938
+ parsed.push(parseString(str, ptr, eos));
8939
+ } else {
8940
+ dot = str.indexOf(".", ptr);
8941
+ let part = str.slice(ptr, dot < 0 || dot > endPtr ? endPtr : dot);
8942
+ if (!KEY_PART_RE.test(part)) {
8943
+ throw new TomlError("only letter, numbers, dashes and underscores are allowed in keys", {
8944
+ toml: str,
8945
+ ptr
8946
+ });
8947
+ }
8948
+ parsed.push(part.trimEnd());
8949
+ }
8950
+ }
8951
+ } while (dot + 1 && dot < endPtr);
8952
+ return [parsed, skipVoid(str, endPtr + 1, true, true)];
8953
+ }
8954
+ function parseInlineTable(str, ptr, depth, integersAsBigInt) {
8955
+ let res = {};
8956
+ let seen = /* @__PURE__ */ new Set();
8957
+ let c;
8958
+ ptr++;
8959
+ while ((c = str[ptr++]) !== "}" && c) {
8960
+ if (c === ",") {
8961
+ throw new TomlError("expected value, found comma", {
8962
+ toml: str,
8963
+ ptr: ptr - 1
8964
+ });
8965
+ } else if (c === "#")
8966
+ ptr = skipComment(str, ptr);
8967
+ else if (c !== " " && c !== " " && c !== "\n" && c !== "\r") {
8968
+ let k;
8969
+ let t = res;
8970
+ let hasOwn = false;
8971
+ let [key, keyEndPtr] = parseKey(str, ptr - 1);
8972
+ for (let i = 0; i < key.length; i++) {
8973
+ if (i)
8974
+ t = hasOwn ? t[k] : t[k] = {};
8975
+ k = key[i];
8976
+ if ((hasOwn = Object.hasOwn(t, k)) && (typeof t[k] !== "object" || seen.has(t[k]))) {
8977
+ throw new TomlError("trying to redefine an already defined value", {
8978
+ toml: str,
8979
+ ptr
8980
+ });
8981
+ }
8982
+ if (!hasOwn && k === "__proto__") {
8983
+ Object.defineProperty(t, k, { enumerable: true, configurable: true, writable: true });
8984
+ }
8985
+ }
8986
+ if (hasOwn) {
8987
+ throw new TomlError("trying to redefine an already defined value", {
8988
+ toml: str,
8989
+ ptr
8990
+ });
8991
+ }
8992
+ let [value, valueEndPtr] = extractValue(str, keyEndPtr, "}", depth - 1, integersAsBigInt);
8993
+ seen.add(value);
8994
+ t[k] = value;
8995
+ ptr = valueEndPtr;
8996
+ }
8997
+ }
8998
+ if (!c) {
8999
+ throw new TomlError("unfinished table encountered", {
9000
+ toml: str,
9001
+ ptr
9002
+ });
9003
+ }
9004
+ return [res, ptr];
8379
9005
  }
8380
- function containerOwnershipError(info, inventory) {
8381
- if (inventory.containerId && info.Id !== inventory.containerId) {
8382
- return { code: "CONTAINER_ID_MISMATCH", message: "container identity changed" };
9006
+ function parseArray(str, ptr, depth, integersAsBigInt) {
9007
+ let res = [];
9008
+ let c;
9009
+ ptr++;
9010
+ while ((c = str[ptr++]) !== "]" && c) {
9011
+ if (c === ",") {
9012
+ throw new TomlError("expected value, found comma", {
9013
+ toml: str,
9014
+ ptr: ptr - 1
9015
+ });
9016
+ } else if (c === "#")
9017
+ ptr = skipComment(str, ptr);
9018
+ else if (c !== " " && c !== " " && c !== "\n" && c !== "\r") {
9019
+ let e = extractValue(str, ptr - 1, "]", depth - 1, integersAsBigInt);
9020
+ res.push(e[0]);
9021
+ ptr = e[1];
9022
+ }
9023
+ }
9024
+ if (!c) {
9025
+ throw new TomlError("unfinished array encountered", {
9026
+ toml: str,
9027
+ ptr
9028
+ });
8383
9029
  }
8384
- if (info.Config?.Labels?.[INSTANCE_LABEL] !== "default") {
8385
- return { code: "CONTAINER_OWNERSHIP_MISMATCH", message: "container is not owned by this instance" };
9030
+ return [res, ptr];
9031
+ }
9032
+
9033
+ // ../../node_modules/.pnpm/smol-toml@1.6.0/node_modules/smol-toml/dist/parse.js
9034
+ function peekTable(key, table, meta, type) {
9035
+ let t = table;
9036
+ let m = meta;
9037
+ let k;
9038
+ let hasOwn = false;
9039
+ let state;
9040
+ for (let i = 0; i < key.length; i++) {
9041
+ if (i) {
9042
+ t = hasOwn ? t[k] : t[k] = {};
9043
+ m = (state = m[k]).c;
9044
+ if (type === 0 && (state.t === 1 || state.t === 2)) {
9045
+ return null;
9046
+ }
9047
+ if (state.t === 2) {
9048
+ let l = t.length - 1;
9049
+ t = t[l];
9050
+ m = m[l].c;
9051
+ }
9052
+ }
9053
+ k = key[i];
9054
+ if ((hasOwn = Object.hasOwn(t, k)) && m[k]?.t === 0 && m[k]?.d) {
9055
+ return null;
9056
+ }
9057
+ if (!hasOwn) {
9058
+ if (k === "__proto__") {
9059
+ Object.defineProperty(t, k, { enumerable: true, configurable: true, writable: true });
9060
+ Object.defineProperty(m, k, { enumerable: true, configurable: true, writable: true });
9061
+ }
9062
+ m[k] = {
9063
+ t: i < key.length - 1 && type === 2 ? 3 : type,
9064
+ d: false,
9065
+ i: 0,
9066
+ c: {}
9067
+ };
9068
+ }
8386
9069
  }
8387
- const expectedImage = inventory.imageDigest ?? inventory.imageRef;
8388
- if (info.Config?.Image !== expectedImage || info.Config?.Labels?.[IMAGE_LABEL] !== expectedImage) {
8389
- return { code: "CONTAINER_IMAGE_MISMATCH", message: "container image does not match inventory" };
9070
+ state = m[k];
9071
+ if (state.t !== type && !(type === 1 && state.t === 3)) {
9072
+ return null;
8390
9073
  }
8391
- const env = info.Config?.Env;
8392
- if (info.Config?.Hostname !== inventory.identity.hostname || exactEnv(env, "AGENT_WECHAT_MACHINE_ID") !== inventory.identity.machineId || exactEnv(env, "AGENT_WECHAT_HOSTNAME") !== inventory.identity.hostname || exactEnv(env, "AGENT_WECHAT_MAC") !== inventory.identity.mac) {
8393
- return { code: "CONTAINER_IDENTITY_MISMATCH", message: "container identity does not match inventory" };
9074
+ if (type === 2) {
9075
+ if (!state.d) {
9076
+ state.d = true;
9077
+ t[k] = [];
9078
+ }
9079
+ t[k].push(t = {});
9080
+ state.c[state.i++] = state = { t: 1, d: false, i: 0, c: {} };
8394
9081
  }
8395
- const mounts = new Map((info.Mounts ?? []).map((mount) => [mount.Destination, mount]));
8396
- const data = mounts.get("/data");
8397
- const home = mounts.get("/home/wechat");
8398
- const token = mounts.get("/data/auth-token");
8399
- if (data?.Type !== "volume" || data.Name !== inventory.volumes[0] || home?.Type !== "volume" || home.Name !== inventory.volumes[1] || token?.Type !== "bind" || token.Source !== inventory.tokenPath) {
8400
- return { code: "CONTAINER_RESOURCE_MISMATCH", message: "container mounts do not match inventory" };
9082
+ if (state.d) {
9083
+ return null;
8401
9084
  }
8402
- const bindings = info.HostConfig?.PortBindings?.[`${inventory.port}/tcp`];
8403
- if (!bindings || bindings.length !== 1 || bindings[0]?.HostPort !== String(inventory.port)) {
8404
- return { code: "CONTAINER_PORT_MISMATCH", message: "container port does not match inventory" };
9085
+ state.d = true;
9086
+ if (type === 1) {
9087
+ t = hasOwn ? t[k] : t[k] = {};
9088
+ } else if (type === 0 && hasOwn) {
9089
+ return null;
8405
9090
  }
8406
- return void 0;
9091
+ return [k, t, state.c];
8407
9092
  }
8408
- function volumeOwnershipError(info, expectedName, expectedRole) {
8409
- if (info.Name !== expectedName || info.Driver !== "local" || info.Labels?.[INSTANCE_LABEL] !== "default" || info.Labels?.[VOLUME_ROLE_LABEL] !== expectedRole) {
8410
- return { code: "VOLUME_OWNERSHIP_MISMATCH", message: `volume ${expectedName} is outside the trusted instance scope` };
9093
+ function parse(toml, { maxDepth = 1e3, integersAsBigInt } = {}) {
9094
+ let res = {};
9095
+ let meta = {};
9096
+ let tbl = res;
9097
+ let m = meta;
9098
+ for (let ptr = skipVoid(toml, 0); ptr < toml.length; ) {
9099
+ if (toml[ptr] === "[") {
9100
+ let isTableArray = toml[++ptr] === "[";
9101
+ let k = parseKey(toml, ptr += +isTableArray, "]");
9102
+ if (isTableArray) {
9103
+ if (toml[k[1] - 1] !== "]") {
9104
+ throw new TomlError("expected end of table declaration", {
9105
+ toml,
9106
+ ptr: k[1] - 1
9107
+ });
9108
+ }
9109
+ k[1]++;
9110
+ }
9111
+ let p = peekTable(
9112
+ k[0],
9113
+ res,
9114
+ meta,
9115
+ isTableArray ? 2 : 1
9116
+ /* Type.EXPLICIT */
9117
+ );
9118
+ if (!p) {
9119
+ throw new TomlError("trying to redefine an already defined table or value", {
9120
+ toml,
9121
+ ptr
9122
+ });
9123
+ }
9124
+ m = p[2];
9125
+ tbl = p[1];
9126
+ ptr = k[1];
9127
+ } else {
9128
+ let k = parseKey(toml, ptr);
9129
+ let p = peekTable(
9130
+ k[0],
9131
+ tbl,
9132
+ m,
9133
+ 0
9134
+ /* Type.DOTTED */
9135
+ );
9136
+ if (!p) {
9137
+ throw new TomlError("trying to redefine an already defined table or value", {
9138
+ toml,
9139
+ ptr
9140
+ });
9141
+ }
9142
+ let v = extractValue(toml, k[1], void 0, maxDepth, integersAsBigInt);
9143
+ p[1][p[0]] = v[0];
9144
+ ptr = v[1];
9145
+ }
9146
+ ptr = skipVoid(toml, ptr, true);
9147
+ if (toml[ptr] && toml[ptr] !== "\n" && toml[ptr] !== "\r") {
9148
+ throw new TomlError("each key-value declaration must be followed by an end-of-line", {
9149
+ toml,
9150
+ ptr
9151
+ });
9152
+ }
9153
+ ptr = skipVoid(toml, ptr);
8411
9154
  }
8412
- return void 0;
9155
+ return res;
9156
+ }
9157
+
9158
+ // ../../node_modules/.pnpm/smol-toml@1.6.0/node_modules/smol-toml/dist/stringify.js
9159
+ var BARE_KEY = /^[a-z0-9-_]+$/i;
9160
+ function extendedTypeOf(obj) {
9161
+ let type = typeof obj;
9162
+ if (type === "object") {
9163
+ if (Array.isArray(obj))
9164
+ return "array";
9165
+ if (obj instanceof Date)
9166
+ return "date";
9167
+ }
9168
+ return type;
9169
+ }
9170
+ function isArrayOfTables(obj) {
9171
+ for (let i = 0; i < obj.length; i++) {
9172
+ if (extendedTypeOf(obj[i]) !== "object")
9173
+ return false;
9174
+ }
9175
+ return obj.length != 0;
9176
+ }
9177
+ function formatString(s) {
9178
+ return JSON.stringify(s).replace(/\x7f/g, "\\u007f");
9179
+ }
9180
+ function stringifyValue(val, type, depth, numberAsFloat) {
9181
+ if (depth === 0) {
9182
+ throw new Error("Could not stringify the object: maximum object depth exceeded");
9183
+ }
9184
+ if (type === "number") {
9185
+ if (isNaN(val))
9186
+ return "nan";
9187
+ if (val === Infinity)
9188
+ return "inf";
9189
+ if (val === -Infinity)
9190
+ return "-inf";
9191
+ if (numberAsFloat && Number.isInteger(val))
9192
+ return val.toFixed(1);
9193
+ return val.toString();
9194
+ }
9195
+ if (type === "bigint" || type === "boolean") {
9196
+ return val.toString();
9197
+ }
9198
+ if (type === "string") {
9199
+ return formatString(val);
9200
+ }
9201
+ if (type === "date") {
9202
+ if (isNaN(val.getTime())) {
9203
+ throw new TypeError("cannot serialize invalid date");
9204
+ }
9205
+ return val.toISOString();
9206
+ }
9207
+ if (type === "object") {
9208
+ return stringifyInlineTable(val, depth, numberAsFloat);
9209
+ }
9210
+ if (type === "array") {
9211
+ return stringifyArray(val, depth, numberAsFloat);
9212
+ }
9213
+ }
9214
+ function stringifyInlineTable(obj, depth, numberAsFloat) {
9215
+ let keys = Object.keys(obj);
9216
+ if (keys.length === 0)
9217
+ return "{}";
9218
+ let res = "{ ";
9219
+ for (let i = 0; i < keys.length; i++) {
9220
+ let k = keys[i];
9221
+ if (i)
9222
+ res += ", ";
9223
+ res += BARE_KEY.test(k) ? k : formatString(k);
9224
+ res += " = ";
9225
+ res += stringifyValue(obj[k], extendedTypeOf(obj[k]), depth - 1, numberAsFloat);
9226
+ }
9227
+ return res + " }";
9228
+ }
9229
+ function stringifyArray(array, depth, numberAsFloat) {
9230
+ if (array.length === 0)
9231
+ return "[]";
9232
+ let res = "[ ";
9233
+ for (let i = 0; i < array.length; i++) {
9234
+ if (i)
9235
+ res += ", ";
9236
+ if (array[i] === null || array[i] === void 0) {
9237
+ throw new TypeError("arrays cannot contain null or undefined values");
9238
+ }
9239
+ res += stringifyValue(array[i], extendedTypeOf(array[i]), depth - 1, numberAsFloat);
9240
+ }
9241
+ return res + " ]";
9242
+ }
9243
+ function stringifyArrayTable(array, key, depth, numberAsFloat) {
9244
+ if (depth === 0) {
9245
+ throw new Error("Could not stringify the object: maximum object depth exceeded");
9246
+ }
9247
+ let res = "";
9248
+ for (let i = 0; i < array.length; i++) {
9249
+ res += `${res && "\n"}[[${key}]]
9250
+ `;
9251
+ res += stringifyTable(0, array[i], key, depth, numberAsFloat);
9252
+ }
9253
+ return res;
9254
+ }
9255
+ function stringifyTable(tableKey, obj, prefix, depth, numberAsFloat) {
9256
+ if (depth === 0) {
9257
+ throw new Error("Could not stringify the object: maximum object depth exceeded");
9258
+ }
9259
+ let preamble = "";
9260
+ let tables = "";
9261
+ let keys = Object.keys(obj);
9262
+ for (let i = 0; i < keys.length; i++) {
9263
+ let k = keys[i];
9264
+ if (obj[k] !== null && obj[k] !== void 0) {
9265
+ let type = extendedTypeOf(obj[k]);
9266
+ if (type === "symbol" || type === "function") {
9267
+ throw new TypeError(`cannot serialize values of type '${type}'`);
9268
+ }
9269
+ let key = BARE_KEY.test(k) ? k : formatString(k);
9270
+ if (type === "array" && isArrayOfTables(obj[k])) {
9271
+ tables += (tables && "\n") + stringifyArrayTable(obj[k], prefix ? `${prefix}.${key}` : key, depth - 1, numberAsFloat);
9272
+ } else if (type === "object") {
9273
+ let tblKey = prefix ? `${prefix}.${key}` : key;
9274
+ tables += (tables && "\n") + stringifyTable(tblKey, obj[k], tblKey, depth - 1, numberAsFloat);
9275
+ } else {
9276
+ preamble += key;
9277
+ preamble += " = ";
9278
+ preamble += stringifyValue(obj[k], type, depth, numberAsFloat);
9279
+ preamble += "\n";
9280
+ }
9281
+ }
9282
+ }
9283
+ if (tableKey && (preamble || !tables))
9284
+ preamble = preamble ? `[${tableKey}]
9285
+ ${preamble}` : `[${tableKey}]`;
9286
+ return preamble && tables ? `${preamble}
9287
+ ${tables}` : preamble || tables;
9288
+ }
9289
+ function stringify(obj, { maxDepth = 1e3, numbersAsFloat = false } = {}) {
9290
+ if (extendedTypeOf(obj) !== "object") {
9291
+ throw new TypeError("stringify can only be called with an object");
9292
+ }
9293
+ let str = stringifyTable(0, obj, "", maxDepth, numbersAsFloat);
9294
+ if (str[str.length - 1] !== "\n")
9295
+ return str + "\n";
9296
+ return str;
9297
+ }
9298
+
9299
+ // ../../node_modules/.pnpm/smol-toml@1.6.0/node_modules/smol-toml/dist/index.js
9300
+ var dist_default = { parse, stringify, TomlDate, TomlError };
9301
+
9302
+ // src/outbound-config.ts
9303
+ var OUTBOUND_ENV_KEYS = [
9304
+ "AGENT_WECHAT_OUTBOUND_QUEUE_CAPACITY",
9305
+ "AGENT_WECHAT_OUTBOUND_MIN_SPACING_MS",
9306
+ "AGENT_WECHAT_OUTBOUND_JITTER_MS",
9307
+ "AGENT_WECHAT_CHAT_COOLDOWN_MS",
9308
+ "AGENT_WECHAT_HOURLY_BUDGET",
9309
+ "AGENT_WECHAT_DAILY_BUDGET",
9310
+ "AGENT_WECHAT_QUIET_START_MIN",
9311
+ "AGENT_WECHAT_QUIET_END_MIN",
9312
+ "AGENT_WECHAT_OUTBOUND_LONG_TAIL_JITTER_MS",
9313
+ "AGENT_WECHAT_OUTBOUND_LONG_TAIL_CHANCE_PERCENT",
9314
+ "AGENT_WECHAT_OUTBOUND_TASK_TTL_MS",
9315
+ "AGENT_WECHAT_OUTBOUND_IDEMPOTENCY_TTL_MS",
9316
+ "AGENT_WECHAT_OUTBOUND_IDEMPOTENCY_MAX_ROWS",
9317
+ "AGENT_WECHAT_OUTBOUND_DISABLED"
9318
+ ];
9319
+ var aliases = {
9320
+ queueCapacity: "AGENT_WECHAT_OUTBOUND_QUEUE_CAPACITY",
9321
+ minSpacingMs: "AGENT_WECHAT_OUTBOUND_MIN_SPACING_MS",
9322
+ jitterMs: "AGENT_WECHAT_OUTBOUND_JITTER_MS",
9323
+ chatCooldownMs: "AGENT_WECHAT_CHAT_COOLDOWN_MS",
9324
+ hourlyBudget: "AGENT_WECHAT_HOURLY_BUDGET",
9325
+ dailyBudget: "AGENT_WECHAT_DAILY_BUDGET",
9326
+ quietStartMin: "AGENT_WECHAT_QUIET_START_MIN",
9327
+ quietEndMin: "AGENT_WECHAT_QUIET_END_MIN",
9328
+ longTailJitterMs: "AGENT_WECHAT_OUTBOUND_LONG_TAIL_JITTER_MS",
9329
+ longTailChancePercent: "AGENT_WECHAT_OUTBOUND_LONG_TAIL_CHANCE_PERCENT",
9330
+ taskTtlMs: "AGENT_WECHAT_OUTBOUND_TASK_TTL_MS",
9331
+ idempotencyTtlMs: "AGENT_WECHAT_OUTBOUND_IDEMPOTENCY_TTL_MS",
9332
+ idempotencyMaxRows: "AGENT_WECHAT_OUTBOUND_IDEMPOTENCY_MAX_ROWS",
9333
+ disabled: "AGENT_WECHAT_OUTBOUND_DISABLED"
9334
+ };
9335
+ function readOutboundConfig(file) {
9336
+ if (!file) return {};
9337
+ const parsed = dist_default.parse(fs3.readFileSync(path3.resolve(file), "utf8"));
9338
+ const result = {};
9339
+ for (const [key, env] of Object.entries(aliases)) if (parsed[key] !== void 0) result[env] = String(parsed[key]);
9340
+ for (const key of OUTBOUND_ENV_KEYS) if (parsed[key] !== void 0) result[key] = String(parsed[key]);
9341
+ return result;
9342
+ }
9343
+ function outboundFromEnvEntries(entries) {
9344
+ const result = {};
9345
+ const allowed = new Set(OUTBOUND_ENV_KEYS);
9346
+ for (const entry of entries) {
9347
+ const index = entry.indexOf("=");
9348
+ if (index > 0 && allowed.has(entry.slice(0, index))) result[entry.slice(0, index)] = entry.slice(index + 1);
9349
+ }
9350
+ return result;
9351
+ }
9352
+ function resolveOutboundConfig(file, cli = {}) {
9353
+ const config = file ? readOutboundConfig(file) : {};
9354
+ const env = {};
9355
+ for (const key of OUTBOUND_ENV_KEYS) if (process.env[key] !== void 0) env[key] = process.env[key];
9356
+ return { ...config, ...env, ...cli };
9357
+ }
9358
+
9359
+ // src/lifecycle-policy.ts
9360
+ var INSTANCE_LABEL = "dev.visionclaw.agent-wechat.instance";
9361
+ var IMAGE_LABEL = "dev.visionclaw.agent-wechat.image";
9362
+ var VOLUME_ROLE_LABEL = "dev.visionclaw.agent-wechat.volume-role";
9363
+ function hasOwnedContainer(info) {
9364
+ return info.Config?.Labels?.[INSTANCE_LABEL] === "default";
9365
+ }
9366
+ function isReconcileableContainer(info, inventory) {
9367
+ if (hasOwnedContainer(info)) return true;
9368
+ const id = inventory?.containerId;
9369
+ return Boolean(id && (info.Id === id || info.Id.startsWith(id) || id.startsWith(info.Id)));
9370
+ }
9371
+ function hasOwnedVolume(existing, name, role) {
9372
+ return existing.Name === name && existing.Driver === "local" && existing.Labels?.[INSTANCE_LABEL] === "default" && existing.Labels?.[VOLUME_ROLE_LABEL] === role;
8413
9373
  }
8414
9374
 
8415
9375
  // src/lifecycle.ts
@@ -8490,8 +9450,12 @@ function ensureOwnedVolumes(inventory) {
8490
9450
  docker(["volume", "create", "--driver", "local", "--label", `${INSTANCE_LABEL}=default`, "--label", `${VOLUME_ROLE_LABEL}=${roles[index]}`, name]);
8491
9451
  continue;
8492
9452
  }
8493
- const violation = volumeOwnershipError(existing, name, roles[index]);
8494
- if (violation) throw new CliError(violation.code, violation.message, EXIT.ENVIRONMENT);
9453
+ assertVolumeOwnership(existing, name, roles[index]);
9454
+ }
9455
+ }
9456
+ function assertVolumeOwnership(existing, name, role) {
9457
+ if (!hasOwnedVolume(existing, name, role)) {
9458
+ throw new CliError("VOLUME_OWNERSHIP_MISMATCH", `refusing to use volume ${name} without trusted ownership`, EXIT.ENVIRONMENT);
8495
9459
  }
8496
9460
  }
8497
9461
  function assertOwnedVolumes(inventory) {
@@ -8499,10 +9463,12 @@ function assertOwnedVolumes(inventory) {
8499
9463
  for (const [index, name] of inventory.volumes.entries()) {
8500
9464
  const existing = inspectVolume(name);
8501
9465
  if (!existing) continue;
8502
- const violation = volumeOwnershipError(existing, name, roles[index]);
8503
- if (violation) throw new CliError(violation.code, violation.message, EXIT.CLEANUP);
9466
+ assertVolumeOwnership(existing, name, roles[index]);
8504
9467
  }
8505
9468
  }
9469
+ function assertOwnedContainer(info, inventory) {
9470
+ if (!isReconcileableContainer(info, inventory)) throw new CliError("CONTAINER_OWNERSHIP_MISMATCH", "refusing to operate on an unowned container with the fixed name", EXIT.ENVIRONMENT);
9471
+ }
8506
9472
  function clearContainerIdentity(inventory) {
8507
9473
  const info = inspectContainer();
8508
9474
  if (!info) throw new CliError("INSTANCE_NOT_RUNNING", "trusted container is required to clear container identity", EXIT.CLEANUP);
@@ -8519,8 +9485,7 @@ function removeOwnedVolume(inventory, index, role) {
8519
9485
  const name = inventory.volumes[index];
8520
9486
  const existing = inspectVolume(name);
8521
9487
  if (!existing) return false;
8522
- const violation = volumeOwnershipError(existing, name, role);
8523
- if (violation) throw new CliError(violation.code, violation.message, EXIT.CLEANUP);
9488
+ assertVolumeOwnership(existing, name, role);
8524
9489
  docker(["volume", "rm", name]);
8525
9490
  return true;
8526
9491
  }
@@ -8542,10 +9507,6 @@ function resolveImage(options) {
8542
9507
  if (published && !digest) throw new CliError("IMAGE_DIGEST_UNRESOLVED", "published image did not resolve to a fork repo digest", EXIT.ENVIRONMENT);
8543
9508
  return { runReference: digest || requested, requestedReference: requested, digest };
8544
9509
  }
8545
- function assertOwnedContainer(info, inventory) {
8546
- const violation = containerOwnershipError(info, inventory);
8547
- if (violation) throw new CliError(violation.code, violation.message, EXIT.ENVIRONMENT);
8548
- }
8549
9510
  async function waitCompatible(token, timeoutMs = 3e4) {
8550
9511
  await waitHealthy(timeoutMs);
8551
9512
  try {
@@ -8572,21 +9533,19 @@ async function waitHealthy(timeoutMs = 3e4) {
8572
9533
  }
8573
9534
  async function startInstance(options) {
8574
9535
  if (!dockerAvailable()) throw new CliError("DOCKER_UNAVAILABLE", "Docker daemon is unavailable", EXIT.ENVIRONMENT);
8575
- const selected = resolveImage({ explicit: options.image, pull: options.pull, noPull: options.noPull, localDefault: options.localDefault });
8576
9536
  const existing = inspectContainer();
8577
9537
  const current = loadInventory();
8578
9538
  if (existing) {
8579
- if (!current) throw new CliError("INSTANCE_INVENTORY_MISSING", "existing container has no trusted inventory", EXIT.ENVIRONMENT);
8580
9539
  assertOwnedContainer(existing, current);
8581
- const expected = selected.digest || selected.runReference;
8582
- const recorded = existing.Config?.Labels?.["dev.visionclaw.agent-wechat.image"];
8583
- if (existing.Config?.Image !== expected || recorded !== expected) {
8584
- throw new CliError("IMAGE_COMPATIBILITY_MISMATCH", "existing container image does not match requested compatible image", EXIT.ENVIRONMENT);
9540
+ if (options.outbound && Object.keys(options.outbound).length > 0) {
9541
+ throw new CliError("OUTBOUND_RESTART_REQUIRED", "outbound policy changed; run wx restart to recreate the container", EXIT.ARGUMENT);
8585
9542
  }
8586
- if (!existing.State?.Running) docker(["start", existing.Id], { inherit: true });
9543
+ if (!current) throw new CliError("INSTANCE_INVENTORY_MISSING", "existing container has no trusted inventory", EXIT.ENVIRONMENT);
9544
+ if (!existing.State?.Running) docker(["start", CONTAINER_NAME], { inherit: true });
8587
9545
  await waitCompatible(options.token);
8588
9546
  return current;
8589
9547
  }
9548
+ const selected = resolveImage({ explicit: options.image, pull: options.pull, noPull: options.noPull, localDefault: options.localDefault });
8590
9549
  const provisional = createInventory(selected.requestedReference, options.identity, selected.digest);
8591
9550
  saveInventory(provisional);
8592
9551
  ensureOwnedVolumes(provisional);
@@ -8595,7 +9554,8 @@ async function startInstance(options) {
8595
9554
  containerName: CONTAINER_NAME,
8596
9555
  tokenPath: TOKEN_PATH,
8597
9556
  port: DEFAULT_PORT,
8598
- proxy: options.proxy
9557
+ proxy: options.proxy,
9558
+ outbound: options.outbound
8599
9559
  });
8600
9560
  args.splice(args.length - 1, 0, "--label", `${INSTANCE_LABEL}=default`, "--label", `${IMAGE_LABEL}=${selected.digest || selected.runReference}`);
8601
9561
  docker(args, { inherit: true });
@@ -8612,13 +9572,28 @@ async function startInstance(options) {
8612
9572
  }
8613
9573
  return inventory;
8614
9574
  }
9575
+ function outboundFromContainer(info) {
9576
+ return outboundFromEnvEntries(info.Config?.Env ?? []);
9577
+ }
9578
+ function stopContainer(idOrName) {
9579
+ docker(["stop", idOrName], { inherit: true });
9580
+ }
9581
+ function renameContainer(id, name) {
9582
+ docker(["rename", id, name], { inherit: true });
9583
+ }
9584
+ function removeContainer(id) {
9585
+ docker(["rm", "-f", id], { inherit: true });
9586
+ }
9587
+ function startContainer(idOrName) {
9588
+ docker(["start", idOrName], { inherit: true });
9589
+ }
8615
9590
  function stopInstance() {
8616
9591
  if (!dockerAvailable()) throw new CliError("DOCKER_UNAVAILABLE", "Docker daemon is unavailable", EXIT.ENVIRONMENT);
8617
9592
  const info = inspectContainer();
8618
9593
  if (!info) return { stopped: false };
8619
9594
  const inventory = loadInventory();
8620
- if (!inventory) throw new CliError("INSTANCE_INVENTORY_MISSING", "refusing to remove an unowned container", EXIT.ENVIRONMENT);
8621
9595
  assertOwnedContainer(info, inventory);
9596
+ if (!inventory) throw new CliError("INSTANCE_INVENTORY_MISSING", "refusing to remove an unowned container", EXIT.ENVIRONMENT);
8622
9597
  docker(["rm", "-f", info.Id], { inherit: true });
8623
9598
  return { stopped: true };
8624
9599
  }
@@ -8637,18 +9612,18 @@ function purgeInstance() {
8637
9612
  remaining.push(`volume:${volume}`);
8638
9613
  }
8639
9614
  }
8640
- for (const file of [TOKEN_PATH, path3.join(CONFIG_DIR, "device-identity.env"), path3.join(CONFIG_DIR, "device-identity.json")]) {
9615
+ for (const file of [TOKEN_PATH, path4.join(CONFIG_DIR, "device-identity.env"), path4.join(CONFIG_DIR, "device-identity.json")]) {
8641
9616
  try {
8642
9617
  assertSafePurgePath(file, CONFIG_DIR);
8643
9618
  } catch {
8644
- remaining.push(path3.basename(file));
9619
+ remaining.push(path4.basename(file));
8645
9620
  continue;
8646
9621
  }
8647
9622
  try {
8648
- fs3.unlinkSync(file);
8649
- removed.push(path3.basename(file));
9623
+ fs4.unlinkSync(file);
9624
+ removed.push(path4.basename(file));
8650
9625
  } catch (error) {
8651
- if (error.code !== "ENOENT") remaining.push(path3.basename(file));
9626
+ if (error.code !== "ENOENT") remaining.push(path4.basename(file));
8652
9627
  }
8653
9628
  }
8654
9629
  if (remaining.length === 0) removeInventory();
@@ -8660,8 +9635,9 @@ async function replaceImage(options) {
8660
9635
  if (!previous) throw new CliError("INSTANCE_INVENTORY_MISSING", "start the instance before image upgrade", EXIT.ENVIRONMENT);
8661
9636
  const info = inspectContainer();
8662
9637
  if (info) assertOwnedContainer(info, previous);
9638
+ const reconciled = previous;
8663
9639
  const selected = resolveImage({ explicit: options.image, pull: true, localDefault: previous.imageRef });
8664
- if (selected.digest === previous.imageDigest) return previous;
9640
+ if (selected.digest === reconciled.imageDigest) return reconciled;
8665
9641
  if (info) {
8666
9642
  if (info.State?.Running) docker(["stop", info.Id], { inherit: true });
8667
9643
  docker(["rename", info.Id, `${CONTAINER_NAME}-rollback`]);
@@ -8675,7 +9651,7 @@ async function replaceImage(options) {
8675
9651
  const failed = inspectContainer();
8676
9652
  if (failed) docker(["rm", "-f", failed.Id]);
8677
9653
  if (info) docker(["rename", info.Id, CONTAINER_NAME]);
8678
- saveInventory(previous);
9654
+ saveInventory(reconciled);
8679
9655
  if (info?.State?.Running) docker(["start", CONTAINER_NAME]);
8680
9656
  } catch {
8681
9657
  throw new CliError("ROLLBACK_FAILED", "image upgrade failed and rollback was incomplete", EXIT.ROLLBACK);
@@ -8761,12 +9737,12 @@ function checkCliUpgrade(currentVersion, readLatest = () => execFileSync2("npm",
8761
9737
  }
8762
9738
 
8763
9739
  // src/cli.ts
8764
- var VERSION = false ? "0.0.0-test" : "0.13.3";
9740
+ var VERSION = false ? "0.0.0-test" : "0.14.1";
8765
9741
  var MAX_UPLOAD_BYTES = 50 * 1024 * 1024;
8766
9742
  function readToken() {
8767
9743
  try {
8768
9744
  secureRegularFile(TOKEN_PATH);
8769
- const value = fs4.readFileSync(TOKEN_PATH, "utf8").trim();
9745
+ const value = fs5.readFileSync(TOKEN_PATH, "utf8").trim();
8770
9746
  if (!/^[a-f0-9]{64}$/.test(value)) throw new Error("INVALID_AUTH_TOKEN");
8771
9747
  return value;
8772
9748
  } catch (error) {
@@ -8778,9 +9754,9 @@ function readToken() {
8778
9754
  function ensureToken() {
8779
9755
  const current = readToken();
8780
9756
  if (current) return current;
8781
- fs4.mkdirSync(CONFIG_DIR, { recursive: true, mode: 448 });
9757
+ fs5.mkdirSync(CONFIG_DIR, { recursive: true, mode: 448 });
8782
9758
  const token = randomBytes2(32).toString("hex");
8783
- fs4.writeFileSync(TOKEN_PATH, `${token}
9759
+ fs5.writeFileSync(TOKEN_PATH, `${token}
8784
9760
  `, { mode: 384, flag: "wx" });
8785
9761
  return token;
8786
9762
  }
@@ -8880,13 +9856,13 @@ async function login(timeoutMs, newAccount) {
8880
9856
  function regularUpload(target) {
8881
9857
  let stat;
8882
9858
  try {
8883
- stat = fs4.lstatSync(target);
9859
+ stat = fs5.lstatSync(target);
8884
9860
  } catch {
8885
9861
  throw new CliError("FILE_NOT_FOUND", `file not found: ${target}`, EXIT.ARGUMENT);
8886
9862
  }
8887
9863
  if (!stat.isFile() || stat.isSymbolicLink()) throw new CliError("INVALID_UPLOAD_FILE", "upload must be a regular non-symlink file", EXIT.ARGUMENT);
8888
9864
  if (stat.size > MAX_UPLOAD_BYTES) throw new CliError("UPLOAD_TOO_LARGE", `upload exceeds ${MAX_UPLOAD_BYTES} bytes`, EXIT.ARGUMENT);
8889
- return fs4.readFileSync(target);
9865
+ return fs5.readFileSync(target);
8890
9866
  }
8891
9867
  function renderMessages(page) {
8892
9868
  for (const item of [...page.items].reverse()) console.log(`${item.localId} ${item.timestamp} ${item.senderName || item.sender || ""} ${item.content}`);
@@ -8903,15 +9879,15 @@ async function resetAuth(yes) {
8903
9879
  try {
8904
9880
  removeOwnedVolume(inventory, 1, "wechat-home");
8905
9881
  } catch (error) {
8906
- if (error instanceof CliError && error.code === "VOLUME_OWNERSHIP_MISMATCH") throw error;
9882
+ if (error instanceof CliError && error.code === "VOLUME_DRIVER_UNSUPPORTED") throw error;
8907
9883
  throw new CliError("AUTH_RESET_CLEANUP_INCOMPLETE", `remaining resource: volume:${inventory.volumes[1]}`, EXIT.CLEANUP);
8908
9884
  }
8909
9885
  const remaining = [];
8910
9886
  for (const file of ["device-identity.env", "device-identity.json"]) {
8911
- const target = path4.join(CONFIG_DIR, file);
9887
+ const target = path5.join(CONFIG_DIR, file);
8912
9888
  try {
8913
9889
  assertSafePurgePath(target, CONFIG_DIR);
8914
- fs4.unlinkSync(target);
9890
+ fs5.unlinkSync(target);
8915
9891
  } catch (error) {
8916
9892
  if (error.code !== "ENOENT") remaining.push(file);
8917
9893
  }
@@ -8926,7 +9902,6 @@ function programStatus() {
8926
9902
  const container = inspectContainer();
8927
9903
  if (container) {
8928
9904
  if (!inventory) throw new CliError("INSTANCE_INVENTORY_MISSING", "existing container has no trusted inventory", EXIT.ENVIRONMENT);
8929
- assertOwnedContainer(container, inventory);
8930
9905
  }
8931
9906
  return {
8932
9907
  cliVersion: VERSION,
@@ -8959,9 +9934,9 @@ program2.name("wx").description("Single-instance agent-wechat CLI").version(VERS
8959
9934
  program2.showSuggestionAfterError();
8960
9935
  program2.configureOutput({ outputError: (text) => process.stderr.write(text) });
8961
9936
  program2.exitOverride();
8962
- program2.command("start").description("Start the compatible single-instance container").option("--image <reference>", `exact ${GHCR_IMAGE} semver tag or digest`).option("--pull", "pull and resolve the selected image").option("--offline", "offline mode; never pull").option("--proxy <url>", "transparent proxy URL").action((options) => action(async () => {
9937
+ program2.command("start").description("Start the compatible single-instance container").option("--image <reference>", `exact ${GHCR_IMAGE} semver/commit tag or digest`).option("--pull", "pull and resolve the selected image").option("--offline", "offline mode; never pull").option("--proxy <url>", "transparent proxy URL").option("--outbound-config <file>", "TOML outbound policy file (never logged)").option("--outbound-queue-capacity <count>", "outbound queue capacity").option("--outbound-disabled", "disable outbound sending").option("--outbound-quiet-start <minutes>", "quiet-hours start minute").option("--outbound-quiet-end <minutes>", "quiet-hours end minute").option("--outbound-hourly-budget <count>", "hourly outbound budget").option("--outbound-daily-budget <count>", "daily outbound budget").option("--outbound-chat-cooldown-ms <ms>", "per-chat cooldown").option("--outbound-min-spacing-ms <ms>", "minimum spacing").option("--outbound-jitter-ms <ms>", "spacing jitter").option("--outbound-long-tail-jitter-ms <ms>", "long-tail jitter").option("--outbound-long-tail-chance-percent <percent>", "long-tail chance").option("--outbound-task-ttl-ms <ms>", "task TTL").option("--outbound-idempotency-ttl-ms <ms>", "idempotency TTL").option("--outbound-idempotency-max-rows <rows>", "idempotency max rows").action((options) => action(async () => {
8963
9938
  if (options.pull && options.offline) throw new CliError("ARGUMENT_CONFLICT", "--pull and --offline are mutually exclusive", EXIT.ARGUMENT);
8964
- const inventory = await startInstance({ identity: ensureDeviceIdentity(CONFIG_DIR), token: ensureToken(), localDefault: localBuildImage(), noPull: options.offline === true, ...options });
9939
+ const inventory = await startInstance({ identity: ensureDeviceIdentity(CONFIG_DIR), token: ensureToken(), localDefault: localBuildImage(), noPull: options.offline === true, outbound: resolveOutboundConfig(options.outboundConfig, Object.fromEntries([["AGENT_WECHAT_OUTBOUND_QUEUE_CAPACITY", options.outboundQueueCapacity], ["AGENT_WECHAT_OUTBOUND_DISABLED", options.outboundDisabled ? "true" : void 0], ["AGENT_WECHAT_QUIET_START_MIN", options.outboundQuietStart], ["AGENT_WECHAT_QUIET_END_MIN", options.outboundQuietEnd], ["AGENT_WECHAT_HOURLY_BUDGET", options.outboundHourlyBudget], ["AGENT_WECHAT_DAILY_BUDGET", options.outboundDailyBudget], ["AGENT_WECHAT_CHAT_COOLDOWN_MS", options.outboundChatCooldownMs], ["AGENT_WECHAT_OUTBOUND_MIN_SPACING_MS", options.outboundMinSpacingMs], ["AGENT_WECHAT_OUTBOUND_JITTER_MS", options.outboundJitterMs], ["AGENT_WECHAT_OUTBOUND_LONG_TAIL_JITTER_MS", options.outboundLongTailJitterMs], ["AGENT_WECHAT_OUTBOUND_LONG_TAIL_CHANCE_PERCENT", options.outboundLongTailChancePercent], ["AGENT_WECHAT_OUTBOUND_TASK_TTL_MS", options.outboundTaskTtlMs], ["AGENT_WECHAT_OUTBOUND_IDEMPOTENCY_TTL_MS", options.outboundIdempotencyTtlMs], ["AGENT_WECHAT_OUTBOUND_IDEMPOTENCY_MAX_ROWS", options.outboundIdempotencyMaxRows]].filter(([, v]) => v !== void 0))), ...options });
8965
9940
  output({ container: inventory.containerName, imageDigest: inventory.imageDigest, port: inventory.port }, () => console.log(`Started ${inventory.containerName} on port ${inventory.port}`));
8966
9941
  }));
8967
9942
  program2.command("stop").description("Stop and remove the container; preserve data unless --purge").option("--purge", "delete every resource in the trusted default-instance inventory").option("--yes", "skip only the interactive confirmation").action((options) => action(async () => {
@@ -8976,12 +9951,59 @@ program2.command("stop").description("Stop and remove the container; preserve da
8976
9951
  output(result, () => console.log(result.stopped ? "Container stopped and removed" : "Container is absent"));
8977
9952
  }
8978
9953
  }));
8979
- program2.command("restart").description("Restart while preserving data").action(() => action(async () => {
9954
+ program2.command("restart").description("Restart while preserving data").option("--outbound-config <file>", "TOML outbound policy file (never logged)").option("--outbound-queue-capacity <count>", "outbound queue capacity").option("--outbound-disabled", "disable outbound sending").option("--outbound-quiet-start <minutes>").option("--outbound-quiet-end <minutes>").option("--outbound-hourly-budget <count>").option("--outbound-daily-budget <count>").option("--outbound-chat-cooldown-ms <ms>").option("--outbound-min-spacing-ms <ms>").option("--outbound-jitter-ms <ms>").option("--outbound-long-tail-jitter-ms <ms>").option("--outbound-long-tail-chance-percent <percent>").option("--outbound-task-ttl-ms <ms>").option("--outbound-idempotency-ttl-ms <ms>").option("--outbound-idempotency-max-rows <rows>").action((options) => action(async () => {
8980
9955
  const inventory = loadInventory();
8981
9956
  if (!inventory) throw new CliError("INSTANCE_INVENTORY_MISSING", "start the instance first", EXIT.SERVICE);
8982
- stopInstance();
8983
- const result = await startInstance({ identity: ensureDeviceIdentity(CONFIG_DIR), token: ensureToken(), image: inventory.imageDigest || inventory.imageRef, noPull: true, localDefault: localBuildImage() });
8984
- output({ restarted: true, imageDigest: result.imageDigest }, () => console.log("Instance restarted"));
9957
+ const oldContainer = inspectContainer();
9958
+ const cliOverrides = Object.fromEntries([
9959
+ ["AGENT_WECHAT_OUTBOUND_QUEUE_CAPACITY", options.outboundQueueCapacity],
9960
+ ["AGENT_WECHAT_OUTBOUND_DISABLED", options.outboundDisabled ? "true" : void 0],
9961
+ ["AGENT_WECHAT_QUIET_START_MIN", options.outboundQuietStart],
9962
+ ["AGENT_WECHAT_QUIET_END_MIN", options.outboundQuietEnd],
9963
+ ["AGENT_WECHAT_HOURLY_BUDGET", options.outboundHourlyBudget],
9964
+ ["AGENT_WECHAT_DAILY_BUDGET", options.outboundDailyBudget],
9965
+ ["AGENT_WECHAT_CHAT_COOLDOWN_MS", options.outboundChatCooldownMs],
9966
+ ["AGENT_WECHAT_OUTBOUND_MIN_SPACING_MS", options.outboundMinSpacingMs],
9967
+ ["AGENT_WECHAT_OUTBOUND_JITTER_MS", options.outboundJitterMs],
9968
+ ["AGENT_WECHAT_OUTBOUND_LONG_TAIL_JITTER_MS", options.outboundLongTailJitterMs],
9969
+ ["AGENT_WECHAT_OUTBOUND_LONG_TAIL_CHANCE_PERCENT", options.outboundLongTailChancePercent],
9970
+ ["AGENT_WECHAT_OUTBOUND_TASK_TTL_MS", options.outboundTaskTtlMs],
9971
+ ["AGENT_WECHAT_OUTBOUND_IDEMPOTENCY_TTL_MS", options.outboundIdempotencyTtlMs],
9972
+ ["AGENT_WECHAT_OUTBOUND_IDEMPOTENCY_MAX_ROWS", options.outboundIdempotencyMaxRows]
9973
+ ].filter(([, v]) => v !== void 0));
9974
+ const outbound = resolveOutboundConfig(options.outboundConfig, cliOverrides);
9975
+ const oldOutbound = oldContainer ? outboundFromContainer(oldContainer) : {};
9976
+ const effectiveOutbound = { ...oldOutbound, ...outbound };
9977
+ const previousInventory = inventory;
9978
+ const rollbackName = `${CONTAINER_NAME}-rollback`;
9979
+ let oldStopped = false;
9980
+ let oldRenamed = false;
9981
+ try {
9982
+ if (oldContainer) {
9983
+ if (oldContainer.State?.Running) {
9984
+ stopContainer(oldContainer.Id);
9985
+ oldStopped = true;
9986
+ }
9987
+ renameContainer(oldContainer.Id, rollbackName);
9988
+ oldRenamed = true;
9989
+ }
9990
+ const result = await startInstance({ identity: ensureDeviceIdentity(CONFIG_DIR), token: ensureToken(), image: inventory.imageDigest || inventory.imageRef, noPull: true, localDefault: localBuildImage(), outbound: effectiveOutbound });
9991
+ if (oldRenamed) removeContainer(rollbackName);
9992
+ output({ restarted: true, imageDigest: result.imageDigest }, () => console.log("Instance restarted"));
9993
+ } catch (error) {
9994
+ try {
9995
+ const failed = inspectContainer();
9996
+ if (failed && failed.Id !== oldContainer?.Id) removeContainer(failed.Id);
9997
+ if (oldContainer) {
9998
+ if (oldRenamed) renameContainer(rollbackName, CONTAINER_NAME);
9999
+ if (oldStopped && oldContainer.State?.Running) startContainer(CONTAINER_NAME);
10000
+ }
10001
+ saveInventory(previousInventory);
10002
+ } catch {
10003
+ throw new CliError("ROLLBACK_FAILED", "restart failed and rollback was incomplete", EXIT.ROLLBACK);
10004
+ }
10005
+ throw error;
10006
+ }
8985
10007
  }));
8986
10008
  program2.command("status").description("Show read-only lifecycle and authentication status").action(() => action(async () => {
8987
10009
  const status = programStatus();
@@ -9008,7 +10030,7 @@ program2.command("doctor").description("Run read-only environment and compatibil
9008
10030
  const checks = programStatus();
9009
10031
  checks.architecture = os2.arch();
9010
10032
  checks.token = readToken() ? "present" : "absent";
9011
- checks.configPermissions = fs4.existsSync(CONFIG_DIR) ? (fs4.statSync(CONFIG_DIR).mode & 511).toString(8) : "absent";
10033
+ checks.configPermissions = fs5.existsSync(CONFIG_DIR) ? (fs5.statSync(CONFIG_DIR).mode & 511).toString(8) : "absent";
9012
10034
  if (checks.container === "running") {
9013
10035
  try {
9014
10036
  checks.health = (await fetch(`http://localhost:${DEFAULT_PORT}/health`)).ok ? "ok" : "failed";
@@ -9067,6 +10089,13 @@ chats.command("mark-read <chat-id>").action((chatId) => action(async () => {
9067
10089
  if (!result.ok) throw new CliError(result.errorCode || "MARK_READ_FAILED", result.error || "mark-read was not verified", EXIT.SERVICE);
9068
10090
  output(result, () => console.log(`${chatId}: ${result.beforeUnread} -> ${result.afterUnread}`));
9069
10091
  }));
10092
+ chats.command("members <group-id>").description("List a bounded page of group members read-only").option("--limit <number>", "page size", "50").option("--cursor <cursor>", "opaque next-page cursor").action((groupId, options) => action(async () => {
10093
+ const page = await client().listGroupMembersPage(groupId, integer(options.limit, "limit", 1, 100), options.cursor);
10094
+ output(page, () => {
10095
+ for (const member of page.items) console.log(`${member.memberId} ${member.displayName} ${member.groupAlias ?? ""}`);
10096
+ if (page.nextCursor) console.error(`next cursor: ${page.nextCursor}`);
10097
+ });
10098
+ }));
9070
10099
  var contacts = program2.command("contacts").description("List contacts read-only").option("--limit <number>", "page size", "200").option("--cursor <cursor>", "opaque next-page cursor").action((options) => action(async () => {
9071
10100
  const page = await client().listContactsPage(integer(options.limit, "limit", 1, 200), options.cursor);
9072
10101
  output(page, () => {
@@ -9083,8 +10112,8 @@ program2.command("messages <chat-id>").description("Read messages without openin
9083
10112
  output(page, () => renderMessages(page));
9084
10113
  }));
9085
10114
  program2.command("send <chat-id>").description("Send exactly one payload to a stable chat ID").option("--text <message>").option("--image <path>").option("--file <path>").option("--idempotency-key <key>", "stable key for retry/reconciliation").option("--confirm-similar", "confirm operator-reviewed similar text").action((chatId, options) => action(async () => {
9086
- const image = options.image ? { data: regularUpload(options.image).toString("base64"), mimeType: path4.extname(options.image).toLowerCase() === ".jpg" ? "image/jpeg" : "image/png" } : void 0;
9087
- const file = options.file ? { data: regularUpload(options.file).toString("base64"), filename: path4.basename(options.file) } : void 0;
10115
+ const image = options.image ? { data: regularUpload(options.image).toString("base64"), mimeType: path5.extname(options.image).toLowerCase() === ".jpg" ? "image/jpeg" : "image/png" } : void 0;
10116
+ const file = options.file ? { data: regularUpload(options.file).toString("base64"), filename: path5.basename(options.file) } : void 0;
9088
10117
  let params;
9089
10118
  try {
9090
10119
  params = buildCliSendParams({ chatId, text: options.text, image, file, confirmSimilar: options.confirmSimilar, idempotencyKey: options.idempotencyKey });
@@ -9098,7 +10127,7 @@ program2.command("send <chat-id>").description("Send exactly one payload to a st
9098
10127
  }
9099
10128
  output(result, () => console.log("Message sent"));
9100
10129
  }));
9101
- program2.command("upgrade").description("Check CLI/image upgrades without claiming atomic self-upgrade").addOption(new Option("--check", "read-only upgrade check")).addOption(new Option("--cli", "print an exact npm CLI upgrade command")).addOption(new Option("--image <reference>", "transactionally rebuild with an exact compatible image")).action((options) => action(async () => {
10130
+ program2.command("upgrade").description("Check CLI/image upgrades without claiming atomic self-upgrade").addOption(new Option("--check", "read-only upgrade check")).addOption(new Option("--cli", "print an exact npm CLI upgrade command")).addOption(new Option("--image <reference>", "transactionally rebuild with an exact semver/commit tag or digest")).action((options) => action(async () => {
9102
10131
  const selected = [options.check === true, options.cli === true, typeof options.image === "string"].filter(Boolean).length;
9103
10132
  if (selected > 1) throw new CliError("ARGUMENT_CONFLICT", "--check, --cli, and --image are mutually exclusive", EXIT.ARGUMENT);
9104
10133
  if (options.cli) {
@@ -9132,9 +10161,9 @@ for (const [oldName, replacement] of [["up", "start"], ["down", "stop"], ["updat
9132
10161
  }
9133
10162
  function sameEntrypoint(left, right) {
9134
10163
  try {
9135
- return fs4.realpathSync(left) === fs4.realpathSync(right);
10164
+ return fs5.realpathSync(left) === fs5.realpathSync(right);
9136
10165
  } catch {
9137
- return path4.resolve(left) === path4.resolve(right);
10166
+ return path5.resolve(left) === path5.resolve(right);
9138
10167
  }
9139
10168
  }
9140
10169
  if (process.argv[1] && sameEntrypoint(process.argv[1], fileURLToPath(import.meta.url))) {
@@ -9148,3 +10177,42 @@ if (process.argv[1] && sameEntrypoint(process.argv[1], fileURLToPath(import.meta
9148
10177
  process.exitCode = EXIT.SERVICE;
9149
10178
  });
9150
10179
  }
10180
+ /*! Bundled license information:
10181
+
10182
+ smol-toml/dist/error.js:
10183
+ smol-toml/dist/util.js:
10184
+ smol-toml/dist/date.js:
10185
+ smol-toml/dist/primitive.js:
10186
+ smol-toml/dist/extract.js:
10187
+ smol-toml/dist/struct.js:
10188
+ smol-toml/dist/parse.js:
10189
+ smol-toml/dist/stringify.js:
10190
+ smol-toml/dist/index.js:
10191
+ (*!
10192
+ * Copyright (c) Squirrel Chat et al., All rights reserved.
10193
+ * SPDX-License-Identifier: BSD-3-Clause
10194
+ *
10195
+ * Redistribution and use in source and binary forms, with or without
10196
+ * modification, are permitted provided that the following conditions are met:
10197
+ *
10198
+ * 1. Redistributions of source code must retain the above copyright notice, this
10199
+ * list of conditions and the following disclaimer.
10200
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
10201
+ * this list of conditions and the following disclaimer in the
10202
+ * documentation and/or other materials provided with the distribution.
10203
+ * 3. Neither the name of the copyright holder nor the names of its contributors
10204
+ * may be used to endorse or promote products derived from this software without
10205
+ * specific prior written permission.
10206
+ *
10207
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
10208
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
10209
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
10210
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
10211
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
10212
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
10213
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
10214
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
10215
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
10216
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
10217
+ *)
10218
+ */