@okxweb3/a2a-node 0.2.17 → 0.2.18-beta-4dd056b897-261015113839

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.
Files changed (3) hide show
  1. package/dist/cli.js +1682 -124
  2. package/dist/index.js +1585 -127
  3. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -3788,6 +3788,10 @@ function isDaemonCommand(value) {
3788
3788
  if (parsed.type === "xmtp-send") {
3789
3789
  return typeof parsed.jobId === "string" && typeof parsed.message === "string";
3790
3790
  }
3791
+ if (parsed.type === "xmtp-subscription-push") {
3792
+ const command = parsed;
3793
+ return typeof command.myAgentId === "string" && typeof command.content === "string" && Array.isArray(command.conversationIds) && command.conversationIds.every((item) => typeof item === "string");
3794
+ }
3791
3795
  if (parsed.type === "xmtp-get-pending-list") {
3792
3796
  return true;
3793
3797
  }
@@ -3843,6 +3847,7 @@ var init_command_store = __esm({
3843
3847
  import_node_fs8 = require("node:fs");
3844
3848
  import_node_path11 = require("node:path");
3845
3849
  init_paths();
3850
+ init_session_store();
3846
3851
  ({ DatabaseSync: DatabaseSync2 } = loadSqlite2());
3847
3852
  XMTP_DEBUG_RESULT_TTL_MS = 5 * 6e4;
3848
3853
  CommandStore = class {
@@ -3872,6 +3877,17 @@ var init_command_store = __esm({
3872
3877
  createdAt: Date.now()
3873
3878
  };
3874
3879
  }
3880
+ createXmtpSubscriptionPushCommand(params) {
3881
+ return {
3882
+ id: (0, import_node_crypto2.randomUUID)(),
3883
+ type: "xmtp-subscription-push",
3884
+ myAgentId: params.myAgentId,
3885
+ content: params.content,
3886
+ conversationIds: params.conversationIds,
3887
+ deliveryId: params.deliveryId,
3888
+ createdAt: Date.now()
3889
+ };
3890
+ }
3875
3891
  createXmtpGetPendingListCommand() {
3876
3892
  return {
3877
3893
  id: (0, import_node_crypto2.randomUUID)(),
@@ -3965,6 +3981,65 @@ var init_command_store = __esm({
3965
3981
  );
3966
3982
  return this.commandPath(command.id);
3967
3983
  }
3984
+ /**
3985
+ * Atomic idempotent submit keyed on a business `idempotencyKey` (DEXMARKET-9035,
3986
+ * M2). The FIRST caller for a given key inserts its command and gets
3987
+ * `created: true`; every later caller with the SAME key — including after a
3988
+ * daemon / CommandStore rebuild — gets the ALREADY-persisted command back with
3989
+ * `created: false` and NO new row. Concurrency-safe: correctness rests on the
3990
+ * partial UNIQUE index on `idempotency_key`, not a check-then-insert (which
3991
+ * races), so a `INSERT ... ON CONFLICT DO NOTHING` collapses concurrent
3992
+ * duplicates to exactly one row. Recoverable SQLITE_BUSY/LOCKED errors reuse
3993
+ * the repo's bounded exponential backoff. This never changes the plain
3994
+ * `submit` path used by `xmtp-send` and every other daemon command.
3995
+ */
3996
+ async submitIdempotent(command, idempotencyKey) {
3997
+ if (!idempotencyKey) {
3998
+ throw new Error("submitIdempotent requires a non-empty idempotencyKey");
3999
+ }
4000
+ return this.withBusyRetry(
4001
+ () => this.transaction(() => {
4002
+ const now = Date.now();
4003
+ const insert = this.db.prepare(`
4004
+ INSERT INTO command_queue (
4005
+ id, type, status, command_json, result_json,
4006
+ created_at_ms, updated_at_ms, processing_started_at_ms, completed_at_ms,
4007
+ idempotency_key
4008
+ )
4009
+ VALUES (?, ?, 'pending', ?, NULL, ?, ?, NULL, NULL, ?)
4010
+ ON CONFLICT(idempotency_key) WHERE idempotency_key IS NOT NULL DO NOTHING
4011
+ `).run(
4012
+ command.id,
4013
+ command.type,
4014
+ stringifyForStorage(command),
4015
+ normalizeCreatedAt(command.createdAt, now),
4016
+ now,
4017
+ idempotencyKey
4018
+ );
4019
+ if (Number(insert.changes ?? 0) > 0) {
4020
+ return { path: this.commandPath(command.id), commandId: command.id, created: true };
4021
+ }
4022
+ const existing = this.db.prepare(`
4023
+ SELECT id FROM command_queue WHERE idempotency_key = ?
4024
+ `).get(idempotencyKey);
4025
+ const existingId = existing?.id ?? command.id;
4026
+ return { path: this.commandPath(existingId), commandId: existingId, created: false };
4027
+ })
4028
+ );
4029
+ }
4030
+ async withBusyRetry(operation) {
4031
+ const attempts = 6;
4032
+ for (let attempt = 1; ; attempt += 1) {
4033
+ try {
4034
+ return operation();
4035
+ } catch (err2) {
4036
+ if (!isSqliteBusyError(err2) || attempt >= attempts) {
4037
+ throw err2;
4038
+ }
4039
+ await new Promise((resolve14) => setTimeout(resolve14, Math.min(50 * 2 ** (attempt - 1), 1e3)));
4040
+ }
4041
+ }
4042
+ }
3968
4043
  async listPending() {
3969
4044
  return (await this.listPendingEntries()).map((entry) => entry.command);
3970
4045
  }
@@ -4030,10 +4105,25 @@ var init_command_store = __esm({
4030
4105
  this.db.exec(`
4031
4106
  CREATE INDEX IF NOT EXISTS idx_command_queue_status_created
4032
4107
  ON command_queue(status, created_at_ms, id)
4108
+ `);
4109
+ this.ensureColumn("command_queue", "idempotency_key", "TEXT");
4110
+ this.db.exec(`
4111
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_command_queue_idempotency_key
4112
+ ON command_queue(idempotency_key)
4113
+ WHERE idempotency_key IS NOT NULL
4033
4114
  `);
4034
4115
  this.purgeExpiredXmtpDebugResults();
4035
4116
  this.importLegacyFileCommands();
4036
4117
  }
4118
+ ensureColumn(table, column, definition) {
4119
+ this.transaction(() => {
4120
+ const rows = this.db.prepare(`PRAGMA table_info(${table})`).all();
4121
+ if (rows.some((row) => row.name === column)) {
4122
+ return;
4123
+ }
4124
+ this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${definition}`);
4125
+ });
4126
+ }
4037
4127
  purgeExpiredXmtpDebugResults() {
4038
4128
  this.db.prepare(`
4039
4129
  DELETE FROM command_queue
@@ -4687,7 +4777,7 @@ var require_is = __commonJS({
4687
4777
  function isPrimitive(wat) {
4688
4778
  return wat === null || typeof wat !== "object" && typeof wat !== "function";
4689
4779
  }
4690
- function isPlainObject4(wat) {
4780
+ function isPlainObject6(wat) {
4691
4781
  return isBuiltin(wat, "Object");
4692
4782
  }
4693
4783
  function isEvent(wat) {
@@ -4703,7 +4793,7 @@ var require_is = __commonJS({
4703
4793
  return Boolean(wat && wat.then && typeof wat.then === "function");
4704
4794
  }
4705
4795
  function isSyntheticEvent(wat) {
4706
- return isPlainObject4(wat) && "nativeEvent" in wat && "preventDefault" in wat && "stopPropagation" in wat;
4796
+ return isPlainObject6(wat) && "nativeEvent" in wat && "preventDefault" in wat && "stopPropagation" in wat;
4707
4797
  }
4708
4798
  function isNaN2(wat) {
4709
4799
  return typeof wat === "number" && wat !== wat;
@@ -4726,7 +4816,7 @@ var require_is = __commonJS({
4726
4816
  exports2.isEvent = isEvent;
4727
4817
  exports2.isInstanceOf = isInstanceOf;
4728
4818
  exports2.isNaN = isNaN2;
4729
- exports2.isPlainObject = isPlainObject4;
4819
+ exports2.isPlainObject = isPlainObject6;
4730
4820
  exports2.isPrimitive = isPrimitive;
4731
4821
  exports2.isRegExp = isRegExp;
4732
4822
  exports2.isString = isString;
@@ -19989,6 +20079,23 @@ var init_events = __esm({
19989
20079
  XMTP_RATE_LIMIT_CREATE_SUPPRESSED: "XMTP rate limit create suppressed",
19990
20080
  XMTP_RATE_LIMIT_PROBE: "XMTP rate limit probe",
19991
20081
  XMTP_RATE_LIMIT_RECOVERED: "XMTP rate limit recovered",
20082
+ // ── subscription observability info (DEXMARKET-9096) ───────────
20083
+ // All of the following logger.info checkpoints MUST also be listed in
20084
+ // SENTRY_INFO_ALLOWLIST (index.ts): info() returns early for an unlisted
20085
+ // event, which ships a checkpoint that looks implemented but emits nothing.
20086
+ SUBSCRIPTION_PUSH_STARTED: "Subscription push started",
20087
+ SUBSCRIPTION_PUSH_TARGETS_RESOLVED: "Subscription push targets resolved",
20088
+ SUBSCRIPTION_PUSH_EMPTY_TARGETS: "Subscription push empty targets",
20089
+ SUBSCRIPTION_PUSH_SUMMARY: "Subscription push summary",
20090
+ SUBSCRIPTION_INBOUND_RECEIVED: "Subscription inbound received",
20091
+ SUBSCRIPTION_INBOUND_GATE_DROPPED: "Subscription inbound gate dropped",
20092
+ SUBSCRIPTION_JOBS_QUERY_STARTED: "Subscription jobs query started",
20093
+ SUBSCRIPTION_JOBS_QUERY_SUCCEEDED: "Subscription jobs query succeeded",
20094
+ SUBSCRIPTION_JOBS_EMPTY: "Subscription jobs empty",
20095
+ SUBSCRIPTION_INBOUND_OWNERSHIP_ROUTED: "Subscription inbound ownership routed",
20096
+ SUBSCRIPTION_JOB_DISPATCHED: "Subscription job dispatched",
20097
+ SUBSCRIPTION_JOB_DUPLICATE_IGNORED: "Subscription job duplicate ignored",
20098
+ SUBSCRIPTION_FANOUT_SUMMARY: "Subscription fan-out summary",
19992
20099
  // ── error ─────────────────────────────────────────────────────
19993
20100
  XMTP_CONNECTION_FAILED: "XMTP connection failed",
19994
20101
  CONVERSATIONS_STREAM_FAILED: "conversations.stream failed",
@@ -20055,6 +20162,13 @@ var init_events = __esm({
20055
20162
  AGENT_INSTALLATION_LIMIT_NEAR: "Agent installation limit near",
20056
20163
  XMTP_HISTORY_SYNC_REQUEST_FAILED: "XMTP history sync request failed",
20057
20164
  XMTP_DEBUG_SYNC_FAILED: "XMTP debug sync failed",
20165
+ // ── subscription observability error (DEXMARKET-9096) ──────────
20166
+ // error-level: always emitted (no allowlist gate). Error objects are
20167
+ // classified by buildErrorDiagnostics — never attach raw Error.message.
20168
+ SUBSCRIPTION_PUSH_TARGET_FAILED: "Subscription push target failed",
20169
+ SUBSCRIPTION_INBOUND_AGENTIC_ID_MISSING: "Subscription inbound agenticId missing",
20170
+ SUBSCRIPTION_JOBS_FAIL_CLOSED: "Subscription jobs fail closed",
20171
+ SUBSCRIPTION_JOB_FAILED: "Subscription job failed",
20058
20172
  // Underlying XMTP SDK client close failed during recycle/removal/shutdown
20059
20173
  // (DEXMARKET-9116). Error-level → NOT added to SENTRY_INFO_ALLOWLIST.
20060
20174
  XMTP_CLIENT_CLOSE_FAILED: "XMTP client close failed"
@@ -20730,6 +20844,13 @@ var init_sentry_logger = __esm({
20730
20844
  ]);
20731
20845
  SENTRY_TAG_KEYS = /* @__PURE__ */ new Set([
20732
20846
  "agentId",
20847
+ // Subscription correlation agent ids (DEXMARKET-9096). Bounded-cardinality,
20848
+ // same class as agentId/senderAgentId/onchainosAgentId — promoted to indexed
20849
+ // tags for Sentry filtering. NOT added to SENTRY_FINGERPRINT_KEYS, so the
20850
+ // global issue fingerprint is unchanged. High-cardinality ids
20851
+ // (conversationId/messageId/deliveryId) are deliberately NOT here.
20852
+ "aspAgentId",
20853
+ "userAgentId",
20733
20854
  "agentPlatform",
20734
20855
  "aiProvider",
20735
20856
  "cacheName",
@@ -20897,6 +21018,22 @@ var init_sentry_logger = __esm({
20897
21018
  LogEvent.SESSION_KEY_DERIVED,
20898
21019
  LogEvent.SESSIONS_QUERIED,
20899
21020
  LogEvent.SYNC_TICK_COMPLETED,
21021
+ // Subscription observability info checkpoints (DEXMARKET-9096). Each is a
21022
+ // logger.info event; omitting any makes info() drop it silently so the whole
21023
+ // subscription trace would look implemented and emit nothing in production.
21024
+ LogEvent.SUBSCRIPTION_PUSH_STARTED,
21025
+ LogEvent.SUBSCRIPTION_PUSH_TARGETS_RESOLVED,
21026
+ LogEvent.SUBSCRIPTION_PUSH_EMPTY_TARGETS,
21027
+ LogEvent.SUBSCRIPTION_PUSH_SUMMARY,
21028
+ LogEvent.SUBSCRIPTION_INBOUND_RECEIVED,
21029
+ LogEvent.SUBSCRIPTION_INBOUND_GATE_DROPPED,
21030
+ LogEvent.SUBSCRIPTION_JOBS_QUERY_STARTED,
21031
+ LogEvent.SUBSCRIPTION_JOBS_QUERY_SUCCEEDED,
21032
+ LogEvent.SUBSCRIPTION_JOBS_EMPTY,
21033
+ LogEvent.SUBSCRIPTION_INBOUND_OWNERSHIP_ROUTED,
21034
+ LogEvent.SUBSCRIPTION_JOB_DISPATCHED,
21035
+ LogEvent.SUBSCRIPTION_JOB_DUPLICATE_IGNORED,
21036
+ LogEvent.SUBSCRIPTION_FANOUT_SUMMARY,
20900
21037
  // XMTP creation rate-limit cooldown checkpoints (DEXMARKET-9116). info() drops
20901
21038
  // any event not listed here, so these must stay registered.
20902
21039
  LogEvent.XMTP_RATE_LIMIT_COOLDOWN_STARTED,
@@ -25263,7 +25400,7 @@ var require_websocket2 = __commonJS({
25263
25400
  var http2 = require("http");
25264
25401
  var net = require("net");
25265
25402
  var tls = require("tls");
25266
- var { randomBytes: randomBytes4, createHash: createHash6 } = require("crypto");
25403
+ var { randomBytes: randomBytes4, createHash: createHash7 } = require("crypto");
25267
25404
  var { Duplex, Readable } = require("stream");
25268
25405
  var { URL: URL2 } = require("url");
25269
25406
  var PerMessageDeflate = require_permessage_deflate();
@@ -25923,7 +26060,7 @@ var require_websocket2 = __commonJS({
25923
26060
  abortHandshake(websocket, socket, "Invalid Upgrade header");
25924
26061
  return;
25925
26062
  }
25926
- const digest = createHash6("sha1").update(key + GUID).digest("base64");
26063
+ const digest = createHash7("sha1").update(key + GUID).digest("base64");
25927
26064
  if (res.headers["sec-websocket-accept"] !== digest) {
25928
26065
  abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
25929
26066
  return;
@@ -26290,7 +26427,7 @@ var require_websocket_server = __commonJS({
26290
26427
  var EventEmitter2 = require("events");
26291
26428
  var http2 = require("http");
26292
26429
  var { Duplex } = require("stream");
26293
- var { createHash: createHash6 } = require("crypto");
26430
+ var { createHash: createHash7 } = require("crypto");
26294
26431
  var extension = require_extension();
26295
26432
  var PerMessageDeflate = require_permessage_deflate();
26296
26433
  var subprotocol = require_subprotocol();
@@ -26591,7 +26728,7 @@ var require_websocket_server = __commonJS({
26591
26728
  );
26592
26729
  }
26593
26730
  if (this._state > RUNNING) return abortHandshake(socket, 503);
26594
- const digest = createHash6("sha1").update(key + GUID).digest("base64");
26731
+ const digest = createHash7("sha1").update(key + GUID).digest("base64");
26595
26732
  const headers = [
26596
26733
  "HTTP/1.1 101 Switching Protocols",
26597
26734
  "Upgrade: websocket",
@@ -27018,7 +27155,7 @@ function buildConnectParams(config, instanceId, protocolVersion) {
27018
27155
  client: {
27019
27156
  id: "gateway-client",
27020
27157
  displayName: "okx-a2a-node",
27021
- version: "0.2.17",
27158
+ version: "0.2.18-beta-4dd056b897-261015113839",
27022
27159
  platform: "node",
27023
27160
  mode: "backend",
27024
27161
  instanceId
@@ -27029,7 +27166,7 @@ function buildConnectParams(config, instanceId, protocolVersion) {
27029
27166
  commands: [],
27030
27167
  permissions: {},
27031
27168
  locale: Intl.DateTimeFormat().resolvedOptions().locale || "en-US",
27032
- userAgent: `okx-a2a-node/${"0.2.17"}`,
27169
+ userAgent: `okx-a2a-node/${"0.2.18-beta-4dd056b897-261015113839"}`,
27033
27170
  auth: {
27034
27171
  ...config.token ? { token: config.token } : {},
27035
27172
  ...config.password ? { password: config.password } : {}
@@ -29089,7 +29226,7 @@ var init_sentry_config = __esm({
29089
29226
  environment = process.env.SENTRY_ENV === "dev" ? "dev" : "prod";
29090
29227
  SENTRY_CONFIG = {
29091
29228
  projectName: "okx/openclaw-okx-a2a-extension",
29092
- release: "0.2.17",
29229
+ release: "0.2.18-beta-4dd056b897-261015113839",
29093
29230
  environment,
29094
29231
  runtimeContainer: normalizeRuntimeContainer(process.env.OKX_A2A_RUNTIME_CONTAINER)
29095
29232
  };
@@ -40259,7 +40396,7 @@ async function exportDiagnosticLogs(options) {
40259
40396
  node: process.version,
40260
40397
  platform: process.platform,
40261
40398
  arch: process.arch,
40262
- packageVersion: true ? "0.2.17" : "unknown",
40399
+ packageVersion: true ? "0.2.18-beta-4dd056b897-261015113839" : "unknown",
40263
40400
  sensitiveContentIncluded: options.includeSensitiveContent,
40264
40401
  listenerAndLlmContentIncluded: true,
40265
40402
  credentialsAlwaysRedacted: true,
@@ -61990,6 +62127,247 @@ var init_onchainos = __esm({
61990
62127
  }
61991
62128
  });
61992
62129
 
62130
+ // ../core/src/xmtp-sdk/onchainos/list-jobs-by-verified-sender.ts
62131
+ function isPlainObject(value) {
62132
+ return !!value && typeof value === "object" && !Array.isArray(value);
62133
+ }
62134
+ function isNonEmptyString(value) {
62135
+ return typeof value === "string" && value.length > 0;
62136
+ }
62137
+ function redactLogValue(value, left = 6, right = 4) {
62138
+ if (!value) {
62139
+ return "(none)";
62140
+ }
62141
+ if (value.length <= left + right + 1) {
62142
+ return "(redacted)";
62143
+ }
62144
+ return `${value.slice(0, left)}\u2026${value.slice(-right)}`;
62145
+ }
62146
+ function guardSessionExpired3(stdout, command) {
62147
+ if (isSessionExpired(stdout)) {
62148
+ reportSessionExpired(command);
62149
+ }
62150
+ }
62151
+ function guardCatchSessionExpired3(err2, command) {
62152
+ const stdout = String(err2?.stdout ?? "");
62153
+ if (stdout && isSessionExpired(stdout)) {
62154
+ reportSessionExpired(command);
62155
+ }
62156
+ }
62157
+ function buildListJobsByVerifiedSenderArgs(params) {
62158
+ return [
62159
+ "agent",
62160
+ "list-jobs-by-verified-sender",
62161
+ "--group-id",
62162
+ params.groupId,
62163
+ "--sender-inbox-id",
62164
+ params.senderInboxId,
62165
+ "--agentic-id",
62166
+ params.agenticId
62167
+ ];
62168
+ }
62169
+ function extractErrorText(errorLike) {
62170
+ if (typeof errorLike === "string") {
62171
+ return errorLike;
62172
+ }
62173
+ if (typeof errorLike === "number") {
62174
+ return String(errorLike);
62175
+ }
62176
+ if (errorLike && typeof errorLike === "object") {
62177
+ const anyErr = errorLike;
62178
+ return [anyErr.code, anyErr.message, anyErr.stdout, anyErr.stderr].filter((value) => typeof value === "string" || typeof value === "number").map(String).join(" ");
62179
+ }
62180
+ return errorLike == null ? "" : String(errorLike);
62181
+ }
62182
+ function classifyListJobsError(errorLike) {
62183
+ const text = extractErrorText(errorLike);
62184
+ for (const [code, reason] of Object.entries(KNOWN_ERROR_CODES2)) {
62185
+ if (text.includes(code)) {
62186
+ return { reason, errorCode: code };
62187
+ }
62188
+ }
62189
+ if (/\b401\b/.test(text)) {
62190
+ return { reason: "auth_error", errorCode: "401" };
62191
+ }
62192
+ if (/\b403\b/.test(text)) {
62193
+ return { reason: "auth_error", errorCode: "403" };
62194
+ }
62195
+ return { reason: "invalid_response" };
62196
+ }
62197
+ function normalizeListJobsResponse(data) {
62198
+ const rawJobs = isPlainObject(data) && Array.isArray(data.jobs) ? data.jobs : [];
62199
+ const seen = /* @__PURE__ */ new Set();
62200
+ const jobs = [];
62201
+ let invalidCount = 0;
62202
+ for (const item of rawJobs) {
62203
+ if (!isPlainObject(item) || !isNonEmptyString(item.jobId) || !isNonEmptyString(item.userAgentId) || !isNonEmptyString(item.aspAgentId)) {
62204
+ invalidCount += 1;
62205
+ continue;
62206
+ }
62207
+ const key = `${item.jobId}|${item.userAgentId}|${item.aspAgentId}`;
62208
+ if (seen.has(key)) {
62209
+ continue;
62210
+ }
62211
+ seen.add(key);
62212
+ jobs.push({ jobId: item.jobId, userAgentId: item.userAgentId, aspAgentId: item.aspAgentId });
62213
+ }
62214
+ return { jobs, invalidCount };
62215
+ }
62216
+ async function listJobsByVerifiedSender(params, execFn = exec) {
62217
+ logWithTimestamp(
62218
+ `[onchainos] listing jobs by verified sender: group=${params.groupId} sender=${redactLogValue(params.senderInboxId)} agentic=${params.agenticId}`
62219
+ );
62220
+ let stdout;
62221
+ let stderr;
62222
+ try {
62223
+ ({ stdout, stderr } = await execFn(buildListJobsByVerifiedSenderArgs(params)));
62224
+ } catch (err2) {
62225
+ guardCatchSessionExpired3(err2, "list-jobs-by-verified-sender");
62226
+ const classified = classifyListJobsError(err2);
62227
+ const reason = classified.reason === "invalid_response" ? "network_error" : classified.reason;
62228
+ warnWithTimestamp(
62229
+ `[onchainos] list-jobs-by-verified-sender exec failed (group=${params.groupId} sender=${redactLogValue(params.senderInboxId)}); fail-closed reason=${reason}`
62230
+ );
62231
+ return { status: "fail_closed", jobs: [], invalidCount: 0, reason, errorCode: classified.errorCode };
62232
+ }
62233
+ if (stderr) {
62234
+ logWithTimestamp(`[onchainos] stderr (list-jobs-by-verified-sender): ${stderr}`);
62235
+ }
62236
+ let res;
62237
+ try {
62238
+ res = parseCliJson(stdout, "list-jobs-by-verified-sender", {
62239
+ groupId: params.groupId,
62240
+ agenticId: params.agenticId
62241
+ });
62242
+ } catch {
62243
+ return { status: "fail_closed", jobs: [], invalidCount: 0, reason: "invalid_response" };
62244
+ }
62245
+ if (!res.ok) {
62246
+ guardSessionExpired3(stdout, "list-jobs-by-verified-sender");
62247
+ const classified = classifyListJobsError(res.error);
62248
+ warnWithTimestamp(
62249
+ `[onchainos] list-jobs-by-verified-sender ok=false (group=${params.groupId}); fail-closed reason=${classified.reason}`
62250
+ );
62251
+ logger.error(
62252
+ LogEvent.ONCHAINOS_CLI_ERROR,
62253
+ new Error("list-jobs-by-verified-sender ok=false"),
62254
+ cliOkFalseExtras(stdout, "list-jobs-by-verified-sender", {
62255
+ groupId: params.groupId,
62256
+ agenticId: params.agenticId,
62257
+ reason: classified.reason
62258
+ })
62259
+ );
62260
+ return { status: "fail_closed", jobs: [], invalidCount: 0, reason: classified.reason, errorCode: classified.errorCode };
62261
+ }
62262
+ const { jobs, invalidCount } = normalizeListJobsResponse(res.data);
62263
+ if (jobs.length === 0) {
62264
+ logWithTimestamp(
62265
+ `[onchainos] list-jobs-by-verified-sender: empty (group=${params.groupId} invalidCount=${invalidCount})`
62266
+ );
62267
+ return { status: "empty", jobs, invalidCount };
62268
+ }
62269
+ logWithTimestamp(
62270
+ `[onchainos] list-jobs-by-verified-sender: ok (group=${params.groupId} jobs=${jobs.length} invalidCount=${invalidCount})`
62271
+ );
62272
+ return { status: "ok", jobs, invalidCount };
62273
+ }
62274
+ function listJobsByVerifiedSenderHelpAdvertisesCommand(helpText) {
62275
+ if (typeof helpText !== "string" || helpText.length === 0) {
62276
+ return false;
62277
+ }
62278
+ const lower = helpText.toLowerCase();
62279
+ if (/unknown command|unrecognized|not a valid command|no such (sub)?command|command not found/.test(lower)) {
62280
+ return false;
62281
+ }
62282
+ return lower.includes(LIST_JOBS_BY_VERIFIED_SENDER_HELP_TOKEN);
62283
+ }
62284
+ function maybeRecommendListJobsUpgrade(capability) {
62285
+ if (capability.supported || capabilityRecommendedPairs.has(CAPABILITY_UPGRADE_PAIR_KEY)) {
62286
+ return;
62287
+ }
62288
+ warnWithTimestamp(
62289
+ `[onchainos] list-jobs-by-verified-sender unsupported (reason=${capability.reason}); ${CAPABILITY_UPGRADE_REASON}`
62290
+ );
62291
+ const hook = XmtpService.getInstance().tools?.notifyUpgradeRecommendation;
62292
+ if (!hook) {
62293
+ return;
62294
+ }
62295
+ capabilityRecommendedPairs.add(CAPABILITY_UPGRADE_PAIR_KEY);
62296
+ try {
62297
+ void Promise.resolve(
62298
+ hook({
62299
+ component: "onchainos",
62300
+ capability: LIST_JOBS_BY_VERIFIED_SENDER_HELP_TOKEN,
62301
+ reason: CAPABILITY_UPGRADE_REASON
62302
+ })
62303
+ ).catch((err2) => {
62304
+ warnWithTimestamp("[onchainos] list-jobs-by-verified-sender upgrade notice async delivery failed:", err2);
62305
+ });
62306
+ } catch (err2) {
62307
+ warnWithTimestamp("[onchainos] list-jobs-by-verified-sender upgrade notice threw:", err2);
62308
+ }
62309
+ }
62310
+ function detectListJobsByVerifiedSenderSupport(execFn = exec) {
62311
+ if (!capabilityProbe2) {
62312
+ capabilityProbe2 = (async () => {
62313
+ try {
62314
+ const { stdout, stderr } = await execFn([
62315
+ "agent",
62316
+ "list-jobs-by-verified-sender",
62317
+ "--help"
62318
+ ]);
62319
+ const supported = listJobsByVerifiedSenderHelpAdvertisesCommand(`${stdout}
62320
+ ${stderr}`);
62321
+ logWithTimestamp(
62322
+ `[onchainos] list-jobs-by-verified-sender capability: ${supported ? "supported" : "unsupported_cli_capability"}`
62323
+ );
62324
+ return supported ? { supported: true } : { supported: false, reason: "unsupported_cli_capability" };
62325
+ } catch (err2) {
62326
+ logWithTimestamp(
62327
+ "[onchainos] list-jobs-by-verified-sender --help probe failed; treating as onchainos_upgrade_required:",
62328
+ err2
62329
+ );
62330
+ return { supported: false, reason: "onchainos_upgrade_required" };
62331
+ }
62332
+ })().then((capability) => {
62333
+ maybeRecommendListJobsUpgrade(capability);
62334
+ return capability;
62335
+ });
62336
+ }
62337
+ return capabilityProbe2;
62338
+ }
62339
+ async function listJobsByVerifiedSenderWithCapability(params, execFn = exec) {
62340
+ const capability = await detectListJobsByVerifiedSenderSupport(execFn);
62341
+ if (!capability.supported) {
62342
+ warnWithTimestamp(
62343
+ `[onchainos] list-jobs-by-verified-sender skipped: CLI capability unavailable (reason=${capability.reason}) group=${params.groupId} sender=${redactLogValue(params.senderInboxId)}`
62344
+ );
62345
+ return { status: "fail_closed", jobs: [], invalidCount: 0, reason: capability.reason };
62346
+ }
62347
+ return listJobsByVerifiedSender(params, execFn);
62348
+ }
62349
+ var KNOWN_ERROR_CODES2, LIST_JOBS_BY_VERIFIED_SENDER_HELP_TOKEN, CAPABILITY_UPGRADE_PAIR_KEY, CAPABILITY_UPGRADE_REASON, capabilityProbe2, capabilityRecommendedPairs;
62350
+ var init_list_jobs_by_verified_sender = __esm({
62351
+ "../core/src/xmtp-sdk/onchainos/list-jobs-by-verified-sender.ts"() {
62352
+ "use strict";
62353
+ init_log();
62354
+ init_sentry_logger();
62355
+ init_bin();
62356
+ init_cli_response();
62357
+ init_session_expired();
62358
+ init_xmtp_sdk();
62359
+ KNOWN_ERROR_CODES2 = {
62360
+ "130100079": "sender_not_authorized",
62361
+ "130100073": "sender_not_eligible"
62362
+ };
62363
+ LIST_JOBS_BY_VERIFIED_SENDER_HELP_TOKEN = "list-jobs-by-verified-sender";
62364
+ CAPABILITY_UPGRADE_PAIR_KEY = "onchainos:list-jobs-by-verified-sender";
62365
+ CAPABILITY_UPGRADE_REASON = "OnchainOS does not support `agent list-jobs-by-verified-sender`; the V2 subscription shared-group inbound path is fail-closed until OnchainOS is upgraded. Upgrade the OnchainOS CLI, then re-run.";
62366
+ capabilityProbe2 = null;
62367
+ capabilityRecommendedPairs = /* @__PURE__ */ new Set();
62368
+ }
62369
+ });
62370
+
61993
62371
  // ../core/src/xmtp-sdk/onchainos/signer.ts
61994
62372
  async function onchainosSign(agentId, keyUuid, walletAddress, message) {
61995
62373
  logWithTimestamp(
@@ -62659,7 +63037,7 @@ function buildXmtpMessageSentExtra(params) {
62659
63037
  function logXmtpMessageSent(params) {
62660
63038
  logger.info(LogEvent.MESSAGE_SENT, buildXmtpMessageSentExtra(params));
62661
63039
  }
62662
- function isPlainObject(value) {
63040
+ function isPlainObject2(value) {
62663
63041
  return !!value && typeof value === "object" && !Array.isArray(value);
62664
63042
  }
62665
63043
  function readString3(value) {
@@ -62845,8 +63223,8 @@ function buildDmReplyRaw(command, target, localAgentId) {
62845
63223
  });
62846
63224
  }
62847
63225
  function buildGroupReplyRaw(command, target, myXmtpAddress, localAgent) {
62848
- const payload = isPlainObject(target.payload) ? target.payload : null;
62849
- const sender = isPlainObject(payload?.sender) ? payload.sender : null;
63226
+ const payload = isPlainObject2(target.payload) ? target.payload : null;
63227
+ const sender = isPlainObject2(payload?.sender) ? payload.sender : null;
62850
63228
  return buildSendEnvelope({
62851
63229
  content: command.message,
62852
63230
  myXmtpAddress,
@@ -63443,8 +63821,8 @@ async function handleXmtpSendCommand(params) {
63443
63821
  payload: parseJson(rawText)
63444
63822
  };
63445
63823
  await store.append(command.jobId, stored, sessionAgentId);
63446
- const payloadObject = isPlainObject(target.payload) ? target.payload : null;
63447
- const sender = isPlainObject(payloadObject?.sender) ? payloadObject.sender : null;
63824
+ const payloadObject = isPlainObject2(target.payload) ? target.payload : null;
63825
+ const sender = isPlainObject2(payloadObject?.sender) ? payloadObject.sender : null;
63448
63826
  const remoteAgentId = readString3(sender?.agentId);
63449
63827
  await notifyAgentMessageToUserAttention({
63450
63828
  direction: "outbound" /* OUTBOUND */,
@@ -63497,6 +63875,438 @@ var init_xmtp_send = __esm({
63497
63875
  }
63498
63876
  });
63499
63877
 
63878
+ // ../core/src/a2a/subscription-types.ts
63879
+ var SUBSCRIPTION_MESSAGE_TYPE;
63880
+ var init_subscription_types = __esm({
63881
+ "../core/src/a2a/subscription-types.ts"() {
63882
+ "use strict";
63883
+ SUBSCRIPTION_MESSAGE_TYPE = "a2a-subscription-push";
63884
+ }
63885
+ });
63886
+
63887
+ // ../core/src/a2a/subscription-envelope.ts
63888
+ function isPlainObject3(value) {
63889
+ return !!value && typeof value === "object" && !Array.isArray(value);
63890
+ }
63891
+ function buildSubscriptionEnvelope(params) {
63892
+ const envelope = {
63893
+ msgType: SUBSCRIPTION_MESSAGE_TYPE,
63894
+ content: params.content,
63895
+ senderAgentId: params.senderAgentId
63896
+ };
63897
+ if (params.deliveryId) {
63898
+ envelope.deliveryId = params.deliveryId;
63899
+ }
63900
+ return JSON.stringify(envelope);
63901
+ }
63902
+ function isSubscriptionEnvelope(payload) {
63903
+ return isPlainObject3(payload) && payload.msgType === SUBSCRIPTION_MESSAGE_TYPE;
63904
+ }
63905
+ function isNonEmptyString2(value) {
63906
+ return typeof value === "string" && value.length > 0;
63907
+ }
63908
+ function readSubscriptionEnvelope(payload) {
63909
+ if (!isSubscriptionEnvelope(payload)) {
63910
+ return { ok: false, reason: "not_subscription" };
63911
+ }
63912
+ const record = payload;
63913
+ if (!isNonEmptyString2(record.content)) {
63914
+ return { ok: false, reason: "missing_content" };
63915
+ }
63916
+ const envelope = {
63917
+ msgType: SUBSCRIPTION_MESSAGE_TYPE,
63918
+ content: record.content,
63919
+ senderAgentId: isNonEmptyString2(record.senderAgentId) ? record.senderAgentId : ""
63920
+ };
63921
+ if (isNonEmptyString2(record.deliveryId)) {
63922
+ envelope.deliveryId = record.deliveryId;
63923
+ }
63924
+ return { ok: true, envelope };
63925
+ }
63926
+ var init_subscription_envelope = __esm({
63927
+ "../core/src/a2a/subscription-envelope.ts"() {
63928
+ "use strict";
63929
+ init_subscription_types();
63930
+ }
63931
+ });
63932
+
63933
+ // src/subscription-telemetry.ts
63934
+ function definedExtras(input) {
63935
+ return Object.entries(input).reduce((out, [key, value]) => {
63936
+ if (typeof value === "string" && value.trim().length > 0) {
63937
+ out[key] = value.trim();
63938
+ }
63939
+ return out;
63940
+ }, {});
63941
+ }
63942
+ function countExtras(input) {
63943
+ return Object.entries(input).reduce((out, [key, value]) => {
63944
+ if (typeof value === "number" && Number.isFinite(value)) {
63945
+ out[key] = String(value);
63946
+ }
63947
+ return out;
63948
+ }, {});
63949
+ }
63950
+ function buildSubscriptionSendExtras(input) {
63951
+ return {
63952
+ stage: input.checkpoint,
63953
+ ...logFieldExtras({
63954
+ checkpoint: input.checkpoint,
63955
+ outcome: input.outcome ?? null,
63956
+ transport: SUBSCRIPTION_TRANSPORT,
63957
+ eventFamily: "outbound",
63958
+ deliveryId: input.deliveryId ?? null
63959
+ }),
63960
+ ...definedExtras({
63961
+ aspAgentId: input.aspAgentId,
63962
+ conversationId: input.conversationId,
63963
+ reason: input.reason
63964
+ }),
63965
+ ...countExtras({
63966
+ targetCount: input.targetCount,
63967
+ sentCount: input.sentCount,
63968
+ failedCount: input.failedCount,
63969
+ notGroupCount: input.notGroupCount
63970
+ })
63971
+ };
63972
+ }
63973
+ function buildSubscriptionInboundExtras(input) {
63974
+ return {
63975
+ stage: input.checkpoint,
63976
+ ...logFieldExtras({
63977
+ checkpoint: input.checkpoint,
63978
+ outcome: input.outcome ?? null,
63979
+ transport: SUBSCRIPTION_TRANSPORT,
63980
+ eventFamily: "inbound",
63981
+ deliveryId: input.deliveryId ?? null
63982
+ }),
63983
+ ...definedExtras({
63984
+ userAgentId: input.userAgentId,
63985
+ conversationId: input.conversationId,
63986
+ messageId: input.messageId,
63987
+ senderInboxId: input.senderInboxId,
63988
+ reason: input.reason,
63989
+ errorCode: input.errorCode
63990
+ }),
63991
+ ...countExtras({
63992
+ returnedCount: input.returnedCount,
63993
+ invalidCount: input.invalidCount,
63994
+ ownedCount: input.ownedCount,
63995
+ ignoredCount: input.ignoredCount
63996
+ })
63997
+ };
63998
+ }
63999
+ function buildSubscriptionJobExtras(input) {
64000
+ return {
64001
+ stage: input.checkpoint,
64002
+ ...logFieldExtras({
64003
+ checkpoint: input.checkpoint,
64004
+ outcome: input.outcome,
64005
+ transport: SUBSCRIPTION_TRANSPORT,
64006
+ eventFamily: "dispatch",
64007
+ deliveryId: input.deliveryId ?? null
64008
+ }),
64009
+ ...definedExtras({
64010
+ jobId: input.jobId,
64011
+ userAgentId: input.userAgentId,
64012
+ aspAgentId: input.aspAgentId,
64013
+ conversationId: input.conversationId,
64014
+ reason: input.reason
64015
+ }),
64016
+ ...countExtras({
64017
+ total: input.total,
64018
+ started: input.started,
64019
+ failed: input.failed,
64020
+ skippedDuplicate: input.skippedDuplicate
64021
+ })
64022
+ };
64023
+ }
64024
+ var SUBSCRIPTION_TRANSPORT;
64025
+ var init_subscription_telemetry = __esm({
64026
+ "src/subscription-telemetry.ts"() {
64027
+ "use strict";
64028
+ init_log_fields();
64029
+ SUBSCRIPTION_TRANSPORT = "xmtp";
64030
+ }
64031
+ });
64032
+
64033
+ // src/subscription-push.ts
64034
+ function describeError(err2) {
64035
+ if (err2 instanceof Error) {
64036
+ return err2.message;
64037
+ }
64038
+ if (typeof err2 === "string" && err2.length > 0) {
64039
+ return err2;
64040
+ }
64041
+ return "unknown error";
64042
+ }
64043
+ async function requireXmtpClient2(service, address) {
64044
+ const existing = service.getClientByAddress(address);
64045
+ if (existing) {
64046
+ return existing;
64047
+ }
64048
+ await service.refreshAgents();
64049
+ const refreshed = service.getClientByAddress(address);
64050
+ if (!refreshed) {
64051
+ throw new Error(`no XMTP client found for ${address}`);
64052
+ }
64053
+ return refreshed;
64054
+ }
64055
+ function resolveLocalAddress(service, myAgentId) {
64056
+ const address = service.getAddressByAgentId(myAgentId);
64057
+ if (!address) {
64058
+ throw new Error(`No local communication address found for myAgentId=${myAgentId}`);
64059
+ }
64060
+ return address;
64061
+ }
64062
+ async function syncConversationsOnLocalMiss2(agent) {
64063
+ const conversations = agent.client.conversations;
64064
+ const existing = conversationSyncPromises2.get(conversations);
64065
+ if (existing) {
64066
+ return existing;
64067
+ }
64068
+ const pending = conversations.sync();
64069
+ conversationSyncPromises2.set(conversations, pending);
64070
+ try {
64071
+ await pending;
64072
+ } finally {
64073
+ if (conversationSyncPromises2.get(conversations) === pending) {
64074
+ conversationSyncPromises2.delete(conversations);
64075
+ }
64076
+ }
64077
+ }
64078
+ async function findGroupConversation2(agent, conversationId) {
64079
+ let conversation = await agent.client.conversations.getConversationById(conversationId);
64080
+ if (!(conversation instanceof Group)) {
64081
+ await syncConversationsOnLocalMiss2(agent);
64082
+ conversation = await agent.client.conversations.getConversationById(conversationId);
64083
+ }
64084
+ return conversation instanceof Group ? conversation : null;
64085
+ }
64086
+ async function sendSubscriptionToGroup(params) {
64087
+ const { agent, conversationId, command, myXmtpAddress, senderRole } = params;
64088
+ let conversation;
64089
+ try {
64090
+ conversation = await findGroupConversation2(agent, conversationId);
64091
+ } catch (err2) {
64092
+ return { conversationId, status: "not_group", error: describeError(err2) };
64093
+ }
64094
+ if (!conversation) {
64095
+ return {
64096
+ conversationId,
64097
+ status: "not_group",
64098
+ error: `conversationId=${conversationId} is not a Group (DM or unknown conversation)`
64099
+ };
64100
+ }
64101
+ const envelope = buildSubscriptionEnvelope({
64102
+ content: command.content,
64103
+ senderAgentId: command.myAgentId,
64104
+ deliveryId: command.deliveryId
64105
+ });
64106
+ const sendStartMs = Date.now();
64107
+ try {
64108
+ const xmtpMessageId = await conversation.sendText(envelope);
64109
+ logXmtpMessageSent({
64110
+ xmtpMessageId,
64111
+ conversationId,
64112
+ // V2 has no job dimension (FR-1) — deliveryId (or a fixed marker)
64113
+ // stands in for the telemetry taxonomy's jobId slot.
64114
+ jobId: command.deliveryId ?? "subscription-push",
64115
+ chatType: "group",
64116
+ component: "node_xmtp_subscription_push",
64117
+ myXmtpAddress,
64118
+ senderAgentId: command.myAgentId,
64119
+ role: senderRole,
64120
+ sendDurationMs: Date.now() - sendStartMs
64121
+ });
64122
+ const messageId = typeof xmtpMessageId === "string" && xmtpMessageId.length > 0 ? xmtpMessageId : String(xmtpMessageId);
64123
+ logWithTimestamp(
64124
+ `[okx-agent-task] subscription-push sent conversationId=${conversationId} deliveryId=${command.deliveryId ?? "(none)"}`
64125
+ );
64126
+ return { conversationId, status: "sent", messageId };
64127
+ } catch (err2) {
64128
+ logWithTimestamp(
64129
+ `[okx-agent-task] subscription-push failed conversationId=${conversationId} deliveryId=${command.deliveryId ?? "(none)"}`
64130
+ );
64131
+ return { conversationId, status: "failed", error: describeError(err2) };
64132
+ }
64133
+ }
64134
+ function summarizeSubscriptionPush(results) {
64135
+ let sentCount = 0;
64136
+ let failedCount = 0;
64137
+ for (const result of results) {
64138
+ if (result.status === "sent") {
64139
+ sentCount++;
64140
+ } else {
64141
+ failedCount++;
64142
+ }
64143
+ }
64144
+ return { conversationCount: results.length, sentCount, failedCount };
64145
+ }
64146
+ async function handleSubscriptionPushCommand(params) {
64147
+ const { command, service } = params;
64148
+ const aspAgentId = command.myAgentId;
64149
+ const deliveryId = command.deliveryId;
64150
+ const conversationIds = command.conversationIds ?? [];
64151
+ logger.info(
64152
+ LogEvent.SUBSCRIPTION_PUSH_STARTED,
64153
+ buildSubscriptionSendExtras({
64154
+ checkpoint: "outbound/subscription_push_started",
64155
+ aspAgentId,
64156
+ deliveryId,
64157
+ targetCount: conversationIds.length
64158
+ })
64159
+ );
64160
+ if (conversationIds.length === 0) {
64161
+ logger.info(
64162
+ LogEvent.SUBSCRIPTION_PUSH_EMPTY_TARGETS,
64163
+ buildSubscriptionSendExtras({
64164
+ checkpoint: "outbound/subscription_push_empty_targets",
64165
+ outcome: "ignored",
64166
+ aspAgentId,
64167
+ deliveryId,
64168
+ targetCount: 0,
64169
+ reason: "empty_targets"
64170
+ })
64171
+ );
64172
+ return { conversationCount: 0, sentCount: 0, failedCount: 0, results: [] };
64173
+ }
64174
+ const myXmtpAddress = resolveLocalAddress(service, command.myAgentId);
64175
+ const agent = await requireXmtpClient2(service, myXmtpAddress);
64176
+ const senderRole = service.getAgentByAddress?.(myXmtpAddress)?.role;
64177
+ logger.info(
64178
+ LogEvent.SUBSCRIPTION_PUSH_TARGETS_RESOLVED,
64179
+ buildSubscriptionSendExtras({
64180
+ checkpoint: "outbound/subscription_push_targets_resolved",
64181
+ aspAgentId,
64182
+ deliveryId,
64183
+ targetCount: conversationIds.length
64184
+ })
64185
+ );
64186
+ const settled = await parallelMap(
64187
+ conversationIds,
64188
+ (conversationId) => sendSubscriptionToGroup({
64189
+ agent,
64190
+ conversationId,
64191
+ command,
64192
+ myXmtpAddress,
64193
+ senderRole
64194
+ }),
64195
+ DEFAULT_CONCURRENCY
64196
+ );
64197
+ const results = settled.map((outcome, index2) => {
64198
+ if (outcome.status === "fulfilled") {
64199
+ return outcome.value;
64200
+ }
64201
+ return {
64202
+ conversationId: conversationIds[index2],
64203
+ status: "failed",
64204
+ error: describeError(outcome.reason)
64205
+ };
64206
+ });
64207
+ for (const result of results) {
64208
+ if (result.status === "sent") {
64209
+ continue;
64210
+ }
64211
+ logger.error(
64212
+ LogEvent.SUBSCRIPTION_PUSH_TARGET_FAILED,
64213
+ void 0,
64214
+ buildSubscriptionSendExtras({
64215
+ checkpoint: "outbound/subscription_push_target_failed",
64216
+ outcome: "failed",
64217
+ aspAgentId,
64218
+ deliveryId,
64219
+ conversationId: result.conversationId,
64220
+ reason: result.status
64221
+ })
64222
+ );
64223
+ }
64224
+ const summary = summarizeSubscriptionPush(results);
64225
+ const summaryOutcome = summary.failedCount === 0 ? "success" : summary.sentCount > 0 ? "partial" : "failed";
64226
+ logger.info(
64227
+ LogEvent.SUBSCRIPTION_PUSH_SUMMARY,
64228
+ buildSubscriptionSendExtras({
64229
+ checkpoint: "outbound/subscription_push_summary",
64230
+ outcome: summaryOutcome,
64231
+ aspAgentId,
64232
+ deliveryId,
64233
+ targetCount: summary.conversationCount,
64234
+ sentCount: summary.sentCount,
64235
+ failedCount: summary.failedCount
64236
+ })
64237
+ );
64238
+ return { ...summary, results };
64239
+ }
64240
+ var DEFAULT_CONCURRENCY, conversationSyncPromises2;
64241
+ var init_subscription_push = __esm({
64242
+ "src/subscription-push.ts"() {
64243
+ "use strict";
64244
+ init_log();
64245
+ init_sentry_logger();
64246
+ init_dist2();
64247
+ init_subscription_envelope();
64248
+ init_concurrency();
64249
+ init_xmtp_send();
64250
+ init_subscription_telemetry();
64251
+ DEFAULT_CONCURRENCY = 20;
64252
+ conversationSyncPromises2 = /* @__PURE__ */ new WeakMap();
64253
+ }
64254
+ });
64255
+
64256
+ // src/ai-run-log-path.ts
64257
+ function buildRunLogFileName(request) {
64258
+ const source = request.source;
64259
+ const sessionKey = request.sessionKey;
64260
+ const messageId = request.messageId;
64261
+ const jobId = request.jobId;
64262
+ const safeMessageId = encodeURIComponent(messageId);
64263
+ const prefix = source === "job-dispatch" ? "ai-" : "ai-session-";
64264
+ const encodedContext = source === "job-dispatch" ? encodeURIComponent(jobId ?? "unknown") : encodeURIComponent(sessionKey);
64265
+ const readable = `${prefix}${encodedContext}-${safeMessageId}.log`;
64266
+ if (Buffer.byteLength(readable, "utf8") <= RUN_LOG_FILENAME_MAX_BYTES) {
64267
+ return readable;
64268
+ }
64269
+ const readableWithoutExt = readable.slice(0, -".log".length);
64270
+ const digest = (0, import_node_crypto13.createHash)("sha256").update(readableWithoutExt).digest("hex").slice(0, 24);
64271
+ const suffix = `-${digest}.log`;
64272
+ const budgetForContext = RUN_LOG_FILENAME_MAX_BYTES - Buffer.byteLength(prefix, "utf8") - Buffer.byteLength(suffix, "utf8");
64273
+ const contextHead = truncateToUtf8ByteBudgetSafe(encodedContext, Math.max(budgetForContext, 0));
64274
+ return `${prefix}${contextHead}${suffix}`;
64275
+ }
64276
+ function truncateToUtf8ByteBudgetSafe(encoded, maxBytes) {
64277
+ if (maxBytes <= 0) {
64278
+ return "";
64279
+ }
64280
+ if (Buffer.byteLength(encoded, "utf8") <= maxBytes) {
64281
+ return encoded;
64282
+ }
64283
+ let candidateLength = Math.min(maxBytes, encoded.length);
64284
+ while (candidateLength > 0) {
64285
+ const candidate = encoded.slice(0, candidateLength);
64286
+ if (!endsWithSplitEscape(candidate)) {
64287
+ return candidate;
64288
+ }
64289
+ candidateLength -= 1;
64290
+ }
64291
+ return "";
64292
+ }
64293
+ function endsWithSplitEscape(value) {
64294
+ const percentIndex = value.lastIndexOf("%");
64295
+ if (percentIndex === -1) {
64296
+ return false;
64297
+ }
64298
+ const tail = value.length - percentIndex - 1;
64299
+ return tail < 2;
64300
+ }
64301
+ var import_node_crypto13, RUN_LOG_FILENAME_MAX_BYTES;
64302
+ var init_ai_run_log_path = __esm({
64303
+ "src/ai-run-log-path.ts"() {
64304
+ "use strict";
64305
+ import_node_crypto13 = require("node:crypto");
64306
+ RUN_LOG_FILENAME_MAX_BYTES = 240;
64307
+ }
64308
+ });
64309
+
63500
64310
  // src/ai-adapter.ts
63501
64311
  function buildClaudeAllowedToolsArgs() {
63502
64312
  return ["--allowedTools", ...CLAUDE_ALLOWED_TOOLS];
@@ -65344,7 +66154,7 @@ function normalizeAttentionProvider(provider) {
65344
66154
  return provider === "unknown" ? null : provider;
65345
66155
  }
65346
66156
  function stableHash2(value) {
65347
- return (0, import_node_crypto13.createHash)("sha256").update(value).digest("hex").slice(0, 24);
66157
+ return (0, import_node_crypto14.createHash)("sha256").update(value).digest("hex").slice(0, 24);
65348
66158
  }
65349
66159
  function formatCommand(args) {
65350
66160
  return args.map(shellQuote).join(" ");
@@ -65368,17 +66178,18 @@ function shortenJobId2(jobId) {
65368
66178
  }
65369
66179
  return `${jobId.slice(0, 6)}\u2026${jobId.slice(-4)}`;
65370
66180
  }
65371
- var import_node_crypto13, import_node_fs26, import_promises12, import_node_os9, import_node_path31, AiRunner, CODEX_TOOL_FAILURE_MARKER;
66181
+ var import_node_crypto14, import_node_fs26, import_promises12, import_node_os9, import_node_path31, AiRunner, CODEX_TOOL_FAILURE_MARKER;
65372
66182
  var init_ai_runner = __esm({
65373
66183
  "src/ai-runner.ts"() {
65374
66184
  "use strict";
65375
66185
  init_log();
65376
- import_node_crypto13 = require("node:crypto");
66186
+ import_node_crypto14 = require("node:crypto");
65377
66187
  import_node_fs26 = require("node:fs");
65378
66188
  import_promises12 = require("node:fs/promises");
65379
66189
  import_node_os9 = require("node:os");
65380
66190
  import_node_path31 = require("node:path");
65381
66191
  init_ai_command();
66192
+ init_ai_run_log_path();
65382
66193
  init_paths();
65383
66194
  init_file_store();
65384
66195
  init_ai_provider();
@@ -66006,13 +66817,15 @@ var init_ai_runner = __esm({
66006
66817
  );
66007
66818
  }
66008
66819
  buildRunLogPath(request) {
66009
- const safeMessageId = encodeURIComponent(request.messageId);
66010
- if (request.source === "job-dispatch") {
66011
- const safeJobId = encodeURIComponent(request.jobId ?? "unknown");
66012
- return (0, import_node_path31.join)(this.logsDir, `ai-${safeJobId}-${safeMessageId}.log`);
66013
- }
66014
- const safeSessionKey = encodeURIComponent(request.sessionKey);
66015
- return (0, import_node_path31.join)(this.logsDir, `ai-session-${safeSessionKey}-${safeMessageId}.log`);
66820
+ return (0, import_node_path31.join)(
66821
+ this.logsDir,
66822
+ buildRunLogFileName({
66823
+ source: request.source,
66824
+ sessionKey: request.sessionKey,
66825
+ messageId: request.messageId,
66826
+ jobId: request.jobId
66827
+ })
66828
+ );
66016
66829
  }
66017
66830
  readRunAiSessionId(provider, request, file, sessionMeta) {
66018
66831
  if (request.source === "job-dispatch" && request.jobId) {
@@ -67037,6 +67850,9 @@ function commandSentryExtra(command, extra = {}) {
67037
67850
  base.myAgentId = command.myAgentId ?? "";
67038
67851
  base.toAgentId = command.toAgentId ?? "";
67039
67852
  base.toXmtpAddress = command.toXmtpAddress ?? "";
67853
+ } else if (command.type === "xmtp-subscription-push") {
67854
+ base.myAgentId = command.myAgentId;
67855
+ base.conversationCount = command.conversationIds.length;
67040
67856
  } else if (command.type === "xmtp-deny-pending-conversation") {
67041
67857
  base.groupId = command.groupId;
67042
67858
  base.jobId = command.jobId ?? "";
@@ -67392,6 +68208,10 @@ async function handleDaemonCommand(command, params) {
67392
68208
  });
67393
68209
  return { ok: true, messageId: result.messageId };
67394
68210
  }
68211
+ if (command.type === "xmtp-subscription-push") {
68212
+ const result = await handleSubscriptionPushCommand({ command, service: params.service });
68213
+ return { ok: true, payload: result };
68214
+ }
67395
68215
  if (command.type === "xmtp-get-pending-list") {
67396
68216
  return { ok: true, payload: await listPendingTaskRequests(params) };
67397
68217
  }
@@ -67474,12 +68294,12 @@ async function listPendingTaskRequests(params) {
67474
68294
  }
67475
68295
  async function denyPendingConversationsByJobId(jobId, params, content2) {
67476
68296
  const pending = await listPendingTaskRequests(params);
67477
- const matched = pending.filter((item) => isPlainObject2(item) && readString4(item.jobId) === jobId);
68297
+ const matched = pending.filter((item) => isPlainObject4(item) && readString4(item.jobId) === jobId);
67478
68298
  const clientAddresses = [...params.service.getClients().keys()];
67479
68299
  const targetsByKey = collectDenyTargetsFromPending(matched, clientAddresses);
67480
68300
  const groupIds = /* @__PURE__ */ new Set();
67481
68301
  for (const item of matched) {
67482
- if (!isPlainObject2(item)) {
68302
+ if (!isPlainObject4(item)) {
67483
68303
  continue;
67484
68304
  }
67485
68305
  const groupId = readString4(item.groupId);
@@ -67506,7 +68326,7 @@ async function denyPendingConversationsByJobId(jobId, params, content2) {
67506
68326
  function collectDenyTargetsFromPending(pending, fallbackAddresses) {
67507
68327
  const targetsByKey = /* @__PURE__ */ new Map();
67508
68328
  for (const item of pending) {
67509
- if (!isPlainObject2(item)) {
68329
+ if (!isPlainObject4(item)) {
67510
68330
  continue;
67511
68331
  }
67512
68332
  const groupId = readString4(item.groupId);
@@ -67531,7 +68351,7 @@ ${groupId}`, {
67531
68351
  async function resolvePendingTargetsByGroupId(groupId, addresses, params) {
67532
68352
  const addressSet = addresses ? new Set(addresses) : null;
67533
68353
  const pending = (await listPendingTaskRequests(params)).filter((item) => {
67534
- if (!isPlainObject2(item) || readString4(item.groupId) !== groupId) {
68354
+ if (!isPlainObject4(item) || readString4(item.groupId) !== groupId) {
67535
68355
  return false;
67536
68356
  }
67537
68357
  const address = readString4(item.myXmtpAddress);
@@ -67651,7 +68471,7 @@ function filterKnownPendingGroups(items, sessionStore) {
67651
68471
  return items;
67652
68472
  }
67653
68473
  return items.filter((item) => {
67654
- if (!isPlainObject2(item)) {
68474
+ if (!isPlainObject4(item)) {
67655
68475
  return true;
67656
68476
  }
67657
68477
  const jobId = readString4(item.jobId);
@@ -67662,7 +68482,7 @@ function filterKnownPendingGroups(items, sessionStore) {
67662
68482
  return sessionStore.querySessions({ jobId, toAgentId, limit: 1 }).length === 0;
67663
68483
  });
67664
68484
  }
67665
- function isPlainObject2(value) {
68485
+ function isPlainObject4(value) {
67666
68486
  return !!value && typeof value === "object" && !Array.isArray(value);
67667
68487
  }
67668
68488
  function readString4(value) {
@@ -67796,6 +68616,10 @@ function formatCommandCompleted(command, result) {
67796
68616
  if (command.type === "xmtp-send") {
67797
68617
  return `[okx-agent-task] xmtp-send command completed id=${command.id} job=${command.jobId} messageId=${result.messageId}`;
67798
68618
  }
68619
+ if (command.type === "xmtp-subscription-push") {
68620
+ const payload = isPlainObject4(result.payload) ? result.payload : {};
68621
+ return `[okx-agent-task] xmtp-subscription-push command completed id=${command.id} deliveryId=${command.deliveryId ?? "(none)"} conversationCount=${payload.conversationCount ?? 0} sentCount=${payload.sentCount ?? 0} failedCount=${payload.failedCount ?? 0}`;
68622
+ }
67799
68623
  if (command.type === "xmtp-get-pending-list") {
67800
68624
  return `[okx-agent-task] xmtp-get-pending-list command completed id=${command.id}`;
67801
68625
  }
@@ -67816,8 +68640,8 @@ function formatCommandCompleted(command, result) {
67816
68640
  return `[okx-agent-task] xmtp-refresh-agents command completed id=${command.id}`;
67817
68641
  }
67818
68642
  function formatXmtpDebugCompleted(command, payload) {
67819
- const record = isPlainObject2(payload) ? payload : {};
67820
- const summary = isPlainObject2(record.summary) ? record.summary : {};
68643
+ const record = isPlainObject4(payload) ? payload : {};
68644
+ const summary = isPlainObject4(record.summary) ? record.summary : {};
67821
68645
  const count = command.action === "group-list" ? record.groupCount ?? 0 : command.action === "group-inspect" ? record.conversationFound ? 1 : 0 : summary.matchedCount ?? 0;
67822
68646
  return `[okx-agent-task] xmtp-debug command completed id=${command.id} action=${command.action} rows=${String(count)}`;
67823
68647
  }
@@ -67978,6 +68802,7 @@ var init_command_processor = __esm({
67978
68802
  init_command_store();
67979
68803
  init_invalid_message_store();
67980
68804
  init_xmtp_send();
68805
+ init_subscription_push();
67981
68806
  init_ai_runner();
67982
68807
  init_session_store();
67983
68808
  init_ai_provider();
@@ -68005,7 +68830,7 @@ async function enqueueAiJobMessageDispatch(params) {
68005
68830
  await commands.submit(command);
68006
68831
  return command;
68007
68832
  }
68008
- async function enqueueAiSessionMessageDispatch(params) {
68833
+ async function enqueueAiSessionMessageDispatchIdempotent(params) {
68009
68834
  const commands = params.commands ?? new CommandStore(params.homeDir);
68010
68835
  const messageId = params.event.messageId ?? `session-${Date.now()}`;
68011
68836
  const command = commands.createAiSessionMessageDispatchCommand({
@@ -68015,7 +68840,21 @@ async function enqueueAiSessionMessageDispatch(params) {
68015
68840
  jobId: params.event.jobId,
68016
68841
  agentId: params.event.agentId
68017
68842
  });
68843
+ const idempotencyKey = params.event.idempotencyKey?.trim();
68844
+ if (idempotencyKey) {
68845
+ const dbKey = `${command.sessionKey}
68846
+ ${command.messageId}`;
68847
+ const result = await commands.submitIdempotent(command, dbKey);
68848
+ return {
68849
+ command: result.created ? command : { ...command, id: result.commandId },
68850
+ created: result.created
68851
+ };
68852
+ }
68018
68853
  await commands.submit(command);
68854
+ return { command, created: true };
68855
+ }
68856
+ async function enqueueAiSessionMessageDispatch(params) {
68857
+ const { command } = await enqueueAiSessionMessageDispatchIdempotent(params);
68019
68858
  return command;
68020
68859
  }
68021
68860
  var init_ai_dispatch_queue = __esm({
@@ -68025,6 +68864,156 @@ var init_ai_dispatch_queue = __esm({
68025
68864
  }
68026
68865
  });
68027
68866
 
68867
+ // src/subscription-inbound.ts
68868
+ function decideSubscriptionGate(input) {
68869
+ if (!isSubscriptionEnvelope(input.payload)) {
68870
+ return { kind: "not_subscription" };
68871
+ }
68872
+ if (input.chatType === "dm") {
68873
+ return { kind: "drop", reason: "dm_subscription" };
68874
+ }
68875
+ if (input.chatType === "unknown") {
68876
+ return { kind: "drop", reason: "unknown_conversation_subscription" };
68877
+ }
68878
+ const read = readSubscriptionEnvelope(input.payload);
68879
+ if (!read.ok) {
68880
+ return { kind: "drop", reason: "invalid_envelope", detail: read.reason };
68881
+ }
68882
+ if (!input.senderInboxId) {
68883
+ return { kind: "drop", reason: "missing_sender_inbox_id" };
68884
+ }
68885
+ return { kind: "v2", envelope: read.envelope };
68886
+ }
68887
+ function partitionSubscriptionJobsByOwner(input) {
68888
+ const owned = [];
68889
+ for (const job of input.jobs) {
68890
+ if (job.userAgentId === input.localAgentId) {
68891
+ owned.push(job);
68892
+ }
68893
+ }
68894
+ return {
68895
+ owned,
68896
+ returnedCount: input.jobs.length,
68897
+ ownedCount: owned.length,
68898
+ ignoredCount: input.jobs.length - owned.length
68899
+ };
68900
+ }
68901
+ function normalizeSubscriptionJobs(input) {
68902
+ const seen = /* @__PURE__ */ new Set();
68903
+ const normalized = [];
68904
+ for (const job of input.jobs) {
68905
+ const dedupKey = `${job.jobId}|${job.userAgentId}|${job.aspAgentId}`;
68906
+ if (seen.has(dedupKey)) {
68907
+ continue;
68908
+ }
68909
+ seen.add(dedupKey);
68910
+ normalized.push({
68911
+ jobId: job.jobId,
68912
+ userAgentId: job.userAgentId,
68913
+ aspAgentId: job.aspAgentId,
68914
+ msgType: SUBSCRIPTION_MESSAGE_TYPE,
68915
+ content: input.envelope.content,
68916
+ ...input.envelope.deliveryId ? { deliveryId: input.envelope.deliveryId } : {},
68917
+ conversationId: input.conversationId,
68918
+ senderInboxId: input.senderInboxId
68919
+ });
68920
+ }
68921
+ return normalized;
68922
+ }
68923
+ function subscriptionIdempotencyKey(input) {
68924
+ const messageDimension = input.deliveryId || input.messageId;
68925
+ return `${input.myXmtpAddress}|${messageDimension}|${input.jobId}`;
68926
+ }
68927
+ function tryMarkSubscriptionTarget(key) {
68928
+ if (subscriptionTargets.has(key)) {
68929
+ return false;
68930
+ }
68931
+ subscriptionTargets.set(key, Date.now());
68932
+ while (subscriptionTargets.size > SUBSCRIPTION_TARGETS_MAX) {
68933
+ const oldest = subscriptionTargets.keys().next().value;
68934
+ if (oldest === void 0) {
68935
+ break;
68936
+ }
68937
+ subscriptionTargets.delete(oldest);
68938
+ }
68939
+ return true;
68940
+ }
68941
+ function redactSenderInboxId(senderInboxId, left = 8, right = 6) {
68942
+ if (!senderInboxId) {
68943
+ return "(none)";
68944
+ }
68945
+ if (senderInboxId.length <= left + right + 1) {
68946
+ return senderInboxId;
68947
+ }
68948
+ return `${senderInboxId.slice(0, left)}\u2026${senderInboxId.slice(-right)}`;
68949
+ }
68950
+ async function fanOutSubscriptionJobs(input) {
68951
+ const { jobs, myXmtpAddress, messageId, dispatch, concurrency } = input;
68952
+ const total = jobs.length;
68953
+ const capacityAnomaly = total > SUBSCRIPTION_FANOUT_MAX;
68954
+ if (capacityAnomaly) {
68955
+ logWithTimestamp(
68956
+ `[okx-agent-task:${myXmtpAddress}] subscription fan-out capacity anomaly: jobs=${total} max=${SUBSCRIPTION_FANOUT_MAX} message=${messageId}`
68957
+ );
68958
+ }
68959
+ const settled = await parallelMap(
68960
+ jobs,
68961
+ async (job) => {
68962
+ const idempotencyKey = subscriptionIdempotencyKey({
68963
+ myXmtpAddress,
68964
+ deliveryId: job.deliveryId,
68965
+ messageId,
68966
+ jobId: job.jobId
68967
+ });
68968
+ if (subscriptionTargets.has(idempotencyKey)) {
68969
+ return { key: idempotencyKey, outcome: "duplicate" };
68970
+ }
68971
+ const dispatchOutcome = await dispatch(job, idempotencyKey);
68972
+ const isDurableDuplicate = !!dispatchOutcome && dispatchOutcome.created === false;
68973
+ return { key: idempotencyKey, outcome: isDurableDuplicate ? "duplicate" : "started" };
68974
+ },
68975
+ concurrency ?? 20
68976
+ );
68977
+ let started = 0;
68978
+ let failed = 0;
68979
+ let skippedDuplicate = 0;
68980
+ const results = [];
68981
+ settled.forEach((result, idx) => {
68982
+ const job = jobs[idx];
68983
+ if (result.status === "rejected") {
68984
+ failed += 1;
68985
+ results.push({ jobId: job?.jobId ?? "(unknown)", outcome: "failed", error: result.reason });
68986
+ logWithTimestamp(
68987
+ `[okx-agent-task:${myXmtpAddress}] subscription dispatch failed message=${messageId} job=${job?.jobId ?? "(unknown)"} sender=${redactSenderInboxId(job?.senderInboxId)}:`,
68988
+ result.reason
68989
+ );
68990
+ return;
68991
+ }
68992
+ tryMarkSubscriptionTarget(result.value.key);
68993
+ if (result.value.outcome === "duplicate") {
68994
+ skippedDuplicate += 1;
68995
+ results.push({ jobId: job?.jobId ?? "(unknown)", outcome: "duplicate" });
68996
+ } else {
68997
+ started += 1;
68998
+ results.push({ jobId: job?.jobId ?? "(unknown)", outcome: "started" });
68999
+ }
69000
+ });
69001
+ return { total, started, failed, skippedDuplicate, capacityAnomaly, results };
69002
+ }
69003
+ var SUBSCRIPTION_TARGETS_MAX, subscriptionTargets, SUBSCRIPTION_FANOUT_MAX;
69004
+ var init_subscription_inbound = __esm({
69005
+ "src/subscription-inbound.ts"() {
69006
+ "use strict";
69007
+ init_log();
69008
+ init_concurrency();
69009
+ init_subscription_types();
69010
+ init_subscription_envelope();
69011
+ SUBSCRIPTION_TARGETS_MAX = 2e3;
69012
+ subscriptionTargets = /* @__PURE__ */ new Map();
69013
+ SUBSCRIPTION_FANOUT_MAX = 250;
69014
+ }
69015
+ });
69016
+
68028
69017
  // src/message-handler.ts
68029
69018
  function tryMarkProcessed(messageId) {
68030
69019
  if (processedMessageIds.has(messageId)) {
@@ -68040,7 +69029,7 @@ function tryMarkProcessed(messageId) {
68040
69029
  }
68041
69030
  return true;
68042
69031
  }
68043
- function isPlainObject3(value) {
69032
+ function isPlainObject5(value) {
68044
69033
  return !!value && typeof value === "object" && !Array.isArray(value);
68045
69034
  }
68046
69035
  function safeStringify(value) {
@@ -68100,9 +69089,9 @@ function readNumber(value) {
68100
69089
  }
68101
69090
  function resolveSessionAgentId(params) {
68102
69091
  const { chatType, payload, myXmtpAddress } = params;
68103
- const payloadObject = isPlainObject3(payload) ? payload : null;
68104
- const sender = isPlainObject3(payloadObject?.sender) ? payloadObject.sender : null;
68105
- const message = isPlainObject3(payloadObject?.message) ? payloadObject.message : null;
69092
+ const payloadObject = isPlainObject5(payload) ? payload : null;
69093
+ const sender = isPlainObject5(payloadObject?.sender) ? payloadObject.sender : null;
69094
+ const message = isPlainObject5(payloadObject?.message) ? payloadObject.message : null;
68106
69095
  const localAgentId = XmtpService.getInstance().getAgentByAddress(myXmtpAddress)?.agentId ?? null;
68107
69096
  if (chatType === "group") {
68108
69097
  return readString5(sender?.agentId) ?? readString5(payloadObject?.receiverAgentId) ?? localAgentId;
@@ -68113,7 +69102,7 @@ function resolveSessionAgentId(params) {
68113
69102
  return localAgentId;
68114
69103
  }
68115
69104
  function routeMessage(chatType, parsed, payload) {
68116
- if (!parsed || !isPlainObject3(payload)) {
69105
+ if (!parsed || !isPlainObject5(payload)) {
68117
69106
  return {
68118
69107
  jobId: BACKUP_JOB_ID,
68119
69108
  route: "backup",
@@ -68125,7 +69114,7 @@ function routeMessage(chatType, parsed, payload) {
68125
69114
  return jobId ? { jobId, route: "job", routeReason: "group-job-id" } : { jobId: BACKUP_JOB_ID, route: "backup", routeReason: "group-missing-job-id" };
68126
69115
  }
68127
69116
  if (chatType === "dm") {
68128
- const message = isPlainObject3(payload.message) ? payload.message : null;
69117
+ const message = isPlainObject5(payload.message) ? payload.message : null;
68129
69118
  const nestedJobId = readString5(message?.jobId);
68130
69119
  const directJobId = readString5(payload.jobId);
68131
69120
  const jobId = nestedJobId ?? directJobId;
@@ -68138,10 +69127,10 @@ function routeMessage(chatType, parsed, payload) {
68138
69127
  return { jobId: BACKUP_JOB_ID, route: "backup", routeReason: "unknown-conversation" };
68139
69128
  }
68140
69129
  function extractDirectToUserNotification(payload) {
68141
- if (!isPlainObject3(payload)) {
69130
+ if (!isPlainObject5(payload)) {
68142
69131
  return { kind: "none" };
68143
69132
  }
68144
- const message = isPlainObject3(payload.message) ? payload.message : null;
69133
+ const message = isPlainObject5(payload.message) ? payload.message : null;
68145
69134
  if (message?.isDirectToUser !== true) {
68146
69135
  return { kind: "none" };
68147
69136
  }
@@ -68165,10 +69154,10 @@ function extractDirectToUserNotification(payload) {
68165
69154
  };
68166
69155
  }
68167
69156
  function extractSystemNotification(payload) {
68168
- if (!isPlainObject3(payload)) {
69157
+ if (!isPlainObject5(payload)) {
68169
69158
  return null;
68170
69159
  }
68171
- const message = isPlainObject3(payload.message) ? payload.message : null;
69160
+ const message = isPlainObject5(payload.message) ? payload.message : null;
68172
69161
  const isDirectCommunication = message?.isDirectCommunication === true;
68173
69162
  if (message?.source !== "system" && !isDirectCommunication) {
68174
69163
  return null;
@@ -68394,7 +69383,7 @@ function inboundStageExtras(stage, outcome) {
68394
69383
  })
68395
69384
  };
68396
69385
  }
68397
- function definedExtras(input) {
69386
+ function definedExtras2(input) {
68398
69387
  return Object.entries(input).reduce((out, [key, value]) => {
68399
69388
  const text = readString5(value);
68400
69389
  if (text) {
@@ -68426,7 +69415,7 @@ function buildInboundTerminalExtras(input) {
68426
69415
  originPlatform: classifyOriginPlatform({ env: process.env }),
68427
69416
  deliveryId: resolveDeliveryId({ messageId, jobId, sessionKey })
68428
69417
  }),
68429
- ...definedExtras({
69418
+ ...definedExtras2({
68430
69419
  messageId,
68431
69420
  conversationId,
68432
69421
  chatType,
@@ -68460,7 +69449,7 @@ function maybeNotifyInboundAgentMessage(params) {
68460
69449
  return;
68461
69450
  }
68462
69451
  const content2 = payloadObject.content;
68463
- const sender = isPlainObject3(payloadObject.sender) ? payloadObject.sender : null;
69452
+ const sender = isPlainObject5(payloadObject.sender) ? payloadObject.sender : null;
68464
69453
  const localAgent = XmtpService.getInstance().getAgentByAddress(params.deps.myXmtpAddress);
68465
69454
  void notifyAgentMessageToUserAttention({
68466
69455
  direction: "inbound" /* INBOUND */,
@@ -68491,11 +69480,11 @@ function stringifySystemPayload(payload) {
68491
69480
  }
68492
69481
  }
68493
69482
  function summarizePayloadForLog(payload) {
68494
- if (!isPlainObject3(payload)) {
69483
+ if (!isPlainObject5(payload)) {
68495
69484
  return "payload=non-object";
68496
69485
  }
68497
- const message = isPlainObject3(payload.message) ? payload.message : null;
68498
- const sender = isPlainObject3(payload.sender) ? payload.sender : null;
69486
+ const message = isPlainObject5(payload.message) ? payload.message : null;
69487
+ const sender = isPlainObject5(payload.sender) ? payload.sender : null;
68499
69488
  const jobId = readString5(payload.jobId) ?? readString5(message?.jobId);
68500
69489
  const parts = [
68501
69490
  readString5(payload.msgType) ? `type=${readString5(payload.msgType)}` : null,
@@ -68764,7 +69753,7 @@ async function verifyInboundA2AGroupMessage(params) {
68764
69753
  messageId,
68765
69754
  groupId,
68766
69755
  jobId,
68767
- agentId: readString5(isPlainObject3(payload.sender) ? payload.sender.agentId : null),
69756
+ agentId: readString5(isPlainObject5(payload.sender) ? payload.sender.agentId : null),
68768
69757
  localAgentId: myAgent?.agentId ?? null,
68769
69758
  senderAddress,
68770
69759
  senderInboxId,
@@ -68790,7 +69779,7 @@ async function verifyInboundA2AGroupMessage(params) {
68790
69779
  });
68791
69780
  return false;
68792
69781
  }
68793
- const sender = isPlainObject3(payload.sender) ? payload.sender : null;
69782
+ const sender = isPlainObject5(payload.sender) ? payload.sender : null;
68794
69783
  const senderAgentId = readString5(sender?.agentId);
68795
69784
  const senderRole = readNumber(sender?.role);
68796
69785
  if (!senderAgentId || !myAgent) {
@@ -68959,9 +69948,302 @@ ${failureMessage}`,
68959
69948
  }
68960
69949
  return true;
68961
69950
  }
69951
+ async function processSubscriptionInbound(ctx, deps, input) {
69952
+ const { decision, conversationId, messageId } = input;
69953
+ const senderInboxId = ctx.message.senderInboxId ?? "";
69954
+ if (decision.kind === "drop") {
69955
+ logger.info(
69956
+ LogEvent.SUBSCRIPTION_INBOUND_GATE_DROPPED,
69957
+ buildSubscriptionInboundExtras({
69958
+ checkpoint: "inbound/subscription_gate_dropped",
69959
+ outcome: "dropped",
69960
+ conversationId,
69961
+ messageId,
69962
+ senderInboxId: shortenLogValue(senderInboxId),
69963
+ reason: decision.reason
69964
+ })
69965
+ );
69966
+ logWithTimestamp(
69967
+ `[okx-agent-task:${deps.myXmtpAddress}] subscription message dropped reason=${decision.reason}${decision.detail ? ` detail=${decision.detail}` : ""} conv=${conversationId} message=${shortenLogValue(messageId)} sender=${shortenLogValue(senderInboxId)}`
69968
+ );
69969
+ return true;
69970
+ }
69971
+ if (decision.kind === "not_subscription") {
69972
+ return true;
69973
+ }
69974
+ const { envelope } = decision;
69975
+ const agenticId = XmtpService.getInstance().getAgentByAddress(deps.myXmtpAddress)?.agentId;
69976
+ if (!agenticId) {
69977
+ logWithTimestamp(
69978
+ `[okx-agent-task:${deps.myXmtpAddress}] subscription fail-closed: no local agenticId for ${deps.myXmtpAddress}, conv=${conversationId} message=${shortenLogValue(messageId)}`
69979
+ );
69980
+ logger.error(
69981
+ LogEvent.SUBSCRIPTION_INBOUND_AGENTIC_ID_MISSING,
69982
+ new Error("Subscription inbound: local agenticId not resolved"),
69983
+ buildSubscriptionInboundExtras({
69984
+ checkpoint: "inbound/subscription_agentic_id",
69985
+ outcome: "blocked",
69986
+ conversationId,
69987
+ messageId,
69988
+ senderInboxId: shortenLogValue(senderInboxId),
69989
+ reason: "missing_agentic_id"
69990
+ })
69991
+ );
69992
+ return true;
69993
+ }
69994
+ logWithTimestamp(
69995
+ `[okx-agent-task:${deps.myXmtpAddress}] subscription received msgType=${envelope.msgType} conv=${conversationId} message=${shortenLogValue(messageId)} sender=${shortenLogValue(senderInboxId)}`
69996
+ );
69997
+ logger.info(
69998
+ LogEvent.SUBSCRIPTION_INBOUND_RECEIVED,
69999
+ buildSubscriptionInboundExtras({
70000
+ checkpoint: "inbound/subscription_received",
70001
+ userAgentId: agenticId,
70002
+ conversationId,
70003
+ messageId,
70004
+ deliveryId: envelope.deliveryId,
70005
+ senderInboxId: shortenLogValue(senderInboxId)
70006
+ })
70007
+ );
70008
+ const listJobs = deps.listJobsByVerifiedSenderImpl ?? listJobsByVerifiedSenderWithCapability;
70009
+ logger.info(
70010
+ LogEvent.SUBSCRIPTION_JOBS_QUERY_STARTED,
70011
+ buildSubscriptionInboundExtras({
70012
+ checkpoint: "inbound/subscription_jobs_query_started",
70013
+ userAgentId: agenticId,
70014
+ conversationId,
70015
+ messageId
70016
+ })
70017
+ );
70018
+ const listJobsResult = await listJobs({
70019
+ groupId: conversationId,
70020
+ senderInboxId,
70021
+ agenticId
70022
+ });
70023
+ logWithTimestamp(
70024
+ `[okx-agent-task:${deps.myXmtpAddress}] subscription queried status=${listJobsResult.status} jobs=${listJobsResult.jobs.length} invalid=${listJobsResult.invalidCount} conv=${conversationId} message=${shortenLogValue(messageId)}`
70025
+ );
70026
+ if (listJobsResult.status === "fail_closed") {
70027
+ logger.error(
70028
+ LogEvent.SUBSCRIPTION_JOBS_FAIL_CLOSED,
70029
+ new Error("Subscription inbound: listJobsByVerifiedSender fail-closed"),
70030
+ buildSubscriptionInboundExtras({
70031
+ checkpoint: "inbound/subscription_jobs_fail_closed",
70032
+ outcome: "blocked",
70033
+ userAgentId: agenticId,
70034
+ conversationId,
70035
+ messageId,
70036
+ senderInboxId: shortenLogValue(senderInboxId),
70037
+ reason: listJobsResult.reason ?? "unknown",
70038
+ errorCode: listJobsResult.errorCode
70039
+ })
70040
+ );
70041
+ return true;
70042
+ }
70043
+ if (listJobsResult.status === "empty") {
70044
+ logWithTimestamp(
70045
+ `[okx-agent-task:${deps.myXmtpAddress}] subscription empty: no verified jobs for conv=${conversationId} message=${shortenLogValue(messageId)}`
70046
+ );
70047
+ logger.info(
70048
+ LogEvent.SUBSCRIPTION_JOBS_EMPTY,
70049
+ buildSubscriptionInboundExtras({
70050
+ checkpoint: "inbound/subscription_jobs_empty",
70051
+ outcome: "ignored",
70052
+ userAgentId: agenticId,
70053
+ conversationId,
70054
+ messageId,
70055
+ reason: "jobs_empty",
70056
+ returnedCount: 0,
70057
+ invalidCount: listJobsResult.invalidCount
70058
+ })
70059
+ );
70060
+ return true;
70061
+ }
70062
+ logger.info(
70063
+ LogEvent.SUBSCRIPTION_JOBS_QUERY_SUCCEEDED,
70064
+ buildSubscriptionInboundExtras({
70065
+ checkpoint: "inbound/subscription_jobs_query_succeeded",
70066
+ userAgentId: agenticId,
70067
+ conversationId,
70068
+ messageId,
70069
+ returnedCount: listJobsResult.jobs.length,
70070
+ invalidCount: listJobsResult.invalidCount
70071
+ })
70072
+ );
70073
+ const ownership = partitionSubscriptionJobsByOwner({
70074
+ jobs: listJobsResult.jobs,
70075
+ localAgentId: agenticId
70076
+ });
70077
+ logWithTimestamp(
70078
+ `[okx-agent-task:${deps.myXmtpAddress}] subscription routed receiver=${agenticId} returned=${ownership.returnedCount} localOwned=${ownership.ownedCount} ignored=${ownership.ignoredCount} conv=${conversationId} message=${shortenLogValue(messageId)}`
70079
+ );
70080
+ logger.info(
70081
+ LogEvent.SUBSCRIPTION_INBOUND_OWNERSHIP_ROUTED,
70082
+ buildSubscriptionInboundExtras({
70083
+ checkpoint: "inbound/subscription_ownership_routed",
70084
+ outcome: ownership.ownedCount === 0 ? "ignored" : null,
70085
+ userAgentId: agenticId,
70086
+ conversationId,
70087
+ messageId,
70088
+ returnedCount: ownership.returnedCount,
70089
+ ownedCount: ownership.ownedCount,
70090
+ ignoredCount: ownership.ignoredCount
70091
+ })
70092
+ );
70093
+ if (ownership.ownedCount === 0) {
70094
+ return true;
70095
+ }
70096
+ const normalizedJobs = normalizeSubscriptionJobs({
70097
+ jobs: ownership.owned,
70098
+ envelope,
70099
+ conversationId,
70100
+ senderInboxId
70101
+ });
70102
+ logWithTimestamp(
70103
+ `[okx-agent-task:${deps.myXmtpAddress}] subscription expanded jobs=${normalizedJobs.length} conv=${conversationId} message=${shortenLogValue(messageId)}`
70104
+ );
70105
+ let senderXmtpAddress = null;
70106
+ try {
70107
+ const resolved = await ctx.getSenderAddress();
70108
+ senderXmtpAddress = resolved ? resolved : null;
70109
+ } catch (err2) {
70110
+ logWithTimestamp(
70111
+ `[okx-agent-task:${deps.myXmtpAddress}] subscription sender address unresolved (degrading to null) conv=${conversationId} message=${shortenLogValue(messageId)}:`,
70112
+ err2
70113
+ );
70114
+ }
70115
+ const fanoutResult = await fanOutSubscriptionJobs({
70116
+ jobs: normalizedJobs,
70117
+ myXmtpAddress: deps.myXmtpAddress,
70118
+ messageId,
70119
+ dispatch: async (job, idempotencyKey) => {
70120
+ const sessionKey = buildSessionKey({
70121
+ jobId: job.jobId,
70122
+ myAgentId: job.userAgentId,
70123
+ toAgentId: job.aspAgentId
70124
+ });
70125
+ deps.sessionStore?.upsertSession({
70126
+ sessionKey,
70127
+ jobId: job.jobId,
70128
+ myAgentId: job.userAgentId,
70129
+ toAgentId: job.aspAgentId,
70130
+ groupId: job.conversationId,
70131
+ myAgentXmtpAddress: deps.myXmtpAddress,
70132
+ toAgentXmtpAddress: senderXmtpAddress
70133
+ });
70134
+ return deps.onSessionMessage?.({
70135
+ sessionKey,
70136
+ content: JSON.stringify({
70137
+ msgType: job.msgType,
70138
+ content: job.content,
70139
+ ...job.deliveryId ? { deliveryId: job.deliveryId } : {},
70140
+ jobId: job.jobId,
70141
+ userAgentId: job.userAgentId,
70142
+ aspAgentId: job.aspAgentId,
70143
+ conversationId: job.conversationId,
70144
+ senderInboxId: job.senderInboxId,
70145
+ // Raw XMTP messageId preserved for sender/receiver telemetry join;
70146
+ // the durable dedup identity is the idempotencyKey passed as this
70147
+ // session-message's own `messageId` field below (M1 fix).
70148
+ xmtpMessageId: messageId
70149
+ }),
70150
+ messageId: idempotencyKey,
70151
+ // Durable exactly-once enqueue key (M2): the wiring persists via
70152
+ // CommandStore.submitIdempotent keyed on sessionKey+messageId so a
70153
+ // re-processed / replayed message never creates a second command.
70154
+ idempotencyKey,
70155
+ jobId: job.jobId,
70156
+ agentId: job.userAgentId
70157
+ });
70158
+ }
70159
+ });
70160
+ logWithTimestamp(
70161
+ `[okx-agent-task:${deps.myXmtpAddress}] subscription fan-out succeeded=${fanoutResult.started} failed=${fanoutResult.failed} skippedDuplicate=${fanoutResult.skippedDuplicate} total=${fanoutResult.total} capacityAnomaly=${fanoutResult.capacityAnomaly} conv=${conversationId} message=${shortenLogValue(messageId)}`
70162
+ );
70163
+ const normalizedJobsById = new Map(normalizedJobs.map((job) => [job.jobId, job]));
70164
+ for (const result of fanoutResult.results) {
70165
+ const job = normalizedJobsById.get(result.jobId);
70166
+ if (result.outcome === "started") {
70167
+ logger.info(
70168
+ LogEvent.SUBSCRIPTION_JOB_DISPATCHED,
70169
+ buildSubscriptionJobExtras({
70170
+ checkpoint: "dispatch/subscription_job_dispatched",
70171
+ outcome: "success",
70172
+ jobId: result.jobId,
70173
+ userAgentId: agenticId,
70174
+ aspAgentId: job?.aspAgentId,
70175
+ conversationId,
70176
+ deliveryId: envelope.deliveryId
70177
+ })
70178
+ );
70179
+ } else if (result.outcome === "duplicate") {
70180
+ logger.info(
70181
+ LogEvent.SUBSCRIPTION_JOB_DUPLICATE_IGNORED,
70182
+ buildSubscriptionJobExtras({
70183
+ checkpoint: "dispatch/subscription_job_duplicate_ignored",
70184
+ outcome: "ignored",
70185
+ jobId: result.jobId,
70186
+ userAgentId: agenticId,
70187
+ aspAgentId: job?.aspAgentId,
70188
+ conversationId,
70189
+ deliveryId: envelope.deliveryId,
70190
+ reason: "duplicate_delivery"
70191
+ })
70192
+ );
70193
+ } else {
70194
+ logger.error(
70195
+ LogEvent.SUBSCRIPTION_JOB_FAILED,
70196
+ toLoggableError(result.error),
70197
+ buildSubscriptionJobExtras({
70198
+ checkpoint: "dispatch/subscription_job_failed",
70199
+ outcome: "failed",
70200
+ jobId: result.jobId,
70201
+ userAgentId: agenticId,
70202
+ aspAgentId: job?.aspAgentId,
70203
+ conversationId,
70204
+ deliveryId: envelope.deliveryId,
70205
+ reason: "session_dispatch_failed"
70206
+ })
70207
+ );
70208
+ }
70209
+ }
70210
+ logger.info(
70211
+ LogEvent.SUBSCRIPTION_FANOUT_SUMMARY,
70212
+ buildSubscriptionJobExtras({
70213
+ checkpoint: "dispatch/subscription_fanout_summary",
70214
+ outcome: fanoutResult.failed === 0 ? "success" : fanoutResult.started > 0 || fanoutResult.skippedDuplicate > 0 ? "partial" : "failed",
70215
+ conversationId,
70216
+ userAgentId: agenticId,
70217
+ total: fanoutResult.total,
70218
+ started: fanoutResult.started,
70219
+ failed: fanoutResult.failed,
70220
+ skippedDuplicate: fanoutResult.skippedDuplicate
70221
+ })
70222
+ );
70223
+ return true;
70224
+ }
68962
70225
  async function processFileMessage(ctx, deps, options = {}) {
68963
70226
  const messageId = ctx.message.id ?? "";
68964
70227
  const timing = createInboundTimingTracker(extractXmtpSentAtMs2(ctx.message));
70228
+ const subscriptionPrecheckRawText = extractRawText(ctx.message.content);
70229
+ const subscriptionPrecheckParse = parseJson2(subscriptionPrecheckRawText);
70230
+ if (subscriptionPrecheckParse.ok && isSubscriptionEnvelope(subscriptionPrecheckParse.data)) {
70231
+ const { chatType: subscriptionChatType, conversationId: subscriptionConversationId } = getConversationInfo(ctx.conversation);
70232
+ const gateDecision = decideSubscriptionGate({
70233
+ // getConversationInfo's declared StoredChatType also lists "system",
70234
+ // but that value is only ever produced for stored-message bookkeeping
70235
+ // elsewhere in this file — the conversation-type branch above never
70236
+ // returns it, so this narrowing is safe.
70237
+ chatType: subscriptionChatType === "system" ? "unknown" : subscriptionChatType,
70238
+ payload: subscriptionPrecheckParse.data,
70239
+ senderInboxId: ctx.message.senderInboxId ?? ""
70240
+ });
70241
+ return processSubscriptionInbound(ctx, deps, {
70242
+ decision: gateDecision,
70243
+ conversationId: subscriptionConversationId,
70244
+ messageId: messageId || `subscription-${(0, import_node_crypto15.randomUUID)()}`
70245
+ });
70246
+ }
68965
70247
  if (messageId && !tryMarkProcessed(messageId)) {
68966
70248
  logWithTimestamp(`[okx-agent-task:${deps.myXmtpAddress}] duplicate message ignored: ${messageId}`);
68967
70249
  logger.info(LogEvent.INBOUND_IGNORED_DUPLICATE, timing.extras({
@@ -68984,19 +70266,6 @@ async function processFileMessage(ctx, deps, options = {}) {
68984
70266
  chatType,
68985
70267
  ...extra
68986
70268
  });
68987
- if (chatType === "dm") {
68988
- const dmTrustStartedAt = Date.now();
68989
- const trusted = await isTrustedDmSender({
68990
- myXmtpAddress: deps.myXmtpAddress,
68991
- senderInboxId: ctx.message.senderInboxId ?? "",
68992
- messageId,
68993
- conversationId
68994
- });
68995
- timing.mark("dmTrustCheck", dmTrustStartedAt);
68996
- if (!trusted) {
68997
- return true;
68998
- }
68999
- }
69000
70269
  const parseStartedAt = Date.now();
69001
70270
  const receivedRawText = extractRawText(ctx.message.content);
69002
70271
  const parseResult = parseJson2(receivedRawText);
@@ -69012,9 +70281,26 @@ async function processFileMessage(ctx, deps, options = {}) {
69012
70281
  }
69013
70282
  }
69014
70283
  timing.mark("payloadParse", parseStartedAt);
70284
+ const parsedDmData = parseResult.ok ? parseResult.data : null;
70285
+ const parsedDmMessage = isPlainObject5(parsedDmData) && isPlainObject5(parsedDmData.message) ? parsedDmData.message : null;
70286
+ const dmCarriesSystemMarkers = parsedDmMessage?.isDirectToUser === true || parsedDmMessage?.source === "system" || parsedDmMessage?.isDirectCommunication === true;
70287
+ const isA2ADm = chatType === "dm" && parseResult.ok && isA2AEnvelope(parseResult.data) && !dmCarriesSystemMarkers;
70288
+ if (chatType === "dm" && !isA2ADm) {
70289
+ const dmTrustStartedAt = Date.now();
70290
+ const trusted = await isTrustedDmSender({
70291
+ myXmtpAddress: deps.myXmtpAddress,
70292
+ senderInboxId: ctx.message.senderInboxId ?? "",
70293
+ messageId,
70294
+ conversationId
70295
+ });
70296
+ timing.mark("dmTrustCheck", dmTrustStartedAt);
70297
+ if (!trusted) {
70298
+ return true;
70299
+ }
70300
+ }
69015
70301
  const directToUser = chatType === "dm" && parseResult.ok ? extractDirectToUserNotification(parseResult.data) : { kind: "none" };
69016
70302
  if (directToUser.kind === "invalid") {
69017
- const droppedMessageId = messageId || `direct-to-user-${(0, import_node_crypto14.randomUUID)()}`;
70303
+ const droppedMessageId = messageId || `direct-to-user-${(0, import_node_crypto15.randomUUID)()}`;
69018
70304
  logger.error(
69019
70305
  LogEvent.INBOUND_DROP_DM_INVALID_PAYLOAD,
69020
70306
  new Error("Invalid direct-to-user system DM"),
@@ -69038,7 +70324,7 @@ async function processFileMessage(ctx, deps, options = {}) {
69038
70324
  await dispatchDirectToUserNotification({
69039
70325
  deps,
69040
70326
  notification: directToUser.notification,
69041
- messageId: messageId || `direct-to-user-${(0, import_node_crypto14.randomUUID)()}`,
70327
+ messageId: messageId || `direct-to-user-${(0, import_node_crypto15.randomUUID)()}`,
69042
70328
  timing,
69043
70329
  source: options.source ?? "live"
69044
70330
  });
@@ -69046,7 +70332,7 @@ async function processFileMessage(ctx, deps, options = {}) {
69046
70332
  }
69047
70333
  const systemNotification = chatType === "dm" && parseResult.ok ? extractSystemNotification(parseResult.data) : null;
69048
70334
  if (systemNotification) {
69049
- const systemMessageId = messageId || `system-${(0, import_node_crypto14.randomUUID)()}`;
70335
+ const systemMessageId = messageId || `system-${(0, import_node_crypto15.randomUUID)()}`;
69050
70336
  logWithTimestamp(
69051
70337
  `[okx-agent-task:${deps.myXmtpAddress}] xmtp_system_message_received agentId=${systemNotification.agentId ?? ""} msg=${shortenLogValue(systemMessageId)} event=${systemNotification.event ?? ""} job=${shortenLogValue(systemNotification.jobId)} conv=${conversationId} source=${options.source ?? "live"} rawBytes=${Buffer.byteLength(rawText, "utf8")} rawPreview=${previewRawEnvelope(rawText)}`
69052
70338
  );
@@ -69207,7 +70493,7 @@ async function processFileMessage(ctx, deps, options = {}) {
69207
70493
  timing.mark("routeResolve", routeStartedAt);
69208
70494
  if (route.route === "job" && parseResult.ok && isA2AEnvelope(parseResult.data) && !isValidExternalJobId(route.jobId)) {
69209
70495
  const payloadObject = parseResult.data;
69210
- const sender = isPlainObject3(payloadObject.sender) ? payloadObject.sender : null;
70496
+ const sender = isPlainObject5(payloadObject.sender) ? payloadObject.sender : null;
69211
70497
  logWithTimestamp(
69212
70498
  `[okx-agent-task:${deps.myXmtpAddress}] inbound A2A message has invalid jobId, dropping: jobLength=${route.jobId.length} group=${conversationId}`
69213
70499
  );
@@ -69236,7 +70522,32 @@ async function processFileMessage(ctx, deps, options = {}) {
69236
70522
  });
69237
70523
  return true;
69238
70524
  }
69239
- if (route.route === "job" && chatType !== "group") {
70525
+ if (isA2ADm && route.route !== "job") {
70526
+ const payloadObject = parseResult.data;
70527
+ const sender = isPlainObject5(payloadObject.sender) ? payloadObject.sender : null;
70528
+ recordInvalidXmtpMessageBestEffort({
70529
+ messageId,
70530
+ groupId: conversationId,
70531
+ jobId: null,
70532
+ agentId: readString5(sender?.agentId),
70533
+ localAgentId: XmtpService.getInstance().getAgentByAddress(deps.myXmtpAddress)?.agentId ?? null,
70534
+ senderInboxId: ctx.message.senderInboxId ?? "",
70535
+ reason: "dm_a2a_missing_job_id",
70536
+ stage: "inbound/job_id_validation",
70537
+ logTag: `[okx-agent-task:${deps.myXmtpAddress}]`
70538
+ });
70539
+ logger.info(LogEvent.INBOUND_DROP_INVALID_JOBID, {
70540
+ ...agentExtras({ walletAddress: deps.myXmtpAddress }),
70541
+ conversationId,
70542
+ messageId,
70543
+ chatType,
70544
+ peerInboxId: ctx.message.senderInboxId ?? "",
70545
+ ...inboundStageExtras("inbound/job_id_validation", "dropped"),
70546
+ reason: "dm_a2a_missing_job_id"
70547
+ });
70548
+ return true;
70549
+ }
70550
+ if (route.route === "job" && chatType !== "group" && !isA2ADm) {
69240
70551
  const providerBindStartedAt = Date.now();
69241
70552
  const providerGate = await bindMessageJobProviderToCurrentDefault(deps, route.jobId, "inbound-xmtp");
69242
70553
  timing.mark("providerBind", providerBindStartedAt);
@@ -69276,7 +70587,7 @@ async function processFileMessage(ctx, deps, options = {}) {
69276
70587
  );
69277
70588
  }
69278
70589
  if (route.route === "job" && chatType === "group") {
69279
- const payloadObject = isPlainObject3(parseResult.ok ? parseResult.data : null) ? parseResult.data : null;
70590
+ const payloadObject = isPlainObject5(parseResult.ok ? parseResult.data : null) ? parseResult.data : null;
69280
70591
  const service = XmtpService.getInstance();
69281
70592
  const trustedSystemSender = service.isTrustedSystemAccountSender({
69282
70593
  senderAddress,
@@ -69302,7 +70613,7 @@ async function processFileMessage(ctx, deps, options = {}) {
69302
70613
  });
69303
70614
  return true;
69304
70615
  }
69305
- const sender = isPlainObject3(payloadObject?.sender) ? payloadObject.sender : null;
70616
+ const sender = isPlainObject5(payloadObject?.sender) ? payloadObject.sender : null;
69306
70617
  const localAgent = service.getAgentByAddress(deps.myXmtpAddress);
69307
70618
  const myAgentId = localAgent?.agentId ?? readString5(payloadObject?.receiverAgentId) ?? sessionAgentId ?? null;
69308
70619
  const toAgentId = readString5(sender?.agentId);
@@ -69342,7 +70653,7 @@ async function processFileMessage(ctx, deps, options = {}) {
69342
70653
  }));
69343
70654
  return true;
69344
70655
  }
69345
- const routedMessageId = messageId || `group-${(0, import_node_crypto14.randomUUID)()}`;
70656
+ const routedMessageId = messageId || `group-${(0, import_node_crypto15.randomUUID)()}`;
69346
70657
  const sessionKey = ensureInboundGroupSession(deps, {
69347
70658
  jobId: route.jobId,
69348
70659
  myAgentId,
@@ -69407,8 +70718,102 @@ async function processFileMessage(ctx, deps, options = {}) {
69407
70718
  );
69408
70719
  return true;
69409
70720
  }
70721
+ if (route.route === "job" && isA2ADm) {
70722
+ const payloadObject = parseResult.data;
70723
+ const accepted = await verifyInboundA2AGroupMessage({
70724
+ payload: parseResult.data,
70725
+ myXmtpAddress: deps.myXmtpAddress,
70726
+ senderAddress,
70727
+ senderInboxId: ctx.message.senderInboxId ?? "",
70728
+ jobId: route.jobId,
70729
+ groupId: conversationId,
70730
+ messageId,
70731
+ consentState: void 0,
70732
+ trustedSystemSender: false,
70733
+ isOfflineReplay: options.isOfflineReplay ?? false,
70734
+ timing
70735
+ });
70736
+ if (!accepted) {
70737
+ return true;
70738
+ }
70739
+ const providerBindStartedAt = Date.now();
70740
+ const providerGate = await bindMessageJobProviderToCurrentDefault(deps, route.jobId, "inbound-xmtp");
70741
+ timing.mark("providerBind", providerBindStartedAt);
70742
+ if (!providerGate.allowed) {
70743
+ logger.info(LogEvent.INBOUND_IGNORED_NON_BUSINESS, timing.extras({
70744
+ ...buildInboundTerminalExtras({
70745
+ terminalState: "provider_gate_denied",
70746
+ myXmtpAddress: deps.myXmtpAddress,
70747
+ messageId,
70748
+ conversationId,
70749
+ chatType,
70750
+ jobId: route.jobId,
70751
+ route: route.route,
70752
+ reason: "job_provider_gate_denied"
70753
+ }),
70754
+ routeReason: route.routeReason,
70755
+ providerBinding: providerGate.provider ?? ""
70756
+ }));
70757
+ return true;
70758
+ }
70759
+ const myAgentId = XmtpService.getInstance().getAgentByAddress(deps.myXmtpAddress)?.agentId ?? readString5(payloadObject.receiverAgentId) ?? sessionAgentId ?? null;
70760
+ const sender = isPlainObject5(payloadObject.sender) ? payloadObject.sender : null;
70761
+ const toAgentId = readString5(sender?.agentId);
70762
+ const routedMessageId = messageId || `dm-${(0, import_node_crypto15.randomUUID)()}`;
70763
+ const sessionKey = ensureInboundGroupSession(deps, {
70764
+ jobId: route.jobId,
70765
+ myAgentId,
70766
+ toAgentId,
70767
+ groupId: null,
70768
+ toAgentXmtpAddress: senderAddress || readString5(payloadObject.fromXmtpAddress)
70769
+ });
70770
+ const dispatchStartedAt = Date.now();
70771
+ void Promise.resolve(deps.onSessionMessage?.({
70772
+ sessionKey,
70773
+ content: rawText,
70774
+ messageId: routedMessageId,
70775
+ jobId: route.jobId,
70776
+ agentId: myAgentId
70777
+ })).then(() => {
70778
+ timing.mark("sessionDispatch", dispatchStartedAt);
70779
+ logger.info(LogEvent.INBOUND_DELIVERED, commonDeliveryExtras({
70780
+ taskId: route.jobId,
70781
+ agentId: myAgentId ?? "",
70782
+ peerWalletAddress: senderAddress,
70783
+ peerInboxId: ctx.message.senderInboxId ?? "",
70784
+ peerAgentId: toAgentId ?? "",
70785
+ sessionKey,
70786
+ messageId: routedMessageId,
70787
+ route: "dm",
70788
+ ...inboundStageExtras("inbound/dm_session_dispatch", "success"),
70789
+ dispatchTarget: deps.onSessionMessage ? "session" : "none"
70790
+ }));
70791
+ }).catch((err2) => {
70792
+ logWithTimestamp(`[okx-agent-task:${deps.myXmtpAddress}] onSessionMessage failed:`, err2);
70793
+ logger.error(LogEvent.INBOUND_DISPATCH_FAILED, toLoggableError(err2), timing.extras({
70794
+ ...buildInboundTerminalExtras({
70795
+ terminalState: "dispatch_failed",
70796
+ myXmtpAddress: deps.myXmtpAddress,
70797
+ messageId: routedMessageId,
70798
+ conversationId,
70799
+ chatType,
70800
+ jobId: route.jobId,
70801
+ sessionKey,
70802
+ route: "dm",
70803
+ reason: "session_dispatch_failed"
70804
+ }),
70805
+ checkpoint: "inbound/dm_session_dispatch_failed",
70806
+ agentId: myAgentId ?? "",
70807
+ peerAgentId: toAgentId ?? ""
70808
+ }));
70809
+ });
70810
+ logWithTimestamp(
70811
+ `[okx-agent-task:${deps.myXmtpAddress}] session dispatch queued route=dm session=${sessionKey} message=${shortenLogValue(routedMessageId)} ${summarizePayloadForLog(parseResult.data)}`
70812
+ );
70813
+ return true;
70814
+ }
69410
70815
  const stored = {
69411
- id: messageId || `local-${(0, import_node_crypto14.randomUUID)()}`,
70816
+ id: messageId || `local-${(0, import_node_crypto15.randomUUID)()}`,
69412
70817
  direction: "inbound",
69413
70818
  route: route.route,
69414
70819
  routeReason: route.routeReason,
@@ -69426,8 +70831,8 @@ async function processFileMessage(ctx, deps, options = {}) {
69426
70831
  await deps.store.append(route.jobId, stored, sessionAgentId);
69427
70832
  timing.mark("storeAppend", storeAppendStartedAt);
69428
70833
  if ((route.route === "job" || parseResult.ok && !isA2AEnvelope(stored.payload)) && !options.skipNotify) {
69429
- const payloadObject = isPlainObject3(stored.payload) ? stored.payload : null;
69430
- const sender = isPlainObject3(payloadObject?.sender) ? payloadObject.sender : null;
70834
+ const payloadObject = isPlainObject5(stored.payload) ? stored.payload : null;
70835
+ const sender = isPlainObject5(payloadObject?.sender) ? payloadObject.sender : null;
69431
70836
  const localAgent = XmtpService.getInstance().getAgentByAddress(deps.myXmtpAddress);
69432
70837
  const sessionKey = route.route === "job" ? buildSessionKey({
69433
70838
  jobId: route.jobId,
@@ -69445,8 +70850,8 @@ async function processFileMessage(ctx, deps, options = {}) {
69445
70850
  });
69446
70851
  }
69447
70852
  if (route.route === "job" && chatType === "group" && conversationId && senderAddress) {
69448
- const payloadObject = isPlainObject3(stored.payload) ? stored.payload : null;
69449
- const sender = isPlainObject3(payloadObject?.sender) ? payloadObject.sender : null;
70853
+ const payloadObject = isPlainObject5(stored.payload) ? stored.payload : null;
70854
+ const sender = isPlainObject5(payloadObject?.sender) ? payloadObject.sender : null;
69450
70855
  await deps.store.updateXmtpSession(route.jobId, {
69451
70856
  xmtpGroupId: conversationId,
69452
70857
  myXmtpAddress: deps.myXmtpAddress,
@@ -69589,7 +70994,7 @@ function processOfflineFromCoreDeps(store, ctx, deps, options) {
69589
70994
  function buildSystemStoredMessage(params) {
69590
70995
  const now = Date.now();
69591
70996
  return {
69592
- id: params.id ?? `system-${now}-${(0, import_node_crypto14.randomUUID)()}`,
70997
+ id: params.id ?? `system-${now}-${(0, import_node_crypto15.randomUUID)()}`,
69593
70998
  direction: "system",
69594
70999
  route: "backup",
69595
71000
  routeReason: params.reason,
@@ -69605,14 +71010,14 @@ function buildSystemStoredMessage(params) {
69605
71010
  };
69606
71011
  }
69607
71012
  function isA2AEnvelope(payload) {
69608
- return isPlainObject3(payload) && payload.msgType === A2A_MESSAGE_TYPE;
71013
+ return isPlainObject5(payload) && payload.msgType === A2A_MESSAGE_TYPE;
69609
71014
  }
69610
- var import_node_crypto14, PROCESSED_IDS_MAX, processedMessageIds, EXTERNAL_JOB_ID_PATTERN, INBOUND_TRANSPORT, INBOUND_EVENT_FAMILY, INBOUND_TERMINAL_FIELDS;
71015
+ var import_node_crypto15, PROCESSED_IDS_MAX, processedMessageIds, EXTERNAL_JOB_ID_PATTERN, INBOUND_TRANSPORT, INBOUND_EVENT_FAMILY, INBOUND_TERMINAL_FIELDS;
69611
71016
  var init_message_handler = __esm({
69612
71017
  "src/message-handler.ts"() {
69613
71018
  "use strict";
69614
71019
  init_log();
69615
- import_node_crypto14 = require("node:crypto");
71020
+ import_node_crypto15 = require("node:crypto");
69616
71021
  init_dist2();
69617
71022
  init_a2a();
69618
71023
  init_envelope_shared();
@@ -69627,6 +71032,10 @@ var init_message_handler = __esm({
69627
71032
  init_session_store();
69628
71033
  init_agent_message_notice();
69629
71034
  init_ai_provider();
71035
+ init_subscription_envelope();
71036
+ init_subscription_inbound();
71037
+ init_list_jobs_by_verified_sender();
71038
+ init_subscription_telemetry();
69630
71039
  PROCESSED_IDS_MAX = 1e3;
69631
71040
  processedMessageIds = /* @__PURE__ */ new Map();
69632
71041
  EXTERNAL_JOB_ID_PATTERN = /^0x[0-9a-fA-F]{64}$/;
@@ -69680,7 +71089,7 @@ function isOriginalParentProcessAlive(options) {
69680
71089
  }
69681
71090
  function registerUserAttentionWatcher(options) {
69682
71091
  return options.store.registerUserAttentionWatcher({
69683
- id: options.id ?? (0, import_node_crypto15.randomUUID)(),
71092
+ id: options.id ?? (0, import_node_crypto16.randomUUID)(),
69684
71093
  provider: options.provider,
69685
71094
  jobId: options.jobId,
69686
71095
  nowMs: options.nowMs ?? Date.now(),
@@ -69759,11 +71168,11 @@ function userWatchEventDeliveredSentryExtra(event) {
69759
71168
  hasDecision: String(event.hasDecision)
69760
71169
  };
69761
71170
  }
69762
- var import_node_crypto15, USER_ATTENTION_WATCH_REPLACED_MESSAGE, USER_ATTENTION_WATCHER_SCAN_MS, USER_ATTENTION_WATCHER_TTL_MS, USER_ATTENTION_WATCH_PARENT_CHECK_MS;
71171
+ var import_node_crypto16, USER_ATTENTION_WATCH_REPLACED_MESSAGE, USER_ATTENTION_WATCHER_SCAN_MS, USER_ATTENTION_WATCHER_TTL_MS, USER_ATTENTION_WATCH_PARENT_CHECK_MS;
69763
71172
  var init_user_attention_watchers = __esm({
69764
71173
  "src/user-attention-watchers.ts"() {
69765
71174
  "use strict";
69766
- import_node_crypto15 = require("node:crypto");
71175
+ import_node_crypto16 = require("node:crypto");
69767
71176
  init_sentry_logger();
69768
71177
  USER_ATTENTION_WATCH_REPLACED_MESSAGE = "Another okx-a2a user watch with the same job scope was started, so this watcher has been closed. Do not run this watch command again automatically, because starting another watcher may interrupt a different session that is already monitoring task progress.";
69769
71178
  USER_ATTENTION_WATCHER_SCAN_MS = 500;
@@ -69785,6 +71194,7 @@ __export(listener_exports, {
69785
71194
  ListenerDeferredTaskScheduler: () => ListenerDeferredTaskScheduler,
69786
71195
  ListenerMaintenanceGate: () => ListenerMaintenanceGate,
69787
71196
  XMTP_CLIENT_RECYCLE_INTERVAL_ENV: () => XMTP_CLIENT_RECYCLE_INTERVAL_ENV,
71197
+ buildUpgradeRecommendationNotice: () => buildUpgradeRecommendationNotice,
69788
71198
  getHermesGatewayPluginStatus: () => getHermesGatewayPluginStatus,
69789
71199
  hermesConfigEnablesOkxA2a: () => hermesConfigEnablesOkxA2a,
69790
71200
  isGatewayAvailableForHeartbeat: () => isGatewayAvailableForHeartbeat,
@@ -69801,6 +71211,34 @@ async function runHeartbeatRefreshCycle(options) {
69801
71211
  const refresh = options.refresh && refreshAllowed ? await timeSettled(options.refresh) : void 0;
69802
71212
  return { heartbeat, versionRefresh, refresh };
69803
71213
  }
71214
+ function buildUpgradeRecommendationNotice(input) {
71215
+ const capability = input.capability?.trim() ?? "";
71216
+ if (capability === MESSAGE_ELIGIBLE_OFFLINE_REPLAY_HELP_TOKEN) {
71217
+ return {
71218
+ title: "OnchainOS upgrade recommended",
71219
+ content: "The installed OnchainOS version does not support offline-replay preferences. Subscription messages will continue to replay normally. Upgrade OnchainOS to enable this capability.\n\nSuggested command: onchainos upgrade",
71220
+ idempotencyKey: UPGRADE_RECOMMENDATION_IDEMPOTENCY_KEY
71221
+ };
71222
+ }
71223
+ if (capability === LIST_JOBS_BY_VERIFIED_SENDER_HELP_TOKEN) {
71224
+ return {
71225
+ title: "OnchainOS upgrade required for Subscription Shared Group",
71226
+ content: "The installed OnchainOS version does not support the verified-sender task query required by Subscription Shared Group. Until OnchainOS is upgraded, Subscription Shared Group messages fail closed and are NOT routed to an AI Session. Upgrade OnchainOS to enable this capability.\n\nSuggested command: onchainos upgrade",
71227
+ idempotencyKey: LIST_JOBS_UPGRADE_RECOMMENDATION_IDEMPOTENCY_KEY
71228
+ };
71229
+ }
71230
+ const capabilitySlug = capability ? capability.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") : "";
71231
+ const idempotencyKey = capabilitySlug ? `system:onchainos-${capabilitySlug}-upgrade` : "system:onchainos-capability-upgrade";
71232
+ const capabilityLabel = capability || "a required capability";
71233
+ const reasonSuffix = input.reason?.trim() ? ` ${input.reason.trim()}` : "";
71234
+ return {
71235
+ title: "OnchainOS upgrade recommended",
71236
+ content: `The installed OnchainOS version does not support ${capabilityLabel}.${reasonSuffix} Upgrade OnchainOS to enable this capability.
71237
+
71238
+ Suggested command: onchainos upgrade`,
71239
+ idempotencyKey
71240
+ };
71241
+ }
69804
71242
  function resolveOfflineReplayIntervalSec(systemConfig) {
69805
71243
  const interval = systemConfig.offlineReplayInterval;
69806
71244
  if (typeof interval !== "number" || !Number.isFinite(interval) || interval <= 0) {
@@ -70028,8 +71466,12 @@ async function runListenerWithLock(options, paths) {
70028
71466
  onJobMessageStored: async (event) => {
70029
71467
  await enqueueAiJobMessageDispatch({ commands, event });
70030
71468
  },
71469
+ // Returns { created } so the V2 subscription fan-out can distinguish a
71470
+ // durable DB duplicate (idempotent success) from a fresh enqueue. V1
71471
+ // group/system callers ignore the return; their events carry no
71472
+ // idempotencyKey, so the plain submit path (unchanged) is used.
70031
71473
  onSessionMessage: async (event) => {
70032
- await enqueueAiSessionMessageDispatch({ commands, event });
71474
+ return enqueueAiSessionMessageDispatchIdempotent({ commands, event });
70033
71475
  }
70034
71476
  }),
70035
71477
  processOfflineMessage: (ctx, deps, replayOptions) => processOfflineFromCoreDeps(store, ctx, deps, {
@@ -70039,7 +71481,7 @@ async function runListenerWithLock(options, paths) {
70039
71481
  await enqueueAiJobMessageDispatch({ commands, event });
70040
71482
  },
70041
71483
  onSessionMessage: async (event) => {
70042
- await enqueueAiSessionMessageDispatch({ commands, event });
71484
+ return enqueueAiSessionMessageDispatchIdempotent({ commands, event });
70043
71485
  }
70044
71486
  }),
70045
71487
  notifySessionExpired: (command) => {
@@ -70057,12 +71499,12 @@ async function runListenerWithLock(options, paths) {
70057
71499
  });
70058
71500
  },
70059
71501
  notifyUpgradeRecommendation: (input) => {
70060
- const content2 = "The installed OnchainOS version does not support offline-replay preferences. Subscription messages will continue to replay normally. Upgrade OnchainOS to enable this capability.\n\nSuggested command: onchainos upgrade";
71502
+ const notice = buildUpgradeRecommendationNotice(input);
70061
71503
  void notifySystemMessageToUser({
70062
- title: "OnchainOS upgrade recommended",
70063
- content: content2,
71504
+ title: notice.title,
71505
+ content: notice.content,
70064
71506
  store: sessionStore,
70065
- idempotencyKey: UPGRADE_RECOMMENDATION_IDEMPOTENCY_KEY
71507
+ idempotencyKey: notice.idempotencyKey
70066
71508
  }).catch((err2) => {
70067
71509
  logWithTimestamp(
70068
71510
  `[okx-agent-task] upgrade recommendation notify failed component=${input.component} capability=${input.capability}:`,
@@ -70071,12 +71513,12 @@ async function runListenerWithLock(options, paths) {
70071
71513
  });
70072
71514
  }
70073
71515
  });
70074
- service.setPluginVersion("0.2.17");
71516
+ service.setPluginVersion("0.2.18-beta-4dd056b897-261015113839");
70075
71517
  await service.init();
70076
71518
  const pluginVersionStatus = service.pluginVersionStatus;
70077
71519
  if (pluginVersionStatus.unavailable) {
70078
71520
  throw new Error(
70079
- `@okxweb3/a2a-node v${"0.2.17"} is below the required minimum v${pluginVersionStatus.minVersion}`
71521
+ `@okxweb3/a2a-node v${"0.2.18-beta-4dd056b897-261015113839"} is below the required minimum v${pluginVersionStatus.minVersion}`
70080
71522
  );
70081
71523
  }
70082
71524
  const systemConfig = service.getSystemConfig();
@@ -70104,7 +71546,7 @@ async function runListenerWithLock(options, paths) {
70104
71546
  onchainosAgentId: "*",
70105
71547
  reason: "system-config missing sentryDsn",
70106
71548
  pluginId: "@okxweb3/a2a-node",
70107
- pluginVersion: "0.2.17"
71549
+ pluginVersion: "0.2.18-beta-4dd056b897-261015113839"
70108
71550
  });
70109
71551
  }
70110
71552
  logWithTimestamp(
@@ -70454,7 +71896,7 @@ async function timeSettled(fn) {
70454
71896
  return { durationMs: Date.now() - startedAt, error };
70455
71897
  }
70456
71898
  }
70457
- var import_node_fs27, import_promises13, import_node_os10, import_node_path32, DEFAULT_OFFLINE_REPLAY_INTERVAL_SEC, DEFAULT_XMTP_CLIENT_RECYCLE_INTERVAL_SEC, XMTP_CLIENT_RECYCLE_INTERVAL_ENV, HEARTBEAT_GATEWAY_CHECK_TIMEOUT_MS, AUTH_RECOVERY_PROBE_INTERVAL_MS, AUTH_RECOVERY_REFRESH_BACKOFF_MS, AgentRefreshAuthGate, AgentRefreshCoordinator, ListenerDeferredTaskScheduler, ListenerMaintenanceGate, UPGRADE_RECOMMENDATION_IDEMPOTENCY_KEY, SHUTDOWN_FORCE_RESOLVE_MS;
71899
+ var import_node_fs27, import_promises13, import_node_os10, import_node_path32, DEFAULT_OFFLINE_REPLAY_INTERVAL_SEC, DEFAULT_XMTP_CLIENT_RECYCLE_INTERVAL_SEC, XMTP_CLIENT_RECYCLE_INTERVAL_ENV, HEARTBEAT_GATEWAY_CHECK_TIMEOUT_MS, AUTH_RECOVERY_PROBE_INTERVAL_MS, AUTH_RECOVERY_REFRESH_BACKOFF_MS, AgentRefreshAuthGate, AgentRefreshCoordinator, ListenerDeferredTaskScheduler, ListenerMaintenanceGate, UPGRADE_RECOMMENDATION_IDEMPOTENCY_KEY, LIST_JOBS_UPGRADE_RECOMMENDATION_IDEMPOTENCY_KEY, SHUTDOWN_FORCE_RESOLVE_MS;
70458
71900
  var init_listener = __esm({
70459
71901
  "src/listener.ts"() {
70460
71902
  "use strict";
@@ -70467,6 +71909,7 @@ var init_listener = __esm({
70467
71909
  init_onchainos();
70468
71910
  init_system_config();
70469
71911
  init_offline_replay_capability();
71912
+ init_list_jobs_by_verified_sender();
70470
71913
  init_signer();
70471
71914
  init_bin();
70472
71915
  init_xmtp_sdk();
@@ -70647,6 +72090,7 @@ var init_listener = __esm({
70647
72090
  }
70648
72091
  };
70649
72092
  UPGRADE_RECOMMENDATION_IDEMPOTENCY_KEY = "system:onchainos-offline-replay-upgrade";
72093
+ LIST_JOBS_UPGRADE_RECOMMENDATION_IDEMPOTENCY_KEY = "system:onchainos-list-jobs-by-verified-sender-upgrade";
70650
72094
  SHUTDOWN_FORCE_RESOLVE_MS = 5e3;
70651
72095
  }
70652
72096
  });
@@ -88736,7 +90180,7 @@ var require_fetch_pb = __commonJS({
88736
90180
  }
88737
90181
  });
88738
90182
  }
88739
- function isPlainObject4(value) {
90183
+ function isPlainObject6(value) {
88740
90184
  const isObject = Object.prototype.toString.call(value).slice(8, -1) === "Object";
88741
90185
  const isObjLike = value !== null && isObject;
88742
90186
  if (!isObjLike || !isObject) {
@@ -88759,7 +90203,7 @@ var require_fetch_pb = __commonJS({
88759
90203
  const isNonEmptyPrimitiveArray = Array.isArray(value) && value.every((v) => isPrimitive(v)) && value.length > 0;
88760
90204
  const isNonZeroValuePrimitive = isPrimitive(value) && !isZeroValuePrimitive(value);
88761
90205
  let objectToMerge = {};
88762
- if (isPlainObject4(value)) {
90206
+ if (isPlainObject6(value)) {
88763
90207
  objectToMerge = flattenRequestPayload(value, newPath);
88764
90208
  } else if (isNonZeroValuePrimitive || isNonEmptyPrimitiveArray) {
88765
90209
  objectToMerge = { [newPath]: value };
@@ -114328,10 +115772,10 @@ var require_node4 = __commonJS({
114328
115772
 
114329
115773
  // ../../node_modules/@xmtp/content-type-remote-attachment/dist/index.js
114330
115774
  async function encrypt(plain, secret, additionalData) {
114331
- const salt = import_node_crypto16.webcrypto.getRandomValues(new Uint8Array(KDFSaltSize));
114332
- const nonce = import_node_crypto16.webcrypto.getRandomValues(new Uint8Array(AESGCMNonceSize));
115775
+ const salt = import_node_crypto17.webcrypto.getRandomValues(new Uint8Array(KDFSaltSize));
115776
+ const nonce = import_node_crypto17.webcrypto.getRandomValues(new Uint8Array(AESGCMNonceSize));
114333
115777
  const key = await hkdf(secret, salt);
114334
- const encrypted = await import_node_crypto16.webcrypto.subtle.encrypt(aesGcmParams(nonce), key, plain);
115778
+ const encrypted = await import_node_crypto17.webcrypto.subtle.encrypt(aesGcmParams(nonce), key, plain);
114335
115779
  return new Ciphertext({
114336
115780
  aes256GcmHkdfSha256: {
114337
115781
  payload: new Uint8Array(encrypted),
@@ -114345,7 +115789,7 @@ async function decrypt(encrypted, secret, additionalData) {
114345
115789
  throw new Error("invalid payload ciphertext");
114346
115790
  }
114347
115791
  const key = await hkdf(secret, encrypted.aes256GcmHkdfSha256.hkdfSalt);
114348
- const decrypted = await import_node_crypto16.webcrypto.subtle.decrypt(aesGcmParams(encrypted.aes256GcmHkdfSha256.gcmNonce), key, encrypted.aes256GcmHkdfSha256.payload);
115792
+ const decrypted = await import_node_crypto17.webcrypto.subtle.decrypt(aesGcmParams(encrypted.aes256GcmHkdfSha256.gcmNonce), key, encrypted.aes256GcmHkdfSha256.payload);
114349
115793
  return new Uint8Array(decrypted);
114350
115794
  }
114351
115795
  function aesGcmParams(nonce, additionalData) {
@@ -114356,18 +115800,18 @@ function aesGcmParams(nonce, additionalData) {
114356
115800
  return spec;
114357
115801
  }
114358
115802
  async function hkdf(secret, salt) {
114359
- const key = await import_node_crypto16.webcrypto.subtle.importKey("raw", secret, "HKDF", false, [
115803
+ const key = await import_node_crypto17.webcrypto.subtle.importKey("raw", secret, "HKDF", false, [
114360
115804
  "deriveKey"
114361
115805
  ]);
114362
- return import_node_crypto16.webcrypto.subtle.deriveKey({ name: "HKDF", hash: "SHA-256", salt, info: hkdfNoInfo }, key, { name: "AES-GCM", length: 256 }, false, ["encrypt", "decrypt"]);
115806
+ return import_node_crypto17.webcrypto.subtle.deriveKey({ name: "HKDF", hash: "SHA-256", salt, info: hkdfNoInfo }, key, { name: "AES-GCM", length: 256 }, false, ["encrypt", "decrypt"]);
114363
115807
  }
114364
- var import_proto, import_node_crypto16, ContentTypeAttachment, AttachmentCodec, KDFSaltSize, AESGCMNonceSize, AESGCMTagLength, Ciphertext, hkdfNoInfo, ContentTypeRemoteAttachment, RemoteAttachmentCodec;
115808
+ var import_proto, import_node_crypto17, ContentTypeAttachment, AttachmentCodec, KDFSaltSize, AESGCMNonceSize, AESGCMTagLength, Ciphertext, hkdfNoInfo, ContentTypeRemoteAttachment, RemoteAttachmentCodec;
114365
115809
  var init_dist6 = __esm({
114366
115810
  "../../node_modules/@xmtp/content-type-remote-attachment/dist/index.js"() {
114367
115811
  init_dist5();
114368
115812
  init_secp256k12();
114369
115813
  import_proto = __toESM(require_node4(), 1);
114370
- import_node_crypto16 = require("node:crypto");
115814
+ import_node_crypto17 = require("node:crypto");
114371
115815
  ContentTypeAttachment = new ContentTypeId({
114372
115816
  authorityId: "xmtp.org",
114373
115817
  typeId: "attachment",
@@ -114447,7 +115891,7 @@ var init_dist6 = __esm({
114447
115891
  if (payload.length === 0) {
114448
115892
  throw new Error(`no payload for remote attachment at ${remoteAttachment.url}`);
114449
115893
  }
114450
- const digestBytes = new Uint8Array(await import_node_crypto16.webcrypto.subtle.digest("SHA-256", payload));
115894
+ const digestBytes = new Uint8Array(await import_node_crypto17.webcrypto.subtle.digest("SHA-256", payload));
114451
115895
  const digest = etc.bytesToHex(digestBytes);
114452
115896
  if (digest !== remoteAttachment.contentDigest) {
114453
115897
  throw new Error("content digest does not match");
@@ -114471,7 +115915,7 @@ var init_dist6 = __esm({
114471
115915
  return codec.decode(encodedContent, codecRegistry);
114472
115916
  }
114473
115917
  static async encodeEncrypted(content$1, codec) {
114474
- const secret = import_node_crypto16.webcrypto.getRandomValues(new Uint8Array(32));
115918
+ const secret = import_node_crypto17.webcrypto.getRandomValues(new Uint8Array(32));
114475
115919
  const encodedContent = import_proto.content.EncodedContent.encode(codec.encode(content$1, {
114476
115920
  codecFor() {
114477
115921
  return void 0;
@@ -114484,7 +115928,7 @@ var init_dist6 = __esm({
114484
115928
  if (!salt || !nonce || !payload) {
114485
115929
  throw new Error("missing encryption key");
114486
115930
  }
114487
- const digestBytes = new Uint8Array(await import_node_crypto16.webcrypto.subtle.digest("SHA-256", payload));
115931
+ const digestBytes = new Uint8Array(await import_node_crypto17.webcrypto.subtle.digest("SHA-256", payload));
114488
115932
  const digest = etc.bytesToHex(digestBytes);
114489
115933
  return {
114490
115934
  digest,
@@ -114702,7 +116146,7 @@ async function uploadFile(params) {
114702
116146
  const attachment = { filename, mimeType, data: new Uint8Array(data) };
114703
116147
  const encrypted = await RemoteAttachmentCodec.encodeEncrypted(attachment, new AttachmentCodec());
114704
116148
  ensureFileDirs();
114705
- const encryptedPath = (0, import_node_path35.resolve)(FILE_WORK_DIR, `${(0, import_node_crypto17.randomUUID)()}.enc`);
116149
+ const encryptedPath = (0, import_node_path35.resolve)(FILE_WORK_DIR, `${(0, import_node_crypto18.randomUUID)()}.enc`);
114706
116150
  (0, import_node_fs29.writeFileSync)(encryptedPath, encrypted.payload);
114707
116151
  try {
114708
116152
  const stdout = runOnchainos([
@@ -114746,7 +116190,7 @@ async function uploadFile(params) {
114746
116190
  }
114747
116191
  async function downloadFile(params) {
114748
116192
  ensureFileDirs();
114749
- const encryptedPath = (0, import_node_path35.resolve)(FILE_WORK_DIR, `${(0, import_node_crypto17.randomUUID)()}.enc`);
116193
+ const encryptedPath = (0, import_node_path35.resolve)(FILE_WORK_DIR, `${(0, import_node_crypto18.randomUUID)()}.enc`);
114750
116194
  try {
114751
116195
  const stdout = runOnchainos([
114752
116196
  "agent",
@@ -114771,7 +116215,7 @@ async function downloadFile(params) {
114771
116215
  throw new Error(`file download failed: ${stdout}`);
114772
116216
  }
114773
116217
  const payload = new Uint8Array((0, import_node_fs29.readFileSync)(encryptedPath));
114774
- const digestBytes = new Uint8Array(await import_node_crypto17.webcrypto.subtle.digest("SHA-256", payload));
116218
+ const digestBytes = new Uint8Array(await import_node_crypto18.webcrypto.subtle.digest("SHA-256", payload));
114775
116219
  const actualDigest = Array.from(digestBytes).map((byte) => byte.toString(16).padStart(2, "0")).join("");
114776
116220
  if (actualDigest !== params.digest) {
114777
116221
  throw new Error(`file download digest verification failed: expected=${params.digest}, actual=${actualDigest}`);
@@ -114779,7 +116223,7 @@ async function downloadFile(params) {
114779
116223
  const decryptedBytes = await decryptAttachmentPayload(payload, params);
114780
116224
  const encodedContent = import_proto2.content.EncodedContent.decode(decryptedBytes);
114781
116225
  const attachment = new AttachmentCodec().decode(encodedContent);
114782
- const outputFilename = params.filename || attachment.filename || `${(0, import_node_crypto17.randomUUID)()}.bin`;
116226
+ const outputFilename = params.filename || attachment.filename || `${(0, import_node_crypto18.randomUUID)()}.bin`;
114783
116227
  const outputDir = DOWNLOADS_DIR;
114784
116228
  ensureA2aTaskDir(outputDir);
114785
116229
  const outputPath = (0, import_node_path35.resolve)(outputDir, (0, import_node_path35.basename)(outputFilename));
@@ -114796,15 +116240,15 @@ async function decryptAttachmentPayload(payload, params) {
114796
116240
  const secretBytes = new Uint8Array(Buffer.from(params.secret, "base64"));
114797
116241
  const saltBytes = new Uint8Array(Buffer.from(params.salt, "base64"));
114798
116242
  const nonceBytes = new Uint8Array(Buffer.from(params.nonce, "base64"));
114799
- const hkdfKey = await import_node_crypto17.webcrypto.subtle.importKey("raw", secretBytes, "HKDF", false, ["deriveKey"]);
114800
- const aesKey = await import_node_crypto17.webcrypto.subtle.deriveKey(
116243
+ const hkdfKey = await import_node_crypto18.webcrypto.subtle.importKey("raw", secretBytes, "HKDF", false, ["deriveKey"]);
116244
+ const aesKey = await import_node_crypto18.webcrypto.subtle.deriveKey(
114801
116245
  { name: "HKDF", hash: "SHA-256", salt: saltBytes, info: new Uint8Array().buffer },
114802
116246
  hkdfKey,
114803
116247
  { name: "AES-GCM", length: 256 },
114804
116248
  false,
114805
116249
  ["decrypt"]
114806
116250
  );
114807
- return new Uint8Array(await import_node_crypto17.webcrypto.subtle.decrypt({ name: "AES-GCM", iv: nonceBytes }, aesKey, toArrayBuffer(payload)));
116251
+ return new Uint8Array(await import_node_crypto18.webcrypto.subtle.decrypt({ name: "AES-GCM", iv: nonceBytes }, aesKey, toArrayBuffer(payload)));
114808
116252
  }
114809
116253
  function toArrayBuffer(bytes) {
114810
116254
  return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
@@ -114944,12 +116388,12 @@ function readRequiredOption(args, name) {
114944
116388
  }
114945
116389
  return value;
114946
116390
  }
114947
- var import_node_crypto17, import_node_fs29, import_node_path35, import_proto2, OKX_A2A_PATHS, OKX_A2A_HOME_DIR, FILE_WORK_DIR, DOWNLOADS_DIR, SYSTEM_CONFIG_PATH;
116391
+ var import_node_crypto18, import_node_fs29, import_node_path35, import_proto2, OKX_A2A_PATHS, OKX_A2A_HOME_DIR, FILE_WORK_DIR, DOWNLOADS_DIR, SYSTEM_CONFIG_PATH;
114948
116392
  var init_file_cli = __esm({
114949
116393
  "src/file-cli.ts"() {
114950
116394
  "use strict";
114951
116395
  init_win_spawn();
114952
- import_node_crypto17 = require("node:crypto");
116396
+ import_node_crypto18 = require("node:crypto");
114953
116397
  import_node_fs29 = require("node:fs");
114954
116398
  import_node_path35 = require("node:path");
114955
116399
  init_dist6();
@@ -118447,7 +119891,7 @@ async function getCurrentNodeCliVersion() {
118447
119891
  return await getGlobalNpmPackageVersion(UPDATE_PACKAGES.node) ?? getBundledNodeCliVersion();
118448
119892
  }
118449
119893
  function getBundledNodeCliVersion() {
118450
- return true ? "0.2.17" : null;
119894
+ return true ? "0.2.18-beta-4dd056b897-261015113839" : null;
118451
119895
  }
118452
119896
  function readConfiguredAiProvider() {
118453
119897
  const explicit = process.env.OKX_A2A_AI_PROVIDER || process.env.OKX_AGENT_TASK_AI_CLI;
@@ -118657,7 +120101,7 @@ async function updateHermes(release, options) {
118657
120101
  }
118658
120102
  }
118659
120103
  async function installGatewayPluginForDoctor(target) {
118660
- const release = isPrereleaseVersion("0.2.17") ? "beta" : "latest";
120104
+ const release = isPrereleaseVersion("0.2.18-beta-4dd056b897-261015113839") ? "beta" : "latest";
118661
120105
  const insideTargetGateway = detectGatewayInvocation() === target;
118662
120106
  const options = {
118663
120107
  restart: !insideTargetGateway,
@@ -119710,7 +121154,7 @@ async function runDoctor(options = {}) {
119710
121154
  platform: options.platform ?? process.platform,
119711
121155
  env: options.env ?? process.env,
119712
121156
  target: options.target ?? resolveDoctorTarget(options.env ?? process.env),
119713
- cliVersion: options.cliVersion ?? (true ? "0.2.17" : "0.0.0"),
121157
+ cliVersion: options.cliVersion ?? (true ? "0.2.18-beta-4dd056b897-261015113839" : "0.0.0"),
119714
121158
  fixMode: options.fix === true,
119715
121159
  nonInteractive: options.nonInteractive === true,
119716
121160
  packageChanged: false,
@@ -120817,7 +122261,7 @@ init_sentry_config();
120817
122261
  init_runtime_metadata();
120818
122262
  var CURRENT_GATEWAY_SESSION_KEYS_ENV4 = "OKX_A2A_CURRENT_GATEWAY_SESSION_KEYS";
120819
122263
  function printUsage3() {
120820
- console.log(`okx-a2a ${"0.2.17"}
122264
+ console.log(`okx-a2a ${"0.2.18-beta-4dd056b897-261015113839"}
120821
122265
 
120822
122266
  Usage:
120823
122267
  okx-a2a <command> [options]
@@ -120847,6 +122291,8 @@ Commands:
120847
122291
  Alias for runtime switch-current
120848
122292
  job-provider Manage job-to-provider bindings
120849
122293
  xmtp-send Queue an XMTP message through the running daemon
122294
+ subscription-push
122295
+ Queue a V2 subscription push to one or more XMTP Group conversations
120850
122296
  trade-records
120851
122297
  Insert, query, and soft-delete local business trade records
120852
122298
  capabilities Print machine-readable A2A capability negotiation status (used by OnchainOS)
@@ -120859,7 +122305,7 @@ Run \`okx-a2a <command> -h\` for command-specific help.
120859
122305
  `);
120860
122306
  }
120861
122307
  function printVersion() {
120862
- console.log("0.2.17");
122308
+ console.log("0.2.18-beta-4dd056b897-261015113839");
120863
122309
  }
120864
122310
  function printDaemonUsage() {
120865
122311
  console.log(`Usage: okx-a2a daemon <start|restart|stop|status|autostart> [options]
@@ -120956,6 +122402,18 @@ Options:
120956
122402
  --json Print machine-readable output with ok boolean
120957
122403
  `);
120958
122404
  }
122405
+ function printSubscriptionPushUsage() {
122406
+ console.log(`Usage: okx-a2a subscription-push --my-agent-id <id> --message <text> (--conversation-id <id> ... | --conversation-ids <id,id,...>) [options]
122407
+
122408
+ Options:
122409
+ --my-agent-id <id> Local sender agent id used to resolve the XMTP client
122410
+ --message <text> Subscription content
122411
+ --conversation-id <id> Target Group conversationId (repeatable)
122412
+ --conversation-ids <id,id> Comma-separated list of target Group conversationIds
122413
+ --delivery-id <id> Optional deliveryId carried in the V2 envelope
122414
+ --json Print machine-readable output with ok boolean
122415
+ `);
122416
+ }
120959
122417
  function printCommandUsage(command) {
120960
122418
  if (command === "daemon" || command === "start" || command === "restart" || command === "stop" || command === "status") {
120961
122419
  printDaemonUsage();
@@ -121025,6 +122483,10 @@ function printCommandUsage(command) {
121025
122483
  printXmtpSendUsage();
121026
122484
  return true;
121027
122485
  }
122486
+ if (command === "subscription-push") {
122487
+ printSubscriptionPushUsage();
122488
+ return true;
122489
+ }
121028
122490
  if (command === "trade-records") {
121029
122491
  printTradeRecordsUsageProxy();
121030
122492
  return true;
@@ -121352,6 +122814,79 @@ async function queueXmtpSend(args) {
121352
122814
  commands.close();
121353
122815
  }
121354
122816
  }
122817
+ async function queueXmtpSubscriptionPush(args) {
122818
+ if (hasHelpFlag5(args)) {
122819
+ printSubscriptionPushUsage();
122820
+ return;
122821
+ }
122822
+ assertOnlyKnownOptions(args, {
122823
+ command: "subscription-push",
122824
+ options: /* @__PURE__ */ new Set([
122825
+ "--my-agent-id",
122826
+ "--message",
122827
+ "--conversation-id",
122828
+ "--conversation-ids",
122829
+ "--delivery-id"
122830
+ ]),
122831
+ flags: /* @__PURE__ */ new Set(["--json"])
122832
+ });
122833
+ const json = hasFlag2(args, "--json");
122834
+ const myAgentId = readOption5(args, "--my-agent-id");
122835
+ const content2 = readOption5(args, "--message");
122836
+ const deliveryId = readOption5(args, "--delivery-id");
122837
+ const conversationIds = readSubscriptionPushConversationIds(args);
122838
+ if (!myAgentId || !content2) {
122839
+ throw new Error("subscription-push requires --my-agent-id and --message");
122840
+ }
122841
+ await assertDaemonRunningForQueuedCommand2("subscription-push");
122842
+ const commands = new CommandStore();
122843
+ try {
122844
+ const command = commands.createXmtpSubscriptionPushCommand({
122845
+ myAgentId,
122846
+ content: content2,
122847
+ conversationIds,
122848
+ deliveryId
122849
+ });
122850
+ await commands.submit(command);
122851
+ const result = await commands.waitForResult(command.id, Number.POSITIVE_INFINITY);
122852
+ if (!result) {
122853
+ printSubscriptionPushCommandResult(json, {
122854
+ id: command.id,
122855
+ ok: false,
122856
+ completedAt: Date.now(),
122857
+ error: "subscription-push finished without result"
122858
+ });
122859
+ process.exitCode = 1;
122860
+ return;
122861
+ }
122862
+ if (!result.ok) {
122863
+ printSubscriptionPushCommandResult(json, result);
122864
+ process.exitCode = 1;
122865
+ return;
122866
+ }
122867
+ printSubscriptionPushCommandResult(json, result);
122868
+ } finally {
122869
+ commands.close();
122870
+ }
122871
+ }
122872
+ function readSubscriptionPushConversationIds(args) {
122873
+ const ids = /* @__PURE__ */ new Set();
122874
+ for (let i = 0; i < args.length; i++) {
122875
+ if (args[i] === "--conversation-id" && args[i + 1]) {
122876
+ ids.add(args[i + 1]);
122877
+ }
122878
+ }
122879
+ const commaList = readOption5(args, "--conversation-ids");
122880
+ if (commaList) {
122881
+ for (const item of commaList.split(",")) {
122882
+ const trimmed = item.trim();
122883
+ if (trimmed) {
122884
+ ids.add(trimmed);
122885
+ }
122886
+ }
122887
+ }
122888
+ return [...ids];
122889
+ }
121355
122890
  function parseXmtpSendPayloadOption(raw) {
121356
122891
  const trimmed = raw?.trim();
121357
122892
  if (!trimmed) {
@@ -121661,6 +123196,23 @@ function printXmtpSendCommandResult(json, result) {
121661
123196
  }
121662
123197
  console.log(`ok=false xmtp-send failed command=${result.id} error=${result.error ?? "unknown error"}`);
121663
123198
  }
123199
+ function printSubscriptionPushCommandResult(json, result) {
123200
+ const payload = result.payload;
123201
+ if (json) {
123202
+ console.log(JSON.stringify({
123203
+ ok: result.ok,
123204
+ commandId: result.id,
123205
+ payload: result.payload ?? null,
123206
+ error: result.error ?? null
123207
+ }));
123208
+ return;
123209
+ }
123210
+ if (result.ok) {
123211
+ console.log(`ok=true subscription-push command=${result.id} conversationCount=${payload?.conversationCount ?? 0} sentCount=${payload?.sentCount ?? 0} failedCount=${payload?.failedCount ?? 0}`);
123212
+ return;
123213
+ }
123214
+ console.log(`ok=false subscription-push failed command=${result.id} error=${result.error ?? "unknown error"}`);
123215
+ }
121664
123216
  function formatDaemonCommandResult(type, result) {
121665
123217
  if (!result.ok) {
121666
123218
  return `ok=false ${type} command=${result.id} error=${result.error ?? "unknown error"}`;
@@ -122354,7 +123906,7 @@ async function main() {
122354
123906
  if (command === "xmtp-test") {
122355
123907
  const { handleXmtpTestCommand: handleXmtpTestCommand2 } = await Promise.resolve().then(() => (init_xmtp_test_cli(), xmtp_test_cli_exports));
122356
123908
  await handleXmtpTestCommand2(process.argv.slice(3), {
122357
- packageVersion: "0.2.17",
123909
+ packageVersion: "0.2.18-beta-4dd056b897-261015113839",
122358
123910
  agentSdkVersion: "2.3.0",
122359
123911
  nodeSdkVersion: "6.1.0",
122360
123912
  nodeBindingsVersion: "1.11.0"
@@ -122367,6 +123919,12 @@ async function main() {
122367
123919
  await flushDirectCliSentry();
122368
123920
  return;
122369
123921
  }
123922
+ if (command === "subscription-push") {
123923
+ await initDirectCliSentry();
123924
+ await queueXmtpSubscriptionPush(process.argv.slice(3));
123925
+ await flushDirectCliSentry();
123926
+ return;
123927
+ }
122370
123928
  if (command === "task") {
122371
123929
  await initDirectCliSentry();
122372
123930
  await handleTaskCommand(process.argv.slice(3));