@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/README.md +133 -86
- package/cli/prod-runner.js +163 -0
- package/cli/prod-runner.test.js +195 -0
- package/cli/runtime-targets.js +66 -0
- package/dist/daemon.js +2010 -904
- package/dist/index.js +1364 -2033
- package/hooks/hooks.json +34 -1
- package/package.json +5 -3
- package/scripts/dev-start.js +14 -0
- package/scripts/elicitation-hook.js +34 -27
- package/scripts/lifecycle-hook.js +3 -0
- package/scripts/notification-hook.js +0 -8
- package/scripts/prod-start.js +7 -0
- package/scripts/user-prompt-submit-hook.js +2 -1
- package/skills/grix/SKILL.md +121 -0
- package/skills/access/SKILL.md +0 -129
- package/skills/status/SKILL.md +0 -11
package/dist/index.js
CHANGED
|
@@ -3221,8 +3221,8 @@ var require_utils = __commonJS({
|
|
|
3221
3221
|
}
|
|
3222
3222
|
return ind;
|
|
3223
3223
|
}
|
|
3224
|
-
function removeDotSegments(
|
|
3225
|
-
let input =
|
|
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 [
|
|
3425
|
-
wsComponent.path =
|
|
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:
|
|
7163
|
-
const fullPath = [...
|
|
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,
|
|
7278
|
+
constructor(parent, value, path8, key) {
|
|
7279
7279
|
this._cachedPath = [];
|
|
7280
7280
|
this.parent = parent;
|
|
7281
7281
|
this.data = value;
|
|
7282
|
-
this._path =
|
|
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,
|
|
10927
|
-
if (!
|
|
10926
|
+
function getElementAtPath(obj, path8) {
|
|
10927
|
+
if (!path8)
|
|
10928
10928
|
return obj;
|
|
10929
|
-
return
|
|
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(
|
|
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(
|
|
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
|
|
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/
|
|
20813
|
-
import {
|
|
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/
|
|
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
|
|
20850
|
+
function normalizeString(value) {
|
|
21241
20851
|
return String(value ?? "").trim();
|
|
21242
20852
|
}
|
|
21243
|
-
function
|
|
21244
|
-
return
|
|
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
|
-
|
|
21319
|
-
|
|
21320
|
-
|
|
21321
|
-
|
|
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
|
-
|
|
21411
|
-
|
|
21412
|
-
|
|
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
|
-
|
|
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 =
|
|
21491
|
-
const
|
|
21492
|
-
const
|
|
21493
|
-
const
|
|
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:
|
|
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
|
-
|
|
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
|
|
20898
|
+
await mkdir(this.contextsDir, { recursive: true });
|
|
21520
20899
|
}
|
|
21521
|
-
resolveSessionPath(sessionID) {
|
|
21522
|
-
|
|
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(
|
|
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 =
|
|
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(
|
|
20920
|
+
const stored = await readJSONFile(
|
|
20921
|
+
this.resolveSessionPath(normalizedSessionID, agentID),
|
|
20922
|
+
null
|
|
20923
|
+
);
|
|
21543
20924
|
return normalizeContext(stored);
|
|
21544
20925
|
}
|
|
21545
|
-
async getMatchingContext({
|
|
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({
|
|
21558
|
-
|
|
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 (
|
|
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 (
|
|
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
|
|
21592
|
-
import { mkdir as
|
|
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
|
|
20989
|
+
function normalizeString2(value) {
|
|
21596
20990
|
return String(value ?? "").trim();
|
|
21597
20991
|
}
|
|
21598
20992
|
function normalizePositiveInt(value) {
|
|
21599
|
-
const normalized =
|
|
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 =
|
|
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
|
|
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:
|
|
21662
|
-
agent_id:
|
|
21663
|
-
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 =
|
|
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
|
|
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
|
|
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
|
|
21771
|
-
import
|
|
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
|
|
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) =>
|
|
21175
|
+
return input.map((value) => normalizeString4(value)).filter(Boolean);
|
|
21788
21176
|
}
|
|
21789
21177
|
function normalizeFieldType(value) {
|
|
21790
|
-
const normalized =
|
|
21178
|
+
const normalized = normalizeString4(value).toLowerCase();
|
|
21791
21179
|
return normalized || "string";
|
|
21792
21180
|
}
|
|
21793
21181
|
function normalizeFieldKind(value) {
|
|
21794
|
-
const normalized =
|
|
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 =
|
|
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 ${
|
|
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 =
|
|
21829
|
-
const title =
|
|
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:
|
|
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
|
|
21862
|
-
function
|
|
21235
|
+
var schemaVersion2 = 1;
|
|
21236
|
+
function normalizeString5(value) {
|
|
21863
21237
|
return String(value ?? "").trim();
|
|
21864
21238
|
}
|
|
21865
|
-
function
|
|
21239
|
+
function cloneJSON(value) {
|
|
21866
21240
|
return JSON.parse(JSON.stringify(value));
|
|
21867
21241
|
}
|
|
21868
|
-
function
|
|
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:
|
|
21875
|
-
|
|
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
|
|
21883
|
-
if (!
|
|
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
|
|
21894
|
-
if (!
|
|
21258
|
+
function normalizeRequestPayload(input) {
|
|
21259
|
+
if (!isRecord(input)) {
|
|
21895
21260
|
return null;
|
|
21896
21261
|
}
|
|
21897
|
-
return
|
|
21262
|
+
return cloneJSON(input);
|
|
21898
21263
|
}
|
|
21899
|
-
function
|
|
21900
|
-
|
|
21901
|
-
|
|
21264
|
+
function allowsEmptyFields(requestPayload) {
|
|
21265
|
+
const payload = normalizeRequestPayload(requestPayload);
|
|
21266
|
+
if (!payload) {
|
|
21267
|
+
return false;
|
|
21902
21268
|
}
|
|
21903
|
-
return
|
|
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
|
|
21917
|
-
if (!
|
|
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:
|
|
21924
|
-
request_id:
|
|
21925
|
-
status:
|
|
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
|
-
|
|
21283
|
+
request_payload: normalizeRequestPayload(input.request_payload),
|
|
21941
21284
|
channel_context: normalizeChannelContext(input.channel_context),
|
|
21942
|
-
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
|
|
21289
|
+
async function listJSONFiles(dirPath) {
|
|
21948
21290
|
let names = [];
|
|
21949
21291
|
try {
|
|
21950
|
-
names = await
|
|
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
|
|
21306
|
+
await mkdir3(this.requestsDir, { recursive: true });
|
|
21965
21307
|
}
|
|
21966
21308
|
resolveRequestPath(requestID) {
|
|
21967
|
-
return
|
|
21309
|
+
return path3.join(this.requestsDir, `${normalizeString5(requestID)}.json`);
|
|
21968
21310
|
}
|
|
21969
21311
|
async createRequest(input) {
|
|
21970
|
-
const requestID =
|
|
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 =
|
|
21980
|
-
schema_version:
|
|
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
|
-
|
|
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
|
|
21338
|
+
return normalizeRequest(stored);
|
|
22009
21339
|
}
|
|
22010
21340
|
async saveRequest(request) {
|
|
22011
|
-
const normalized =
|
|
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
|
|
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()
|
|
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
|
|
22057
|
-
return this.saveRequest(request);
|
|
21384
|
+
return request;
|
|
22058
21385
|
}
|
|
22059
|
-
async resolveRequest(requestID, { action, content
|
|
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 =
|
|
22068
|
-
if (!["accept", "
|
|
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
|
|
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
|
-
|
|
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) =>
|
|
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 =
|
|
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 =
|
|
22158
|
-
const sessionID =
|
|
22159
|
-
const msgID =
|
|
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:
|
|
22178
|
-
sender_id:
|
|
22179
|
-
event_type:
|
|
22180
|
-
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:
|
|
22183
|
-
agent_id:
|
|
22184
|
-
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:
|
|
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(
|
|
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 =
|
|
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 (
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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:
|
|
22317
|
-
code:
|
|
22318
|
-
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(
|
|
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(
|
|
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:
|
|
22344
|
-
code:
|
|
22345
|
-
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(
|
|
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:
|
|
22359
|
-
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
|
|
22378
|
-
import
|
|
22379
|
-
var
|
|
22380
|
-
function
|
|
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:
|
|
22389
|
-
code:
|
|
22390
|
-
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:
|
|
22400
|
-
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) !==
|
|
21725
|
+
if (!input || typeof input !== "object" || Number(input.schema_version) !== schemaVersion3) {
|
|
22406
21726
|
return null;
|
|
22407
21727
|
}
|
|
22408
|
-
const eventID =
|
|
22409
|
-
const sessionID =
|
|
22410
|
-
const msgID =
|
|
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:
|
|
21735
|
+
schema_version: schemaVersion3,
|
|
22416
21736
|
event_id: eventID,
|
|
22417
21737
|
session_id: sessionID,
|
|
22418
21738
|
msg_id: msgID,
|
|
22419
|
-
quoted_message_id:
|
|
22420
|
-
sender_id:
|
|
22421
|
-
event_type:
|
|
22422
|
-
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:
|
|
22425
|
-
agent_id:
|
|
22426
|
-
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) =>
|
|
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:
|
|
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
|
|
22449
|
-
const filePath =
|
|
22450
|
-
await writeJSONFileAtomic(filePath, { schema_version:
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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 &&
|
|
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 =
|
|
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
|
|
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 =
|
|
22585
|
-
const normalizedKind =
|
|
22586
|
-
const normalizedRefEventID =
|
|
22587
|
-
const normalizedRefMsgID =
|
|
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
|
|
22620
|
-
import
|
|
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
|
|
21941
|
+
var schemaVersion4 = 1;
|
|
22623
21942
|
var defaultRecentEventLimit = 20;
|
|
22624
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
22654
|
-
const eventID =
|
|
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:
|
|
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:
|
|
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) !==
|
|
22003
|
+
if (!input || typeof input !== "object" || Number(input.schema_version) !== schemaVersion4) {
|
|
22677
22004
|
return empty;
|
|
22678
22005
|
}
|
|
22679
22006
|
return {
|
|
22680
|
-
schema_version:
|
|
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
|
|
22014
|
+
return path6.join(resolvePluginDataDir(pluginID), "hook-signals.json");
|
|
22688
22015
|
}
|
|
22689
22016
|
function resolveHookSignalsPathFromDataDir(pluginDataDir, pluginID) {
|
|
22690
|
-
const normalizedDir =
|
|
22017
|
+
const normalizedDir = normalizeString10(pluginDataDir);
|
|
22691
22018
|
if (normalizedDir) {
|
|
22692
|
-
return
|
|
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 =
|
|
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(), {
|
|
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
|
|
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
|
-
|
|
22727
|
-
|
|
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
|
|
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
|
-
|
|
22758
|
-
|
|
22759
|
-
|
|
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
|
-
|
|
22768
|
-
|
|
22769
|
-
|
|
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
|
|
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 ${
|
|
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 =
|
|
22797
|
-
this.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(
|
|
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
|
|
22251
|
+
function normalizeString12(value) {
|
|
22854
22252
|
return String(value ?? "").trim();
|
|
22855
22253
|
}
|
|
22856
22254
|
function parseBearerToken(request) {
|
|
22857
|
-
const header =
|
|
22255
|
+
const header = normalizeString12(request.headers.authorization);
|
|
22858
22256
|
if (!header.toLowerCase().startsWith("bearer ")) {
|
|
22859
22257
|
return "";
|
|
22860
22258
|
}
|
|
22861
|
-
return
|
|
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 =
|
|
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
|
|
22409
|
+
function normalizeString13(value) {
|
|
22999
22410
|
return String(value ?? "").trim();
|
|
23000
22411
|
}
|
|
23001
22412
|
function normalizeOptionalString(value) {
|
|
23002
|
-
const normalized =
|
|
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
|
-
"
|
|
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
|
-
|
|
23659
|
-
|
|
23660
|
-
|
|
22879
|
+
}
|
|
22880
|
+
async shutdown() {
|
|
22881
|
+
if (!this.daemonModeEnabled || !this.workerBridgeClient.isConfigured()) {
|
|
22882
|
+
return;
|
|
23661
22883
|
}
|
|
23662
|
-
|
|
23663
|
-
|
|
23664
|
-
|
|
23665
|
-
|
|
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
|
-
|
|
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
|
|
22898
|
+
function normalizeString14(value) {
|
|
23712
22899
|
return String(value ?? "").trim();
|
|
23713
22900
|
}
|
|
23714
22901
|
function parseBooleanValue(value) {
|
|
23715
|
-
const normalized =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 === "
|
|
23007
|
+
if (response?.type === "text") {
|
|
23800
23008
|
if (fields.length !== 1) {
|
|
23801
|
-
throw new Error("multiple answers require
|
|
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
|
|
23023
|
+
const answeredKeys = /* @__PURE__ */ new Set();
|
|
23815
23024
|
for (const entry of response.entries) {
|
|
23816
|
-
const key =
|
|
23817
|
-
const value =
|
|
23818
|
-
|
|
23819
|
-
|
|
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
|
-
|
|
23825
|
-
|
|
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
|
-
|
|
23832
|
-
answeredIndexes.add(index);
|
|
23037
|
+
answeredKeys.add(key);
|
|
23833
23038
|
content[field.key] = parseFieldValue(field, value);
|
|
23834
23039
|
}
|
|
23835
|
-
if (
|
|
23836
|
-
throw new Error(`expected ${fields.length} answers but received ${
|
|
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 =
|
|
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
|
|
23864
|
-
|
|
23865
|
-
|
|
23866
|
-
|
|
23867
|
-
|
|
23868
|
-
|
|
23869
|
-
|
|
23870
|
-
|
|
23871
|
-
|
|
23872
|
-
|
|
23873
|
-
|
|
23874
|
-
|
|
23875
|
-
|
|
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
|
|
23880
|
-
const
|
|
23881
|
-
if (!
|
|
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 (
|
|
23888
|
-
|
|
23889
|
-
|
|
23890
|
-
|
|
23891
|
-
|
|
23892
|
-
|
|
23893
|
-
|
|
23894
|
-
|
|
23895
|
-
|
|
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
|
|
23902
|
-
if (!
|
|
23903
|
-
await this.
|
|
23904
|
-
|
|
23905
|
-
|
|
23906
|
-
|
|
23907
|
-
|
|
23908
|
-
|
|
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
|
-
|
|
23923
|
-
|
|
23924
|
-
|
|
23925
|
-
|
|
23926
|
-
|
|
23927
|
-
|
|
23928
|
-
|
|
23929
|
-
|
|
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.
|
|
23944
|
-
|
|
23945
|
-
|
|
23946
|
-
|
|
23947
|
-
|
|
23948
|
-
|
|
23949
|
-
|
|
23950
|
-
|
|
23951
|
-
|
|
23952
|
-
|
|
23953
|
-
|
|
23954
|
-
|
|
23955
|
-
|
|
23956
|
-
|
|
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,
|
|
23600
|
+
hookOutput = buildElicitationHookOutput(request, resolved.resolution);
|
|
23965
23601
|
} catch (error2) {
|
|
23966
|
-
|
|
23967
|
-
|
|
23968
|
-
|
|
23969
|
-
|
|
23970
|
-
|
|
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(
|
|
23613
|
+
await this.elicitationStore.resolveRequest(requestID, {
|
|
23983
23614
|
action: hookOutput.action,
|
|
23984
|
-
content: hookOutput.content
|
|
23985
|
-
|
|
23986
|
-
|
|
23987
|
-
|
|
23988
|
-
|
|
23989
|
-
|
|
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: "
|
|
23995
|
-
|
|
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
|
|
23668
|
+
function normalizeString17(value) {
|
|
24078
23669
|
return String(value ?? "").trim();
|
|
24079
23670
|
}
|
|
24080
23671
|
function normalizeOptionalString3(value) {
|
|
24081
|
-
return
|
|
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) =>
|
|
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:
|
|
24179
|
-
event_type:
|
|
24180
|
-
|
|
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:
|
|
23816
|
+
msg_id: normalizeString17(rawPayload.msg_id),
|
|
24183
23817
|
quoted_message_id: normalizeOptionalString3(rawPayload.quoted_message_id),
|
|
24184
|
-
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
|
|
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 (
|
|
24213
|
-
target[key] =
|
|
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 =
|
|
23861
|
+
const explicit = normalizeString18(event?.event_type);
|
|
24227
23862
|
if (explicit) {
|
|
24228
23863
|
return explicit;
|
|
24229
23864
|
}
|
|
24230
|
-
return
|
|
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
|
|
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 (
|
|
23931
|
+
if (normalizeString19(probe.kind) !== probeKind) {
|
|
24295
23932
|
return null;
|
|
24296
23933
|
}
|
|
24297
|
-
const probeID =
|
|
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:
|
|
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
|
|
23991
|
+
function normalizeString20(value) {
|
|
24355
23992
|
return String(value ?? "").trim();
|
|
24356
23993
|
}
|
|
24357
23994
|
function normalizeOptionalString4(value) {
|
|
24358
|
-
const normalized =
|
|
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:
|
|
24064
|
+
event_id: normalizeString20(eventID),
|
|
24428
24065
|
timeout_ms: normalizedTimeoutMs,
|
|
24429
24066
|
deadline_at: deadlineAt,
|
|
24430
|
-
timeout_kind:
|
|
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 =
|
|
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 || !
|
|
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 =
|
|
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 =
|
|
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:
|
|
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
|
|
24217
|
+
function normalizeString21(value) {
|
|
24581
24218
|
return String(value ?? "").trim();
|
|
24582
24219
|
}
|
|
24583
24220
|
function normalizeOptionalString5(value) {
|
|
24584
|
-
const normalized =
|
|
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 (!
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
24590
|
+
function normalizeString22(value) {
|
|
25127
24591
|
return String(value ?? "").trim();
|
|
25128
24592
|
}
|
|
25129
24593
|
function normalizeOptionalString6(value) {
|
|
25130
|
-
const normalized =
|
|
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,
|
|
25194
|
-
if (now - Number(
|
|
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
|
|
25224
|
-
|
|
25225
|
-
|
|
25226
|
-
|
|
25227
|
-
|
|
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
|
|
25240
|
-
|
|
25241
|
-
|
|
25242
|
-
|
|
25243
|
-
|
|
25244
|
-
|
|
25245
|
-
|
|
25246
|
-
|
|
25247
|
-
|
|
25248
|
-
|
|
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: "
|
|
25259
|
-
request_id:
|
|
25260
|
-
|
|
25261
|
-
|
|
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=${
|
|
24692
|
+
`permission request dispatch failed request=${requestID}: ${String(error2)}`
|
|
25267
24693
|
);
|
|
25268
24694
|
}
|
|
25269
24695
|
}
|
|
25270
|
-
async
|
|
25271
|
-
|
|
25272
|
-
|
|
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 (
|
|
25292
|
-
|
|
25293
|
-
|
|
25294
|
-
|
|
25295
|
-
|
|
25296
|
-
|
|
25297
|
-
|
|
25298
|
-
|
|
25299
|
-
|
|
25300
|
-
|
|
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
|
-
|
|
25309
|
-
|
|
25310
|
-
|
|
25311
|
-
|
|
25312
|
-
|
|
25313
|
-
|
|
25314
|
-
|
|
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 (
|
|
25327
|
-
|
|
25328
|
-
|
|
25329
|
-
|
|
25330
|
-
|
|
25331
|
-
|
|
25332
|
-
})
|
|
25333
|
-
|
|
25334
|
-
|
|
25335
|
-
|
|
25336
|
-
|
|
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.
|
|
25345
|
-
|
|
25346
|
-
|
|
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
|
-
|
|
25353
|
-
|
|
25354
|
-
|
|
25355
|
-
|
|
25356
|
-
|
|
25357
|
-
|
|
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(
|
|
25370
|
-
|
|
25371
|
-
|
|
25372
|
-
|
|
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: "
|
|
25387
|
-
|
|
25388
|
-
|
|
25389
|
-
|
|
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
|
|
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
|
|
24839
|
+
function normalizeString23(value) {
|
|
25444
24840
|
return String(value ?? "").trim();
|
|
25445
24841
|
}
|
|
25446
24842
|
function extensionOf(fileName) {
|
|
25447
|
-
const extension =
|
|
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 =
|
|
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 =
|
|
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 (!
|
|
24928
|
+
if (!path7.isAbsolute(normalizedPath)) {
|
|
25533
24929
|
throw new Error(`reply.files requires an absolute path: ${normalizedPath}`);
|
|
25534
24930
|
}
|
|
25535
|
-
const fileStat = await
|
|
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 =
|
|
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:
|
|
25564
|
-
attachment_type:
|
|
25565
|
-
file_name:
|
|
25566
|
-
content_type:
|
|
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
|
|
24971
|
+
function normalizeString24(value) {
|
|
25576
24972
|
return String(value ?? "").trim();
|
|
25577
24973
|
}
|
|
25578
24974
|
function trimTrailingSlash(value) {
|
|
25579
|
-
return
|
|
24975
|
+
return normalizeString24(value).replace(/\/+$/u, "");
|
|
25580
24976
|
}
|
|
25581
24977
|
function parseJSONResponseBody(text) {
|
|
25582
|
-
if (!
|
|
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(
|
|
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 ${
|
|
25002
|
+
Authorization: `Bearer ${normalizeString24(apiKey)}`,
|
|
25607
25003
|
"Content-Type": "application/json"
|
|
25608
25004
|
},
|
|
25609
25005
|
body: JSON.stringify({
|
|
25610
|
-
session_id:
|
|
25611
|
-
filename:
|
|
25612
|
-
content_type:
|
|
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 =
|
|
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 =
|
|
25622
|
-
const accessURL =
|
|
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":
|
|
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
|
|
25120
|
+
function normalizeString25(value) {
|
|
25725
25121
|
return String(value ?? "").trim();
|
|
25726
25122
|
}
|
|
25727
25123
|
function normalizeOptionalString7(value) {
|
|
25728
|
-
const normalized =
|
|
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) =>
|
|
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
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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
|
-
|
|
25924
|
-
|
|
25925
|
-
|
|
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 =
|
|
25932
|
-
const eventID =
|
|
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:
|
|
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 =
|
|
26073
|
-
const 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 =
|
|
26123
|
-
const messageID =
|
|
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.
|
|
26174
|
-
|
|
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.
|
|
26183
|
-
|
|
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.
|
|
26192
|
-
|
|
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.
|
|
26206
|
-
|
|
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.
|
|
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 =
|
|
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
|
|
25656
|
+
function normalizeString26(value) {
|
|
26317
25657
|
return String(value ?? "").trim();
|
|
26318
25658
|
}
|
|
26319
25659
|
function normalizeOptionalString8(value) {
|
|
26320
|
-
const normalized =
|
|
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();
|