@dhf-claude/grix 0.1.8 → 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -3221,8 +3221,8 @@ var require_utils = __commonJS({
3221
3221
  }
3222
3222
  return ind;
3223
3223
  }
3224
- function removeDotSegments(path9) {
3225
- let input = path9;
3224
+ function removeDotSegments(path8) {
3225
+ let input = path8;
3226
3226
  const output = [];
3227
3227
  let nextSlash = -1;
3228
3228
  let len = 0;
@@ -3421,8 +3421,8 @@ var require_schemes = __commonJS({
3421
3421
  wsComponent.secure = void 0;
3422
3422
  }
3423
3423
  if (wsComponent.resourceName) {
3424
- const [path9, query] = wsComponent.resourceName.split("?");
3425
- wsComponent.path = path9 && path9 !== "/" ? path9 : void 0;
3424
+ const [path8, query] = wsComponent.resourceName.split("?");
3425
+ wsComponent.path = path8 && path8 !== "/" ? path8 : void 0;
3426
3426
  wsComponent.query = query;
3427
3427
  wsComponent.resourceName = void 0;
3428
3428
  }
@@ -7159,8 +7159,8 @@ function getErrorMap() {
7159
7159
 
7160
7160
  // node_modules/zod/v3/helpers/parseUtil.js
7161
7161
  var makeIssue = (params) => {
7162
- const { data, path: path9, errorMaps, issueData } = params;
7163
- const fullPath = [...path9, ...issueData.path || []];
7162
+ const { data, path: path8, errorMaps, issueData } = params;
7163
+ const fullPath = [...path8, ...issueData.path || []];
7164
7164
  const fullIssue = {
7165
7165
  ...issueData,
7166
7166
  path: fullPath
@@ -7275,11 +7275,11 @@ var errorUtil;
7275
7275
 
7276
7276
  // node_modules/zod/v3/types.js
7277
7277
  var ParseInputLazyPath = class {
7278
- constructor(parent, value, path9, key) {
7278
+ constructor(parent, value, path8, key) {
7279
7279
  this._cachedPath = [];
7280
7280
  this.parent = parent;
7281
7281
  this.data = value;
7282
- this._path = path9;
7282
+ this._path = path8;
7283
7283
  this._key = key;
7284
7284
  }
7285
7285
  get path() {
@@ -10923,10 +10923,10 @@ function mergeDefs(...defs) {
10923
10923
  function cloneDef(schema) {
10924
10924
  return mergeDefs(schema._zod.def);
10925
10925
  }
10926
- function getElementAtPath(obj, path9) {
10927
- if (!path9)
10926
+ function getElementAtPath(obj, path8) {
10927
+ if (!path8)
10928
10928
  return obj;
10929
- return path9.reduce((acc, key) => acc?.[key], obj);
10929
+ return path8.reduce((acc, key) => acc?.[key], obj);
10930
10930
  }
10931
10931
  function promiseAllObject(promisesObj) {
10932
10932
  const keys = Object.keys(promisesObj);
@@ -11309,11 +11309,11 @@ function aborted(x, startIndex = 0) {
11309
11309
  }
11310
11310
  return false;
11311
11311
  }
11312
- function prefixIssues(path9, issues) {
11312
+ function prefixIssues(path8, issues) {
11313
11313
  return issues.map((iss) => {
11314
11314
  var _a2;
11315
11315
  (_a2 = iss).path ?? (_a2.path = []);
11316
- iss.path.unshift(path9);
11316
+ iss.path.unshift(path8);
11317
11317
  return iss;
11318
11318
  });
11319
11319
  }
@@ -20560,9 +20560,9 @@ var Server = class extends Protocol {
20560
20560
  const requestedVersion = request.params.protocolVersion;
20561
20561
  this._clientCapabilities = request.params.capabilities;
20562
20562
  this._clientVersion = request.params.clientInfo;
20563
- const protocolVersion = SUPPORTED_PROTOCOL_VERSIONS.includes(requestedVersion) ? requestedVersion : LATEST_PROTOCOL_VERSION;
20563
+ const protocolVersion2 = SUPPORTED_PROTOCOL_VERSIONS.includes(requestedVersion) ? requestedVersion : LATEST_PROTOCOL_VERSION;
20564
20564
  return {
20565
- protocolVersion,
20565
+ protocolVersion: protocolVersion2,
20566
20566
  capabilities: this.getCapabilities(),
20567
20567
  serverInfo: this._serverInfo,
20568
20568
  ...this._instructions && { instructions: this._instructions }
@@ -20809,50 +20809,9 @@ var StdioServerTransport = class {
20809
20809
  }
20810
20810
  };
20811
20811
 
20812
- // server/access-store.js
20813
- import { randomBytes } from "node:crypto";
20814
-
20815
- // server/paths.js
20816
- import { mkdir as mkdirAsync } from "node:fs/promises";
20817
- import os from "node:os";
20812
+ // server/channel-context-store.js
20813
+ import { mkdir } from "node:fs/promises";
20818
20814
  import path from "node:path";
20819
- var defaultPluginID = "grix-claude";
20820
- function sanitizePluginID(pluginID) {
20821
- return String(pluginID ?? defaultPluginID).trim().replace(/[^a-zA-Z0-9_-]+/g, "-").replace(/^-+|-+$/g, "") || defaultPluginID;
20822
- }
20823
- function resolvePluginDataDir(pluginID = defaultPluginID) {
20824
- const value = String(process.env.CLAUDE_PLUGIN_DATA ?? "").trim();
20825
- if (value) {
20826
- return value;
20827
- }
20828
- return path.join(os.homedir(), ".claude", sanitizePluginID(pluginID));
20829
- }
20830
- async function ensurePluginDataDir(pluginID = defaultPluginID) {
20831
- const dir = resolvePluginDataDir(pluginID);
20832
- await mkdirAsync(dir, { recursive: true });
20833
- return dir;
20834
- }
20835
- function resolveConfigPath(pluginID = defaultPluginID) {
20836
- return path.join(resolvePluginDataDir(pluginID), "grix-claude-config.json");
20837
- }
20838
- function resolveAccessPath(pluginID = defaultPluginID) {
20839
- return path.join(resolvePluginDataDir(pluginID), "grix-claude-access.json");
20840
- }
20841
- function resolveApprovalRequestsDir(pluginID = defaultPluginID) {
20842
- return path.join(resolvePluginDataDir(pluginID), "approval-requests");
20843
- }
20844
- function resolveApprovalNotificationsDir(pluginID = defaultPluginID) {
20845
- return path.join(resolvePluginDataDir(pluginID), "approval-notifications");
20846
- }
20847
- function resolveElicitationRequestsDir(pluginID = defaultPluginID) {
20848
- return path.join(resolvePluginDataDir(pluginID), "elicitation-requests");
20849
- }
20850
- function resolveSessionContextsDir(pluginID = defaultPluginID) {
20851
- return path.join(resolvePluginDataDir(pluginID), "session-contexts");
20852
- }
20853
- function resolveEventStatesDir(pluginID = defaultPluginID) {
20854
- return path.join(resolvePluginDataDir(pluginID), "event-states");
20855
- }
20856
20815
 
20857
20816
  // server/json-file.js
20858
20817
  import { randomUUID } from "node:crypto";
@@ -20886,628 +20845,48 @@ async function writeJSONFileAtomic(filePath, value, { mode = 384 } = {}) {
20886
20845
  }
20887
20846
  }
20888
20847
 
20889
- // server/access-store.js
20890
- var defaultAccess = Object.freeze({
20891
- schema_version: 2,
20892
- policy: "allowlist",
20893
- allowlist: {},
20894
- approver_allowlist: {},
20895
- pending_pairs: {}
20896
- });
20897
- var pairingTTLMS = 10 * 60 * 1e3;
20898
- function normalizeString(value) {
20899
- return String(value ?? "").trim();
20900
- }
20901
- function normalizePolicy(value) {
20902
- const normalized = normalizeString(value);
20903
- if (normalized === "open" || normalized === "disabled" || normalized === "allowlist") {
20904
- return normalized;
20905
- }
20906
- return "allowlist";
20907
- }
20908
- function cloneJSON(value) {
20909
- return JSON.parse(JSON.stringify(value));
20910
- }
20911
- function generatePairingCode() {
20912
- const alphabet = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
20913
- const bytes = randomBytes(6);
20914
- let result = "";
20915
- for (const byte of bytes) {
20916
- result += alphabet[byte % alphabet.length];
20917
- }
20918
- return result;
20919
- }
20920
- function pruneExpiredPairs(state, now = Date.now()) {
20921
- for (const [code, entry] of Object.entries(state.pending_pairs ?? {})) {
20922
- const expiresAt = Number(entry?.expires_at ?? 0);
20923
- if (!Number.isFinite(expiresAt) || expiresAt <= now) {
20924
- delete state.pending_pairs[code];
20925
- }
20926
- }
20927
- }
20928
- function listAllowlistEntries(state) {
20929
- return Object.values(state.allowlist ?? {}).map((entry) => ({
20930
- sender_id: normalizeString(entry?.sender_id),
20931
- paired_at: Number(entry?.paired_at ?? 0)
20932
- })).filter((entry) => entry.sender_id).sort((left, right) => left.sender_id.localeCompare(right.sender_id));
20933
- }
20934
- function listApproverEntries(state) {
20935
- return Object.values(state.approver_allowlist ?? {}).map((entry) => ({
20936
- sender_id: normalizeString(entry?.sender_id),
20937
- added_at: Number(entry?.added_at ?? 0)
20938
- })).filter((entry) => entry.sender_id).sort((left, right) => left.sender_id.localeCompare(right.sender_id));
20939
- }
20940
- function listPendingPairs(state) {
20941
- return Object.entries(state.pending_pairs ?? {}).map(([code, entry]) => ({
20942
- code: normalizeString(code),
20943
- sender_id: normalizeString(entry?.sender_id),
20944
- session_id: normalizeString(entry?.session_id),
20945
- expires_at: Number(entry?.expires_at ?? 0)
20946
- })).filter((entry) => entry.code && entry.sender_id && entry.session_id).sort((left, right) => left.code.localeCompare(right.code));
20947
- }
20948
- function ensureShape(input = {}) {
20949
- const state = cloneJSON(defaultAccess);
20950
- if (Number(input.schema_version ?? 0) !== defaultAccess.schema_version) {
20951
- return state;
20952
- }
20953
- state.policy = normalizePolicy(input.policy);
20954
- state.allowlist = {};
20955
- state.approver_allowlist = {};
20956
- state.pending_pairs = {};
20957
- for (const [senderID, entry] of Object.entries(input.allowlist ?? {})) {
20958
- const normalizedSenderID = normalizeString(senderID || entry?.sender_id);
20959
- if (!normalizedSenderID) {
20960
- continue;
20961
- }
20962
- state.allowlist[normalizedSenderID] = {
20963
- sender_id: normalizedSenderID,
20964
- paired_at: Number(entry?.paired_at ?? Date.now())
20965
- };
20966
- }
20967
- for (const [code, entry] of Object.entries(input.pending_pairs ?? {})) {
20968
- const normalizedCode = normalizeString(code).toUpperCase();
20969
- const senderID = normalizeString(entry?.sender_id);
20970
- const sessionID = normalizeString(entry?.session_id);
20971
- const expiresAt = Number(entry?.expires_at ?? 0);
20972
- if (!normalizedCode || !senderID || !sessionID || !Number.isFinite(expiresAt)) {
20973
- continue;
20974
- }
20975
- state.pending_pairs[normalizedCode] = {
20976
- sender_id: senderID,
20977
- session_id: sessionID,
20978
- expires_at: Math.floor(expiresAt)
20979
- };
20980
- }
20981
- for (const [senderID, entry] of Object.entries(input.approver_allowlist ?? {})) {
20982
- const normalizedSenderID = normalizeString(senderID || entry?.sender_id);
20983
- if (!normalizedSenderID) {
20984
- continue;
20985
- }
20986
- state.approver_allowlist[normalizedSenderID] = {
20987
- sender_id: normalizedSenderID,
20988
- added_at: Number(entry?.added_at ?? Date.now())
20989
- };
20990
- }
20991
- pruneExpiredPairs(state);
20992
- return state;
20993
- }
20994
- var AccessStore = class {
20995
- constructor(filePath) {
20996
- this.filePath = filePath;
20997
- this.state = cloneJSON(defaultAccess);
20998
- }
20999
- async load() {
21000
- await ensurePluginDataDir();
21001
- const stored = await readJSONFile(this.filePath, defaultAccess);
21002
- this.state = ensureShape(stored);
21003
- return this.getStatus();
21004
- }
21005
- getStatus() {
21006
- pruneExpiredPairs(this.state);
21007
- const allowlist = listAllowlistEntries(this.state);
21008
- const approver_allowlist = listApproverEntries(this.state);
21009
- const pending_pairs = listPendingPairs(this.state);
21010
- return {
21011
- policy: this.state.policy,
21012
- allowlist_count: allowlist.length,
21013
- approver_count: approver_allowlist.length,
21014
- pending_pair_count: pending_pairs.length,
21015
- allowlist,
21016
- approver_allowlist,
21017
- pending_pairs
21018
- };
21019
- }
21020
- getPolicy() {
21021
- pruneExpiredPairs(this.state);
21022
- return this.state.policy;
21023
- }
21024
- isSenderAllowed(senderID) {
21025
- const normalizedSenderID = normalizeString(senderID);
21026
- if (!normalizedSenderID) {
21027
- return false;
21028
- }
21029
- const policy = this.getPolicy();
21030
- if (policy === "open") {
21031
- return true;
21032
- }
21033
- if (policy === "disabled") {
21034
- return false;
21035
- }
21036
- return Object.hasOwn(this.state.allowlist, normalizedSenderID);
21037
- }
21038
- isSenderAllowlisted(senderID) {
21039
- const normalizedSenderID = normalizeString(senderID);
21040
- if (!normalizedSenderID) {
21041
- return false;
21042
- }
21043
- return Object.hasOwn(this.state.allowlist, normalizedSenderID);
21044
- }
21045
- isSenderApprover(senderID) {
21046
- const normalizedSenderID = normalizeString(senderID);
21047
- if (!normalizedSenderID) {
21048
- return false;
21049
- }
21050
- return Object.hasOwn(this.state.approver_allowlist, normalizedSenderID);
21051
- }
21052
- hasApprovers() {
21053
- return listApproverEntries(this.state).length > 0;
21054
- }
21055
- hasAllowedSenders() {
21056
- return listAllowlistEntries(this.state).length > 0;
21057
- }
21058
- async setPolicy(policy) {
21059
- this.state.policy = normalizePolicy(policy);
21060
- await this.save();
21061
- return this.getStatus();
21062
- }
21063
- async issuePairingCode({ senderID, sessionID }) {
21064
- const normalizedSenderID = normalizeString(senderID);
21065
- const normalizedSessionID = normalizeString(sessionID);
21066
- if (!normalizedSenderID || !normalizedSessionID) {
21067
- throw new Error("senderID and sessionID are required");
21068
- }
21069
- const now = Date.now();
21070
- pruneExpiredPairs(this.state, now);
21071
- for (const [code2, entry] of Object.entries(this.state.pending_pairs)) {
21072
- if (normalizeString(entry.sender_id) === normalizedSenderID && normalizeString(entry.session_id) === normalizedSessionID) {
21073
- return {
21074
- code: code2,
21075
- sender_id: normalizedSenderID,
21076
- session_id: normalizedSessionID,
21077
- expires_at: entry.expires_at
21078
- };
21079
- }
21080
- }
21081
- let code = generatePairingCode();
21082
- while (Object.hasOwn(this.state.pending_pairs, code)) {
21083
- code = generatePairingCode();
21084
- }
21085
- const pair = {
21086
- sender_id: normalizedSenderID,
21087
- session_id: normalizedSessionID,
21088
- expires_at: now + pairingTTLMS
21089
- };
21090
- this.state.pending_pairs[code] = pair;
21091
- await this.save();
21092
- return {
21093
- code,
21094
- ...pair
21095
- };
21096
- }
21097
- async approvePairing(code) {
21098
- const normalizedCode = normalizeString(code).toUpperCase();
21099
- if (!normalizedCode) {
21100
- throw new Error("pairing code is required");
21101
- }
21102
- pruneExpiredPairs(this.state);
21103
- const pending = this.state.pending_pairs[normalizedCode];
21104
- if (!pending) {
21105
- throw new Error("pairing code not found or expired");
21106
- }
21107
- const senderID = normalizeString(pending.sender_id);
21108
- this.state.allowlist[senderID] = {
21109
- sender_id: senderID,
21110
- paired_at: Date.now()
21111
- };
21112
- delete this.state.pending_pairs[normalizedCode];
21113
- await this.save();
21114
- return {
21115
- sender_id: senderID,
21116
- session_id: normalizeString(pending.session_id),
21117
- policy: this.state.policy
21118
- };
21119
- }
21120
- async denyPairing(code) {
21121
- const normalizedCode = normalizeString(code).toUpperCase();
21122
- if (!normalizedCode) {
21123
- throw new Error("pairing code is required");
21124
- }
21125
- pruneExpiredPairs(this.state);
21126
- const pending = this.state.pending_pairs[normalizedCode];
21127
- if (!pending) {
21128
- throw new Error("pairing code not found or expired");
21129
- }
21130
- delete this.state.pending_pairs[normalizedCode];
21131
- await this.save();
21132
- return {
21133
- code: normalizedCode,
21134
- sender_id: normalizeString(pending.sender_id),
21135
- session_id: normalizeString(pending.session_id),
21136
- policy: this.state.policy
21137
- };
21138
- }
21139
- async allowSender(senderID) {
21140
- const normalizedSenderID = normalizeString(senderID);
21141
- if (!normalizedSenderID) {
21142
- throw new Error("sender_id is required");
21143
- }
21144
- this.state.allowlist[normalizedSenderID] = {
21145
- sender_id: normalizedSenderID,
21146
- paired_at: Date.now()
21147
- };
21148
- await this.save();
21149
- return {
21150
- sender_id: normalizedSenderID,
21151
- policy: this.state.policy
21152
- };
21153
- }
21154
- async bootstrapFirstSender(senderID, { lockPolicyToAllowlist = false } = {}) {
21155
- const normalizedSenderID = normalizeString(senderID);
21156
- if (!normalizedSenderID) {
21157
- throw new Error("sender_id is required");
21158
- }
21159
- if (this.state.policy === "disabled") {
21160
- return {
21161
- sender_id: normalizedSenderID,
21162
- bootstrapped: false,
21163
- policy: this.state.policy
21164
- };
21165
- }
21166
- if (this.hasAllowedSenders() || Object.hasOwn(this.state.allowlist, normalizedSenderID)) {
21167
- return {
21168
- sender_id: normalizedSenderID,
21169
- bootstrapped: false,
21170
- policy: this.state.policy
21171
- };
21172
- }
21173
- this.state.allowlist[normalizedSenderID] = {
21174
- sender_id: normalizedSenderID,
21175
- paired_at: Date.now()
21176
- };
21177
- if (lockPolicyToAllowlist && this.state.policy === "open") {
21178
- this.state.policy = "allowlist";
21179
- }
21180
- await this.save();
21181
- return {
21182
- sender_id: normalizedSenderID,
21183
- bootstrapped: true,
21184
- policy: this.state.policy
21185
- };
21186
- }
21187
- async allowApprover(senderID) {
21188
- const normalizedSenderID = normalizeString(senderID);
21189
- if (!normalizedSenderID) {
21190
- throw new Error("sender_id is required");
21191
- }
21192
- this.state.approver_allowlist[normalizedSenderID] = {
21193
- sender_id: normalizedSenderID,
21194
- added_at: Date.now()
21195
- };
21196
- await this.save();
21197
- return {
21198
- sender_id: normalizedSenderID,
21199
- approver: true
21200
- };
21201
- }
21202
- async removeSender(senderID) {
21203
- const normalizedSenderID = normalizeString(senderID);
21204
- if (!normalizedSenderID) {
21205
- throw new Error("sender_id is required");
21206
- }
21207
- delete this.state.allowlist[normalizedSenderID];
21208
- await this.save();
21209
- return {
21210
- sender_id: normalizedSenderID,
21211
- policy: this.state.policy,
21212
- removed: true
21213
- };
21214
- }
21215
- async removeApprover(senderID) {
21216
- const normalizedSenderID = normalizeString(senderID);
21217
- if (!normalizedSenderID) {
21218
- throw new Error("sender_id is required");
21219
- }
21220
- const removed = Object.hasOwn(this.state.approver_allowlist, normalizedSenderID);
21221
- delete this.state.approver_allowlist[normalizedSenderID];
21222
- await this.save();
21223
- return {
21224
- sender_id: normalizedSenderID,
21225
- approver: true,
21226
- removed
21227
- };
21228
- }
21229
- async save() {
21230
- await ensurePluginDataDir();
21231
- pruneExpiredPairs(this.state);
21232
- await writeJSONFileAtomic(this.filePath, this.state);
21233
- }
21234
- };
21235
-
21236
- // server/approval-store.js
21237
- import { mkdir, readdir } from "node:fs/promises";
21238
- import path2 from "node:path";
20848
+ // server/channel-context-store.js
21239
20849
  var schemaVersion = 1;
21240
- function normalizeString2(value) {
20850
+ function normalizeString(value) {
21241
20851
  return String(value ?? "").trim();
21242
20852
  }
21243
- function cloneJSON2(value) {
21244
- return JSON.parse(JSON.stringify(value));
21245
- }
21246
- function normalizeDecision(input) {
21247
- if (!input || typeof input !== "object") {
21248
- return null;
21249
- }
21250
- const behavior = normalizeString2(input.behavior);
21251
- if (behavior !== "allow" && behavior !== "deny") {
21252
- return null;
21253
- }
21254
- const next = {
21255
- behavior
21256
- };
21257
- if (Array.isArray(input.updatedPermissions) && input.updatedPermissions.length > 0) {
21258
- next.updatedPermissions = cloneJSON2(input.updatedPermissions);
21259
- }
21260
- const message = normalizeString2(input.message);
21261
- if (message) {
21262
- next.message = message;
21263
- }
21264
- if (input.interrupt === true) {
21265
- next.interrupt = true;
21266
- }
21267
- return next;
21268
- }
21269
- function normalizeRequest(input) {
21270
- if (!input || typeof input !== "object" || Number(input.schema_version) !== schemaVersion) {
21271
- return null;
21272
- }
21273
- const status = normalizeString2(input.status) || "pending";
21274
- const channelContext = input.channel_context ?? {};
21275
- return {
21276
- schema_version: schemaVersion,
21277
- request_id: normalizeString2(input.request_id),
21278
- status,
21279
- created_at: Number(input.created_at ?? 0),
21280
- updated_at: Number(input.updated_at ?? 0),
21281
- dispatched_at: Number(input.dispatched_at ?? 0),
21282
- dispatch_error: normalizeString2(input.dispatch_error),
21283
- approval_message_id: normalizeString2(input.approval_message_id),
21284
- session_id: normalizeString2(input.session_id),
21285
- transcript_path: normalizeString2(input.transcript_path),
21286
- tool_name: normalizeString2(input.tool_name),
21287
- tool_input: cloneJSON2(input.tool_input ?? {}),
21288
- permission_suggestions: cloneJSON2(input.permission_suggestions ?? []),
21289
- channel_context: {
21290
- chat_id: normalizeString2(channelContext.chat_id),
21291
- event_id: normalizeString2(channelContext.event_id),
21292
- message_id: normalizeString2(channelContext.message_id),
21293
- sender_id: normalizeString2(channelContext.sender_id),
21294
- user_id: normalizeString2(channelContext.user_id),
21295
- msg_id: normalizeString2(channelContext.msg_id)
21296
- },
21297
- decision: normalizeDecision(input.decision),
21298
- resolved_by: input.resolved_by && typeof input.resolved_by === "object" ? {
21299
- sender_id: normalizeString2(input.resolved_by.sender_id),
21300
- session_id: normalizeString2(input.resolved_by.session_id),
21301
- event_id: normalizeString2(input.resolved_by.event_id),
21302
- msg_id: normalizeString2(input.resolved_by.msg_id)
21303
- } : null
21304
- };
21305
- }
21306
- async function listJSONFiles(dirPath) {
21307
- let names = [];
21308
- try {
21309
- names = await readdir(dirPath);
21310
- } catch (error2) {
21311
- if (error2 && typeof error2 === "object" && "code" in error2 && error2.code === "ENOENT") {
21312
- return [];
21313
- }
21314
- throw error2;
21315
- }
21316
- return names.filter((name) => name.endsWith(".json"));
20853
+ function sanitizeFileName(value) {
20854
+ return normalizeString(value).replace(/[^a-zA-Z0-9_-]+/g, "-");
21317
20855
  }
21318
- var ApprovalStore = class {
21319
- constructor({ requestsDir, notificationsDir }) {
21320
- this.requestsDir = requestsDir;
21321
- this.notificationsDir = notificationsDir;
21322
- }
21323
- async init() {
21324
- await mkdir(this.requestsDir, { recursive: true });
21325
- await mkdir(this.notificationsDir, { recursive: true });
21326
- }
21327
- resolveRequestPath(requestID) {
21328
- return path2.join(this.requestsDir, `${normalizeString2(requestID)}.json`);
21329
- }
21330
- async createPermissionRequest(input) {
21331
- const requestID = normalizeString2(input.request_id);
21332
- const now = Math.floor(input.created_at ?? Date.now());
21333
- if (!requestID) {
21334
- throw new Error("request_id is required");
21335
- }
21336
- const request = normalizeRequest({
21337
- schema_version: schemaVersion,
21338
- request_id: requestID,
21339
- status: "pending",
21340
- created_at: now,
21341
- updated_at: now,
21342
- dispatched_at: 0,
21343
- dispatch_error: "",
21344
- approval_message_id: "",
21345
- session_id: input.session_id,
21346
- transcript_path: input.transcript_path,
21347
- tool_name: input.tool_name,
21348
- tool_input: input.tool_input ?? {},
21349
- permission_suggestions: input.permission_suggestions ?? [],
21350
- channel_context: input.channel_context ?? {},
21351
- decision: null,
21352
- resolved_by: null
21353
- });
21354
- await writeJSONFileAtomic(this.resolveRequestPath(requestID), request);
21355
- return request;
21356
- }
21357
- async getRequest(requestID) {
21358
- const stored = await readJSONFile(this.resolveRequestPath(requestID), null);
21359
- return normalizeRequest(stored);
21360
- }
21361
- async saveRequest(request) {
21362
- const normalized = normalizeRequest(request);
21363
- if (!normalized?.request_id) {
21364
- throw new Error("request_id is required");
21365
- }
21366
- normalized.updated_at = Math.floor(Date.now());
21367
- await writeJSONFileAtomic(this.resolveRequestPath(normalized.request_id), normalized);
21368
- return normalized;
21369
- }
21370
- async listPendingDispatches() {
21371
- const names = await listJSONFiles(this.requestsDir);
21372
- const requests = [];
21373
- for (const name of names) {
21374
- const request = await this.getRequest(name.replace(/\.json$/u, ""));
21375
- if (!request) {
21376
- continue;
21377
- }
21378
- if (request.status !== "pending") {
21379
- continue;
21380
- }
21381
- if (!request.channel_context.chat_id) {
21382
- continue;
21383
- }
21384
- if (request.dispatched_at > 0) {
21385
- continue;
21386
- }
21387
- requests.push(request);
21388
- }
21389
- requests.sort((left, right) => left.created_at - right.created_at);
21390
- return requests;
21391
- }
21392
- async markDispatched(requestID, { dispatchedAt = Date.now(), approvalMessageID = "" } = {}) {
21393
- const request = await this.getRequest(requestID);
21394
- if (!request) {
21395
- throw new Error("approval request not found");
21396
- }
21397
- request.dispatched_at = Math.floor(dispatchedAt);
21398
- request.dispatch_error = "";
21399
- request.approval_message_id = normalizeString2(approvalMessageID);
21400
- return this.saveRequest(request);
21401
- }
21402
- async markDispatchFailed(requestID, errorText) {
21403
- const request = await this.getRequest(requestID);
21404
- if (!request) {
21405
- throw new Error("approval request not found");
21406
- }
21407
- request.dispatch_error = normalizeString2(errorText);
21408
- return this.saveRequest(request);
20856
+ function buildContextFileName(sessionID, agentID = "") {
20857
+ const normalizedSessionID = sanitizeFileName(sessionID);
20858
+ if (!normalizedSessionID) {
20859
+ throw new Error("session_id is required");
21409
20860
  }
21410
- async resolveRequest(requestID, { decision, resolvedBy }) {
21411
- const request = await this.getRequest(requestID);
21412
- if (!request) {
21413
- throw new Error("approval request not found");
21414
- }
21415
- if (request.status !== "pending") {
21416
- throw new Error(`approval request is ${request.status}`);
21417
- }
21418
- const normalizedDecision = normalizeDecision(decision);
21419
- if (!normalizedDecision) {
21420
- throw new Error("invalid approval decision");
21421
- }
21422
- request.status = "resolved";
21423
- request.decision = normalizedDecision;
21424
- request.resolved_by = {
21425
- sender_id: normalizeString2(resolvedBy?.sender_id),
21426
- session_id: normalizeString2(resolvedBy?.session_id),
21427
- event_id: normalizeString2(resolvedBy?.event_id),
21428
- msg_id: normalizeString2(resolvedBy?.msg_id)
21429
- };
21430
- return this.saveRequest(request);
21431
- }
21432
- async markExpired(requestID) {
21433
- const request = await this.getRequest(requestID);
21434
- if (!request || request.status !== "pending") {
21435
- return request;
21436
- }
21437
- request.status = "expired";
21438
- return this.saveRequest(request);
20861
+ const normalizedAgentID = sanitizeFileName(agentID);
20862
+ if (!normalizedAgentID) {
20863
+ return `${normalizedSessionID}.json`;
21439
20864
  }
21440
- async recordNotification(event) {
21441
- const now = Date.now();
21442
- const filePath = path2.join(this.notificationsDir, `${now}.json`);
21443
- await writeJSONFileAtomic(filePath, {
21444
- schema_version: schemaVersion,
21445
- created_at: now,
21446
- payload: cloneJSON2(event ?? {})
21447
- });
21448
- }
21449
- async getStatus() {
21450
- const names = await listJSONFiles(this.requestsDir);
21451
- let pendingCount = 0;
21452
- let resolvedCount = 0;
21453
- let expiredCount = 0;
21454
- for (const name of names) {
21455
- const request = await this.getRequest(name.replace(/\.json$/u, ""));
21456
- if (!request) {
21457
- continue;
21458
- }
21459
- if (request.status === "pending") {
21460
- pendingCount += 1;
21461
- } else if (request.status === "resolved") {
21462
- resolvedCount += 1;
21463
- } else if (request.status === "expired") {
21464
- expiredCount += 1;
21465
- }
21466
- }
21467
- return {
21468
- pending_count: pendingCount,
21469
- resolved_count: resolvedCount,
21470
- expired_count: expiredCount
21471
- };
21472
- }
21473
- };
21474
-
21475
- // server/channel-context-store.js
21476
- import { mkdir as mkdir2 } from "node:fs/promises";
21477
- import path3 from "node:path";
21478
- var schemaVersion2 = 1;
21479
- function normalizeString3(value) {
21480
- return String(value ?? "").trim();
21481
- }
21482
- function sanitizeFileName(value) {
21483
- return normalizeString3(value).replace(/[^a-zA-Z0-9_-]+/g, "-");
20865
+ return `${normalizedSessionID}--${normalizedAgentID}.json`;
21484
20866
  }
21485
20867
  function normalizeContext(input) {
21486
20868
  if (!input || typeof input !== "object") {
21487
20869
  return null;
21488
20870
  }
21489
20871
  const context = input.context ?? {};
21490
- const sessionID = normalizeString3(input.session_id);
21491
- const transcriptPath = normalizeString3(input.transcript_path);
21492
- const cwd = normalizeString3(input.cwd);
21493
- const chatID = normalizeString3(context.chat_id);
20872
+ const sessionID = normalizeString(input.session_id);
20873
+ const agentID = normalizeString(input.agent_id);
20874
+ const transcriptPath = normalizeString(input.transcript_path);
20875
+ const cwd = normalizeString(input.cwd);
20876
+ const chatID = normalizeString(context.chat_id);
21494
20877
  if (!sessionID || !transcriptPath || !chatID) {
21495
20878
  return null;
21496
20879
  }
21497
20880
  return {
21498
- schema_version: schemaVersion2,
20881
+ schema_version: schemaVersion,
21499
20882
  session_id: sessionID,
20883
+ agent_id: agentID,
21500
20884
  transcript_path: transcriptPath,
21501
20885
  cwd,
21502
20886
  updated_at: Number(input.updated_at ?? Date.now()),
21503
20887
  context: {
21504
- raw_tag: normalizeString3(context.raw_tag),
21505
20888
  chat_id: chatID,
21506
- event_id: normalizeString3(context.event_id),
21507
- message_id: normalizeString3(context.message_id),
21508
- sender_id: normalizeString3(context.sender_id),
21509
- user_id: normalizeString3(context.user_id),
21510
- msg_id: normalizeString3(context.msg_id)
20889
+ message_id: normalizeString(context.message_id || context.msg_id)
21511
20890
  }
21512
20891
  };
21513
20892
  }
@@ -21516,14 +20895,10 @@ var ChannelContextStore = class {
21516
20895
  this.contextsDir = contextsDir;
21517
20896
  }
21518
20897
  async init() {
21519
- await mkdir2(this.contextsDir, { recursive: true });
20898
+ await mkdir(this.contextsDir, { recursive: true });
21520
20899
  }
21521
- resolveSessionPath(sessionID) {
21522
- const name = sanitizeFileName(sessionID);
21523
- if (!name) {
21524
- throw new Error("session_id is required");
21525
- }
21526
- return path3.join(this.contextsDir, `${name}.json`);
20900
+ resolveSessionPath(sessionID, agentID = "") {
20901
+ return path.join(this.contextsDir, buildContextFileName(sessionID, agentID));
21527
20902
  }
21528
20903
  async put(input) {
21529
20904
  const normalized = normalizeContext(input);
@@ -21531,20 +20906,33 @@ var ChannelContextStore = class {
21531
20906
  throw new Error("valid session context is required");
21532
20907
  }
21533
20908
  await this.init();
21534
- await writeJSONFileAtomic(this.resolveSessionPath(normalized.session_id), normalized);
20909
+ await writeJSONFileAtomic(
20910
+ this.resolveSessionPath(normalized.session_id, normalized.agent_id),
20911
+ normalized
20912
+ );
21535
20913
  return normalized;
21536
20914
  }
21537
- async get(sessionID) {
21538
- const normalizedSessionID = normalizeString3(sessionID);
20915
+ async get(sessionID, { agentID = "" } = {}) {
20916
+ const normalizedSessionID = normalizeString(sessionID);
21539
20917
  if (!normalizedSessionID) {
21540
20918
  return null;
21541
20919
  }
21542
- const stored = await readJSONFile(this.resolveSessionPath(normalizedSessionID), null);
20920
+ const stored = await readJSONFile(
20921
+ this.resolveSessionPath(normalizedSessionID, agentID),
20922
+ null
20923
+ );
21543
20924
  return normalizeContext(stored);
21544
20925
  }
21545
- async getMatchingContext({ sessionID, transcriptPath, workingDir, maxAgeMs }) {
20926
+ async getMatchingContext({
20927
+ sessionID,
20928
+ agentID,
20929
+ transcriptPath,
20930
+ workingDir,
20931
+ maxAgeMs
20932
+ }) {
21546
20933
  const inspection = await this.inspectMatchingContext({
21547
20934
  sessionID,
20935
+ agentID,
21548
20936
  transcriptPath,
21549
20937
  workingDir,
21550
20938
  maxAgeMs
@@ -21554,21 +20942,27 @@ var ChannelContextStore = class {
21554
20942
  }
21555
20943
  return inspection.context;
21556
20944
  }
21557
- async inspectMatchingContext({ sessionID, transcriptPath, workingDir, maxAgeMs }) {
21558
- const stored = await this.get(sessionID);
20945
+ async inspectMatchingContext({
20946
+ sessionID,
20947
+ agentID,
20948
+ transcriptPath,
20949
+ workingDir,
20950
+ maxAgeMs
20951
+ }) {
20952
+ const stored = await this.get(sessionID, { agentID });
21559
20953
  if (!stored) {
21560
20954
  return {
21561
20955
  status: "missing",
21562
20956
  context: null
21563
20957
  };
21564
20958
  }
21565
- if (normalizeString3(transcriptPath) && stored.transcript_path !== normalizeString3(transcriptPath)) {
20959
+ if (normalizeString(transcriptPath) && stored.transcript_path !== normalizeString(transcriptPath)) {
21566
20960
  return {
21567
20961
  status: "transcript_mismatch",
21568
20962
  context: null
21569
20963
  };
21570
20964
  }
21571
- if (normalizeString3(workingDir) && stored.cwd !== normalizeString3(workingDir)) {
20965
+ if (normalizeString(workingDir) && stored.cwd !== normalizeString(workingDir)) {
21572
20966
  return {
21573
20967
  status: "cwd_mismatch",
21574
20968
  context: null
@@ -21588,15 +20982,15 @@ var ChannelContextStore = class {
21588
20982
  };
21589
20983
 
21590
20984
  // server/config-store.js
21591
- import path4 from "node:path";
21592
- import { mkdir as mkdir3 } from "node:fs/promises";
20985
+ import path2 from "node:path";
20986
+ import { mkdir as mkdir2 } from "node:fs/promises";
21593
20987
 
21594
20988
  // server/connection-env.js
21595
- function normalizeString4(value) {
20989
+ function normalizeString2(value) {
21596
20990
  return String(value ?? "").trim();
21597
20991
  }
21598
20992
  function normalizePositiveInt(value) {
21599
- const normalized = normalizeString4(value);
20993
+ const normalized = normalizeString2(value);
21600
20994
  if (!normalized) {
21601
20995
  return void 0;
21602
20996
  }
@@ -21608,7 +21002,7 @@ function normalizePositiveInt(value) {
21608
21002
  }
21609
21003
  function readFirstNonEmpty(values = []) {
21610
21004
  for (const value of values) {
21611
- const normalized = normalizeString4(value);
21005
+ const normalized = normalizeString2(value);
21612
21006
  if (normalized) {
21613
21007
  return normalized;
21614
21008
  }
@@ -21645,7 +21039,7 @@ var defaultConfig = Object.freeze({
21645
21039
  api_key: "",
21646
21040
  outbound_text_chunk_limit: DEFAULT_OUTBOUND_TEXT_CHUNK_LIMIT
21647
21041
  });
21648
- function normalizeString5(value) {
21042
+ function normalizeString3(value) {
21649
21043
  return String(value ?? "").trim();
21650
21044
  }
21651
21045
  function normalizePositiveInt2(value, fallbackValue) {
@@ -21658,9 +21052,9 @@ function normalizePositiveInt2(value, fallbackValue) {
21658
21052
  function normalizeConfigShape(input = {}) {
21659
21053
  return {
21660
21054
  schema_version: 1,
21661
- ws_url: normalizeString5(input.ws_url),
21662
- agent_id: normalizeString5(input.agent_id),
21663
- api_key: normalizeString5(input.api_key),
21055
+ ws_url: normalizeString3(input.ws_url),
21056
+ agent_id: normalizeString3(input.agent_id),
21057
+ api_key: normalizeString3(input.api_key),
21664
21058
  outbound_text_chunk_limit: normalizePositiveInt2(
21665
21059
  input.outbound_text_chunk_limit,
21666
21060
  DEFAULT_OUTBOUND_TEXT_CHUNK_LIMIT
@@ -21704,7 +21098,7 @@ function validateConfig(config2) {
21704
21098
  }
21705
21099
  }
21706
21100
  function redactAPIKey(apiKey) {
21707
- const normalized = normalizeString5(apiKey);
21101
+ const normalized = normalizeString3(apiKey);
21708
21102
  if (!normalized) {
21709
21103
  return "";
21710
21104
  }
@@ -21720,7 +21114,7 @@ var ConfigStore = class {
21720
21114
  this.config = { ...defaultConfig };
21721
21115
  }
21722
21116
  async load() {
21723
- await mkdir3(path4.dirname(this.filePath), { recursive: true, mode: 448 });
21117
+ await mkdir2(path2.dirname(this.filePath), { recursive: true, mode: 448 });
21724
21118
  const stored = await readJSONFile(this.filePath, defaultConfig);
21725
21119
  this.config = normalizeConfigShape(applyEnvOverrides(stored, this.env));
21726
21120
  validateConfig(this.config);
@@ -21758,7 +21152,7 @@ var ConfigStore = class {
21758
21152
  ...input
21759
21153
  });
21760
21154
  validateConfig(next);
21761
- await mkdir3(path4.dirname(this.filePath), { recursive: true, mode: 448 });
21155
+ await mkdir2(path2.dirname(this.filePath), { recursive: true, mode: 448 });
21762
21156
  await writeJSONFileAtomic(this.filePath, next, { mode: 384 });
21763
21157
  this.config = applyEnvOverrides(next, this.env);
21764
21158
  validateConfig(this.config);
@@ -21767,31 +21161,25 @@ var ConfigStore = class {
21767
21161
  };
21768
21162
 
21769
21163
  // server/elicitation-store.js
21770
- import { mkdir as mkdir4, readdir as readdir2 } from "node:fs/promises";
21771
- import path5 from "node:path";
21164
+ import { mkdir as mkdir3, readdir } from "node:fs/promises";
21165
+ import path3 from "node:path";
21772
21166
 
21773
21167
  // server/elicitation-schema.js
21774
- function normalizeString6(value) {
21168
+ function normalizeString4(value) {
21775
21169
  return String(value ?? "").trim();
21776
21170
  }
21777
- function cloneJSON3(value) {
21778
- return JSON.parse(JSON.stringify(value));
21779
- }
21780
- function isRecord(value) {
21781
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
21782
- }
21783
21171
  function normalizeStringArray(input) {
21784
21172
  if (!Array.isArray(input)) {
21785
21173
  return [];
21786
21174
  }
21787
- return input.map((value) => normalizeString6(value)).filter(Boolean);
21175
+ return input.map((value) => normalizeString4(value)).filter(Boolean);
21788
21176
  }
21789
21177
  function normalizeFieldType(value) {
21790
- const normalized = normalizeString6(value).toLowerCase();
21178
+ const normalized = normalizeString4(value).toLowerCase();
21791
21179
  return normalized || "string";
21792
21180
  }
21793
21181
  function normalizeFieldKind(value) {
21794
- const normalized = normalizeString6(value).toLowerCase();
21182
+ const normalized = normalizeString4(value).toLowerCase();
21795
21183
  if (!normalized) {
21796
21184
  return "string";
21797
21185
  }
@@ -21799,7 +21187,7 @@ function normalizeFieldKind(value) {
21799
21187
  }
21800
21188
  function normalizeFieldPrompt(title, description, kind) {
21801
21189
  const lines = [];
21802
- const normalizedDescription = normalizeString6(description);
21190
+ const normalizedDescription = normalizeString4(description);
21803
21191
  if (normalizedDescription) {
21804
21192
  lines.push(normalizedDescription);
21805
21193
  }
@@ -21815,7 +21203,7 @@ function normalizeFieldPrompt(title, description, kind) {
21815
21203
  if (typeHint) {
21816
21204
  lines.push(typeHint);
21817
21205
  }
21818
- return lines.join(" ").trim() || `Provide ${normalizeString6(title) || "a value"}.`;
21206
+ return lines.join(" ").trim() || `Provide ${normalizeString4(title) || "a value"}.`;
21819
21207
  }
21820
21208
  function normalizeFieldOptions(input) {
21821
21209
  return normalizeStringArray(input);
@@ -21825,15 +21213,15 @@ function normalizeElicitationFields(input) {
21825
21213
  return [];
21826
21214
  }
21827
21215
  return input.map((field) => {
21828
- const key = normalizeString6(field?.key);
21829
- const title = normalizeString6(field?.title) || key;
21216
+ const key = normalizeString4(field?.key);
21217
+ const title = normalizeString4(field?.title) || key;
21830
21218
  if (!key || !title) {
21831
21219
  return null;
21832
21220
  }
21833
21221
  return {
21834
21222
  key,
21835
21223
  title,
21836
- prompt: normalizeString6(field?.prompt) || normalizeFieldPrompt(title, "", field?.kind),
21224
+ prompt: normalizeString4(field?.prompt) || normalizeFieldPrompt(title, "", field?.kind),
21837
21225
  type: normalizeFieldType(field?.type),
21838
21226
  kind: normalizeFieldKind(field?.kind),
21839
21227
  options: normalizeFieldOptions(field?.options),
@@ -21842,112 +21230,66 @@ function normalizeElicitationFields(input) {
21842
21230
  };
21843
21231
  }).filter(Boolean);
21844
21232
  }
21845
- function buildQuestionPromptsFromFields(fields) {
21846
- return normalizeElicitationFields(fields).map((field) => ({
21847
- header: field.title,
21848
- question: field.prompt,
21849
- options: field.options.map((label) => ({ label })),
21850
- multiSelect: field.multi_select
21851
- }));
21852
- }
21853
- function cloneRequestedSchema(requestedSchema) {
21854
- if (!isRecord(requestedSchema)) {
21855
- return null;
21856
- }
21857
- return cloneJSON3(requestedSchema);
21858
- }
21859
21233
 
21860
21234
  // server/elicitation-store.js
21861
- var schemaVersion3 = 1;
21862
- function normalizeString7(value) {
21235
+ var schemaVersion2 = 1;
21236
+ function normalizeString5(value) {
21863
21237
  return String(value ?? "").trim();
21864
21238
  }
21865
- function cloneJSON4(value) {
21239
+ function cloneJSON(value) {
21866
21240
  return JSON.parse(JSON.stringify(value));
21867
21241
  }
21868
- function isRecord2(value) {
21242
+ function isRecord(value) {
21869
21243
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
21870
21244
  }
21871
21245
  function normalizeChannelContext(input) {
21872
21246
  const context = input ?? {};
21873
21247
  return {
21874
- chat_id: normalizeString7(context.chat_id),
21875
- event_id: normalizeString7(context.event_id),
21876
- message_id: normalizeString7(context.message_id),
21877
- sender_id: normalizeString7(context.sender_id),
21878
- user_id: normalizeString7(context.user_id),
21879
- msg_id: normalizeString7(context.msg_id)
21248
+ chat_id: normalizeString5(context.chat_id),
21249
+ message_id: normalizeString5(context.message_id || context.msg_id)
21880
21250
  };
21881
21251
  }
21882
- function normalizeResolvedBy(input) {
21883
- if (!isRecord2(input)) {
21252
+ function normalizeResponseContent(input) {
21253
+ if (!isRecord(input)) {
21884
21254
  return null;
21885
21255
  }
21886
- return {
21887
- sender_id: normalizeString7(input.sender_id),
21888
- session_id: normalizeString7(input.session_id),
21889
- event_id: normalizeString7(input.event_id),
21890
- msg_id: normalizeString7(input.msg_id)
21891
- };
21256
+ return cloneJSON(input);
21892
21257
  }
21893
- function normalizeResponseContent(input) {
21894
- if (!isRecord2(input)) {
21258
+ function normalizeRequestPayload(input) {
21259
+ if (!isRecord(input)) {
21895
21260
  return null;
21896
21261
  }
21897
- return cloneJSON4(input);
21262
+ return cloneJSON(input);
21898
21263
  }
21899
- function normalizeQuestions(input) {
21900
- if (!Array.isArray(input)) {
21901
- return [];
21264
+ function allowsEmptyFields(requestPayload) {
21265
+ const payload = normalizeRequestPayload(requestPayload);
21266
+ if (!payload) {
21267
+ return false;
21902
21268
  }
21903
- return input.map((question) => {
21904
- const prompt = normalizeString7(question?.question);
21905
- if (!prompt) {
21906
- return null;
21907
- }
21908
- return {
21909
- question: prompt,
21910
- header: normalizeString7(question?.header),
21911
- options: Array.isArray(question?.options) ? cloneJSON4(question.options) : [],
21912
- multiSelect: question?.multiSelect === true
21913
- };
21914
- }).filter(Boolean);
21269
+ return normalizeString5(payload.mode).toLowerCase() === "url" && normalizeString5(payload.url) !== "";
21915
21270
  }
21916
- function normalizeRequest2(input) {
21917
- if (!isRecord2(input) || Number(input.schema_version) !== schemaVersion3) {
21271
+ function normalizeRequest(input) {
21272
+ if (!isRecord(input) || Number(input.schema_version) !== schemaVersion2) {
21918
21273
  return null;
21919
21274
  }
21920
21275
  const fields = normalizeElicitationFields(input.fields);
21921
- const storedQuestions = normalizeQuestions(input.questions);
21922
21276
  return {
21923
- schema_version: schemaVersion3,
21924
- request_id: normalizeString7(input.request_id),
21925
- status: normalizeString7(input.status) || "pending",
21277
+ schema_version: schemaVersion2,
21278
+ request_id: normalizeString5(input.request_id),
21279
+ status: normalizeString5(input.status) || "pending",
21926
21280
  created_at: Number(input.created_at ?? 0),
21927
- updated_at: Number(input.updated_at ?? 0),
21928
21281
  dispatched_at: Number(input.dispatched_at ?? 0),
21929
- dispatch_error: normalizeString7(input.dispatch_error),
21930
- prompt_message_id: normalizeString7(input.prompt_message_id),
21931
- session_id: normalizeString7(input.session_id),
21932
- transcript_path: normalizeString7(input.transcript_path),
21933
- mcp_server_name: normalizeString7(input.mcp_server_name),
21934
- elicitation_id: normalizeString7(input.elicitation_id),
21935
- message: normalizeString7(input.message),
21936
- mode: normalizeString7(input.mode) || "form",
21937
- url: normalizeString7(input.url),
21938
- requested_schema: input.requested_schema === null ? null : cloneRequestedSchema(input.requested_schema),
21939
21282
  fields,
21940
- questions: storedQuestions.length > 0 ? storedQuestions : buildQuestionPromptsFromFields(fields),
21283
+ request_payload: normalizeRequestPayload(input.request_payload),
21941
21284
  channel_context: normalizeChannelContext(input.channel_context),
21942
- response_action: normalizeString7(input.response_action),
21943
- response_content: input.response_content === null ? null : normalizeResponseContent(input.response_content),
21944
- resolved_by: normalizeResolvedBy(input.resolved_by)
21285
+ response_action: normalizeString5(input.response_action),
21286
+ response_content: input.response_content === null ? null : normalizeResponseContent(input.response_content)
21945
21287
  };
21946
21288
  }
21947
- async function listJSONFiles2(dirPath) {
21289
+ async function listJSONFiles(dirPath) {
21948
21290
  let names = [];
21949
21291
  try {
21950
- names = await readdir2(dirPath);
21292
+ names = await readdir(dirPath);
21951
21293
  } catch (error2) {
21952
21294
  if (error2 && typeof error2 === "object" && "code" in error2 && error2.code === "ENOENT") {
21953
21295
  return [];
@@ -21961,63 +21303,50 @@ var ElicitationStore = class {
21961
21303
  this.requestsDir = requestsDir;
21962
21304
  }
21963
21305
  async init() {
21964
- await mkdir4(this.requestsDir, { recursive: true });
21306
+ await mkdir3(this.requestsDir, { recursive: true });
21965
21307
  }
21966
21308
  resolveRequestPath(requestID) {
21967
- return path5.join(this.requestsDir, `${normalizeString7(requestID)}.json`);
21309
+ return path3.join(this.requestsDir, `${normalizeString5(requestID)}.json`);
21968
21310
  }
21969
21311
  async createRequest(input) {
21970
- const requestID = normalizeString7(input.request_id);
21312
+ const requestID = normalizeString5(input.request_id);
21971
21313
  const now = Math.floor(input.created_at ?? Date.now());
21972
21314
  if (!requestID) {
21973
21315
  throw new Error("request_id is required");
21974
21316
  }
21975
21317
  const fields = normalizeElicitationFields(input.fields);
21976
- if (fields.length === 0) {
21318
+ if (fields.length === 0 && !allowsEmptyFields(input.request_payload)) {
21977
21319
  throw new Error("elicitation request fields are required");
21978
21320
  }
21979
- const request = normalizeRequest2({
21980
- schema_version: schemaVersion3,
21321
+ const request = normalizeRequest({
21322
+ schema_version: schemaVersion2,
21981
21323
  request_id: requestID,
21982
21324
  status: "pending",
21983
21325
  created_at: now,
21984
- updated_at: now,
21985
21326
  dispatched_at: 0,
21986
- dispatch_error: "",
21987
- prompt_message_id: "",
21988
- session_id: input.session_id,
21989
- transcript_path: input.transcript_path,
21990
- mcp_server_name: input.mcp_server_name,
21991
- elicitation_id: input.elicitation_id,
21992
- message: input.message,
21993
- mode: input.mode,
21994
- url: input.url,
21995
- requested_schema: input.requested_schema ?? null,
21996
21327
  fields,
21997
- questions: buildQuestionPromptsFromFields(fields),
21328
+ request_payload: input.request_payload ?? null,
21998
21329
  channel_context: input.channel_context ?? {},
21999
21330
  response_action: "",
22000
- response_content: null,
22001
- resolved_by: null
21331
+ response_content: null
22002
21332
  });
22003
21333
  await writeJSONFileAtomic(this.resolveRequestPath(requestID), request);
22004
21334
  return request;
22005
21335
  }
22006
21336
  async getRequest(requestID) {
22007
21337
  const stored = await readJSONFile(this.resolveRequestPath(requestID), null);
22008
- return normalizeRequest2(stored);
21338
+ return normalizeRequest(stored);
22009
21339
  }
22010
21340
  async saveRequest(request) {
22011
- const normalized = normalizeRequest2(request);
21341
+ const normalized = normalizeRequest(request);
22012
21342
  if (!normalized?.request_id) {
22013
21343
  throw new Error("request_id is required");
22014
21344
  }
22015
- normalized.updated_at = Math.floor(Date.now());
22016
21345
  await writeJSONFileAtomic(this.resolveRequestPath(normalized.request_id), normalized);
22017
21346
  return normalized;
22018
21347
  }
22019
21348
  async listPendingDispatches() {
22020
- const names = await listJSONFiles2(this.requestsDir);
21349
+ const names = await listJSONFiles(this.requestsDir);
22021
21350
  const requests = [];
22022
21351
  for (const name of names) {
22023
21352
  const request = await this.getRequest(name.replace(/\.json$/u, ""));
@@ -22038,25 +21367,23 @@ var ElicitationStore = class {
22038
21367
  requests.sort((left, right) => left.created_at - right.created_at);
22039
21368
  return requests;
22040
21369
  }
22041
- async markDispatched(requestID, { dispatchedAt = Date.now(), promptMessageID = "" } = {}) {
21370
+ async markDispatched(requestID, { dispatchedAt = Date.now() } = {}) {
22042
21371
  const request = await this.getRequest(requestID);
22043
21372
  if (!request) {
22044
21373
  throw new Error("elicitation request not found");
22045
21374
  }
22046
21375
  request.dispatched_at = Math.floor(dispatchedAt);
22047
- request.dispatch_error = "";
22048
- request.prompt_message_id = normalizeString7(promptMessageID);
22049
21376
  return this.saveRequest(request);
22050
21377
  }
22051
21378
  async markDispatchFailed(requestID, errorText) {
21379
+ void errorText;
22052
21380
  const request = await this.getRequest(requestID);
22053
21381
  if (!request) {
22054
21382
  throw new Error("elicitation request not found");
22055
21383
  }
22056
- request.dispatch_error = normalizeString7(errorText);
22057
- return this.saveRequest(request);
21384
+ return request;
22058
21385
  }
22059
- async resolveRequest(requestID, { action, content, resolvedBy }) {
21386
+ async resolveRequest(requestID, { action, content }) {
22060
21387
  const request = await this.getRequest(requestID);
22061
21388
  if (!request) {
22062
21389
  throw new Error("elicitation request not found");
@@ -22064,8 +21391,8 @@ var ElicitationStore = class {
22064
21391
  if (request.status !== "pending") {
22065
21392
  throw new Error(`elicitation request is ${request.status}`);
22066
21393
  }
22067
- const normalizedAction = normalizeString7(action);
22068
- if (!["accept", "decline", "cancel"].includes(normalizedAction)) {
21394
+ const normalizedAction = normalizeString5(action);
21395
+ if (!["accept", "cancel"].includes(normalizedAction)) {
22069
21396
  throw new Error("invalid elicitation action");
22070
21397
  }
22071
21398
  const normalizedContent = normalizedAction === "accept" ? normalizeResponseContent(content) : null;
@@ -22075,7 +21402,6 @@ var ElicitationStore = class {
22075
21402
  request.status = "resolved";
22076
21403
  request.response_action = normalizedAction;
22077
21404
  request.response_content = normalizedContent;
22078
- request.resolved_by = normalizeResolvedBy(resolvedBy);
22079
21405
  return this.saveRequest(request);
22080
21406
  }
22081
21407
  async markExpired(requestID) {
@@ -22087,10 +21413,8 @@ var ElicitationStore = class {
22087
21413
  return this.saveRequest(request);
22088
21414
  }
22089
21415
  async getStatus() {
22090
- const names = await listJSONFiles2(this.requestsDir);
21416
+ const names = await listJSONFiles(this.requestsDir);
22091
21417
  let pendingCount = 0;
22092
- let resolvedCount = 0;
22093
- let expiredCount = 0;
22094
21418
  for (const name of names) {
22095
21419
  const request = await this.getRequest(name.replace(/\.json$/u, ""));
22096
21420
  if (!request) {
@@ -22098,16 +21422,10 @@ var ElicitationStore = class {
22098
21422
  }
22099
21423
  if (request.status === "pending") {
22100
21424
  pendingCount += 1;
22101
- } else if (request.status === "resolved") {
22102
- resolvedCount += 1;
22103
- } else if (request.status === "expired") {
22104
- expiredCount += 1;
22105
21425
  }
22106
21426
  }
22107
21427
  return {
22108
- pending_count: pendingCount,
22109
- resolved_count: resolvedCount,
22110
- expired_count: expiredCount
21428
+ pending_count: pendingCount
22111
21429
  };
22112
21430
  }
22113
21431
  };
@@ -22115,15 +21433,14 @@ var ElicitationStore = class {
22115
21433
  // server/event-state.js
22116
21434
  var defaultTTLMS = 30 * 60 * 1e3;
22117
21435
  var defaultPendingTTLMS = 48 * 60 * 60 * 1e3;
22118
- var defaultPairingCooldownMs = 60 * 1e3;
22119
- function normalizeString8(value) {
21436
+ function normalizeString6(value) {
22120
21437
  return String(value ?? "").trim();
22121
21438
  }
22122
21439
  function normalizeStringArray2(value) {
22123
21440
  if (!Array.isArray(value)) {
22124
21441
  return [];
22125
21442
  }
22126
- return value.map((item) => normalizeString8(item)).filter((item) => item);
21443
+ return value.map((item) => normalizeString6(item)).filter((item) => item);
22127
21444
  }
22128
21445
  function cloneEntry(entry) {
22129
21446
  return entry ? JSON.parse(JSON.stringify(entry)) : null;
@@ -22132,12 +21449,10 @@ var EventState = class {
22132
21449
  constructor({
22133
21450
  ttlMs = defaultTTLMS,
22134
21451
  pendingTTLms = defaultPendingTTLMS,
22135
- pairingCooldownMs = defaultPairingCooldownMs,
22136
21452
  onChange = null
22137
21453
  } = {}) {
22138
21454
  this.ttlMs = ttlMs;
22139
21455
  this.pendingTTLms = pendingTTLms;
22140
- this.pairingCooldownMs = pairingCooldownMs;
22141
21456
  this.onChange = typeof onChange === "function" ? onChange : null;
22142
21457
  this.events = /* @__PURE__ */ new Map();
22143
21458
  }
@@ -22147,16 +21462,16 @@ var EventState = class {
22147
21462
  }
22148
21463
  }
22149
21464
  restore(rawEntry) {
22150
- const eventID = normalizeString8(rawEntry?.event_id);
21465
+ const eventID = normalizeString6(rawEntry?.event_id);
22151
21466
  if (!eventID || this.events.has(eventID)) {
22152
21467
  return;
22153
21468
  }
22154
21469
  this.events.set(eventID, JSON.parse(JSON.stringify(rawEntry)));
22155
21470
  }
22156
21471
  registerInbound(payload) {
22157
- const eventID = normalizeString8(payload.event_id);
22158
- const sessionID = normalizeString8(payload.session_id);
22159
- const msgID = normalizeString8(payload.msg_id);
21472
+ const eventID = normalizeString6(payload.event_id);
21473
+ const sessionID = normalizeString6(payload.session_id);
21474
+ const msgID = normalizeString6(payload.msg_id);
22160
21475
  if (!eventID || !sessionID || !msgID) {
22161
21476
  throw new Error("event_id, session_id, and msg_id are required");
22162
21477
  }
@@ -22174,26 +21489,25 @@ var EventState = class {
22174
21489
  event_id: eventID,
22175
21490
  session_id: sessionID,
22176
21491
  msg_id: msgID,
22177
- quoted_message_id: normalizeString8(payload.quoted_message_id),
22178
- sender_id: normalizeString8(payload.sender_id),
22179
- event_type: normalizeString8(payload.event_type),
22180
- session_type: normalizeString8(payload.session_type),
21492
+ quoted_message_id: normalizeString6(payload.quoted_message_id),
21493
+ sender_id: normalizeString6(payload.sender_id),
21494
+ event_type: normalizeString6(payload.event_type),
21495
+ session_type: normalizeString6(payload.session_type),
22181
21496
  content: String(payload.content ?? ""),
22182
- owner_id: normalizeString8(payload.owner_id),
22183
- agent_id: normalizeString8(payload.agent_id),
22184
- msg_type: normalizeString8(payload.msg_type),
21497
+ owner_id: normalizeString6(payload.owner_id),
21498
+ agent_id: normalizeString6(payload.agent_id),
21499
+ msg_type: normalizeString6(payload.msg_type),
22185
21500
  message_created_at: Number(payload.message_created_at ?? payload.created_at ?? 0),
22186
21501
  mention_user_ids: normalizeStringArray2(payload.mention_user_ids),
22187
21502
  extra_json: String(payload.extra_json ?? ""),
22188
21503
  attachments_json: String(payload.attachments_json ?? ""),
22189
- attachment_count: normalizeString8(payload.attachment_count),
21504
+ attachment_count: normalizeString6(payload.attachment_count),
22190
21505
  biz_card_json: String(payload.biz_card_json ?? ""),
22191
21506
  channel_data_json: String(payload.channel_data_json ?? ""),
21507
+ context_messages_json: String(payload.context_messages_json ?? ""),
22192
21508
  acked: false,
22193
21509
  ack_at: 0,
22194
21510
  notification_dispatched_at: 0,
22195
- pairing_sent_at: 0,
22196
- pairing_retry_after: 0,
22197
21511
  result_deadline_at: 0,
22198
21512
  result_intent: null,
22199
21513
  completed: null,
@@ -22212,7 +21526,7 @@ var EventState = class {
22212
21526
  };
22213
21527
  }
22214
21528
  get(eventID) {
22215
- const existing = this.events.get(normalizeString8(eventID));
21529
+ const existing = this.events.get(normalizeString6(eventID));
22216
21530
  if (!existing) {
22217
21531
  return null;
22218
21532
  }
@@ -22220,13 +21534,13 @@ var EventState = class {
22220
21534
  return cloneEntry(existing);
22221
21535
  }
22222
21536
  getLatestActiveBySession(sessionID) {
22223
- const normalizedSessionID = normalizeString8(sessionID);
21537
+ const normalizedSessionID = normalizeString6(sessionID);
22224
21538
  if (!normalizedSessionID) {
22225
21539
  return null;
22226
21540
  }
22227
21541
  let latest = null;
22228
21542
  for (const entry of this.events.values()) {
22229
- if (normalizeString8(entry.session_id) !== normalizedSessionID) {
21543
+ if (normalizeString6(entry.session_id) !== normalizedSessionID) {
22230
21544
  continue;
22231
21545
  }
22232
21546
  if (entry.internal_probe === true) {
@@ -22246,7 +21560,7 @@ var EventState = class {
22246
21560
  return cloneEntry(latest);
22247
21561
  }
22248
21562
  markAcked(eventID, { ackedAt = Date.now() } = {}) {
22249
- const entry = this.events.get(normalizeString8(eventID));
21563
+ const entry = this.events.get(normalizeString6(eventID));
22250
21564
  if (!entry) {
22251
21565
  return null;
22252
21566
  }
@@ -22257,7 +21571,7 @@ var EventState = class {
22257
21571
  return cloneEntry(entry);
22258
21572
  }
22259
21573
  markNotificationDispatched(eventID, { dispatchedAt = Date.now() } = {}) {
22260
- const entry = this.events.get(normalizeString8(eventID));
21574
+ const entry = this.events.get(normalizeString6(eventID));
22261
21575
  if (!entry) {
22262
21576
  return null;
22263
21577
  }
@@ -22266,29 +21580,8 @@ var EventState = class {
22266
21580
  this._notify(entry);
22267
21581
  return cloneEntry(entry);
22268
21582
  }
22269
- markPairingSent(eventID, { sentAt = Date.now(), cooldownMs = this.pairingCooldownMs } = {}) {
22270
- const entry = this.events.get(normalizeString8(eventID));
22271
- if (!entry) {
22272
- return null;
22273
- }
22274
- entry.pairing_sent_at = Math.floor(sentAt);
22275
- entry.pairing_retry_after = Math.floor(sentAt + cooldownMs);
22276
- entry.last_seen_at = Date.now();
22277
- this._notify(entry);
22278
- return cloneEntry(entry);
22279
- }
22280
- canResendPairing(eventID, now = Date.now()) {
22281
- const entry = this.events.get(normalizeString8(eventID));
22282
- if (!entry) {
22283
- return false;
22284
- }
22285
- if (!entry.pairing_sent_at) {
22286
- return true;
22287
- }
22288
- return now >= Number(entry.pairing_retry_after ?? 0);
22289
- }
22290
21583
  setResultDeadline(eventID, { deadlineAt } = {}) {
22291
- const entry = this.events.get(normalizeString8(eventID));
21584
+ const entry = this.events.get(normalizeString6(eventID));
22292
21585
  if (!entry) {
22293
21586
  return null;
22294
21587
  }
@@ -22298,7 +21591,7 @@ var EventState = class {
22298
21591
  return cloneEntry(entry);
22299
21592
  }
22300
21593
  clearResultDeadline(eventID) {
22301
- const entry = this.events.get(normalizeString8(eventID));
21594
+ const entry = this.events.get(normalizeString6(eventID));
22302
21595
  if (!entry) {
22303
21596
  return null;
22304
21597
  }
@@ -22308,14 +21601,14 @@ var EventState = class {
22308
21601
  return cloneEntry(entry);
22309
21602
  }
22310
21603
  setResultIntent(eventID, result) {
22311
- const entry = this.events.get(normalizeString8(eventID));
21604
+ const entry = this.events.get(normalizeString6(eventID));
22312
21605
  if (!entry) {
22313
21606
  return null;
22314
21607
  }
22315
21608
  entry.result_intent = {
22316
- status: normalizeString8(result.status),
22317
- code: normalizeString8(result.code),
22318
- msg: normalizeString8(result.msg),
21609
+ status: normalizeString6(result.status),
21610
+ code: normalizeString6(result.code),
21611
+ msg: normalizeString6(result.msg),
22319
21612
  updated_at: Number(result.updated_at ?? Date.now())
22320
21613
  };
22321
21614
  entry.last_seen_at = Date.now();
@@ -22323,7 +21616,7 @@ var EventState = class {
22323
21616
  return cloneEntry(entry);
22324
21617
  }
22325
21618
  clearResultIntent(eventID) {
22326
- const entry = this.events.get(normalizeString8(eventID));
21619
+ const entry = this.events.get(normalizeString6(eventID));
22327
21620
  if (!entry) {
22328
21621
  return null;
22329
21622
  }
@@ -22333,16 +21626,16 @@ var EventState = class {
22333
21626
  return cloneEntry(entry);
22334
21627
  }
22335
21628
  markCompleted(eventID, result) {
22336
- const entry = this.events.get(normalizeString8(eventID));
21629
+ const entry = this.events.get(normalizeString6(eventID));
22337
21630
  if (!entry) {
22338
21631
  return null;
22339
21632
  }
22340
21633
  entry.result_deadline_at = 0;
22341
21634
  entry.result_intent = null;
22342
21635
  entry.completed = {
22343
- status: normalizeString8(result.status),
22344
- code: normalizeString8(result.code),
22345
- msg: normalizeString8(result.msg),
21636
+ status: normalizeString6(result.status),
21637
+ code: normalizeString6(result.code),
21638
+ msg: normalizeString6(result.msg),
22346
21639
  updated_at: Number(result.updated_at ?? Date.now())
22347
21640
  };
22348
21641
  entry.last_seen_at = Date.now();
@@ -22350,13 +21643,13 @@ var EventState = class {
22350
21643
  return cloneEntry(entry);
22351
21644
  }
22352
21645
  markStopped(eventID, stop) {
22353
- const entry = this.events.get(normalizeString8(eventID));
21646
+ const entry = this.events.get(normalizeString6(eventID));
22354
21647
  if (!entry) {
22355
21648
  return null;
22356
21649
  }
22357
21650
  entry.stopped = {
22358
- stop_id: normalizeString8(stop.stop_id),
22359
- reason: normalizeString8(stop.reason),
21651
+ stop_id: normalizeString6(stop.stop_id),
21652
+ reason: normalizeString6(stop.reason),
22360
21653
  updated_at: Number(stop.updated_at ?? Date.now())
22361
21654
  };
22362
21655
  entry.last_seen_at = Date.now();
@@ -22373,11 +21666,38 @@ var EventState = class {
22373
21666
  }
22374
21667
  };
22375
21668
 
21669
+ // server/paths.js
21670
+ import os from "node:os";
21671
+ import path4 from "node:path";
21672
+ var defaultPluginID = "grix-claude";
21673
+ function sanitizePluginID(pluginID) {
21674
+ return String(pluginID ?? defaultPluginID).trim().replace(/[^a-zA-Z0-9_-]+/g, "-").replace(/^-+|-+$/g, "") || defaultPluginID;
21675
+ }
21676
+ function resolvePluginDataDir(pluginID = defaultPluginID) {
21677
+ const value = String(process.env.CLAUDE_PLUGIN_DATA ?? "").trim();
21678
+ if (value) {
21679
+ return value;
21680
+ }
21681
+ return path4.join(os.homedir(), ".claude", sanitizePluginID(pluginID));
21682
+ }
21683
+ function resolveConfigPath(pluginID = defaultPluginID) {
21684
+ return path4.join(resolvePluginDataDir(pluginID), "grix-claude-config.json");
21685
+ }
21686
+ function resolveElicitationRequestsDir(pluginID = defaultPluginID) {
21687
+ return path4.join(resolvePluginDataDir(pluginID), "elicitation-requests");
21688
+ }
21689
+ function resolveSessionContextsDir(pluginID = defaultPluginID) {
21690
+ return path4.join(resolvePluginDataDir(pluginID), "session-contexts");
21691
+ }
21692
+ function resolveEventStatesDir(pluginID = defaultPluginID) {
21693
+ return path4.join(resolvePluginDataDir(pluginID), "event-states");
21694
+ }
21695
+
22376
21696
  // server/event-state-persistence.js
22377
- import { mkdir as mkdir5, readdir as readdir3 } from "node:fs/promises";
22378
- import path6 from "node:path";
22379
- var schemaVersion4 = 1;
22380
- function normalizeString9(value) {
21697
+ import { mkdir as mkdir4, readdir as readdir2 } from "node:fs/promises";
21698
+ import path5 from "node:path";
21699
+ var schemaVersion3 = 1;
21700
+ function normalizeString7(value) {
22381
21701
  return String(value ?? "").trim();
22382
21702
  }
22383
21703
  function normalizeResultField(input) {
@@ -22385,9 +21705,9 @@ function normalizeResultField(input) {
22385
21705
  return null;
22386
21706
  }
22387
21707
  return {
22388
- status: normalizeString9(input.status),
22389
- code: normalizeString9(input.code),
22390
- msg: normalizeString9(input.msg),
21708
+ status: normalizeString7(input.status),
21709
+ code: normalizeString7(input.code),
21710
+ msg: normalizeString7(input.msg),
22391
21711
  updated_at: Number(input.updated_at ?? 0)
22392
21712
  };
22393
21713
  }
@@ -22396,46 +21716,45 @@ function normalizeStopField(input) {
22396
21716
  return null;
22397
21717
  }
22398
21718
  return {
22399
- stop_id: normalizeString9(input.stop_id),
22400
- reason: normalizeString9(input.reason),
21719
+ stop_id: normalizeString7(input.stop_id),
21720
+ reason: normalizeString7(input.reason),
22401
21721
  updated_at: Number(input.updated_at ?? 0)
22402
21722
  };
22403
21723
  }
22404
21724
  function normalizeEntry(input) {
22405
- if (!input || typeof input !== "object" || Number(input.schema_version) !== schemaVersion4) {
21725
+ if (!input || typeof input !== "object" || Number(input.schema_version) !== schemaVersion3) {
22406
21726
  return null;
22407
21727
  }
22408
- const eventID = normalizeString9(input.event_id);
22409
- const sessionID = normalizeString9(input.session_id);
22410
- const msgID = normalizeString9(input.msg_id);
21728
+ const eventID = normalizeString7(input.event_id);
21729
+ const sessionID = normalizeString7(input.session_id);
21730
+ const msgID = normalizeString7(input.msg_id);
22411
21731
  if (!eventID || !sessionID || !msgID) {
22412
21732
  return null;
22413
21733
  }
22414
21734
  return {
22415
- schema_version: schemaVersion4,
21735
+ schema_version: schemaVersion3,
22416
21736
  event_id: eventID,
22417
21737
  session_id: sessionID,
22418
21738
  msg_id: msgID,
22419
- quoted_message_id: normalizeString9(input.quoted_message_id),
22420
- sender_id: normalizeString9(input.sender_id),
22421
- event_type: normalizeString9(input.event_type),
22422
- session_type: normalizeString9(input.session_type),
21739
+ quoted_message_id: normalizeString7(input.quoted_message_id),
21740
+ sender_id: normalizeString7(input.sender_id),
21741
+ event_type: normalizeString7(input.event_type),
21742
+ session_type: normalizeString7(input.session_type),
22423
21743
  content: String(input.content ?? ""),
22424
- owner_id: normalizeString9(input.owner_id),
22425
- agent_id: normalizeString9(input.agent_id),
22426
- msg_type: normalizeString9(input.msg_type),
21744
+ owner_id: normalizeString7(input.owner_id),
21745
+ agent_id: normalizeString7(input.agent_id),
21746
+ msg_type: normalizeString7(input.msg_type),
22427
21747
  message_created_at: Number(input.message_created_at ?? 0),
22428
- mention_user_ids: Array.isArray(input.mention_user_ids) ? input.mention_user_ids.map((item) => normalizeString9(item)).filter((item) => item) : [],
21748
+ mention_user_ids: Array.isArray(input.mention_user_ids) ? input.mention_user_ids.map((item) => normalizeString7(item)).filter((item) => item) : [],
22429
21749
  extra_json: String(input.extra_json ?? ""),
22430
21750
  attachments_json: String(input.attachments_json ?? ""),
22431
- attachment_count: normalizeString9(input.attachment_count),
21751
+ attachment_count: normalizeString7(input.attachment_count),
22432
21752
  biz_card_json: String(input.biz_card_json ?? ""),
22433
21753
  channel_data_json: String(input.channel_data_json ?? ""),
21754
+ context_messages_json: String(input.context_messages_json ?? ""),
22434
21755
  acked: input.acked === true,
22435
21756
  ack_at: Number(input.ack_at ?? 0),
22436
21757
  notification_dispatched_at: Number(input.notification_dispatched_at ?? 0),
22437
- pairing_sent_at: Number(input.pairing_sent_at ?? 0),
22438
- pairing_retry_after: Number(input.pairing_retry_after ?? 0),
22439
21758
  result_deadline_at: Number(input.result_deadline_at ?? 0),
22440
21759
  result_intent: normalizeResultField(input.result_intent),
22441
21760
  completed: normalizeResultField(input.completed),
@@ -22445,14 +21764,14 @@ function normalizeEntry(input) {
22445
21764
  };
22446
21765
  }
22447
21766
  async function saveEventEntry(dir, entry) {
22448
- await mkdir5(dir, { recursive: true });
22449
- const filePath = path6.join(dir, `${entry.event_id}.json`);
22450
- await writeJSONFileAtomic(filePath, { schema_version: schemaVersion4, ...entry });
21767
+ await mkdir4(dir, { recursive: true });
21768
+ const filePath = path5.join(dir, `${entry.event_id}.json`);
21769
+ await writeJSONFileAtomic(filePath, { schema_version: schemaVersion3, ...entry });
22451
21770
  }
22452
21771
  async function loadEventEntries(dir, ttlMs) {
22453
21772
  let names;
22454
21773
  try {
22455
- names = await readdir3(dir);
21774
+ names = await readdir2(dir);
22456
21775
  } catch (error2) {
22457
21776
  if (error2 && typeof error2 === "object" && "code" in error2 && error2.code === "ENOENT") {
22458
21777
  return [];
@@ -22465,7 +21784,7 @@ async function loadEventEntries(dir, ttlMs) {
22465
21784
  if (!name.endsWith(".json")) {
22466
21785
  continue;
22467
21786
  }
22468
- const filePath = path6.join(dir, name);
21787
+ const filePath = path5.join(dir, name);
22469
21788
  const raw = await readJSONFile(filePath, null);
22470
21789
  const entry = normalizeEntry(raw);
22471
21790
  if (!entry) {
@@ -22483,7 +21802,7 @@ async function loadEventEntries(dir, ttlMs) {
22483
21802
 
22484
21803
  // server/logging.js
22485
21804
  import { appendFileSync } from "node:fs";
22486
- function normalizeString10(value) {
21805
+ function normalizeString8(value) {
22487
21806
  return String(value ?? "").trim();
22488
21807
  }
22489
21808
  function formatTraceValue(value) {
@@ -22493,7 +21812,7 @@ function formatTraceValue(value) {
22493
21812
  if (typeof value === "number" || typeof value === "boolean") {
22494
21813
  return String(value);
22495
21814
  }
22496
- const text = normalizeString10(value);
21815
+ const text = normalizeString8(value);
22497
21816
  if (!text) {
22498
21817
  return "";
22499
21818
  }
@@ -22503,7 +21822,7 @@ function formatTraceValue(value) {
22503
21822
  return JSON.stringify(text);
22504
21823
  }
22505
21824
  function listTraceEntries(fields) {
22506
- return Object.entries(fields).filter(([, value]) => value !== void 0 && value !== null && normalizeString10(value) !== "").sort(([leftKey], [rightKey]) => leftKey.localeCompare(rightKey));
21825
+ return Object.entries(fields).filter(([, value]) => value !== void 0 && value !== null && normalizeString8(value) !== "").sort(([leftKey], [rightKey]) => leftKey.localeCompare(rightKey));
22507
21826
  }
22508
21827
  function isTraceLoggingEnabled(env = process.env) {
22509
21828
  return env.GRIX_CLAUDE_TRACE_LOG === "1" || env.GRIX_CLAUDE_E2E_DEBUG === "1";
@@ -22524,7 +21843,7 @@ function createProcessLogger({
22524
21843
  onTrace = null
22525
21844
  } = {}) {
22526
21845
  const verboseDebugEnabled = env.GRIX_CLAUDE_E2E_DEBUG === "1";
22527
- const verboseDebugLogPath = normalizeString10(env.GRIX_CLAUDE_E2E_DEBUG_LOG);
21846
+ const verboseDebugLogPath = normalizeString8(env.GRIX_CLAUDE_E2E_DEBUG_LOG);
22528
21847
  const traceCallback = typeof onTrace === "function" ? onTrace : null;
22529
21848
  function write(prefix, message) {
22530
21849
  if (verboseDebugLogPath) {
@@ -22572,7 +21891,7 @@ function createProcessLogger({
22572
21891
  }
22573
21892
 
22574
21893
  // server/session-activity-dispatcher.js
22575
- function normalizeString11(value) {
21894
+ function normalizeString9(value) {
22576
21895
  return String(value ?? "").trim();
22577
21896
  }
22578
21897
  function buildSessionActivityDispatchKey({
@@ -22581,10 +21900,10 @@ function buildSessionActivityDispatchKey({
22581
21900
  refEventID = "",
22582
21901
  refMsgID = ""
22583
21902
  } = {}) {
22584
- const normalizedSessionID = normalizeString11(sessionID);
22585
- const normalizedKind = normalizeString11(kind) || "composing";
22586
- const normalizedRefEventID = normalizeString11(refEventID);
22587
- const normalizedRefMsgID = normalizeString11(refMsgID);
21903
+ const normalizedSessionID = normalizeString9(sessionID);
21904
+ const normalizedKind = normalizeString9(kind) || "composing";
21905
+ const normalizedRefEventID = normalizeString9(refEventID);
21906
+ const normalizedRefMsgID = normalizeString9(refMsgID);
22588
21907
  if (normalizedRefEventID) {
22589
21908
  return `event:${normalizedRefEventID}`;
22590
21909
  }
@@ -22616,12 +21935,15 @@ function createSessionActivityDispatcher(sendFn) {
22616
21935
  }
22617
21936
 
22618
21937
  // server/hook-signal-store.js
22619
- import { appendFile, mkdir as mkdir6 } from "node:fs/promises";
22620
- import path7 from "node:path";
21938
+ import { appendFile, mkdir as mkdir5, open, rm, stat } from "node:fs/promises";
21939
+ import path6 from "node:path";
22621
21940
  import { randomUUID as randomUUID2 } from "node:crypto";
22622
- var schemaVersion5 = 1;
21941
+ var schemaVersion4 = 1;
22623
21942
  var defaultRecentEventLimit = 20;
22624
- function normalizeString12(value) {
21943
+ var defaultLockTimeoutMs = 5e3;
21944
+ var defaultLockPollMs = 25;
21945
+ var defaultLockStaleMs = 3e4;
21946
+ function normalizeString10(value) {
22625
21947
  return String(value ?? "").trim();
22626
21948
  }
22627
21949
  function normalizeOptionalTimestamp(value) {
@@ -22633,16 +21955,25 @@ function normalizeOptionalTimestamp(value) {
22633
21955
  }
22634
21956
  function pickHookEventDetail(input = {}, hookEventName) {
22635
21957
  if (hookEventName === "SessionStart") {
22636
- return normalizeString12(input.source);
21958
+ return normalizeString10(input.source);
21959
+ }
21960
+ if (hookEventName === "PermissionRequest") {
21961
+ return normalizeString10(input.tool_name);
22637
21962
  }
22638
21963
  if (hookEventName === "PostToolUse" || hookEventName === "PostToolUseFailure") {
22639
- return normalizeString12(input.tool_name);
21964
+ return normalizeString10(input.tool_name);
21965
+ }
21966
+ if (hookEventName === "PermissionDenied") {
21967
+ return normalizeString10(input.tool_name) || normalizeString10(input.reason);
22640
21968
  }
22641
21969
  if (hookEventName === "Elicitation") {
22642
- return normalizeString12(input.mode);
21970
+ return normalizeString10(input.mode);
21971
+ }
21972
+ if (hookEventName === "ElicitationResult") {
21973
+ return normalizeString10(input.action) || normalizeString10(input.mode);
22643
21974
  }
22644
21975
  if (hookEventName === "Notification") {
22645
- return normalizeString12(input.matcher) || normalizeString12(input.notification_type) || normalizeString12(input.type);
21976
+ return normalizeString10(input.matcher) || normalizeString10(input.notification_type) || normalizeString10(input.type);
22646
21977
  }
22647
21978
  return "";
22648
21979
  }
@@ -22650,8 +21981,8 @@ function normalizeHookSignalEvent(input) {
22650
21981
  if (!input || typeof input !== "object") {
22651
21982
  return null;
22652
21983
  }
22653
- const hookEventName = normalizeString12(input.hook_event_name);
22654
- const eventID = normalizeString12(input.event_id);
21984
+ const hookEventName = normalizeString10(input.hook_event_name);
21985
+ const eventID = normalizeString10(input.event_id);
22655
21986
  if (!hookEventName || !eventID) {
22656
21987
  return null;
22657
21988
  }
@@ -22659,42 +21990,58 @@ function normalizeHookSignalEvent(input) {
22659
21990
  event_id: eventID,
22660
21991
  hook_event_name: hookEventName,
22661
21992
  event_at: normalizeOptionalTimestamp(input.event_at),
22662
- detail: normalizeString12(input.detail),
22663
- tool_name: normalizeString12(input.tool_name),
22664
- session_id: normalizeString12(input.session_id),
22665
- cwd: normalizeString12(input.cwd),
22666
- transcript_path: normalizeString12(input.transcript_path)
21993
+ detail: normalizeString10(input.detail)
22667
21994
  };
22668
21995
  }
22669
21996
  function normalizeState(input) {
22670
21997
  const empty = {
22671
- schema_version: schemaVersion5,
21998
+ schema_version: schemaVersion4,
22672
21999
  updated_at: 0,
22673
22000
  latest_event: null,
22674
22001
  recent_events: []
22675
22002
  };
22676
- if (!input || typeof input !== "object" || Number(input.schema_version) !== schemaVersion5) {
22003
+ if (!input || typeof input !== "object" || Number(input.schema_version) !== schemaVersion4) {
22677
22004
  return empty;
22678
22005
  }
22679
22006
  return {
22680
- schema_version: schemaVersion5,
22007
+ schema_version: schemaVersion4,
22681
22008
  updated_at: normalizeOptionalTimestamp(input.updated_at),
22682
22009
  latest_event: normalizeHookSignalEvent(input.latest_event),
22683
22010
  recent_events: Array.isArray(input.recent_events) ? input.recent_events.map((item) => normalizeHookSignalEvent(item)).filter(Boolean) : []
22684
22011
  };
22685
22012
  }
22686
22013
  function resolveHookSignalsPath(pluginID) {
22687
- return path7.join(resolvePluginDataDir(pluginID), "hook-signals.json");
22014
+ return path6.join(resolvePluginDataDir(pluginID), "hook-signals.json");
22688
22015
  }
22689
22016
  function resolveHookSignalsPathFromDataDir(pluginDataDir, pluginID) {
22690
- const normalizedDir = normalizeString12(pluginDataDir);
22017
+ const normalizedDir = normalizeString10(pluginDataDir);
22691
22018
  if (normalizedDir) {
22692
- return path7.join(normalizedDir, "hook-signals.json");
22019
+ return path6.join(normalizedDir, "hook-signals.json");
22693
22020
  }
22694
22021
  return resolveHookSignalsPath(pluginID);
22695
22022
  }
22023
+ function resolveHookSignalsLockPathFromDataDir(pluginDataDir, pluginID) {
22024
+ const normalizedDir = normalizeString10(pluginDataDir);
22025
+ if (normalizedDir) {
22026
+ return path6.join(normalizedDir, "hook-signals.lock");
22027
+ }
22028
+ return path6.join(resolvePluginDataDir(pluginID), "hook-signals.lock");
22029
+ }
22030
+ function sleep(delayMs) {
22031
+ return new Promise((resolve) => {
22032
+ setTimeout(resolve, delayMs);
22033
+ });
22034
+ }
22035
+ async function readLockAgeMs(filePath) {
22036
+ try {
22037
+ const details = await stat(filePath);
22038
+ return Date.now() - Number(details.mtimeMs ?? 0);
22039
+ } catch {
22040
+ return 0;
22041
+ }
22042
+ }
22696
22043
  function buildHookSignalEvent(input, { recordedAt = Date.now() } = {}) {
22697
- const hookEventName = normalizeString12(input?.hook_event_name);
22044
+ const hookEventName = normalizeString10(input?.hook_event_name);
22698
22045
  if (!hookEventName) {
22699
22046
  return null;
22700
22047
  }
@@ -22702,46 +22049,87 @@ function buildHookSignalEvent(input, { recordedAt = Date.now() } = {}) {
22702
22049
  event_id: randomUUID2(),
22703
22050
  hook_event_name: hookEventName,
22704
22051
  event_at: normalizeOptionalTimestamp(recordedAt) || Date.now(),
22705
- detail: pickHookEventDetail(input, hookEventName),
22706
- tool_name: normalizeString12(input?.tool_name),
22707
- session_id: normalizeString12(input?.session_id),
22708
- cwd: normalizeString12(input?.cwd),
22709
- transcript_path: normalizeString12(input?.transcript_path)
22052
+ detail: pickHookEventDetail(input, hookEventName)
22710
22053
  };
22711
22054
  }
22712
22055
  var HookSignalStore = class {
22713
- constructor(filePath = resolveHookSignalsPath(), { logPath = path7.join(path7.dirname(filePath), "hook-signals.log") } = {}) {
22056
+ constructor(filePath = resolveHookSignalsPath(), {
22057
+ logPath = path6.join(path6.dirname(filePath), "hook-signals.log"),
22058
+ lockPath = resolveHookSignalsLockPathFromDataDir(path6.dirname(filePath))
22059
+ } = {}) {
22714
22060
  this.filePath = filePath;
22715
22061
  this.logPath = logPath;
22062
+ this.lockPath = lockPath;
22716
22063
  }
22717
22064
  async readState() {
22718
22065
  const stored = await readJSONFile(this.filePath, null);
22719
22066
  return normalizeState(stored);
22720
22067
  }
22721
22068
  async writeState(state) {
22722
- await mkdir6(path7.dirname(this.filePath), { recursive: true });
22069
+ await mkdir5(path6.dirname(this.filePath), { recursive: true });
22723
22070
  await writeJSONFileAtomic(this.filePath, normalizeState(state));
22724
22071
  }
22072
+ async withStateLock(callback, {
22073
+ timeoutMs = defaultLockTimeoutMs,
22074
+ pollMs = defaultLockPollMs,
22075
+ staleMs = defaultLockStaleMs
22076
+ } = {}) {
22077
+ await mkdir5(path6.dirname(this.lockPath), { recursive: true });
22078
+ const deadlineAt = Date.now() + Math.max(1, Number(timeoutMs) || defaultLockTimeoutMs);
22079
+ while (Date.now() < deadlineAt) {
22080
+ let handle = null;
22081
+ try {
22082
+ handle = await open(this.lockPath, "wx", 384);
22083
+ await handle.writeFile(JSON.stringify({
22084
+ schema_version: schemaVersion4,
22085
+ pid: process.pid,
22086
+ acquired_at: Date.now()
22087
+ }), "utf8");
22088
+ try {
22089
+ return await callback();
22090
+ } finally {
22091
+ await handle.close().catch(() => {
22092
+ });
22093
+ await rm(this.lockPath, { force: true }).catch(() => {
22094
+ });
22095
+ }
22096
+ } catch (error2) {
22097
+ if (!error2 || typeof error2 !== "object" || !("code" in error2) || error2.code !== "EEXIST") {
22098
+ if (handle) {
22099
+ await handle.close().catch(() => {
22100
+ });
22101
+ }
22102
+ throw error2;
22103
+ }
22104
+ const lockAgeMs = await readLockAgeMs(this.lockPath);
22105
+ if (lockAgeMs > staleMs) {
22106
+ await rm(this.lockPath, { force: true }).catch(() => {
22107
+ });
22108
+ continue;
22109
+ }
22110
+ await sleep(Math.min(pollMs, Math.max(1, deadlineAt - Date.now())));
22111
+ }
22112
+ }
22113
+ throw new Error("hook signal store lock timeout");
22114
+ }
22725
22115
  async reset() {
22726
- await this.writeState(normalizeState(null));
22727
- return this.readState();
22116
+ return this.withStateLock(async () => {
22117
+ await this.writeState(normalizeState(null));
22118
+ return this.readState();
22119
+ });
22728
22120
  }
22729
22121
  async appendEventLog(event) {
22730
22122
  const normalized = normalizeHookSignalEvent(event);
22731
22123
  if (!normalized) {
22732
22124
  return false;
22733
22125
  }
22734
- await mkdir6(path7.dirname(this.logPath), { recursive: true });
22126
+ await mkdir5(path6.dirname(this.logPath), { recursive: true });
22735
22127
  await appendFile(this.logPath, `${formatTraceLine({
22736
22128
  stage: "hook_signal_recorded",
22737
22129
  event_id: normalized.event_id,
22738
22130
  hook_event_name: normalized.hook_event_name,
22739
22131
  hook_detail: normalized.detail,
22740
- event_at: normalized.event_at,
22741
- tool_name: normalized.tool_name,
22742
- session_id: normalized.session_id,
22743
- cwd: normalized.cwd,
22744
- transcript_path: normalized.transcript_path
22132
+ event_at: normalized.event_at
22745
22133
  })}
22746
22134
  `, "utf8");
22747
22135
  return true;
@@ -22754,30 +22142,34 @@ var HookSignalStore = class {
22754
22142
  if (!nextEvent) {
22755
22143
  return this.readState();
22756
22144
  }
22757
- const current = await this.readState();
22758
- const nextState = {
22759
- schema_version: schemaVersion5,
22760
- updated_at: nextEvent.event_at,
22761
- latest_event: nextEvent,
22762
- recent_events: [
22145
+ return this.withStateLock(async () => {
22146
+ const current = await this.readState();
22147
+ const recentEvents = [
22763
22148
  ...current.recent_events,
22764
22149
  nextEvent
22765
- ].slice(-Math.max(1, Number(recentEventLimit) || defaultRecentEventLimit))
22766
- };
22767
- await this.writeState(nextState);
22768
- await this.appendEventLog(nextEvent);
22769
- return normalizeState(nextState);
22150
+ ].sort((left, right) => left.event_at - right.event_at).slice(-Math.max(1, Number(recentEventLimit) || defaultRecentEventLimit));
22151
+ const latestEvent = !current.latest_event || nextEvent.event_at >= current.updated_at ? nextEvent : current.latest_event;
22152
+ const nextState = {
22153
+ schema_version: schemaVersion4,
22154
+ updated_at: Math.max(current.updated_at, nextEvent.event_at),
22155
+ latest_event: latestEvent,
22156
+ recent_events: recentEvents
22157
+ };
22158
+ await this.writeState(nextState);
22159
+ await this.appendEventLog(nextEvent);
22160
+ return normalizeState(nextState);
22161
+ });
22770
22162
  }
22771
22163
  };
22772
22164
 
22773
22165
  // server/worker/worker-bridge-client.js
22774
- function normalizeString13(value) {
22166
+ function normalizeString11(value) {
22775
22167
  return String(value ?? "").trim();
22776
22168
  }
22777
22169
  function buildHeaders(token) {
22778
22170
  return {
22779
22171
  "content-type": "application/json",
22780
- authorization: `Bearer ${normalizeString13(token)}`
22172
+ authorization: `Bearer ${normalizeString11(token)}`
22781
22173
  };
22782
22174
  }
22783
22175
  async function parseJSONResponse(response) {
@@ -22793,8 +22185,8 @@ async function parseJSONResponse(response) {
22793
22185
  }
22794
22186
  var WorkerBridgeClient = class {
22795
22187
  constructor({ bridgeURL, token, fetchImpl = globalThis.fetch } = {}) {
22796
- this.bridgeURL = normalizeString13(bridgeURL).replace(/\/+$/u, "");
22797
- this.token = normalizeString13(token);
22188
+ this.bridgeURL = normalizeString11(bridgeURL).replace(/\/+$/u, "");
22189
+ this.token = normalizeString11(token);
22798
22190
  this.fetchImpl = fetchImpl;
22799
22191
  }
22800
22192
  isConfigured() {
@@ -22811,7 +22203,7 @@ var WorkerBridgeClient = class {
22811
22203
  });
22812
22204
  const json2 = await parseJSONResponse(response);
22813
22205
  if (!response.ok) {
22814
- throw new Error(normalizeString13(json2.error) || `bridge request failed ${response.status}`);
22206
+ throw new Error(normalizeString11(json2.error) || `bridge request failed ${response.status}`);
22815
22207
  }
22816
22208
  return json2;
22817
22209
  }
@@ -22845,20 +22237,26 @@ var WorkerBridgeClient = class {
22845
22237
  async setSessionComposing(payload) {
22846
22238
  return this.post("/v1/worker/session-composing", payload);
22847
22239
  }
22240
+ async agentInvoke(payload) {
22241
+ return this.post("/v1/worker/agent-invoke", payload);
22242
+ }
22243
+ async sendLocalActionResult(payload) {
22244
+ return this.post("/v1/worker/local-action-result", payload);
22245
+ }
22848
22246
  };
22849
22247
 
22850
22248
  // server/worker/inbound-bridge-server.js
22851
22249
  import http from "node:http";
22852
22250
  import { randomUUID as randomUUID3 } from "node:crypto";
22853
- function normalizeString14(value) {
22251
+ function normalizeString12(value) {
22854
22252
  return String(value ?? "").trim();
22855
22253
  }
22856
22254
  function parseBearerToken(request) {
22857
- const header = normalizeString14(request.headers.authorization);
22255
+ const header = normalizeString12(request.headers.authorization);
22858
22256
  if (!header.toLowerCase().startsWith("bearer ")) {
22859
22257
  return "";
22860
22258
  }
22861
- return normalizeString14(header.slice("bearer ".length));
22259
+ return normalizeString12(header.slice("bearer ".length));
22862
22260
  }
22863
22261
  async function readJSONBody(request) {
22864
22262
  const chunks = [];
@@ -22880,6 +22278,7 @@ var WorkerInboundBridgeServer = class {
22880
22278
  onDeliverEvent = null,
22881
22279
  onDeliverStop = null,
22882
22280
  onDeliverRevoke = null,
22281
+ onDeliverLocalAction = null,
22883
22282
  onPing = null
22884
22283
  } = {}) {
22885
22284
  this.host = host;
@@ -22888,6 +22287,7 @@ var WorkerInboundBridgeServer = class {
22888
22287
  this.onDeliverEvent = typeof onDeliverEvent === "function" ? onDeliverEvent : null;
22889
22288
  this.onDeliverStop = typeof onDeliverStop === "function" ? onDeliverStop : null;
22890
22289
  this.onDeliverRevoke = typeof onDeliverRevoke === "function" ? onDeliverRevoke : null;
22290
+ this.onDeliverLocalAction = typeof onDeliverLocalAction === "function" ? onDeliverLocalAction : null;
22891
22291
  this.onPing = typeof onPing === "function" ? onPing : null;
22892
22292
  this.server = null;
22893
22293
  this.address = null;
@@ -22927,6 +22327,8 @@ var WorkerInboundBridgeServer = class {
22927
22327
  const current = this.server;
22928
22328
  this.server = null;
22929
22329
  this.address = null;
22330
+ current.closeIdleConnections?.();
22331
+ current.closeAllConnections?.();
22930
22332
  await new Promise((resolve, reject) => {
22931
22333
  current.close((error2) => {
22932
22334
  if (error2) {
@@ -22946,7 +22348,7 @@ var WorkerInboundBridgeServer = class {
22946
22348
  writeJSON(response, 405, { error: "method_not_allowed" });
22947
22349
  return;
22948
22350
  }
22949
- const pathname = normalizeString14(new URL(request.url, "http://localhost").pathname);
22351
+ const pathname = normalizeString12(new URL(request.url, "http://localhost").pathname);
22950
22352
  const payload = await readJSONBody(request);
22951
22353
  if (pathname === "/v1/worker/deliver-event") {
22952
22354
  if (!this.onDeliverEvent) {
@@ -22975,6 +22377,15 @@ var WorkerInboundBridgeServer = class {
22975
22377
  writeJSON(response, 200, result ?? { ok: true });
22976
22378
  return;
22977
22379
  }
22380
+ if (pathname === "/v1/worker/deliver-local-action") {
22381
+ if (!this.onDeliverLocalAction) {
22382
+ writeJSON(response, 200, { ok: true });
22383
+ return;
22384
+ }
22385
+ const result = await this.onDeliverLocalAction(payload);
22386
+ writeJSON(response, 200, result ?? { ok: true });
22387
+ return;
22388
+ }
22978
22389
  if (pathname === "/v1/worker/ping") {
22979
22390
  if (!this.onPing) {
22980
22391
  writeJSON(response, 200, {
@@ -22995,11 +22406,11 @@ var WorkerInboundBridgeServer = class {
22995
22406
  };
22996
22407
 
22997
22408
  // server/worker/bridge-runtime.js
22998
- function normalizeString15(value) {
22409
+ function normalizeString13(value) {
22999
22410
  return String(value ?? "").trim();
23000
22411
  }
23001
22412
  function normalizeOptionalString(value) {
23002
- const normalized = normalizeString15(value);
22413
+ const normalized = normalizeString13(value);
23003
22414
  return normalized || "";
23004
22415
  }
23005
22416
  var DaemonBridgeRuntime = class {
@@ -23008,7 +22419,8 @@ var DaemonBridgeRuntime = class {
23008
22419
  logger,
23009
22420
  onDeliverEvent = null,
23010
22421
  onDeliverStop = null,
23011
- onDeliverRevoke = null
22422
+ onDeliverRevoke = null,
22423
+ onDeliverLocalAction = null
23012
22424
  } = {}) {
23013
22425
  this.env = env;
23014
22426
  this.logger = logger;
@@ -23067,6 +22479,20 @@ var DaemonBridgeRuntime = class {
23067
22479
  this.markMcpActivity();
23068
22480
  return { ok: true };
23069
22481
  },
22482
+ onDeliverLocalAction: async (input) => {
22483
+ this.logger?.trace?.({
22484
+ component: "worker.bridge",
22485
+ stage: "deliver_local_action_received",
22486
+ action_id: input?.payload?.action_id,
22487
+ action_type: input?.payload?.action_type,
22488
+ event_id: input?.payload?.event_id
22489
+ });
22490
+ if (typeof onDeliverLocalAction === "function") {
22491
+ await onDeliverLocalAction(input?.payload ?? {});
22492
+ }
22493
+ this.markMcpActivity();
22494
+ return { ok: true };
22495
+ },
23070
22496
  onPing: async () => {
23071
22497
  const hookSignalState = await this.hookSignalStore.readState();
23072
22498
  return {
@@ -23138,7 +22564,7 @@ var DaemonBridgeRuntime = class {
23138
22564
  const hints = [
23139
22565
  "This worker is managed by grix-claude daemon.",
23140
22566
  "Use the daemon CLI to change ws_url, agent_id, or api_key.",
23141
- "Send open <\u76EE\u5F55> from Grix to let daemon start or recover a Claude session."
22567
+ "Ask Grix to bind a working directory for this chat before sending Claude requests."
23142
22568
  ];
23143
22569
  if (!this.isDaemonBridgeActive()) {
23144
22570
  hints.unshift("This worker must be started by grix-claude daemon.");
@@ -23297,6 +22723,62 @@ var DaemonBridgeRuntime = class {
23297
22723
  this.markMcpActivity();
23298
22724
  return response;
23299
22725
  }
22726
+ async agentInvoke({
22727
+ invokeID = "",
22728
+ action,
22729
+ params = {},
22730
+ timeoutMs = 15e3
22731
+ } = {}) {
22732
+ this.requireDaemonBridge();
22733
+ this.logger?.trace?.({
22734
+ component: "worker.bridge",
22735
+ stage: "agent_invoke_requested",
22736
+ invoke_id: invokeID,
22737
+ action
22738
+ });
22739
+ const response = await this.workerBridgeClient.agentInvoke({
22740
+ ...this.buildWorkerIdentityPayload(),
22741
+ invoke_id: normalizeOptionalString(invokeID),
22742
+ action: normalizeOptionalString(action),
22743
+ params: params && typeof params === "object" && !Array.isArray(params) ? params : {},
22744
+ timeout_ms: Number.isFinite(Number(timeoutMs)) && Number(timeoutMs) > 0 ? Math.floor(Number(timeoutMs)) : 15e3
22745
+ });
22746
+ this.markMcpActivity();
22747
+ return response;
22748
+ }
22749
+ async sendLocalActionResult({
22750
+ actionID,
22751
+ status,
22752
+ result,
22753
+ errorCode = "",
22754
+ errorMsg = ""
22755
+ } = {}) {
22756
+ this.requireDaemonBridge();
22757
+ this.logger?.trace?.({
22758
+ component: "worker.bridge",
22759
+ stage: "local_action_result_requested",
22760
+ action_id: actionID,
22761
+ status,
22762
+ error_code: errorCode
22763
+ });
22764
+ const payload = {
22765
+ ...this.buildWorkerIdentityPayload(),
22766
+ action_id: normalizeOptionalString(actionID),
22767
+ status: normalizeOptionalString(status)
22768
+ };
22769
+ if (arguments[0] && Object.prototype.hasOwnProperty.call(arguments[0], "result")) {
22770
+ payload.result = result;
22771
+ }
22772
+ if (normalizeOptionalString(errorCode)) {
22773
+ payload.error_code = normalizeOptionalString(errorCode);
22774
+ }
22775
+ if (normalizeOptionalString(errorMsg)) {
22776
+ payload.error_msg = normalizeOptionalString(errorMsg);
22777
+ }
22778
+ const response = await this.workerBridgeClient.sendLocalActionResult(payload);
22779
+ this.markMcpActivity();
22780
+ return response;
22781
+ }
23300
22782
  async setSessionComposing({
23301
22783
  sessionID,
23302
22784
  kind = "composing",
@@ -23378,341 +22860,46 @@ var DaemonBridgeRuntime = class {
23378
22860
  aibot_session_id: this.env.GRIX_CLAUDE_AIBOT_SESSION_ID,
23379
22861
  worker_id: this.env.GRIX_CLAUDE_WORKER_ID,
23380
22862
  status: "ready"
23381
- });
23382
- await this.workerBridgeClient.sendStatusUpdate({
23383
- ...this.buildWorkerIdentityPayload(),
23384
- worker_control_url: this.getWorkerControlURL(),
23385
- worker_control_token: this.getWorkerControlToken(),
23386
- status: "ready"
23387
- });
23388
- this.markMcpActivity();
23389
- this.workerReadyReported = true;
23390
- this.logger?.info?.("worker ready after MCP tools handshake");
23391
- })();
23392
- try {
23393
- await this.workerReadyPromise;
23394
- } finally {
23395
- this.workerReadyPromise = null;
23396
- }
23397
- }
23398
- async shutdown() {
23399
- if (!this.daemonModeEnabled || !this.workerBridgeClient.isConfigured()) {
23400
- return;
23401
- }
23402
- try {
23403
- await this.stopControlServer();
23404
- await this.workerBridgeClient.sendStatusUpdate({
23405
- ...this.buildWorkerIdentityPayload(),
23406
- status: "stopped"
23407
- });
23408
- this.markMcpActivity();
23409
- } catch (error2) {
23410
- this.logger?.error?.(`worker bridge stop update failed: ${String(error2)}`);
23411
- }
23412
- }
23413
- };
23414
-
23415
- // server/permission-relay-text.js
23416
- function normalizeString16(value) {
23417
- return String(value ?? "").trim();
23418
- }
23419
- function truncateText(value, maxLength = 240) {
23420
- const normalized = normalizeString16(value);
23421
- if (!normalized) {
23422
- return "";
23423
- }
23424
- if (normalized.length <= maxLength) {
23425
- return normalized;
23426
- }
23427
- return `${normalized.slice(0, maxLength - 3)}...`;
23428
- }
23429
- function buildPermissionRelayCommandText(request) {
23430
- const toolName = normalizeString16(request?.tool_name) || "unknown";
23431
- const description = truncateText(request?.description, 200);
23432
- const inputPreview = truncateText(request?.input_preview, 240);
23433
- const lines = [
23434
- `Tool: ${toolName}`,
23435
- description ? `Description: ${description}` : "",
23436
- inputPreview ? `Input: ${inputPreview}` : ""
23437
- ].filter(Boolean);
23438
- return lines.join("\n");
23439
- }
23440
- function buildPermissionRelayRequestText(request) {
23441
- const requestID = normalizeString16(request?.request_id);
23442
- const commandText = buildPermissionRelayCommandText(request);
23443
- const lines = [
23444
- "Claude needs permission to continue this Grix turn.",
23445
- `Request ID: ${requestID}`,
23446
- commandText,
23447
- `Reply "yes ${requestID}" to allow once, or "no ${requestID}" to deny.`
23448
- ].filter(Boolean);
23449
- return lines.join("\n");
23450
- }
23451
- function buildPermissionRelayVerdictText({ requestID, behavior }) {
23452
- const normalizedRequestID = normalizeString16(requestID);
23453
- const normalizedBehavior = normalizeString16(behavior);
23454
- if (normalizedBehavior === "allow") {
23455
- return `Allow reply for request ${normalizedRequestID} sent to Claude.`;
23456
- }
23457
- return `Deny reply for request ${normalizedRequestID} sent to Claude.`;
23458
- }
23459
-
23460
- // server/elicitation-text.js
23461
- function normalizeString17(value) {
23462
- return String(value ?? "").trim();
23463
- }
23464
- function formatOptions(question) {
23465
- if (!Array.isArray(question?.options) || question.options.length === 0) {
23466
- return [];
23467
- }
23468
- const labels = question.options.map((option) => normalizeString17(option?.label)).filter(Boolean);
23469
- if (labels.length === 0) {
23470
- return [];
23471
- }
23472
- return [
23473
- ` Options: ${labels.join(" | ")}`
23474
- ];
23475
- }
23476
- function buildElicitationFooterText() {
23477
- return "Use the card to answer. Free text is allowed when none of the listed options fit.";
23478
- }
23479
- function buildElicitationRequestText(request) {
23480
- const lines = [
23481
- "Claude needs more input to continue this Grix turn."
23482
- ];
23483
- const message = normalizeString17(request?.message);
23484
- if (message) {
23485
- lines.push(message);
23486
- }
23487
- const questions = Array.isArray(request?.questions) ? request.questions : [];
23488
- questions.forEach((question, index) => {
23489
- const header = normalizeString17(question?.header) || `Question ${index + 1}`;
23490
- const prompt = normalizeString17(question?.question) || "(missing question text)";
23491
- lines.push(`${index + 1}. ${header}`);
23492
- lines.push(` ${prompt}`);
23493
- lines.push(...formatOptions(question));
23494
- if (question?.multiSelect === true) {
23495
- lines.push(" Multiple selections: join values with commas.");
23496
- }
23497
- });
23498
- lines.push(buildElicitationFooterText());
23499
- return lines.join("\n");
23500
- }
23501
- function buildElicitationResponseText({ requestID }) {
23502
- return `Input request ${requestID} answers recorded.`;
23503
- }
23504
-
23505
- // server/message-card-envelope.js
23506
- var currentVersion = 1;
23507
- function buildMessageCardEnvelope(type, payload) {
23508
- return {
23509
- version: currentVersion,
23510
- type,
23511
- payload
23512
- };
23513
- }
23514
-
23515
- // server/claude-card-payload.js
23516
- var claudeHostLabel = "Claude Grix";
23517
- var pairingCommandHint = "/grix:access pair <code>";
23518
- function normalizeString18(value) {
23519
- return String(value ?? "").trim();
23520
- }
23521
- function normalizeQuestionOptions(question) {
23522
- if (!Array.isArray(question?.options)) {
23523
- return [];
23524
- }
23525
- return question.options.map((option) => normalizeString18(option?.label)).filter(Boolean);
23526
- }
23527
- function buildPermissionRelayRequestBizCard(request, { expiresAtMs } = {}) {
23528
- const requestID = normalizeString18(request?.request_id);
23529
- const payload = {
23530
- approval_id: requestID,
23531
- approval_slug: requestID,
23532
- approval_command_id: requestID,
23533
- command: buildPermissionRelayCommandText(request),
23534
- host: claudeHostLabel,
23535
- allowed_decisions: ["allow-once", "deny"],
23536
- decision_commands: {
23537
- "allow-once": `yes ${requestID}`,
23538
- deny: `no ${requestID}`
23539
- }
23540
- };
23541
- if (expiresAtMs > 0) {
23542
- payload.expires_at_ms = expiresAtMs;
23543
- payload.expires_in_seconds = Math.max(0, Math.floor((expiresAtMs - Date.now()) / 1e3));
23544
- }
23545
- return buildMessageCardEnvelope("exec_approval", payload);
23546
- }
23547
- function buildApprovalCommandStatusBizCard({
23548
- summary,
23549
- referenceID = "",
23550
- detailText = "",
23551
- status = "warning"
23552
- }) {
23553
- return buildMessageCardEnvelope("claude_status", {
23554
- category: "approval",
23555
- status,
23556
- summary: normalizeString18(summary),
23557
- detail_text: normalizeString18(detailText),
23558
- reference_id: normalizeString18(referenceID)
23559
- });
23560
- }
23561
- function buildPermissionRelaySubmittedBizCard({
23562
- request,
23563
- behavior,
23564
- resolvedByID = ""
23565
- }) {
23566
- const normalizedBehavior = normalizeString18(behavior) === "allow" ? "allow" : "deny";
23567
- return buildMessageCardEnvelope("exec_status", {
23568
- status: "approval-forwarded",
23569
- summary: buildPermissionRelayVerdictText({
23570
- requestID: request?.request_id,
23571
- behavior: normalizedBehavior
23572
- }),
23573
- detail_text: normalizedBehavior === "allow" ? "Decision: allow-once" : "Decision: deny",
23574
- approval_id: normalizeString18(request?.request_id),
23575
- approval_command_id: normalizeString18(request?.request_id),
23576
- host: claudeHostLabel,
23577
- decision: normalizedBehavior === "allow" ? "allow-once" : "deny",
23578
- resolved_by_id: normalizeString18(resolvedByID),
23579
- command: buildPermissionRelayCommandText(request),
23580
- channel_label: claudeHostLabel
23581
- });
23582
- }
23583
- function buildQuestionRequestBizCard(request) {
23584
- const questions = Array.isArray(request?.questions) ? request.questions : [];
23585
- return buildMessageCardEnvelope("claude_question", {
23586
- request_id: normalizeString18(request?.request_id),
23587
- questions: questions.map((question, index) => ({
23588
- index: index + 1,
23589
- header: normalizeString18(question?.header) || `Question ${index + 1}`,
23590
- prompt: normalizeString18(question?.question) || "(missing question text)",
23591
- options: normalizeQuestionOptions(question),
23592
- multi_select: question?.multiSelect === true
23593
- })),
23594
- answer_command_hint: "",
23595
- footer_text: buildElicitationFooterText()
23596
- });
23597
- }
23598
- function buildQuestionStatusBizCard({
23599
- summary,
23600
- referenceID = "",
23601
- detailText = "",
23602
- status = "warning"
23603
- }) {
23604
- return buildMessageCardEnvelope("claude_status", {
23605
- category: "question",
23606
- status,
23607
- summary: normalizeString18(summary),
23608
- detail_text: normalizeString18(detailText),
23609
- reference_id: normalizeString18(referenceID)
23610
- });
23611
- }
23612
- function buildAccessStatusBizCard({
23613
- summary,
23614
- detailText = "",
23615
- status = "info",
23616
- referenceID = ""
23617
- }) {
23618
- return buildMessageCardEnvelope("claude_status", {
23619
- category: "access",
23620
- status,
23621
- summary: normalizeString18(summary),
23622
- detail_text: normalizeString18(detailText),
23623
- reference_id: normalizeString18(referenceID)
23624
- });
23625
- }
23626
- function buildPairingBizCard(pairingCode) {
23627
- return buildMessageCardEnvelope("claude_pairing", {
23628
- pairing_code: normalizeString18(pairingCode),
23629
- instruction_text: `Ask the Claude Code user to run ${pairingCommandHint} with this code to approve the sender.`,
23630
- command_hint: pairingCommandHint
23631
- });
23632
- }
23633
-
23634
- // server/question-command.js
23635
- var usageText = [
23636
- "usage:",
23637
- "/grix-question <request_id> <answer>",
23638
- "/grix-question <request_id> 1=first answer; 2=second answer"
23639
- ].join("\n");
23640
- function normalizeString19(value) {
23641
- return String(value ?? "").trim();
23642
- }
23643
- function buildInvalidResult(error2) {
23644
- return {
23645
- matched: true,
23646
- ok: false,
23647
- error: `${error2}
23648
- ${usageText}`
23649
- };
23650
- }
23651
- function parseMappedAnswers(payload) {
23652
- const entries = [];
23653
- for (const segment of payload.split(";")) {
23654
- const trimmed = normalizeString19(segment);
23655
- if (!trimmed) {
23656
- continue;
22863
+ });
22864
+ await this.workerBridgeClient.sendStatusUpdate({
22865
+ ...this.buildWorkerIdentityPayload(),
22866
+ worker_control_url: this.getWorkerControlURL(),
22867
+ worker_control_token: this.getWorkerControlToken(),
22868
+ status: "ready"
22869
+ });
22870
+ this.markMcpActivity();
22871
+ this.workerReadyReported = true;
22872
+ this.logger?.info?.("worker ready after MCP tools handshake");
22873
+ })();
22874
+ try {
22875
+ await this.workerReadyPromise;
22876
+ } finally {
22877
+ this.workerReadyPromise = null;
23657
22878
  }
23658
- const separatorIndex = trimmed.indexOf("=");
23659
- if (separatorIndex <= 0) {
23660
- return null;
22879
+ }
22880
+ async shutdown() {
22881
+ if (!this.daemonModeEnabled || !this.workerBridgeClient.isConfigured()) {
22882
+ return;
23661
22883
  }
23662
- const key = normalizeString19(trimmed.slice(0, separatorIndex));
23663
- const value = normalizeString19(trimmed.slice(separatorIndex + 1));
23664
- if (!key || !value) {
23665
- return null;
22884
+ try {
22885
+ await this.stopControlServer();
22886
+ await this.workerBridgeClient.sendStatusUpdate({
22887
+ ...this.buildWorkerIdentityPayload(),
22888
+ status: "stopped"
22889
+ });
22890
+ this.markMcpActivity();
22891
+ } catch (error2) {
22892
+ this.logger?.error?.(`worker bridge stop update failed: ${String(error2)}`);
23666
22893
  }
23667
- entries.push({ key, value });
23668
- }
23669
- return entries.length > 0 ? entries : null;
23670
- }
23671
- function parseQuestionResponseCommand(text) {
23672
- const trimmed = normalizeString19(text);
23673
- if (!trimmed.startsWith("/grix-question")) {
23674
- return {
23675
- matched: false
23676
- };
23677
- }
23678
- const parts = trimmed.split(/\s+/u);
23679
- if (parts.length < 3) {
23680
- return buildInvalidResult("missing request_id or answer");
23681
- }
23682
- const requestID = normalizeString19(parts[1]);
23683
- const answerPayload = normalizeString19(trimmed.slice(trimmed.indexOf(requestID) + requestID.length));
23684
- if (!requestID || !answerPayload) {
23685
- return buildInvalidResult("missing request_id or answer");
23686
22894
  }
23687
- const responseText = normalizeString19(answerPayload);
23688
- if (!responseText) {
23689
- return buildInvalidResult("missing answer");
23690
- }
23691
- const usesIndexedFormat = /^[1-9][0-9]*\s*=/u.test(responseText) || responseText.includes(";");
23692
- const mappedAnswers = usesIndexedFormat ? parseMappedAnswers(responseText) : null;
23693
- if (usesIndexedFormat && !mappedAnswers) {
23694
- return buildInvalidResult("invalid indexed answer format");
23695
- }
23696
- return {
23697
- matched: true,
23698
- ok: true,
23699
- request_id: requestID,
23700
- response: mappedAnswers ? {
23701
- type: "map",
23702
- entries: mappedAnswers
23703
- } : {
23704
- type: "single",
23705
- value: responseText
23706
- }
23707
- };
23708
- }
22895
+ };
23709
22896
 
23710
22897
  // server/elicitation-response.js
23711
- function normalizeString20(value) {
22898
+ function normalizeString14(value) {
23712
22899
  return String(value ?? "").trim();
23713
22900
  }
23714
22901
  function parseBooleanValue(value) {
23715
- const normalized = normalizeString20(value).toLowerCase();
22902
+ const normalized = normalizeString14(value).toLowerCase();
23716
22903
  if (["true", "1", "yes", "y"].includes(normalized)) {
23717
22904
  return true;
23718
22905
  }
@@ -23722,7 +22909,7 @@ function parseBooleanValue(value) {
23722
22909
  throw new Error("boolean answers must be yes or no");
23723
22910
  }
23724
22911
  function parseNumericValue(value, integerOnly) {
23725
- const normalized = normalizeString20(value);
22912
+ const normalized = normalizeString14(value);
23726
22913
  if (!normalized) {
23727
22914
  throw new Error("answer is required");
23728
22915
  }
@@ -23736,7 +22923,7 @@ function parseNumericValue(value, integerOnly) {
23736
22923
  return parsed;
23737
22924
  }
23738
22925
  function matchOption(value, options) {
23739
- const normalized = normalizeString20(value);
22926
+ const normalized = normalizeString14(value);
23740
22927
  if (!normalized) {
23741
22928
  return "";
23742
22929
  }
@@ -23748,7 +22935,7 @@ function matchOption(value, options) {
23748
22935
  return options.find((option) => option.toLowerCase() === folded) || "";
23749
22936
  }
23750
22937
  function parseArrayValue(value, options) {
23751
- const items = normalizeString20(value).split(",").map((item) => normalizeString20(item)).filter(Boolean);
22938
+ const items = normalizeString14(value).split(",").map((item) => normalizeString14(item)).filter(Boolean);
23752
22939
  if (items.length === 0) {
23753
22940
  throw new Error("at least one value is required");
23754
22941
  }
@@ -23764,7 +22951,7 @@ function parseArrayValue(value, options) {
23764
22951
  });
23765
22952
  }
23766
22953
  function parseFieldValue(field, value) {
23767
- const normalizedValue = normalizeString20(value);
22954
+ const normalizedValue = normalizeString14(value);
23768
22955
  if (!normalizedValue) {
23769
22956
  throw new Error(`${field.title} is required`);
23770
22957
  }
@@ -23791,14 +22978,35 @@ function parseFieldValue(field, value) {
23791
22978
  return normalizedValue;
23792
22979
  }
23793
22980
  }
22981
+ function isURLModeRequest(request) {
22982
+ return normalizeString14(request?.request_payload?.mode).toLowerCase() === "url";
22983
+ }
23794
22984
  function buildElicitationHookOutput(request, response) {
22985
+ if (response?.type === "action") {
22986
+ const action = normalizeString14(response.value).toLowerCase();
22987
+ if (!isURLModeRequest(request)) {
22988
+ throw new Error("elicitation action replies are only supported for url mode");
22989
+ }
22990
+ if (!["accept", "cancel"].includes(action)) {
22991
+ throw new Error("invalid elicitation action");
22992
+ }
22993
+ if (action === "accept") {
22994
+ return {
22995
+ action,
22996
+ content: {}
22997
+ };
22998
+ }
22999
+ return {
23000
+ action
23001
+ };
23002
+ }
23795
23003
  const fields = normalizeElicitationFields(request?.fields);
23796
23004
  if (fields.length === 0) {
23797
23005
  throw new Error("elicitation request has no supported fields");
23798
23006
  }
23799
- if (response?.type === "single") {
23007
+ if (response?.type === "text") {
23800
23008
  if (fields.length !== 1) {
23801
- throw new Error("multiple answers require 1=answer; 2=answer format");
23009
+ throw new Error("multiple answers require map resolution");
23802
23010
  }
23803
23011
  return {
23804
23012
  action: "accept",
@@ -23810,30 +23018,27 @@ function buildElicitationHookOutput(request, response) {
23810
23018
  if (response?.type !== "map" || !Array.isArray(response.entries)) {
23811
23019
  throw new Error("invalid elicitation response");
23812
23020
  }
23021
+ const fieldByKey = new Map(fields.map((field) => [field.key, field]));
23813
23022
  const content = {};
23814
- const answeredIndexes = /* @__PURE__ */ new Set();
23023
+ const answeredKeys = /* @__PURE__ */ new Set();
23815
23024
  for (const entry of response.entries) {
23816
- const key = normalizeString20(entry?.key);
23817
- const value = normalizeString20(entry?.value);
23818
- if (!/^[1-9][0-9]*$/u.test(key)) {
23819
- throw new Error("elicitation answers must use numeric indexes like 1=answer");
23025
+ const key = normalizeString14(entry?.key);
23026
+ const value = normalizeString14(entry?.value);
23027
+ const field = fieldByKey.get(key);
23028
+ if (!field) {
23029
+ throw new Error(`field ${key} is not part of this elicitation request`);
23820
23030
  }
23821
23031
  if (!value) {
23822
23032
  throw new Error(`field ${key} answer is required`);
23823
23033
  }
23824
- const index = Number(key);
23825
- if (index < 1 || index > fields.length) {
23826
- throw new Error(`field index ${index} is out of range`);
23827
- }
23828
- if (answeredIndexes.has(index)) {
23829
- throw new Error(`field index ${index} answered more than once`);
23034
+ if (answeredKeys.has(key)) {
23035
+ throw new Error(`field ${key} answered more than once`);
23830
23036
  }
23831
- const field = fields[index - 1];
23832
- answeredIndexes.add(index);
23037
+ answeredKeys.add(key);
23833
23038
  content[field.key] = parseFieldValue(field, value);
23834
23039
  }
23835
- if (answeredIndexes.size !== fields.length) {
23836
- throw new Error(`expected ${fields.length} answers but received ${answeredIndexes.size}`);
23040
+ if (answeredKeys.size !== fields.length) {
23041
+ throw new Error(`expected ${fields.length} answers but received ${answeredKeys.size}`);
23837
23042
  }
23838
23043
  return {
23839
23044
  action: "accept",
@@ -23841,119 +23046,550 @@ function buildElicitationHookOutput(request, response) {
23841
23046
  };
23842
23047
  }
23843
23048
 
23049
+ // server/protocol-contract.js
23050
+ var contractVersion = 1;
23051
+ var protocolVersion = "aibot-agent-api-v1";
23052
+ var adapterHint = "claude/base";
23053
+ var declaredCapabilities = Object.freeze([
23054
+ "session_route",
23055
+ "local_action_v1",
23056
+ "agent_invoke"
23057
+ ]);
23058
+ var localActionTypes = Object.freeze({
23059
+ sessionControl: "claude_session_control",
23060
+ interactionReply: "claude_interaction_reply"
23061
+ });
23062
+ var declaredLocalActions = Object.freeze([
23063
+ localActionTypes.sessionControl,
23064
+ localActionTypes.interactionReply
23065
+ ]);
23066
+ var agentInvokeActions = Object.freeze({
23067
+ interactionRequestCreate: "claude_interaction_request_create",
23068
+ accessControl: "claude_access_control"
23069
+ });
23070
+ var sessionControlVerbs = Object.freeze({
23071
+ open: "open",
23072
+ status: "status",
23073
+ where: "where",
23074
+ stop: "stop"
23075
+ });
23076
+ var interactionKinds = Object.freeze({
23077
+ permission: "permission",
23078
+ elicitation: "elicitation"
23079
+ });
23080
+ var interactionResolutionTypes = Object.freeze({
23081
+ decision: "decision",
23082
+ action: "action",
23083
+ text: "text",
23084
+ map: "map"
23085
+ });
23086
+ var accessControlVerbs = Object.freeze({
23087
+ statusRead: "status_read",
23088
+ pairApprove: "pair_approve",
23089
+ pairDeny: "pair_deny",
23090
+ senderAllow: "sender_allow",
23091
+ senderRemove: "sender_remove",
23092
+ policySet: "policy_set"
23093
+ });
23094
+ var resultDomains = Object.freeze({
23095
+ sessionControl: "session_control",
23096
+ interactionReply: "interaction_reply"
23097
+ });
23098
+ var bindingWorkerStatuses = Object.freeze({
23099
+ starting: "starting",
23100
+ connected: "connected",
23101
+ ready: "ready",
23102
+ stopped: "stopped",
23103
+ failed: "failed"
23104
+ });
23105
+ var sessionControlOutcomes = Object.freeze({
23106
+ opened: "opened",
23107
+ alreadyBound: "already_bound",
23108
+ status: "status",
23109
+ where: "where",
23110
+ stopped: "stopped"
23111
+ });
23112
+ var localActionResultStatuses = Object.freeze({
23113
+ ok: "ok",
23114
+ failed: "failed"
23115
+ });
23116
+ var localActionErrorCodes = Object.freeze({
23117
+ unsupportedLocalAction: "unsupported_local_action",
23118
+ localActionRouteMissing: "local_action_route_missing"
23119
+ });
23120
+ var sessionControlErrorCodes = Object.freeze({
23121
+ cwdRequired: "session_cwd_required",
23122
+ invalidCwd: "session_invalid_cwd",
23123
+ bindingMissing: "session_binding_missing",
23124
+ rebindForbidden: "session_rebind_forbidden",
23125
+ verbInvalid: "session_verb_invalid",
23126
+ runtimeError: "session_runtime_error"
23127
+ });
23128
+ var interactionReplyOutcomes = Object.freeze({
23129
+ resolved: "resolved"
23130
+ });
23131
+ var interactionReplyErrorCodes = Object.freeze({
23132
+ requestIDRequired: "interaction_request_id_required",
23133
+ requestNotFound: "interaction_request_not_found",
23134
+ requestNotPending: "interaction_request_not_pending",
23135
+ resolutionInvalid: "interaction_resolution_invalid",
23136
+ forwardFailed: "interaction_forward_failed"
23137
+ });
23138
+ var eventResultStatuses = Object.freeze({
23139
+ responded: "responded",
23140
+ failed: "failed",
23141
+ canceled: "canceled"
23142
+ });
23143
+ var publicProtocolContract = Object.freeze({
23144
+ contract_version: contractVersion,
23145
+ protocol_version: protocolVersion,
23146
+ adapter_hint: adapterHint,
23147
+ capabilities: [...declaredCapabilities],
23148
+ local_actions: [...declaredLocalActions],
23149
+ agent_invoke_actions: [
23150
+ agentInvokeActions.interactionRequestCreate,
23151
+ agentInvokeActions.accessControl
23152
+ ],
23153
+ local_action_types: [
23154
+ localActionTypes.sessionControl,
23155
+ localActionTypes.interactionReply
23156
+ ],
23157
+ session_control_verbs: [
23158
+ sessionControlVerbs.open,
23159
+ sessionControlVerbs.status,
23160
+ sessionControlVerbs.where,
23161
+ sessionControlVerbs.stop
23162
+ ],
23163
+ interaction_kinds: [
23164
+ interactionKinds.permission,
23165
+ interactionKinds.elicitation
23166
+ ],
23167
+ interaction_resolution_types: [
23168
+ interactionResolutionTypes.decision,
23169
+ interactionResolutionTypes.action,
23170
+ interactionResolutionTypes.text,
23171
+ interactionResolutionTypes.map
23172
+ ],
23173
+ access_control_verbs: [
23174
+ accessControlVerbs.statusRead,
23175
+ accessControlVerbs.pairApprove,
23176
+ accessControlVerbs.pairDeny,
23177
+ accessControlVerbs.senderAllow,
23178
+ accessControlVerbs.senderRemove,
23179
+ accessControlVerbs.policySet
23180
+ ],
23181
+ result_domains: [
23182
+ resultDomains.sessionControl,
23183
+ resultDomains.interactionReply
23184
+ ],
23185
+ binding_worker_statuses: [
23186
+ bindingWorkerStatuses.starting,
23187
+ bindingWorkerStatuses.connected,
23188
+ bindingWorkerStatuses.ready,
23189
+ bindingWorkerStatuses.stopped,
23190
+ bindingWorkerStatuses.failed
23191
+ ],
23192
+ session_control_outcomes: [
23193
+ sessionControlOutcomes.opened,
23194
+ sessionControlOutcomes.alreadyBound,
23195
+ sessionControlOutcomes.status,
23196
+ sessionControlOutcomes.where,
23197
+ sessionControlOutcomes.stopped
23198
+ ],
23199
+ local_action_result_statuses: [
23200
+ localActionResultStatuses.ok,
23201
+ localActionResultStatuses.failed
23202
+ ],
23203
+ local_action_error_codes: [
23204
+ localActionErrorCodes.unsupportedLocalAction,
23205
+ localActionErrorCodes.localActionRouteMissing
23206
+ ],
23207
+ session_control_error_codes: [
23208
+ sessionControlErrorCodes.cwdRequired,
23209
+ sessionControlErrorCodes.invalidCwd,
23210
+ sessionControlErrorCodes.bindingMissing,
23211
+ sessionControlErrorCodes.rebindForbidden,
23212
+ sessionControlErrorCodes.verbInvalid,
23213
+ sessionControlErrorCodes.runtimeError
23214
+ ],
23215
+ interaction_reply_outcomes: [
23216
+ interactionReplyOutcomes.resolved
23217
+ ],
23218
+ interaction_reply_error_codes: [
23219
+ interactionReplyErrorCodes.requestIDRequired,
23220
+ interactionReplyErrorCodes.requestNotFound,
23221
+ interactionReplyErrorCodes.requestNotPending,
23222
+ interactionReplyErrorCodes.resolutionInvalid,
23223
+ interactionReplyErrorCodes.forwardFailed
23224
+ ],
23225
+ event_result_statuses: [
23226
+ eventResultStatuses.responded,
23227
+ eventResultStatuses.failed,
23228
+ eventResultStatuses.canceled
23229
+ ]
23230
+ });
23231
+
23232
+ // server/worker/interaction-protocol.js
23233
+ function normalizeString15(value) {
23234
+ return String(value ?? "").trim();
23235
+ }
23236
+ function requireNonEmpty(value, errorMessage) {
23237
+ const normalized = normalizeString15(value);
23238
+ if (!normalized) {
23239
+ throw new Error(errorMessage);
23240
+ }
23241
+ return normalized;
23242
+ }
23243
+ function isRecord2(value) {
23244
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
23245
+ }
23246
+ function normalizeMapEntries(value) {
23247
+ if (!Array.isArray(value)) {
23248
+ return [];
23249
+ }
23250
+ return value.map((entry) => {
23251
+ if (!isRecord2(entry)) {
23252
+ return null;
23253
+ }
23254
+ const key = normalizeString15(entry.key);
23255
+ const mappedValue = normalizeString15(entry.value);
23256
+ if (!key || !mappedValue) {
23257
+ return null;
23258
+ }
23259
+ return {
23260
+ key,
23261
+ value: mappedValue
23262
+ };
23263
+ }).filter(Boolean);
23264
+ }
23265
+ function normalizeInteractionField(field) {
23266
+ const normalized = {
23267
+ key: normalizeString15(field?.key),
23268
+ title: normalizeString15(field?.title),
23269
+ prompt: normalizeString15(field?.prompt),
23270
+ kind: normalizeString15(field?.kind),
23271
+ required: field?.required !== false,
23272
+ multi_select: field?.multi_select === true,
23273
+ options: Array.isArray(field?.options) ? field.options.map((option) => normalizeString15(option)).filter(Boolean) : []
23274
+ };
23275
+ if (!normalized.key || !normalized.title || !normalized.prompt || !normalized.kind) {
23276
+ return null;
23277
+ }
23278
+ return normalized;
23279
+ }
23280
+ function normalizeInteractionFields(fields) {
23281
+ return normalizeElicitationFields(fields).map((field) => normalizeInteractionField(field)).filter(Boolean);
23282
+ }
23283
+ function buildInteractionRequestInvokeParams({
23284
+ kind,
23285
+ requestID,
23286
+ sessionID,
23287
+ messageID,
23288
+ payload
23289
+ } = {}) {
23290
+ const normalizedKind = requireNonEmpty(kind, "interaction kind is required");
23291
+ if (!Object.values(interactionKinds).includes(normalizedKind)) {
23292
+ throw new Error(`interaction kind ${normalizedKind} is invalid`);
23293
+ }
23294
+ return {
23295
+ kind: normalizedKind,
23296
+ request_id: requireNonEmpty(requestID, "interaction request id is required"),
23297
+ session_id: requireNonEmpty(sessionID, "interaction session id is required"),
23298
+ message_id: requireNonEmpty(messageID, "interaction message id is required"),
23299
+ payload: isRecord2(payload) ? payload : {}
23300
+ };
23301
+ }
23302
+ function buildPermissionInteractionPayload(input = {}) {
23303
+ return {
23304
+ tool_name: normalizeString15(input.tool_name),
23305
+ description: normalizeString15(input.description),
23306
+ input_preview: normalizeString15(input.input_preview)
23307
+ };
23308
+ }
23309
+ function buildElicitationInteractionPayload(request = {}) {
23310
+ const rawPayload = isRecord2(request?.request_payload) ? request.request_payload : {};
23311
+ const fields = normalizeInteractionFields(request?.fields);
23312
+ const mode = normalizeString15(rawPayload.mode).toLowerCase() === "url" ? "url" : "form";
23313
+ const payload = {
23314
+ mode,
23315
+ prompt: normalizeString15(rawPayload.message || rawPayload.prompt)
23316
+ };
23317
+ if (mode === "url") {
23318
+ payload.url = normalizeString15(rawPayload.url);
23319
+ return payload;
23320
+ }
23321
+ payload.fields = fields;
23322
+ return payload;
23323
+ }
23324
+ function buildInteractionReplyResult({
23325
+ kind,
23326
+ requestID,
23327
+ outcome = interactionReplyOutcomes.resolved
23328
+ } = {}) {
23329
+ return {
23330
+ domain: resultDomains.interactionReply,
23331
+ kind: normalizeString15(kind),
23332
+ request_id: normalizeString15(requestID),
23333
+ outcome: normalizeString15(outcome) || interactionReplyOutcomes.resolved
23334
+ };
23335
+ }
23336
+ function parseInteractionReplyAction(rawPayload) {
23337
+ const actionType = normalizeString15(rawPayload?.action_type).toLowerCase();
23338
+ if (actionType !== localActionTypes.interactionReply) {
23339
+ return {
23340
+ matched: false,
23341
+ kind: "",
23342
+ requestID: "",
23343
+ resolution: null,
23344
+ errorCode: "",
23345
+ errorMsg: ""
23346
+ };
23347
+ }
23348
+ const params = isRecord2(rawPayload?.params) ? rawPayload.params : {};
23349
+ const kind = normalizeString15(params.kind).toLowerCase();
23350
+ if (!Object.values(interactionKinds).includes(kind)) {
23351
+ return {
23352
+ matched: true,
23353
+ kind: "",
23354
+ requestID: normalizeString15(params.request_id),
23355
+ resolution: null,
23356
+ errorCode: interactionReplyErrorCodes.resolutionInvalid,
23357
+ errorMsg: "interaction kind is invalid"
23358
+ };
23359
+ }
23360
+ const requestID = normalizeString15(params.request_id);
23361
+ const resolutionInput = isRecord2(params.resolution) ? params.resolution : null;
23362
+ if (!resolutionInput) {
23363
+ return {
23364
+ matched: true,
23365
+ kind,
23366
+ requestID,
23367
+ resolution: null,
23368
+ errorCode: interactionReplyErrorCodes.resolutionInvalid,
23369
+ errorMsg: "interaction resolution is required"
23370
+ };
23371
+ }
23372
+ const type = normalizeString15(resolutionInput.type).toLowerCase();
23373
+ if (type === interactionResolutionTypes.decision) {
23374
+ const value = normalizeString15(resolutionInput.value).toLowerCase();
23375
+ if (value === "allow" || value === "deny") {
23376
+ return {
23377
+ matched: true,
23378
+ kind,
23379
+ requestID,
23380
+ resolution: {
23381
+ type: interactionResolutionTypes.decision,
23382
+ value
23383
+ },
23384
+ errorCode: "",
23385
+ errorMsg: ""
23386
+ };
23387
+ }
23388
+ return {
23389
+ matched: true,
23390
+ kind,
23391
+ requestID,
23392
+ resolution: null,
23393
+ errorCode: interactionReplyErrorCodes.resolutionInvalid,
23394
+ errorMsg: `interaction decision ${value} is invalid`
23395
+ };
23396
+ }
23397
+ if (type === interactionResolutionTypes.action) {
23398
+ const value = normalizeString15(resolutionInput.value).toLowerCase();
23399
+ if (["accept", "decline", "cancel"].includes(value)) {
23400
+ return {
23401
+ matched: true,
23402
+ kind,
23403
+ requestID,
23404
+ resolution: {
23405
+ type: interactionResolutionTypes.action,
23406
+ value
23407
+ },
23408
+ errorCode: "",
23409
+ errorMsg: ""
23410
+ };
23411
+ }
23412
+ return {
23413
+ matched: true,
23414
+ kind,
23415
+ requestID,
23416
+ resolution: null,
23417
+ errorCode: interactionReplyErrorCodes.resolutionInvalid,
23418
+ errorMsg: `interaction action ${value} is invalid`
23419
+ };
23420
+ }
23421
+ if (type === interactionResolutionTypes.text) {
23422
+ const value = normalizeString15(resolutionInput.value);
23423
+ if (value) {
23424
+ return {
23425
+ matched: true,
23426
+ kind,
23427
+ requestID,
23428
+ resolution: {
23429
+ type: interactionResolutionTypes.text,
23430
+ value
23431
+ },
23432
+ errorCode: "",
23433
+ errorMsg: ""
23434
+ };
23435
+ }
23436
+ return {
23437
+ matched: true,
23438
+ kind,
23439
+ requestID,
23440
+ resolution: null,
23441
+ errorCode: interactionReplyErrorCodes.resolutionInvalid,
23442
+ errorMsg: "interaction text resolution is required"
23443
+ };
23444
+ }
23445
+ if (type === interactionResolutionTypes.map) {
23446
+ const entries = normalizeMapEntries(resolutionInput.entries);
23447
+ if (entries.length > 0) {
23448
+ return {
23449
+ matched: true,
23450
+ kind,
23451
+ requestID,
23452
+ resolution: {
23453
+ type: interactionResolutionTypes.map,
23454
+ entries
23455
+ },
23456
+ errorCode: "",
23457
+ errorMsg: ""
23458
+ };
23459
+ }
23460
+ return {
23461
+ matched: true,
23462
+ kind,
23463
+ requestID,
23464
+ resolution: null,
23465
+ errorCode: interactionReplyErrorCodes.resolutionInvalid,
23466
+ errorMsg: "interaction map resolution is required"
23467
+ };
23468
+ }
23469
+ return {
23470
+ matched: true,
23471
+ kind,
23472
+ requestID,
23473
+ resolution: null,
23474
+ errorCode: interactionReplyErrorCodes.resolutionInvalid,
23475
+ errorMsg: `interaction resolution type ${type} is invalid`
23476
+ };
23477
+ }
23478
+
23844
23479
  // server/worker/elicitation-relay-service.js
23845
23480
  var elicitationPollIntervalMs = 1500;
23481
+ function normalizeString16(value) {
23482
+ return String(value ?? "").trim();
23483
+ }
23846
23484
  function normalizeOptionalString2(value) {
23847
- const normalized = String(value ?? "").trim();
23485
+ const normalized = normalizeString16(value);
23848
23486
  return normalized || "";
23849
23487
  }
23850
23488
  var WorkerElicitationRelayService = class {
23851
23489
  constructor({
23852
23490
  elicitationStore,
23853
23491
  bridge,
23854
- finalizeEvent,
23855
23492
  logger
23856
23493
  }) {
23857
23494
  this.elicitationStore = elicitationStore;
23858
23495
  this.bridge = bridge;
23859
- this.finalizeEvent = typeof finalizeEvent === "function" ? finalizeEvent : async () => false;
23860
23496
  this.logger = logger;
23861
23497
  this.pollTimer = null;
23862
23498
  }
23863
- async finalizeEventSafely(eventID, result, context) {
23864
- return this.finalizeEvent(eventID, result, context);
23865
- }
23866
- async sendCommandReply(event, text, bizCard = null) {
23867
- await this.bridge.sendText({
23868
- eventID: event.event_id,
23869
- sessionID: event.session_id,
23870
- text,
23871
- quotedMessageID: event.msg_id,
23872
- clientMsgID: `elicitation_reply_${event.event_id}`,
23873
- extra: {
23874
- reply_source: "claude_channel_question",
23875
- ...bizCard ? { biz_card: bizCard } : {}
23876
- }
23499
+ async dispatchRequest(request) {
23500
+ const requestID = normalizeString16(request?.request_id);
23501
+ const invokeResult = await this.bridge.agentInvoke({
23502
+ invokeID: `elicitation_request_${requestID}`,
23503
+ action: agentInvokeActions.interactionRequestCreate,
23504
+ params: buildInteractionRequestInvokeParams({
23505
+ kind: interactionKinds.elicitation,
23506
+ requestID,
23507
+ sessionID: normalizeString16(request?.channel_context?.chat_id),
23508
+ messageID: normalizeString16(request?.channel_context?.message_id),
23509
+ payload: buildElicitationInteractionPayload(request)
23510
+ })
23511
+ });
23512
+ if (Number(invokeResult?.code ?? 0) !== 0) {
23513
+ throw new Error(normalizeOptionalString2(invokeResult?.msg) || "agent invoke failed");
23514
+ }
23515
+ await this.elicitationStore.markDispatched(request.request_id, {
23516
+ dispatchedAt: Date.now()
23517
+ });
23518
+ this.logger.trace?.({
23519
+ component: "worker.elicitation",
23520
+ stage: "elicitation_request_dispatched",
23521
+ request_id: request.request_id,
23522
+ chat_id: request.channel_context.chat_id,
23523
+ msg_id: request.channel_context.message_id
23877
23524
  });
23878
23525
  }
23879
- async handleCommandEvent(event) {
23880
- const parsed = parseQuestionResponseCommand(event.content);
23881
- if (!parsed.matched) {
23526
+ async handleLocalAction(rawPayload) {
23527
+ const resolved = parseInteractionReplyAction(rawPayload);
23528
+ if (!resolved.matched) {
23882
23529
  return {
23883
23530
  handled: false,
23884
23531
  kind: ""
23885
23532
  };
23886
23533
  }
23887
- if (!parsed.ok) {
23888
- await this.sendCommandReply(event, parsed.error, buildQuestionStatusBizCard({
23889
- summary: parsed.error
23890
- }));
23891
- await this.finalizeEventSafely(event.event_id, {
23892
- status: "responded",
23893
- code: "elicitation_command_invalid",
23894
- msg: "elicitation command invalid"
23895
- }, "elicitation invalid terminal result failed");
23534
+ if (resolved.kind && resolved.kind !== "elicitation") {
23535
+ return {
23536
+ handled: false,
23537
+ kind: ""
23538
+ };
23539
+ }
23540
+ const actionID = normalizeString16(rawPayload?.action_id);
23541
+ if (!actionID) {
23542
+ this.logger.error(`elicitation local_action missing action_id: ${JSON.stringify(rawPayload)}`);
23896
23543
  return {
23897
23544
  handled: true,
23898
23545
  kind: "elicitation"
23899
23546
  };
23900
23547
  }
23901
- const request = await this.elicitationStore.getRequest(parsed.request_id);
23902
- if (!request) {
23903
- await this.sendCommandReply(
23904
- event,
23905
- `Input request ${parsed.request_id} was not found.`,
23906
- buildQuestionStatusBizCard({
23907
- summary: `Input request ${parsed.request_id} was not found.`,
23908
- referenceID: parsed.request_id,
23909
- status: "warning"
23910
- })
23911
- );
23912
- await this.finalizeEventSafely(event.event_id, {
23913
- status: "responded",
23914
- code: "elicitation_request_not_found",
23915
- msg: "elicitation request not found"
23916
- }, "elicitation not-found terminal result failed");
23548
+ const requestID = normalizeString16(resolved.requestID);
23549
+ if (!requestID) {
23550
+ await this.bridge.sendLocalActionResult({
23551
+ actionID,
23552
+ status: localActionResultStatuses.failed,
23553
+ errorCode: interactionReplyErrorCodes.requestIDRequired,
23554
+ errorMsg: "interaction request id is required"
23555
+ });
23917
23556
  return {
23918
23557
  handled: true,
23919
23558
  kind: "elicitation"
23920
23559
  };
23921
23560
  }
23922
- if (request.channel_context.chat_id !== event.session_id) {
23923
- await this.sendCommandReply(
23924
- event,
23925
- "This input request belongs to a different Grix chat.",
23926
- buildQuestionStatusBizCard({
23927
- summary: "This input request belongs to a different Grix chat.",
23928
- referenceID: parsed.request_id,
23929
- status: "warning"
23930
- })
23931
- );
23932
- await this.finalizeEventSafely(event.event_id, {
23933
- status: "responded",
23934
- code: "elicitation_chat_mismatch",
23935
- msg: "elicitation request belongs to a different chat"
23936
- }, "elicitation chat-mismatch terminal result failed");
23561
+ const request = await this.elicitationStore.getRequest(requestID);
23562
+ if (!request) {
23563
+ await this.bridge.sendLocalActionResult({
23564
+ actionID,
23565
+ status: localActionResultStatuses.failed,
23566
+ errorCode: interactionReplyErrorCodes.requestNotFound,
23567
+ errorMsg: `interaction request ${requestID} was not found`
23568
+ });
23937
23569
  return {
23938
23570
  handled: true,
23939
23571
  kind: "elicitation"
23940
23572
  };
23941
23573
  }
23942
23574
  if (request.status !== "pending") {
23943
- await this.sendCommandReply(
23944
- event,
23945
- `Input request ${parsed.request_id} is ${request.status}.`,
23946
- buildQuestionStatusBizCard({
23947
- summary: `Input request ${parsed.request_id} is ${request.status}.`,
23948
- referenceID: parsed.request_id,
23949
- status: "warning"
23950
- })
23951
- );
23952
- await this.finalizeEventSafely(event.event_id, {
23953
- status: "responded",
23954
- code: "elicitation_request_not_pending",
23955
- msg: `elicitation request is ${request.status}`
23956
- }, "elicitation not-pending terminal result failed");
23575
+ await this.bridge.sendLocalActionResult({
23576
+ actionID,
23577
+ status: localActionResultStatuses.failed,
23578
+ errorCode: interactionReplyErrorCodes.requestNotPending,
23579
+ errorMsg: `interaction request ${requestID} is ${request.status}`
23580
+ });
23581
+ return {
23582
+ handled: true,
23583
+ kind: "elicitation"
23584
+ };
23585
+ }
23586
+ if (!resolved.resolution) {
23587
+ await this.bridge.sendLocalActionResult({
23588
+ actionID,
23589
+ status: localActionResultStatuses.failed,
23590
+ errorCode: resolved.errorCode || interactionReplyErrorCodes.resolutionInvalid,
23591
+ errorMsg: resolved.errorMsg || "interaction resolution is invalid"
23592
+ });
23957
23593
  return {
23958
23594
  handled: true,
23959
23595
  kind: "elicitation"
@@ -23961,87 +23597,42 @@ var WorkerElicitationRelayService = class {
23961
23597
  }
23962
23598
  let hookOutput;
23963
23599
  try {
23964
- hookOutput = buildElicitationHookOutput(request, parsed.response);
23600
+ hookOutput = buildElicitationHookOutput(request, resolved.resolution);
23965
23601
  } catch (error2) {
23966
- const message = String(error2 instanceof Error ? error2.message : error2);
23967
- await this.sendCommandReply(event, message, buildQuestionStatusBizCard({
23968
- summary: message,
23969
- referenceID: parsed.request_id,
23970
- status: "warning"
23971
- }));
23972
- await this.finalizeEventSafely(event.event_id, {
23973
- status: "responded",
23974
- code: "elicitation_answer_invalid",
23975
- msg: message
23976
- }, "elicitation invalid-answer terminal result failed");
23602
+ await this.bridge.sendLocalActionResult({
23603
+ actionID,
23604
+ status: localActionResultStatuses.failed,
23605
+ errorCode: interactionReplyErrorCodes.resolutionInvalid,
23606
+ errorMsg: String(error2 instanceof Error ? error2.message : error2)
23607
+ });
23977
23608
  return {
23978
23609
  handled: true,
23979
23610
  kind: "elicitation"
23980
23611
  };
23981
23612
  }
23982
- await this.elicitationStore.resolveRequest(parsed.request_id, {
23613
+ await this.elicitationStore.resolveRequest(requestID, {
23983
23614
  action: hookOutput.action,
23984
- content: hookOutput.content,
23985
- resolvedBy: {
23986
- sender_id: event.sender_id,
23987
- session_id: event.session_id,
23988
- event_id: event.event_id,
23989
- msg_id: event.msg_id
23990
- }
23615
+ content: hookOutput.content
23616
+ });
23617
+ await this.bridge.sendLocalActionResult({
23618
+ actionID,
23619
+ status: localActionResultStatuses.ok,
23620
+ result: buildInteractionReplyResult({
23621
+ kind: interactionKinds.elicitation,
23622
+ requestID
23623
+ })
23991
23624
  });
23992
23625
  this.logger.trace?.({
23993
23626
  component: "worker.elicitation",
23994
- stage: "elicitation_command_resolved",
23995
- event_id: event.event_id,
23996
- session_id: event.session_id,
23997
- sender_id: event.sender_id,
23998
- request_id: parsed.request_id,
23999
- origin_event_id: request.channel_context.event_id,
23627
+ stage: "elicitation_local_action_resolved",
23628
+ request_id: requestID,
24000
23629
  chat_id: request.channel_context.chat_id
24001
23630
  });
24002
- const responseText = buildElicitationResponseText({
24003
- requestID: parsed.request_id
24004
- });
24005
- await this.sendCommandReply(event, responseText, buildQuestionStatusBizCard({
24006
- summary: responseText,
24007
- referenceID: parsed.request_id,
24008
- status: "success"
24009
- }));
24010
- await this.finalizeEventSafely(event.event_id, {
24011
- status: "responded",
24012
- code: "elicitation_recorded",
24013
- msg: "elicitation answers recorded"
24014
- }, "elicitation recorded terminal result failed");
24015
23631
  return {
24016
23632
  handled: true,
24017
23633
  kind: "elicitation"
24018
23634
  };
24019
23635
  }
24020
- async dispatchRequest(request) {
24021
- const ack = await this.bridge.sendText({
24022
- sessionID: request.channel_context.chat_id,
24023
- text: buildElicitationRequestText(request),
24024
- quotedMessageID: request.channel_context.message_id || request.channel_context.msg_id,
24025
- clientMsgID: `elicitation_request_${request.request_id}`,
24026
- extra: {
24027
- reply_source: "claude_elicitation",
24028
- elicitation_request_id: request.request_id,
24029
- biz_card: buildQuestionRequestBizCard(request)
24030
- }
24031
- });
24032
- await this.elicitationStore.markDispatched(request.request_id, {
24033
- dispatchedAt: Date.now(),
24034
- promptMessageID: normalizeOptionalString2(ack.msg_id)
24035
- });
24036
- this.logger.trace?.({
24037
- component: "worker.elicitation",
24038
- stage: "elicitation_request_dispatched",
24039
- request_id: request.request_id,
24040
- event_id: request.channel_context.event_id,
24041
- chat_id: request.channel_context.chat_id,
24042
- msg_id: request.channel_context.message_id || request.channel_context.msg_id
24043
- });
24044
- }
24045
23636
  async pumpRequests() {
24046
23637
  const pending = await this.elicitationStore.listPendingDispatches();
24047
23638
  for (const request of pending) {
@@ -24074,17 +23665,17 @@ var WorkerElicitationRelayService = class {
24074
23665
  };
24075
23666
 
24076
23667
  // server/inbound-event-meta.js
24077
- function normalizeString21(value) {
23668
+ function normalizeString17(value) {
24078
23669
  return String(value ?? "").trim();
24079
23670
  }
24080
23671
  function normalizeOptionalString3(value) {
24081
- return normalizeString21(value) || "";
23672
+ return normalizeString17(value) || "";
24082
23673
  }
24083
23674
  function normalizeStringArray3(value) {
24084
23675
  if (!Array.isArray(value)) {
24085
23676
  return [];
24086
23677
  }
24087
- return value.map((item) => normalizeString21(item)).filter((item) => item);
23678
+ return value.map((item) => normalizeString17(item)).filter((item) => item);
24088
23679
  }
24089
23680
  function normalizeJSONObject(value) {
24090
23681
  if (!value) {
@@ -24138,7 +23729,44 @@ function normalizeAttachmentRecord(value) {
24138
23729
  if (!attachment.attachment_type && !attachment.media_url && !attachment.file_name && !attachment.content_type) {
24139
23730
  return null;
24140
23731
  }
24141
- return attachment;
23732
+ return attachment;
23733
+ }
23734
+ function normalizeContextMessageRecord(value) {
23735
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
23736
+ return null;
23737
+ }
23738
+ const normalized = {
23739
+ msg_id: normalizeOptionalString3(value.msg_id),
23740
+ sender_id: normalizeOptionalString3(value.sender_id),
23741
+ sender_type: Number(value.sender_type ?? 0),
23742
+ msg_type: Number(value.msg_type ?? 0),
23743
+ content: String(value.content ?? ""),
23744
+ quoted_message_id: normalizeOptionalString3(value.quoted_message_id),
23745
+ mention_user_ids: normalizeStringArray3(value.mention_user_ids),
23746
+ created_at: Number(value.created_at ?? 0)
23747
+ };
23748
+ if (!normalized.msg_id) {
23749
+ return null;
23750
+ }
23751
+ return normalized;
23752
+ }
23753
+ function normalizeContextMessageArray(value) {
23754
+ if (Array.isArray(value)) {
23755
+ return value.map((item) => normalizeContextMessageRecord(item)).filter(Boolean);
23756
+ }
23757
+ if (typeof value === "string") {
23758
+ const trimmed = value.trim();
23759
+ if (!trimmed) {
23760
+ return [];
23761
+ }
23762
+ try {
23763
+ const parsed = JSON.parse(trimmed);
23764
+ return normalizeContextMessageArray(parsed);
23765
+ } catch {
23766
+ return [];
23767
+ }
23768
+ }
23769
+ return [];
24142
23770
  }
24143
23771
  function deriveAttachments(payload, extra) {
24144
23772
  const explicit = normalizeJSONArray(payload.attachments).map((item) => normalizeAttachmentRecord(item)).filter(Boolean);
@@ -24169,19 +23797,25 @@ function stringifyJSON(value) {
24169
23797
  }
24170
23798
  return JSON.stringify(value);
24171
23799
  }
23800
+ function deriveContextMessages(payload) {
23801
+ const currentMsgID = normalizeString17(payload.msg_id);
23802
+ return normalizeContextMessageArray(payload.context_messages).filter((entry) => entry.msg_id !== currentMsgID);
23803
+ }
24172
23804
  function normalizeInboundEventPayload(rawPayload) {
24173
23805
  const extra = normalizeJSONObject(rawPayload.extra);
24174
23806
  const attachments = deriveAttachments(rawPayload, extra);
24175
23807
  const bizCard = normalizeJSONObject(rawPayload.biz_card) ?? normalizeJSONObject(extra?.biz_card);
24176
23808
  const channelData = normalizeJSONObject(rawPayload.channel_data) ?? normalizeJSONObject(extra?.channel_data);
23809
+ const contextMessages = deriveContextMessages(rawPayload);
24177
23810
  return {
24178
- event_id: normalizeString21(rawPayload.event_id),
24179
- event_type: normalizeString21(rawPayload.event_type),
24180
- session_id: normalizeString21(rawPayload.session_id),
23811
+ event_id: normalizeString17(rawPayload.event_id),
23812
+ event_type: normalizeString17(rawPayload.event_type),
23813
+ mirror_mode: normalizeString17(rawPayload.mirror_mode),
23814
+ session_id: normalizeString17(rawPayload.session_id),
24181
23815
  session_type: normalizeOptionalString3(rawPayload.session_type),
24182
- msg_id: normalizeString21(rawPayload.msg_id),
23816
+ msg_id: normalizeString17(rawPayload.msg_id),
24183
23817
  quoted_message_id: normalizeOptionalString3(rawPayload.quoted_message_id),
24184
- sender_id: normalizeString21(rawPayload.sender_id),
23818
+ sender_id: normalizeString17(rawPayload.sender_id),
24185
23819
  owner_id: normalizeOptionalString3(rawPayload.owner_id),
24186
23820
  agent_id: normalizeOptionalString3(rawPayload.agent_id),
24187
23821
  msg_type: normalizeOptionalString3(rawPayload.msg_type),
@@ -24192,12 +23826,13 @@ function normalizeInboundEventPayload(rawPayload) {
24192
23826
  attachments_json: stringifyJSON(attachments),
24193
23827
  attachment_count: attachments.length > 0 ? String(attachments.length) : "",
24194
23828
  biz_card_json: stringifyJSON(bizCard),
24195
- channel_data_json: stringifyJSON(channelData)
23829
+ channel_data_json: stringifyJSON(channelData),
23830
+ context_messages_json: stringifyJSON(contextMessages)
24196
23831
  };
24197
23832
  }
24198
23833
 
24199
23834
  // server/channel-notification.js
24200
- function normalizeString22(value) {
23835
+ function normalizeString18(value) {
24201
23836
  return String(value ?? "").trim();
24202
23837
  }
24203
23838
  function compactRecord(source) {
@@ -24209,8 +23844,8 @@ function compactRecord(source) {
24209
23844
  }
24210
23845
  continue;
24211
23846
  }
24212
- if (normalizeString22(value)) {
24213
- target[key] = normalizeString22(value);
23847
+ if (normalizeString18(value)) {
23848
+ target[key] = normalizeString18(value);
24214
23849
  }
24215
23850
  }
24216
23851
  return target;
@@ -24223,11 +23858,11 @@ function formatTimestamp(value) {
24223
23858
  return new Date(numeric).toISOString();
24224
23859
  }
24225
23860
  function resolveEventType(event) {
24226
- const explicit = normalizeString22(event?.event_type);
23861
+ const explicit = normalizeString18(event?.event_type);
24227
23862
  if (explicit) {
24228
23863
  return explicit;
24229
23864
  }
24230
- return normalizeString22(event?.session_type) === "2" ? "group_message" : "user_chat";
23865
+ return normalizeString18(event?.session_type) === "2" ? "group_message" : "user_chat";
24231
23866
  }
24232
23867
  function buildChannelNotificationParams(event) {
24233
23868
  return {
@@ -24236,6 +23871,7 @@ function buildChannelNotificationParams(event) {
24236
23871
  chat_id: event?.session_id,
24237
23872
  event_id: event?.event_id,
24238
23873
  event_type: resolveEventType(event),
23874
+ mirror_mode: event?.mirror_mode,
24239
23875
  message_id: event?.msg_id,
24240
23876
  sender_id: event?.sender_id,
24241
23877
  user: event?.sender_id,
@@ -24252,7 +23888,8 @@ function buildChannelNotificationParams(event) {
24252
23888
  attachments_json: event?.attachments_json,
24253
23889
  attachment_count: event?.attachment_count,
24254
23890
  biz_card_json: event?.biz_card_json,
24255
- channel_data_json: event?.channel_data_json
23891
+ channel_data_json: event?.channel_data_json,
23892
+ context_messages_json: event?.context_messages_json
24256
23893
  })
24257
23894
  };
24258
23895
  }
@@ -24261,7 +23898,7 @@ function buildChannelNotificationParams(event) {
24261
23898
  var probeChannelNamespace = "grix-claude";
24262
23899
  var probeKind = "ping_pong";
24263
23900
  var defaultWorkerPingProbeTimeoutMs = 15 * 1e3;
24264
- function normalizeString23(value) {
23901
+ function normalizeString19(value) {
24265
23902
  return String(value ?? "").trim();
24266
23903
  }
24267
23904
  function parseJSONObject(value) {
@@ -24291,17 +23928,17 @@ function extractWorkerProbeMeta(payload) {
24291
23928
  if (!probe || typeof probe !== "object") {
24292
23929
  return null;
24293
23930
  }
24294
- if (normalizeString23(probe.kind) !== probeKind) {
23931
+ if (normalizeString19(probe.kind) !== probeKind) {
24295
23932
  return null;
24296
23933
  }
24297
- const probeID = normalizeString23(probe.probe_id);
23934
+ const probeID = normalizeString19(probe.probe_id);
24298
23935
  if (!probeID) {
24299
23936
  return null;
24300
23937
  }
24301
23938
  return {
24302
23939
  probeID,
24303
23940
  kind: probeKind,
24304
- expectedReply: normalizeString23(probe.expected_reply) || "pong"
23941
+ expectedReply: normalizeString19(probe.expected_reply) || "pong"
24305
23942
  };
24306
23943
  }
24307
23944
 
@@ -24351,11 +23988,11 @@ var defaultResultTimeoutMs = 90 * 1e3;
24351
23988
  var defaultResultTimeoutRetryMs = 10 * 1e3;
24352
23989
  var defaultComposingHeartbeatMs = 10 * 1e3;
24353
23990
  var defaultComposingTTLMS = 30 * 1e3;
24354
- function normalizeString24(value) {
23991
+ function normalizeString20(value) {
24355
23992
  return String(value ?? "").trim();
24356
23993
  }
24357
23994
  function normalizeOptionalString4(value) {
24358
- const normalized = normalizeString24(value);
23995
+ const normalized = normalizeString20(value);
24359
23996
  return normalized || "";
24360
23997
  }
24361
23998
  function normalizePositiveInt3(value, fallbackValue) {
@@ -24424,10 +24061,10 @@ var WorkerEventLifecycleManager = class {
24424
24061
  this.logger.trace?.({
24425
24062
  component: "worker.interaction",
24426
24063
  stage: "local_result_timeout_armed",
24427
- event_id: normalizeString24(eventID),
24064
+ event_id: normalizeString20(eventID),
24428
24065
  timeout_ms: normalizedTimeoutMs,
24429
24066
  deadline_at: deadlineAt,
24430
- timeout_kind: normalizeString24(timeoutKind) || "default"
24067
+ timeout_kind: normalizeString20(timeoutKind) || "default"
24431
24068
  });
24432
24069
  return deadlineAt;
24433
24070
  }
@@ -24436,7 +24073,7 @@ var WorkerEventLifecycleManager = class {
24436
24073
  this.eventState.clearResultDeadline(eventID);
24437
24074
  }
24438
24075
  clearComposingKeepaliveTimer(eventID) {
24439
- const normalizedEventID = normalizeString24(eventID);
24076
+ const normalizedEventID = normalizeString20(eventID);
24440
24077
  if (!normalizedEventID) {
24441
24078
  return;
24442
24079
  }
@@ -24451,7 +24088,7 @@ var WorkerEventLifecycleManager = class {
24451
24088
  return event?.suppress_composing === true;
24452
24089
  }
24453
24090
  sendComposingState(event, active) {
24454
- if (!event || !normalizeString24(event.session_id) || this.shouldSuppressComposing(event)) {
24091
+ if (!event || !normalizeString20(event.session_id) || this.shouldSuppressComposing(event)) {
24455
24092
  return false;
24456
24093
  }
24457
24094
  void this.bridge.setSessionComposing({
@@ -24468,7 +24105,7 @@ var WorkerEventLifecycleManager = class {
24468
24105
  return true;
24469
24106
  }
24470
24107
  startComposingKeepalive(eventID) {
24471
- const normalizedEventID = normalizeString24(eventID);
24108
+ const normalizedEventID = normalizeString20(eventID);
24472
24109
  if (!normalizedEventID) {
24473
24110
  return;
24474
24111
  }
@@ -24486,7 +24123,7 @@ var WorkerEventLifecycleManager = class {
24486
24123
  this.composingKeepaliveTimers.set(normalizedEventID, timer);
24487
24124
  }
24488
24125
  stopComposingKeepalive(eventID, fallbackEvent = null) {
24489
- const normalizedEventID = normalizeString24(eventID);
24126
+ const normalizedEventID = normalizeString20(eventID);
24490
24127
  if (!normalizedEventID) {
24491
24128
  return;
24492
24129
  }
@@ -24502,7 +24139,7 @@ var WorkerEventLifecycleManager = class {
24502
24139
  }
24503
24140
  buildTerminalResult({ status, code = "", msg = "" }) {
24504
24141
  return {
24505
- status: normalizeString24(status),
24142
+ status: normalizeString20(status),
24506
24143
  code: normalizeOptionalString4(code),
24507
24144
  msg: normalizeOptionalString4(msg),
24508
24145
  updated_at: Date.now()
@@ -24577,21 +24214,18 @@ var WorkerEventLifecycleManager = class {
24577
24214
  };
24578
24215
 
24579
24216
  // server/worker/interaction-service.js
24580
- function normalizeString25(value) {
24217
+ function normalizeString21(value) {
24581
24218
  return String(value ?? "").trim();
24582
24219
  }
24583
24220
  function normalizeOptionalString5(value) {
24584
- const normalized = normalizeString25(value);
24221
+ const normalized = normalizeString21(value);
24585
24222
  return normalized || "";
24586
24223
  }
24587
24224
  function shouldRestoreLocalResultTimeout(entry) {
24588
24225
  return Boolean(entry?.result_intent);
24589
24226
  }
24590
- function isGroupSession(payload) {
24591
- return Number(payload.session_type ?? 0) === 2 || normalizeString25(payload.event_type).startsWith("group_");
24592
- }
24593
24227
  async function persistEventChannelContext(sessionContextStore, event) {
24594
- if (!normalizeString25(event.session_id) || !normalizeString25(event.msg_id)) {
24228
+ if (!normalizeString21(event.session_id) || !normalizeString21(event.msg_id)) {
24595
24229
  return;
24596
24230
  }
24597
24231
  await sessionContextStore.put({
@@ -24599,13 +24233,8 @@ async function persistEventChannelContext(sessionContextStore, event) {
24599
24233
  transcript_path: `event:${event.event_id}`,
24600
24234
  updated_at: Date.now(),
24601
24235
  context: {
24602
- raw_tag: "",
24603
24236
  chat_id: event.session_id,
24604
- event_id: event.event_id,
24605
- message_id: event.msg_id,
24606
- sender_id: event.sender_id,
24607
- user_id: event.sender_id,
24608
- msg_id: event.msg_id
24237
+ message_id: event.msg_id
24609
24238
  }
24610
24239
  });
24611
24240
  }
@@ -24613,7 +24242,6 @@ var WorkerInteractionService = class {
24613
24242
  constructor({
24614
24243
  eventState,
24615
24244
  sessionContextStore,
24616
- accessStore,
24617
24245
  eventStatesDir,
24618
24246
  mcp,
24619
24247
  bridge,
@@ -24627,7 +24255,6 @@ var WorkerInteractionService = class {
24627
24255
  }) {
24628
24256
  this.eventState = eventState;
24629
24257
  this.sessionContextStore = sessionContextStore;
24630
- this.accessStore = accessStore;
24631
24258
  this.eventStatesDir = eventStatesDir;
24632
24259
  this.mcp = mcp;
24633
24260
  this.bridge = bridge;
@@ -24673,51 +24300,6 @@ var WorkerInteractionService = class {
24673
24300
  });
24674
24301
  this.logger.debug(`event notification-dispatched event=${event.event_id}`);
24675
24302
  }
24676
- async sendAccessStatusMessage(sessionID, text, clientMsgID, bizCard = null) {
24677
- await this.bridge.sendText({
24678
- sessionID,
24679
- text,
24680
- clientMsgID,
24681
- extra: {
24682
- reply_source: "claude_channel_access",
24683
- ...bizCard ? { biz_card: bizCard } : {}
24684
- }
24685
- });
24686
- }
24687
- async sendPairingMessage(event) {
24688
- const pair = await this.accessStore.issuePairingCode({
24689
- senderID: event.sender_id,
24690
- sessionID: event.session_id
24691
- });
24692
- const text = [
24693
- "This sender is not allowlisted for the Claude Grix channel.",
24694
- `Pairing code: ${pair.code}`,
24695
- "Ask the Claude Code user to run /grix:access pair <code> with this code to approve the sender."
24696
- ].join("\n");
24697
- await this.bridge.sendText({
24698
- sessionID: event.session_id,
24699
- text,
24700
- clientMsgID: `pair_${event.event_id}`,
24701
- extra: {
24702
- biz_card: buildPairingBizCard(pair.code)
24703
- }
24704
- });
24705
- this.eventState.markPairingSent(event.event_id, {
24706
- sentAt: Date.now()
24707
- });
24708
- this.logger.trace?.({
24709
- component: "worker.interaction",
24710
- stage: "pairing_sent",
24711
- event_id: event.event_id,
24712
- session_id: event.session_id,
24713
- sender_id: event.sender_id
24714
- });
24715
- await this.finalizeEventSafely(event.event_id, {
24716
- status: "responded",
24717
- code: "pairing_required",
24718
- msg: "pairing code sent"
24719
- }, "pairing terminal result failed");
24720
- }
24721
24303
  async handleDuplicateEvent(event) {
24722
24304
  this.logger.info(`duplicate inbound event handled event=${event.event_id}`);
24723
24305
  if (!event.acked) {
@@ -24738,14 +24320,6 @@ var WorkerInteractionService = class {
24738
24320
  return;
24739
24321
  }
24740
24322
  this.eventLifecycle.startComposingKeepalive(event.event_id);
24741
- if (event.pairing_sent_at && this.eventState.canResendPairing(event.event_id)) {
24742
- try {
24743
- await this.sendPairingMessage(event);
24744
- } catch (error2) {
24745
- this.logger.error(`duplicate pairing resend failed event=${event.event_id}: ${String(error2)}`);
24746
- }
24747
- return;
24748
- }
24749
24323
  if (event.notification_dispatched_at) {
24750
24324
  return;
24751
24325
  }
@@ -24819,10 +24393,6 @@ var WorkerInteractionService = class {
24819
24393
  } catch (error2) {
24820
24394
  this.logger.error(`session-context store failed event=${event.event_id}: ${String(error2)}`);
24821
24395
  }
24822
- let policy = this.accessStore.getPolicy();
24823
- const senderAllowlisted = this.accessStore.isSenderAllowlisted(event.sender_id);
24824
- let senderAllowed = this.accessStore.isSenderAllowed(event.sender_id);
24825
- const hasAllowedSenders = this.accessStore.hasAllowedSenders();
24826
24396
  event = this.markEventAccepted(event);
24827
24397
  this.logger.trace?.({
24828
24398
  component: "worker.interaction",
@@ -24830,142 +24400,8 @@ var WorkerInteractionService = class {
24830
24400
  event_id: event.event_id,
24831
24401
  session_id: event.session_id,
24832
24402
  msg_id: event.msg_id,
24833
- sender_id: event.sender_id,
24834
- policy
24403
+ sender_id: event.sender_id
24835
24404
  });
24836
- if (policy === "disabled") {
24837
- this.logger.debug(`event disabled-policy event=${event.event_id}`);
24838
- try {
24839
- await this.sendAccessStatusMessage(
24840
- event.session_id,
24841
- "Claude Grix access is currently disabled for this channel.",
24842
- `access_disabled_${event.event_id}`,
24843
- buildAccessStatusBizCard({
24844
- summary: "Claude Grix access is currently disabled for this channel.",
24845
- status: "warning",
24846
- referenceID: event.event_id
24847
- })
24848
- );
24849
- } catch (error2) {
24850
- this.logger.error(`disabled-policy notice failed event=${event.event_id}: ${String(error2)}`);
24851
- }
24852
- await this.finalizeEventSafely(event.event_id, {
24853
- status: "canceled",
24854
- code: "policy_disabled",
24855
- msg: "channel policy disabled"
24856
- }, "policy-disabled result send failed");
24857
- return;
24858
- }
24859
- if (!senderAllowlisted && !isGroupSession(rawPayload) && (policy === "open" || policy === "allowlist" && !hasAllowedSenders)) {
24860
- try {
24861
- const bootstrap = await this.accessStore.bootstrapFirstSender(event.sender_id, {
24862
- lockPolicyToAllowlist: policy === "open"
24863
- });
24864
- if (bootstrap.bootstrapped) {
24865
- policy = bootstrap.policy;
24866
- senderAllowed = true;
24867
- this.logger.trace?.({
24868
- component: "worker.interaction",
24869
- stage: "sender_bootstrapped",
24870
- event_id: event.event_id,
24871
- session_id: event.session_id,
24872
- sender_id: event.sender_id,
24873
- policy
24874
- });
24875
- this.logger.info(`bootstrapped sender allowlist sender=${event.sender_id} policy=${policy}`);
24876
- this.logger.debug(`event first-sender-bootstrap event=${event.event_id} sender=${event.sender_id}`);
24877
- }
24878
- } catch (error2) {
24879
- try {
24880
- await this.sendAccessStatusMessage(
24881
- event.session_id,
24882
- `Claude Grix could not auto-authorize this sender: ${String(error2)}.`,
24883
- `sender_bootstrap_failed_${event.event_id}`,
24884
- buildAccessStatusBizCard({
24885
- summary: `Claude Grix could not auto-authorize this sender: ${String(error2)}.`,
24886
- status: "error",
24887
- referenceID: event.event_id
24888
- })
24889
- );
24890
- } catch (notifyError) {
24891
- this.logger.error(`sender bootstrap notice failed event=${event.event_id}: ${String(notifyError)}`);
24892
- }
24893
- await this.finalizeEventSafely(event.event_id, {
24894
- status: "failed",
24895
- code: "sender_bootstrap_failed",
24896
- msg: String(error2)
24897
- }, "sender bootstrap result send failed");
24898
- return;
24899
- }
24900
- }
24901
- if (!senderAllowed) {
24902
- this.logger.trace?.({
24903
- component: "worker.interaction",
24904
- stage: "sender_blocked",
24905
- event_id: event.event_id,
24906
- session_id: event.session_id,
24907
- sender_id: event.sender_id,
24908
- policy,
24909
- group_session: isGroupSession(rawPayload)
24910
- }, { level: "error" });
24911
- this.logger.debug(`event sender-blocked event=${event.event_id} sender=${event.sender_id} group=${isGroupSession(rawPayload)}`);
24912
- if (isGroupSession(rawPayload)) {
24913
- try {
24914
- await this.sendAccessStatusMessage(
24915
- event.session_id,
24916
- "This sender is not allowlisted for the Claude Grix channel.",
24917
- `sender_blocked_${event.event_id}`,
24918
- buildAccessStatusBizCard({
24919
- summary: "This sender is not allowlisted for the Claude Grix channel.",
24920
- status: "warning",
24921
- referenceID: event.event_id
24922
- })
24923
- );
24924
- } catch (error2) {
24925
- this.logger.error(`group allowlist notice failed event=${event.event_id}: ${String(error2)}`);
24926
- }
24927
- await this.finalizeEventSafely(event.event_id, {
24928
- status: "canceled",
24929
- code: "sender_not_allowlisted",
24930
- msg: "sender not allowlisted"
24931
- }, "group allowlist result send failed");
24932
- return;
24933
- }
24934
- try {
24935
- await this.sendPairingMessage(event);
24936
- } catch (error2) {
24937
- await this.finalizeEventSafely(event.event_id, {
24938
- status: "failed",
24939
- code: "pairing_send_failed",
24940
- msg: String(error2)
24941
- }, "pairing failure result send failed");
24942
- }
24943
- return;
24944
- }
24945
- const permissionRelayCommand = await this.permissionRelayService?.handleCommandEvent?.(event);
24946
- if (permissionRelayCommand?.handled) {
24947
- this.logger.trace?.({
24948
- component: "worker.interaction",
24949
- stage: "permission_relay_command_handled",
24950
- event_id: event.event_id,
24951
- session_id: event.session_id,
24952
- kind: permissionRelayCommand.kind
24953
- });
24954
- this.logger.debug(`event ${permissionRelayCommand.kind}-command event=${event.event_id}`);
24955
- return;
24956
- }
24957
- const elicitationCommand = await this.elicitationRelayService?.handleCommandEvent?.(event);
24958
- if (elicitationCommand?.handled) {
24959
- this.logger.trace?.({
24960
- component: "worker.interaction",
24961
- stage: "elicitation_command_handled",
24962
- event_id: event.event_id,
24963
- session_id: event.session_id,
24964
- kind: elicitationCommand.kind
24965
- });
24966
- this.logger.debug(`event ${elicitationCommand.kind}-command event=${event.event_id}`);
24967
- return;
24968
- }
24969
24405
  try {
24970
24406
  await this.dispatchChannelNotification(event);
24971
24407
  this.logger.trace?.({
@@ -24985,7 +24421,7 @@ var WorkerInteractionService = class {
24985
24421
  }
24986
24422
  }
24987
24423
  async handleStopEvent(rawPayload) {
24988
- const eventID = normalizeString25(rawPayload.event_id);
24424
+ const eventID = normalizeString21(rawPayload.event_id);
24989
24425
  if (!eventID) {
24990
24426
  return;
24991
24427
  }
@@ -24996,7 +24432,7 @@ var WorkerInteractionService = class {
24996
24432
  session_id: rawPayload.session_id,
24997
24433
  stop_id: rawPayload.stop_id
24998
24434
  });
24999
- const stopID = normalizeString25(rawPayload.stop_id);
24435
+ const stopID = normalizeString21(rawPayload.stop_id);
25000
24436
  const existing = this.eventState.get(eventID);
25001
24437
  try {
25002
24438
  await this.bridge.sendEventStopAck({
@@ -25053,7 +24489,7 @@ var WorkerInteractionService = class {
25053
24489
  }, "stop terminal result failed");
25054
24490
  }
25055
24491
  async handleRevokeEvent(rawPayload) {
25056
- const eventID = normalizeString25(rawPayload.event_id);
24492
+ const eventID = normalizeString21(rawPayload.event_id);
25057
24493
  if (!eventID) {
25058
24494
  return;
25059
24495
  }
@@ -25072,6 +24508,35 @@ var WorkerInteractionService = class {
25072
24508
  });
25073
24509
  this.logger.info(`event_revoke acked event=${eventID}`);
25074
24510
  }
24511
+ async handleLocalAction(rawPayload) {
24512
+ const actionID = normalizeString21(rawPayload?.action_id);
24513
+ if (!actionID) {
24514
+ this.logger.error(`local_action missing action_id: ${JSON.stringify(rawPayload)}`);
24515
+ return;
24516
+ }
24517
+ const actionType = normalizeString21(rawPayload?.action_type);
24518
+ this.logger.trace?.({
24519
+ component: "worker.interaction",
24520
+ stage: "local_action_received",
24521
+ action_id: actionID,
24522
+ action_type: actionType,
24523
+ event_id: rawPayload?.event_id
24524
+ });
24525
+ const permissionAction = await this.permissionRelayService?.handleLocalAction?.(rawPayload);
24526
+ if (permissionAction?.handled) {
24527
+ return;
24528
+ }
24529
+ const elicitationAction = await this.elicitationRelayService?.handleLocalAction?.(rawPayload);
24530
+ if (elicitationAction?.handled) {
24531
+ return;
24532
+ }
24533
+ await this.bridge.sendLocalActionResult({
24534
+ actionID,
24535
+ status: localActionResultStatuses.failed,
24536
+ errorCode: localActionErrorCodes.unsupportedLocalAction,
24537
+ errorMsg: actionType ? `unsupported local action: ${actionType}` : "local action type is required"
24538
+ });
24539
+ }
25075
24540
  async restoreEventState() {
25076
24541
  const entries = await loadEventEntries(this.eventStatesDir, {
25077
24542
  completedTTLms: this.eventState.ttlMs,
@@ -25113,7 +24578,6 @@ var WorkerInteractionService = class {
25113
24578
 
25114
24579
  // server/worker/permission-relay-service.js
25115
24580
  var pendingRequestTtlMs = 24 * 60 * 60 * 1e3;
25116
- var permissionReplyPattern = /^\s*(y|yes|n|no)\s+([a-km-z]{5})\s*$/iu;
25117
24581
  var PermissionRequestNotificationSchema = NotificationSchema.extend({
25118
24582
  method: literal("notifications/claude/channel/permission_request"),
25119
24583
  params: object2({
@@ -25123,57 +24587,28 @@ var PermissionRequestNotificationSchema = NotificationSchema.extend({
25123
24587
  input_preview: string2()
25124
24588
  })
25125
24589
  });
25126
- function normalizeString26(value) {
24590
+ function normalizeString22(value) {
25127
24591
  return String(value ?? "").trim();
25128
24592
  }
25129
24593
  function normalizeOptionalString6(value) {
25130
- const normalized = normalizeString26(value);
24594
+ const normalized = normalizeString22(value);
25131
24595
  return normalized || "";
25132
24596
  }
25133
- function parsePermissionReply(text) {
25134
- const match = permissionReplyPattern.exec(String(text ?? ""));
25135
- if (!match) {
25136
- return {
25137
- matched: false,
25138
- request_id: "",
25139
- behavior: ""
25140
- };
25141
- }
25142
- return {
25143
- matched: true,
25144
- request_id: normalizeString26(match[2]).toLowerCase(),
25145
- behavior: normalizeString26(match[1]).toLowerCase().startsWith("y") ? "allow" : "deny"
25146
- };
25147
- }
25148
- function buildStatusResult(code, msg) {
25149
- return {
25150
- status: "responded",
25151
- code,
25152
- msg
25153
- };
25154
- }
25155
24597
  var WorkerPermissionRelayService = class {
25156
24598
  constructor({
25157
24599
  mcp,
25158
24600
  bridge,
25159
- accessStore,
25160
24601
  eventState,
25161
- finalizeEvent,
25162
24602
  logger,
25163
24603
  aibotSessionID = ""
25164
24604
  }) {
25165
24605
  this.mcp = mcp;
25166
24606
  this.bridge = bridge;
25167
- this.accessStore = accessStore;
25168
24607
  this.eventState = eventState;
25169
- this.finalizeEvent = typeof finalizeEvent === "function" ? finalizeEvent : async () => false;
25170
24608
  this.logger = logger;
25171
24609
  this.aibotSessionID = normalizeOptionalString6(aibotSessionID);
25172
24610
  this.pendingRequests = /* @__PURE__ */ new Map();
25173
24611
  }
25174
- async finalizeEventSafely(eventID, result, context) {
25175
- return this.finalizeEvent(eventID, result, context);
25176
- }
25177
24612
  registerHandlers() {
25178
24613
  this.mcp.setNotificationHandler(
25179
24614
  PermissionRequestNotificationSchema,
@@ -25185,13 +24620,12 @@ var WorkerPermissionRelayService = class {
25185
24620
  getStatus() {
25186
24621
  this.prunePendingRequests();
25187
24622
  return {
25188
- pending_count: this.pendingRequests.size,
25189
- pending_request_ids: Array.from(this.pendingRequests.keys()).sort()
24623
+ pending_count: this.pendingRequests.size
25190
24624
  };
25191
24625
  }
25192
24626
  prunePendingRequests(now = Date.now()) {
25193
- for (const [requestID, request] of this.pendingRequests.entries()) {
25194
- if (now - Number(request.created_at ?? 0) > pendingRequestTtlMs) {
24627
+ for (const [requestID, createdAt] of this.pendingRequests.entries()) {
24628
+ if (now - Number(createdAt ?? 0) > pendingRequestTtlMs) {
25195
24629
  this.pendingRequests.delete(requestID);
25196
24630
  }
25197
24631
  }
@@ -25200,16 +24634,20 @@ var WorkerPermissionRelayService = class {
25200
24634
  this.prunePendingRequests();
25201
24635
  return this.eventState.getLatestActiveBySession(this.aibotSessionID);
25202
24636
  }
24637
+ hasPendingRequest(requestID) {
24638
+ this.prunePendingRequests();
24639
+ return this.pendingRequests.has(normalizeString22(requestID));
24640
+ }
24641
+ async forwardPermissionReply({ requestID, behavior }) {
24642
+ await this.mcp.notification({
24643
+ method: "notifications/claude/channel/permission",
24644
+ params: {
24645
+ request_id: normalizeString22(requestID),
24646
+ behavior: normalizeString22(behavior)
24647
+ }
24648
+ });
24649
+ }
25203
24650
  async handlePermissionRequest(params) {
25204
- if (!this.accessStore.hasApprovers()) {
25205
- this.logger.trace?.({
25206
- component: "worker.permission_relay",
25207
- stage: "permission_request_skipped",
25208
- reason: "no_approvers",
25209
- request_id: params.request_id
25210
- });
25211
- return;
25212
- }
25213
24651
  const activeEvent = this.getActiveEventContext();
25214
24652
  if (!activeEvent) {
25215
24653
  this.logger.trace?.({
@@ -25220,175 +24658,133 @@ var WorkerPermissionRelayService = class {
25220
24658
  });
25221
24659
  return;
25222
24660
  }
25223
- const request = {
25224
- request_id: normalizeString26(params.request_id),
25225
- tool_name: normalizeString26(params.tool_name),
25226
- description: normalizeString26(params.description),
25227
- input_preview: normalizeString26(params.input_preview),
25228
- created_at: Date.now(),
25229
- channel_context: {
25230
- chat_id: activeEvent.session_id,
25231
- event_id: activeEvent.event_id,
25232
- message_id: activeEvent.msg_id,
25233
- msg_id: activeEvent.msg_id,
25234
- sender_id: activeEvent.sender_id,
25235
- user_id: activeEvent.sender_id
25236
- }
24661
+ const requestID = normalizeString22(params.request_id);
24662
+ const createdAt = Date.now();
24663
+ const channelContext = {
24664
+ chat_id: activeEvent.session_id,
24665
+ message_id: activeEvent.msg_id
25237
24666
  };
25238
24667
  try {
25239
- const ack = await this.bridge.sendText({
25240
- sessionID: request.channel_context.chat_id,
25241
- text: buildPermissionRelayRequestText(request),
25242
- quotedMessageID: request.channel_context.message_id || request.channel_context.msg_id,
25243
- clientMsgID: `permission_request_${request.request_id}`,
25244
- extra: {
25245
- reply_source: "claude_permission_request",
25246
- approval_request_id: request.request_id,
25247
- biz_card: buildPermissionRelayRequestBizCard(request, {
25248
- expiresAtMs: request.created_at + pendingRequestTtlMs
25249
- })
25250
- }
25251
- });
25252
- this.pendingRequests.set(request.request_id, {
25253
- ...request,
25254
- approval_message_id: normalizeOptionalString6(ack?.msg_id)
24668
+ const invokeResult = await this.bridge.agentInvoke({
24669
+ invokeID: `permission_request_${requestID}`,
24670
+ action: agentInvokeActions.interactionRequestCreate,
24671
+ params: buildInteractionRequestInvokeParams({
24672
+ kind: interactionKinds.permission,
24673
+ requestID,
24674
+ sessionID: channelContext.chat_id,
24675
+ messageID: channelContext.message_id,
24676
+ payload: buildPermissionInteractionPayload(params)
24677
+ })
25255
24678
  });
24679
+ if (Number(invokeResult?.code ?? 0) !== 0) {
24680
+ throw new Error(normalizeOptionalString6(invokeResult?.msg) || "agent invoke failed");
24681
+ }
24682
+ this.pendingRequests.set(requestID, createdAt);
25256
24683
  this.logger.trace?.({
25257
24684
  component: "worker.permission_relay",
25258
- stage: "permission_request_dispatched",
25259
- request_id: request.request_id,
25260
- event_id: request.channel_context.event_id,
25261
- chat_id: request.channel_context.chat_id,
25262
- msg_id: request.channel_context.message_id || request.channel_context.msg_id
24685
+ stage: "permission_request_forwarded",
24686
+ request_id: requestID,
24687
+ chat_id: channelContext.chat_id,
24688
+ msg_id: channelContext.message_id
25263
24689
  });
25264
24690
  } catch (error2) {
25265
24691
  this.logger.error(
25266
- `permission request dispatch failed request=${request.request_id}: ${String(error2)}`
24692
+ `permission request dispatch failed request=${requestID}: ${String(error2)}`
25267
24693
  );
25268
24694
  }
25269
24695
  }
25270
- async sendApprovalReply(event, text, bizCard = null) {
25271
- await this.bridge.sendText({
25272
- eventID: event.event_id,
25273
- sessionID: event.session_id,
25274
- text,
25275
- quotedMessageID: event.msg_id,
25276
- clientMsgID: `approval_reply_${event.event_id}`,
25277
- extra: {
25278
- reply_source: "claude_channel_approval",
25279
- ...bizCard ? { biz_card: bizCard } : {}
25280
- }
25281
- });
25282
- }
25283
- async handleCommandEvent(event) {
25284
- const parsed = parsePermissionReply(event.content);
25285
- if (!parsed.matched) {
24696
+ async handleLocalAction(rawPayload) {
24697
+ const resolved = parseInteractionReplyAction(rawPayload);
24698
+ if (!resolved.matched) {
25286
24699
  return {
25287
24700
  handled: false,
25288
24701
  kind: ""
25289
24702
  };
25290
24703
  }
25291
- if (!this.accessStore.isSenderApprover(event.sender_id)) {
25292
- const summary = "This sender is not configured as a Claude approval approver.";
25293
- await this.sendApprovalReply(event, summary, buildApprovalCommandStatusBizCard({
25294
- summary,
25295
- status: "warning",
25296
- referenceID: parsed.request_id
25297
- }));
25298
- await this.finalizeEventSafely(
25299
- event.event_id,
25300
- buildStatusResult("approval_sender_not_authorized", "sender not configured as approver"),
25301
- "permission reply unauthorized terminal result failed"
25302
- );
24704
+ if (resolved.kind && resolved.kind !== "permission") {
24705
+ return {
24706
+ handled: false,
24707
+ kind: ""
24708
+ };
24709
+ }
24710
+ const actionID = normalizeString22(rawPayload?.action_id);
24711
+ const requestID = normalizeString22(resolved.requestID);
24712
+ if (!actionID) {
24713
+ this.logger.error(`permission local_action missing action_id: ${JSON.stringify(rawPayload)}`);
25303
24714
  return {
25304
24715
  handled: true,
25305
24716
  kind: "approval"
25306
24717
  };
25307
24718
  }
25308
- const request = this.pendingRequests.get(parsed.request_id);
25309
- if (!request) {
25310
- const summary = `Approval request ${parsed.request_id} is not open.`;
25311
- await this.sendApprovalReply(event, summary, buildApprovalCommandStatusBizCard({
25312
- summary,
25313
- referenceID: parsed.request_id,
25314
- status: "warning"
25315
- }));
25316
- await this.finalizeEventSafely(
25317
- event.event_id,
25318
- buildStatusResult("approval_request_not_open", "approval request is not open"),
25319
- "permission reply missing terminal result failed"
25320
- );
24719
+ if (!requestID) {
24720
+ await this.bridge.sendLocalActionResult({
24721
+ actionID,
24722
+ status: localActionResultStatuses.failed,
24723
+ errorCode: interactionReplyErrorCodes.requestIDRequired,
24724
+ errorMsg: "interaction request id is required"
24725
+ });
25321
24726
  return {
25322
24727
  handled: true,
25323
24728
  kind: "approval"
25324
24729
  };
25325
24730
  }
25326
- if (request.channel_context.chat_id !== event.session_id) {
25327
- const summary = "This approval request belongs to a different Grix chat.";
25328
- await this.sendApprovalReply(event, summary, buildApprovalCommandStatusBizCard({
25329
- summary,
25330
- referenceID: parsed.request_id,
25331
- status: "warning"
25332
- }));
25333
- await this.finalizeEventSafely(
25334
- event.event_id,
25335
- buildStatusResult("approval_chat_mismatch", "approval request belongs to a different chat"),
25336
- "permission reply chat-mismatch terminal result failed"
25337
- );
24731
+ if (!this.hasPendingRequest(requestID)) {
24732
+ await this.bridge.sendLocalActionResult({
24733
+ actionID,
24734
+ status: localActionResultStatuses.failed,
24735
+ errorCode: interactionReplyErrorCodes.requestNotPending,
24736
+ errorMsg: `interaction request ${requestID} is not pending`
24737
+ });
24738
+ return {
24739
+ handled: true,
24740
+ kind: "approval"
24741
+ };
24742
+ }
24743
+ if (!resolved.resolution || resolved.resolution.type !== "decision") {
24744
+ await this.bridge.sendLocalActionResult({
24745
+ actionID,
24746
+ status: localActionResultStatuses.failed,
24747
+ errorCode: resolved.errorCode || interactionReplyErrorCodes.resolutionInvalid,
24748
+ errorMsg: resolved.errorMsg || "interaction resolution is invalid"
24749
+ });
25338
24750
  return {
25339
24751
  handled: true,
25340
24752
  kind: "approval"
25341
24753
  };
25342
24754
  }
24755
+ const behavior = resolved.resolution.value === "allow" ? "allow" : "deny";
25343
24756
  try {
25344
- await this.mcp.notification({
25345
- method: "notifications/claude/channel/permission",
25346
- params: {
25347
- request_id: parsed.request_id,
25348
- behavior: parsed.behavior
25349
- }
24757
+ await this.forwardPermissionReply({
24758
+ requestID,
24759
+ behavior
25350
24760
  });
25351
24761
  } catch (error2) {
25352
- const summary = `Failed to send approval reply for request ${parsed.request_id}.`;
25353
- await this.sendApprovalReply(event, summary, buildApprovalCommandStatusBizCard({
25354
- summary,
25355
- referenceID: parsed.request_id,
25356
- detailText: String(error2),
25357
- status: "error"
25358
- }));
25359
- await this.finalizeEventSafely(
25360
- event.event_id,
25361
- buildStatusResult("approval_forward_failed", "approval verdict send failed"),
25362
- "permission reply forward terminal result failed"
25363
- );
24762
+ await this.bridge.sendLocalActionResult({
24763
+ actionID,
24764
+ status: localActionResultStatuses.failed,
24765
+ errorCode: interactionReplyErrorCodes.forwardFailed,
24766
+ errorMsg: String(error2)
24767
+ });
25364
24768
  return {
25365
24769
  handled: true,
25366
24770
  kind: "approval"
25367
24771
  };
25368
24772
  }
25369
- this.pendingRequests.delete(parsed.request_id);
25370
- const responseText = buildPermissionRelayVerdictText({
25371
- requestID: parsed.request_id,
25372
- behavior: parsed.behavior
24773
+ this.pendingRequests.delete(requestID);
24774
+ await this.bridge.sendLocalActionResult({
24775
+ actionID,
24776
+ status: localActionResultStatuses.ok,
24777
+ result: buildInteractionReplyResult({
24778
+ kind: interactionKinds.permission,
24779
+ requestID
24780
+ })
25373
24781
  });
25374
- await this.sendApprovalReply(event, responseText, buildPermissionRelaySubmittedBizCard({
25375
- request,
25376
- behavior: parsed.behavior,
25377
- resolvedByID: event.sender_id
25378
- }));
25379
- await this.finalizeEventSafely(
25380
- event.event_id,
25381
- buildStatusResult("approval_forwarded", "approval verdict sent to Claude"),
25382
- "permission reply recorded terminal result failed"
25383
- );
25384
24782
  this.logger.trace?.({
25385
24783
  component: "worker.permission_relay",
25386
- stage: "permission_reply_forwarded",
25387
- event_id: event.event_id,
25388
- session_id: event.session_id,
25389
- sender_id: event.sender_id,
25390
- request_id: parsed.request_id,
25391
- behavior: parsed.behavior
24784
+ stage: "permission_local_action_forwarded",
24785
+ action_id: actionID,
24786
+ request_id: requestID,
24787
+ behavior
25392
24788
  });
25393
24789
  return {
25394
24790
  handled: true,
@@ -25404,8 +24800,8 @@ var WorkerPermissionRelayService = class {
25404
24800
  import { randomUUID as randomUUID4 } from "node:crypto";
25405
24801
 
25406
24802
  // server/attachment-file.js
25407
- import path8 from "node:path";
25408
- import { readFile as readFile2, stat } from "node:fs/promises";
24803
+ import path7 from "node:path";
24804
+ import { readFile as readFile2, stat as stat2 } from "node:fs/promises";
25409
24805
  var maxReplyFileBytes = 50 * 1024 * 1024;
25410
24806
  var uploadableFileExtensions = /* @__PURE__ */ new Set([
25411
24807
  "pdf",
@@ -25440,15 +24836,15 @@ var uploadableFileExtensions = /* @__PURE__ */ new Set([
25440
24836
  "mkv",
25441
24837
  "avi"
25442
24838
  ]);
25443
- function normalizeString27(value) {
24839
+ function normalizeString23(value) {
25444
24840
  return String(value ?? "").trim();
25445
24841
  }
25446
24842
  function extensionOf(fileName) {
25447
- const extension = path8.extname(normalizeString27(fileName)).toLowerCase();
24843
+ const extension = path7.extname(normalizeString23(fileName)).toLowerCase();
25448
24844
  return extension.startsWith(".") ? extension.slice(1) : extension;
25449
24845
  }
25450
24846
  function resolveAttachmentType(contentType) {
25451
- const normalized = normalizeString27(contentType).toLowerCase();
24847
+ const normalized = normalizeString23(contentType).toLowerCase();
25452
24848
  if (normalized.startsWith("image/")) {
25453
24849
  return "image";
25454
24850
  }
@@ -25525,14 +24921,14 @@ function resolveContentType(fileName) {
25525
24921
  }
25526
24922
  }
25527
24923
  async function readReplyFile(filePath) {
25528
- const normalizedPath = normalizeString27(filePath);
24924
+ const normalizedPath = normalizeString23(filePath);
25529
24925
  if (!normalizedPath) {
25530
24926
  throw new Error("reply.files entries must be non-empty absolute paths");
25531
24927
  }
25532
- if (!path8.isAbsolute(normalizedPath)) {
24928
+ if (!path7.isAbsolute(normalizedPath)) {
25533
24929
  throw new Error(`reply.files requires an absolute path: ${normalizedPath}`);
25534
24930
  }
25535
- const fileStat = await stat(normalizedPath);
24931
+ const fileStat = await stat2(normalizedPath);
25536
24932
  if (!fileStat.isFile()) {
25537
24933
  throw new Error(`reply.files path is not a file: ${normalizedPath}`);
25538
24934
  }
@@ -25542,7 +24938,7 @@ async function readReplyFile(filePath) {
25542
24938
  if (fileStat.size > maxReplyFileBytes) {
25543
24939
  throw new Error(`reply.files exceeds 50MB: ${normalizedPath}`);
25544
24940
  }
25545
- const fileName = path8.basename(normalizedPath);
24941
+ const fileName = path7.basename(normalizedPath);
25546
24942
  const extension = extensionOf(fileName);
25547
24943
  if (!extension || !uploadableFileExtensions.has(extension)) {
25548
24944
  throw new Error(`reply.files unsupported file type: ${fileName}`);
@@ -25560,10 +24956,10 @@ async function readReplyFile(filePath) {
25560
24956
  }
25561
24957
  function buildAttachmentExtra({ attachmentType, fileName, accessURL, contentType }) {
25562
24958
  const attachment = {
25563
- media_url: normalizeString27(accessURL),
25564
- attachment_type: normalizeString27(attachmentType),
25565
- file_name: normalizeString27(fileName),
25566
- content_type: normalizeString27(contentType)
24959
+ media_url: normalizeString23(accessURL),
24960
+ attachment_type: normalizeString23(attachmentType),
24961
+ file_name: normalizeString23(fileName),
24962
+ content_type: normalizeString23(contentType)
25567
24963
  };
25568
24964
  return {
25569
24965
  ...attachment,
@@ -25572,20 +24968,20 @@ function buildAttachmentExtra({ attachmentType, fileName, accessURL, contentType
25572
24968
  }
25573
24969
 
25574
24970
  // server/agent-api-media.js
25575
- function normalizeString28(value) {
24971
+ function normalizeString24(value) {
25576
24972
  return String(value ?? "").trim();
25577
24973
  }
25578
24974
  function trimTrailingSlash(value) {
25579
- return normalizeString28(value).replace(/\/+$/u, "");
24975
+ return normalizeString24(value).replace(/\/+$/u, "");
25580
24976
  }
25581
24977
  function parseJSONResponseBody(text) {
25582
- if (!normalizeString28(text)) {
24978
+ if (!normalizeString24(text)) {
25583
24979
  return {};
25584
24980
  }
25585
24981
  return JSON.parse(text);
25586
24982
  }
25587
24983
  function resolveAgentAPIPresignURL(wsURL) {
25588
- const url2 = new URL(normalizeString28(wsURL));
24984
+ const url2 = new URL(normalizeString24(wsURL));
25589
24985
  if (url2.protocol !== "ws:" && url2.protocol !== "wss:") {
25590
24986
  throw new Error("ws_url must start with ws:// or wss://");
25591
24987
  }
@@ -25603,23 +24999,23 @@ async function requestPresign({ wsURL, apiKey, sessionID, fileName, contentType,
25603
24999
  const response = await fetchImpl(resolveAgentAPIPresignURL(wsURL), {
25604
25000
  method: "POST",
25605
25001
  headers: {
25606
- Authorization: `Bearer ${normalizeString28(apiKey)}`,
25002
+ Authorization: `Bearer ${normalizeString24(apiKey)}`,
25607
25003
  "Content-Type": "application/json"
25608
25004
  },
25609
25005
  body: JSON.stringify({
25610
- session_id: normalizeString28(sessionID),
25611
- filename: normalizeString28(fileName),
25612
- content_type: normalizeString28(contentType)
25006
+ session_id: normalizeString24(sessionID),
25007
+ filename: normalizeString24(fileName),
25008
+ content_type: normalizeString24(contentType)
25613
25009
  })
25614
25010
  });
25615
25011
  const rawBody = await response.text();
25616
25012
  const payload = parseJSONResponseBody(rawBody);
25617
25013
  if (!response.ok || Number(payload?.code ?? -1) !== 0) {
25618
- const message = normalizeString28(payload?.msg) || response.statusText || "agent media presign failed";
25014
+ const message = normalizeString24(payload?.msg) || response.statusText || "agent media presign failed";
25619
25015
  throw new Error(`agent media presign failed: ${message}`);
25620
25016
  }
25621
- const uploadURL = normalizeString28(payload?.data?.upload_url);
25622
- const accessURL = normalizeString28(payload?.data?.media_access_url);
25017
+ const uploadURL = normalizeString24(payload?.data?.upload_url);
25018
+ const accessURL = normalizeString24(payload?.data?.media_access_url);
25623
25019
  if (!uploadURL || !accessURL) {
25624
25020
  throw new Error("agent media presign returned incomplete upload_url/media_access_url");
25625
25021
  }
@@ -25632,7 +25028,7 @@ async function uploadPresignedFile({ uploadURL, contentType, bytes, fetchImpl })
25632
25028
  const response = await fetchImpl(uploadURL, {
25633
25029
  method: "PUT",
25634
25030
  headers: {
25635
- "Content-Type": normalizeString28(contentType)
25031
+ "Content-Type": normalizeString24(contentType)
25636
25032
  },
25637
25033
  body: bytes
25638
25034
  });
@@ -25721,21 +25117,18 @@ function splitTextForAibotProtocol(text, preferredRunes) {
25721
25117
  }
25722
25118
 
25723
25119
  // server/worker/tool-service.js
25724
- function normalizeString29(value) {
25120
+ function normalizeString25(value) {
25725
25121
  return String(value ?? "").trim();
25726
25122
  }
25727
25123
  function normalizeOptionalString7(value) {
25728
- const normalized = normalizeString29(value);
25124
+ const normalized = normalizeString25(value);
25729
25125
  return normalized || "";
25730
25126
  }
25731
25127
  function normalizeStringArray4(value) {
25732
25128
  if (!Array.isArray(value)) {
25733
25129
  return [];
25734
25130
  }
25735
- return value.map((item) => normalizeString29(item)).filter((item) => item);
25736
- }
25737
- function mergeRecords(...records) {
25738
- return Object.assign({}, ...records);
25131
+ return value.map((item) => normalizeString25(item)).filter((item) => item);
25739
25132
  }
25740
25133
  function toolTextResult(value) {
25741
25134
  return {
@@ -25809,7 +25202,7 @@ var toolDefinitions = [
25809
25202
  },
25810
25203
  {
25811
25204
  name: "status",
25812
- description: "Show grix-claude configuration, access policy, daemon bridge status, and startup hints.",
25205
+ description: "Show grix-claude runtime status, upstream access state, bridge health, and startup hints.",
25813
25206
  inputSchema: {
25814
25207
  type: "object",
25815
25208
  properties: {}
@@ -25817,7 +25210,7 @@ var toolDefinitions = [
25817
25210
  },
25818
25211
  {
25819
25212
  name: "access_pair",
25820
- description: "Approve a pending sender pairing code and add that sender to the allowlist.",
25213
+ description: "Forward a sender pairing approval code to upstream access control.",
25821
25214
  inputSchema: {
25822
25215
  type: "object",
25823
25216
  properties: {
@@ -25828,7 +25221,7 @@ var toolDefinitions = [
25828
25221
  },
25829
25222
  {
25830
25223
  name: "access_deny",
25831
- description: "Deny a pending sender pairing code and clear it from pending state.",
25224
+ description: "Forward a sender pairing denial code to upstream access control.",
25832
25225
  inputSchema: {
25833
25226
  type: "object",
25834
25227
  properties: {
@@ -25839,7 +25232,7 @@ var toolDefinitions = [
25839
25232
  },
25840
25233
  {
25841
25234
  name: "allow_sender",
25842
- description: "Add a sender_id directly to the allowlist.",
25235
+ description: "Ask upstream access control to allow a sender_id.",
25843
25236
  inputSchema: {
25844
25237
  type: "object",
25845
25238
  properties: {
@@ -25850,29 +25243,7 @@ var toolDefinitions = [
25850
25243
  },
25851
25244
  {
25852
25245
  name: "remove_sender",
25853
- description: "Remove a sender_id from the allowlist.",
25854
- inputSchema: {
25855
- type: "object",
25856
- properties: {
25857
- sender_id: { type: "string" }
25858
- },
25859
- required: ["sender_id"]
25860
- }
25861
- },
25862
- {
25863
- name: "allow_approver",
25864
- description: "Add a sender_id to the Claude remote approval allowlist.",
25865
- inputSchema: {
25866
- type: "object",
25867
- properties: {
25868
- sender_id: { type: "string" }
25869
- },
25870
- required: ["sender_id"]
25871
- }
25872
- },
25873
- {
25874
- name: "remove_approver",
25875
- description: "Remove a sender_id from the Claude remote approval allowlist.",
25246
+ description: "Ask upstream access control to remove a sender_id.",
25876
25247
  inputSchema: {
25877
25248
  type: "object",
25878
25249
  properties: {
@@ -25883,7 +25254,7 @@ var toolDefinitions = [
25883
25254
  },
25884
25255
  {
25885
25256
  name: "access_policy",
25886
- description: "Update the sender access policy for this channel.",
25257
+ description: "Ask upstream access control to update the sender access policy.",
25887
25258
  inputSchema: {
25888
25259
  type: "object",
25889
25260
  properties: {
@@ -25898,8 +25269,6 @@ var WorkerToolService = class {
25898
25269
  mcp,
25899
25270
  bridge,
25900
25271
  configStore,
25901
- accessStore,
25902
- approvalStore,
25903
25272
  elicitationStore,
25904
25273
  permissionRelayService = null,
25905
25274
  eventState,
@@ -25909,27 +25278,55 @@ var WorkerToolService = class {
25909
25278
  this.mcp = mcp;
25910
25279
  this.bridge = bridge;
25911
25280
  this.configStore = configStore;
25912
- this.accessStore = accessStore;
25913
- this.approvalStore = approvalStore;
25914
25281
  this.elicitationStore = elicitationStore;
25915
25282
  this.permissionRelayService = permissionRelayService;
25916
25283
  this.eventState = eventState;
25917
25284
  this.messageRuntime = messageRuntime;
25918
25285
  this.logger = logger;
25919
25286
  }
25287
+ async forwardAccessAction(verb, payload = {}) {
25288
+ const invokeResult = await this.bridge.agentInvoke({
25289
+ invokeID: `${agentInvokeActions.accessControl}_${normalizeString25(verb)}_${randomUUID4()}`,
25290
+ action: agentInvokeActions.accessControl,
25291
+ params: {
25292
+ verb: normalizeString25(verb),
25293
+ payload: payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {}
25294
+ }
25295
+ });
25296
+ const code = Number(invokeResult?.code ?? 5001);
25297
+ if (code !== 0) {
25298
+ throw new Error(normalizeString25(invokeResult?.msg) || `claude access invoke failed: ${verb}`);
25299
+ }
25300
+ return invokeResult?.data ?? {};
25301
+ }
25920
25302
  async buildStatusPayload() {
25303
+ let upstreamAccess = null;
25304
+ try {
25305
+ upstreamAccess = await this.forwardAccessAction(accessControlVerbs.statusRead);
25306
+ } catch (error2) {
25307
+ upstreamAccess = {
25308
+ unavailable: true,
25309
+ error: error2 instanceof Error ? error2.message : String(error2)
25310
+ };
25311
+ }
25312
+ const approvalStatus = this.permissionRelayService?.getStatus?.() ?? {
25313
+ pending_count: 0
25314
+ };
25315
+ const questionStatus = await this.elicitationStore.getStatus();
25921
25316
  return {
25922
25317
  config: this.configStore.getStatus(),
25923
- access: this.accessStore.getStatus(),
25924
- approvals: this.permissionRelayService?.getStatus?.() ?? await this.approvalStore.getStatus(),
25925
- questions: await this.elicitationStore.getStatus(),
25318
+ upstream_access: upstreamAccess,
25319
+ local_bridge: {
25320
+ pending_approval_requests: Number(approvalStatus.pending_count ?? 0),
25321
+ pending_question_requests: Number(questionStatus.pending_count ?? 0)
25322
+ },
25926
25323
  connection: this.bridge.getConnectionStatus(),
25927
25324
  hints: this.bridge.buildStatusHints()
25928
25325
  };
25929
25326
  }
25930
25327
  async handleReplyTool(args) {
25931
- const chatID = normalizeString29(args.chat_id);
25932
- const eventID = normalizeString29(args.event_id);
25328
+ const chatID = normalizeString25(args.chat_id);
25329
+ const eventID = normalizeString25(args.event_id);
25933
25330
  const replyTo = normalizeOptionalString7(args.reply_to);
25934
25331
  const text = String(args.text ?? "");
25935
25332
  const files = normalizeStringArray4(args.files);
@@ -25987,10 +25384,7 @@ var WorkerToolService = class {
25987
25384
  sessionID: chatID,
25988
25385
  text: chunk,
25989
25386
  quotedMessageID: replyTo || current.msg_id,
25990
- clientMsgID: `${randomUUID4()}_${chunkIndex}`,
25991
- extra: {
25992
- reply_source: "claude_channel"
25993
- }
25387
+ clientMsgID: `${randomUUID4()}_${chunkIndex}`
25994
25388
  });
25995
25389
  const messageID = normalizeOptionalString7(ack.msg_id);
25996
25390
  if (messageID) {
@@ -26016,12 +25410,7 @@ var WorkerToolService = class {
26016
25410
  caption: upload.file_name,
26017
25411
  quotedMessageID: replyTo || current.msg_id,
26018
25412
  clientMsgID: `${randomUUID4()}_${chunkIndex}`,
26019
- extra: mergeRecords(
26020
- {
26021
- reply_source: "claude_channel"
26022
- },
26023
- upload.extra
26024
- )
25413
+ extra: upload.extra
26025
25414
  });
26026
25415
  const messageID = normalizeOptionalString7(ack.msg_id);
26027
25416
  if (messageID) {
@@ -26069,8 +25458,8 @@ var WorkerToolService = class {
26069
25458
  return toolTextResult("sent");
26070
25459
  }
26071
25460
  async handleCompleteTool(args) {
26072
- const eventID = normalizeString29(args.event_id);
26073
- const status = normalizeString29(args.status);
25461
+ const eventID = normalizeString25(args.event_id);
25462
+ const status = normalizeString25(args.status);
26074
25463
  const code = normalizeOptionalString7(args.code);
26075
25464
  const msg = normalizeOptionalString7(args.msg);
26076
25465
  this.logger.debug(
@@ -26119,8 +25508,8 @@ var WorkerToolService = class {
26119
25508
  return toolTextResult(await this.buildStatusPayload());
26120
25509
  }
26121
25510
  async handleDeleteMessageTool(args) {
26122
- const chatID = normalizeString29(args.chat_id);
26123
- const messageID = normalizeString29(args.message_id);
25511
+ const chatID = normalizeString25(args.chat_id);
25512
+ const messageID = normalizeString25(args.message_id);
26124
25513
  this.logger.debug(`tool delete_message chat=${chatID} message=${messageID}`);
26125
25514
  if (!chatID || !messageID) {
26126
25515
  throw new Error("delete_message requires chat_id and message_id");
@@ -26134,89 +25523,46 @@ var WorkerToolService = class {
26134
25523
  await this.bridge.deleteMessage(chatID, messageID);
26135
25524
  return toolTextResult(`deleted (${messageID})`);
26136
25525
  }
26137
- async notifyApprovedSender(sessionID) {
26138
- try {
26139
- await this.messageRuntime.sendAccessStatusMessage(
26140
- sessionID,
26141
- "Paired! Say hi to Claude.",
26142
- `pair_ok_${randomUUID4()}`,
26143
- buildAccessStatusBizCard({
26144
- summary: "Paired! Say hi to Claude.",
26145
- status: "success"
26146
- })
26147
- );
26148
- return true;
26149
- } catch (error2) {
26150
- this.logger.error(`pairing confirmation send failed session=${sessionID}: ${String(error2)}`);
26151
- return false;
26152
- }
26153
- }
26154
- async notifyDeniedSender(sessionID, code) {
26155
- try {
26156
- await this.messageRuntime.sendAccessStatusMessage(
26157
- sessionID,
26158
- `Pairing request ${code} was denied. Ask the Claude Code user to request a new pairing code if you still need access.`,
26159
- `pair_denied_${randomUUID4()}`,
26160
- buildAccessStatusBizCard({
26161
- summary: `Pairing request ${code} was denied. Ask the Claude Code user to request a new pairing code if you still need access.`,
26162
- status: "warning",
26163
- referenceID: code
26164
- })
26165
- );
26166
- return true;
26167
- } catch (error2) {
26168
- this.logger.error(`pairing denial send failed session=${sessionID}: ${String(error2)}`);
26169
- return false;
26170
- }
26171
- }
26172
25526
  async handleAccessPairTool(args) {
26173
- const result = await this.accessStore.approvePairing(args.code);
26174
- const pairing_notice_sent = await this.notifyApprovedSender(result.session_id);
25527
+ const result = await this.forwardAccessAction(accessControlVerbs.pairApprove, {
25528
+ code: normalizeString25(args.code)
25529
+ });
26175
25530
  return toolTextResult({
26176
25531
  ...result,
26177
- pairing_notice_sent,
26178
25532
  hints: this.bridge.buildStatusHints()
26179
25533
  });
26180
25534
  }
26181
25535
  async handleAccessDenyTool(args) {
26182
- const result = await this.accessStore.denyPairing(args.code);
26183
- const pairing_notice_sent = await this.notifyDeniedSender(result.session_id, result.code);
25536
+ const result = await this.forwardAccessAction(accessControlVerbs.pairDeny, {
25537
+ code: normalizeString25(args.code)
25538
+ });
26184
25539
  return toolTextResult({
26185
25540
  ...result,
26186
- pairing_notice_sent,
26187
25541
  hints: this.bridge.buildStatusHints()
26188
25542
  });
26189
25543
  }
26190
25544
  async handleAllowSenderTool(args) {
26191
- const result = await this.accessStore.allowSender(args.sender_id);
26192
- return toolTextResult({
26193
- ...result,
26194
- hints: this.bridge.buildStatusHints()
25545
+ const result = await this.forwardAccessAction(accessControlVerbs.senderAllow, {
25546
+ sender_id: normalizeString25(args.sender_id)
26195
25547
  });
26196
- }
26197
- async handleAllowApproverTool(args) {
26198
- const result = await this.accessStore.allowApprover(args.sender_id);
26199
25548
  return toolTextResult({
26200
25549
  ...result,
26201
25550
  hints: this.bridge.buildStatusHints()
26202
25551
  });
26203
25552
  }
26204
25553
  async handleRemoveSenderTool(args) {
26205
- const result = await this.accessStore.removeSender(args.sender_id);
26206
- return toolTextResult({
26207
- ...result,
26208
- hints: this.bridge.buildStatusHints()
25554
+ const result = await this.forwardAccessAction(accessControlVerbs.senderRemove, {
25555
+ sender_id: normalizeString25(args.sender_id)
26209
25556
  });
26210
- }
26211
- async handleRemoveApproverTool(args) {
26212
- const result = await this.accessStore.removeApprover(args.sender_id);
26213
25557
  return toolTextResult({
26214
25558
  ...result,
26215
25559
  hints: this.bridge.buildStatusHints()
26216
25560
  });
26217
25561
  }
26218
25562
  async handleAccessPolicyTool(args) {
26219
- const result = await this.accessStore.setPolicy(args.policy);
25563
+ const result = await this.forwardAccessAction(accessControlVerbs.policySet, {
25564
+ policy: normalizeString25(args.policy)
25565
+ });
26220
25566
  return toolTextResult({
26221
25567
  ...result,
26222
25568
  hints: this.bridge.buildStatusHints()
@@ -26240,7 +25586,7 @@ var WorkerToolService = class {
26240
25586
  };
26241
25587
  });
26242
25588
  this.mcp.setRequestHandler(CallToolRequestSchema, async (request) => {
26243
- const name = normalizeString29(request.params.name);
25589
+ const name = normalizeString25(request.params.name);
26244
25590
  const args = request.params.arguments ?? {};
26245
25591
  this.logger.trace?.({
26246
25592
  component: "worker.tool",
@@ -26277,12 +25623,6 @@ var WorkerToolService = class {
26277
25623
  case "remove_sender":
26278
25624
  result = await this.handleRemoveSenderTool(args);
26279
25625
  break;
26280
- case "allow_approver":
26281
- result = await this.handleAllowApproverTool(args);
26282
- break;
26283
- case "remove_approver":
26284
- result = await this.handleRemoveApproverTool(args);
26285
- break;
26286
25626
  case "access_policy":
26287
25627
  result = await this.handleAccessPolicyTool(args);
26288
25628
  break;
@@ -26313,11 +25653,11 @@ var WorkerToolService = class {
26313
25653
  };
26314
25654
 
26315
25655
  // server/worker/app.js
26316
- function normalizeString30(value) {
25656
+ function normalizeString26(value) {
26317
25657
  return String(value ?? "").trim();
26318
25658
  }
26319
25659
  function normalizeOptionalString8(value) {
26320
- const normalized = normalizeString30(value);
25660
+ const normalized = normalizeString26(value);
26321
25661
  return normalized || "";
26322
25662
  }
26323
25663
  function parsePositiveInt(value, fallbackValue) {
@@ -26331,6 +25671,7 @@ function buildInstructions() {
26331
25671
  return [
26332
25672
  'Messages arrive as <channel source="grix-claude" chat_id="..." event_id="..." message_id="..." user_id="...">text</channel>.',
26333
25673
  "When present, channel metadata includes msg_type plus JSON strings in attachments_json, biz_card_json, channel_data_json, and extra_json. Use those structured fields directly instead of guessing attachment or card semantics from text.",
25674
+ "When context_messages_json is present, treat it as the visible messages immediately before the current trigger message. Use it as context only, and answer only the current message.",
26334
25675
  'If channel_data_json marks {"grix-claude":{"internal_probe":{"kind":"ping_pong"}}}, reply with exactly pong and nothing else.',
26335
25676
  "If you want to send a visible reply back to the same chat, call the reply tool with chat_id, event_id, and text. You may also pass reply_to when you want to quote a specific message_id.",
26336
25677
  "The reply tool also accepts files as absolute local paths. Files are uploaded through the Agent API media presign endpoint before they are sent back to the chat.",
@@ -26343,11 +25684,6 @@ function createWorkerApp({ env = process4.env } = {}) {
26343
25684
  const logger = createProcessLogger({ env });
26344
25685
  const eventStatesDir = resolveEventStatesDir();
26345
25686
  const configStore = new ConfigStore(resolveConfigPath());
26346
- const accessStore = new AccessStore(resolveAccessPath());
26347
- const approvalStore = new ApprovalStore({
26348
- requestsDir: resolveApprovalRequestsDir(),
26349
- notificationsDir: resolveApprovalNotificationsDir()
26350
- });
26351
25687
  const elicitationStore = new ElicitationStore({
26352
25688
  requestsDir: resolveElicitationRequestsDir()
26353
25689
  });
@@ -26390,27 +25726,26 @@ function createWorkerApp({ env = process4.env } = {}) {
26390
25726
  },
26391
25727
  onDeliverRevoke: async (payload) => {
26392
25728
  await interactionService?.handleRevokeEvent(payload);
25729
+ },
25730
+ onDeliverLocalAction: async (payload) => {
25731
+ await interactionService?.handleLocalAction(payload);
26393
25732
  }
26394
25733
  });
26395
25734
  const elicitationRelayService = new WorkerElicitationRelayService({
26396
25735
  elicitationStore,
26397
25736
  bridge,
26398
- finalizeEvent: async (eventID, result, context) => interactionService?.finalizeEventSafely?.(eventID, result, context) ?? false,
26399
25737
  logger
26400
25738
  });
26401
25739
  const permissionRelayService = new WorkerPermissionRelayService({
26402
25740
  mcp,
26403
25741
  bridge,
26404
- accessStore,
26405
25742
  eventState,
26406
- finalizeEvent: async (eventID, result, context) => interactionService?.finalizeEventSafely?.(eventID, result, context) ?? false,
26407
25743
  logger,
26408
25744
  aibotSessionID: normalizeOptionalString8(env.GRIX_CLAUDE_AIBOT_SESSION_ID)
26409
25745
  });
26410
25746
  interactionService = new WorkerInteractionService({
26411
25747
  eventState,
26412
25748
  sessionContextStore,
26413
- accessStore,
26414
25749
  eventStatesDir,
26415
25750
  mcp,
26416
25751
  bridge,
@@ -26426,8 +25761,6 @@ function createWorkerApp({ env = process4.env } = {}) {
26426
25761
  mcp,
26427
25762
  bridge,
26428
25763
  configStore,
26429
- accessStore,
26430
- approvalStore,
26431
25764
  elicitationStore,
26432
25765
  permissionRelayService,
26433
25766
  eventState,
@@ -26440,8 +25773,6 @@ function createWorkerApp({ env = process4.env } = {}) {
26440
25773
  logger,
26441
25774
  async bootstrap() {
26442
25775
  await configStore.load();
26443
- await accessStore.load();
26444
- await approvalStore.init();
26445
25776
  await elicitationStore.init();
26446
25777
  await interactionService.restoreEventState();
26447
25778
  await bridge.startControlServer();