kojee-mcp 0.5.3 → 0.5.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +112 -5
- package/dist/{chunk-YEC7IHIG.js → chunk-2BDAM3TH.js} +92 -523
- package/dist/chunk-2MIISF2W.js +35 -0
- package/dist/chunk-3XDJOHMZ.js +223 -0
- package/dist/{chunk-ZW4SW7LJ.js → chunk-64EOLZNI.js} +14 -5
- package/dist/chunk-6SK6ITFE.js +142 -0
- package/dist/chunk-GI2CKKBL.js +46 -0
- package/dist/chunk-HIZ4NDWN.js +141 -0
- package/dist/chunk-LDZXU3DW.js +170 -0
- package/dist/{resubscribe-SLZNA76S.js → chunk-OT2GILXC.js} +1 -0
- package/dist/{chunk-WBMX4CHB.js → chunk-UEGQGXPY.js} +57 -40
- package/dist/chunk-V5VZPYMZ.js +185 -0
- package/dist/{chunk-C6GZ2L2W.js → chunk-X672ZN7V.js} +5 -2
- package/dist/cli.js +47 -24
- package/dist/{codex-stop-hook-JOTBCS5K.js → codex-stop-hook-SWA53ECG.js} +1 -1
- package/dist/control-token-4BUCTYQB.js +13 -0
- package/dist/{doctor-TSHOMT5X.js → doctor-QCQDFLEH.js} +30 -17
- package/dist/{doctor-codex-BMI5JOO6.js → doctor-codex-NZ53ROQA.js} +12 -5
- package/dist/ensure-join-7AEDJMPE.js +96 -0
- package/dist/gateway-client-93P1E0CZ.d.ts +92 -0
- package/dist/{hook-server-QF5JVUHV.js → hook-server-37E2LUKJ.js} +91 -0
- package/dist/index.d.ts +18 -15
- package/dist/index.js +9 -3
- package/dist/lib.d.ts +427 -0
- package/dist/lib.js +44 -0
- package/dist/reconnect-scheduler-JSXCJKQP.js +26 -0
- package/dist/resubscribe-G5OGDZJD.js +6 -0
- package/dist/send-cli-C2F4WTBN.js +72 -0
- package/dist/{stop-hook-SEPWWETV.js → stop-hook-TRAMQYNE.js} +16 -8
- package/dist/{tail-stream-BYKO4DW6.js → tail-stream-VUZBYKXS.js} +4 -3
- package/dist/{user-prompt-submit-hook-ARPEO6FF.js → user-prompt-submit-hook-ZD2XKN7U.js} +7 -1
- package/dist/webhook-config-O4WMQ532.js +20 -0
- package/dist/{webhook-sink-7OYZBWXA.js → webhook-sink-NWGCUDGY.js} +28 -5
- package/dist/{wizard-7KHD5JT4.js → wizard-OSOAY4GO.js} +64 -27
- package/package.json +11 -2
- package/dist/chunk-F7L25L2J.js +0 -60
- package/dist/webhook-config-5TLLX7RA.js +0 -10
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
monitorHeartbeatPath,
|
|
3
|
+
statusLogPath
|
|
4
|
+
} from "./chunk-2TUAFAIW.js";
|
|
4
5
|
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
} from "./chunk-BJMASMKX.js";
|
|
6
|
+
loadControlToken
|
|
7
|
+
} from "./chunk-GI2CKKBL.js";
|
|
8
8
|
import {
|
|
9
9
|
buildMonitorSpawn,
|
|
10
10
|
buildReplyRecipe
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-X672ZN7V.js";
|
|
12
12
|
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
} from "./chunk-
|
|
13
|
+
deriveDiscoveryKey,
|
|
14
|
+
findClaudeAncestorPid
|
|
15
|
+
} from "./chunk-BJMASMKX.js";
|
|
16
|
+
import {
|
|
17
|
+
loadPairedConfig
|
|
18
|
+
} from "./chunk-YH27B6SW.js";
|
|
16
19
|
import {
|
|
17
20
|
discoveryPathForKey,
|
|
18
21
|
readSessionDiscoveryByKey
|
|
@@ -111,8 +114,17 @@ async function collectDoctorReport(deps = {}) {
|
|
|
111
114
|
ok: health !== null,
|
|
112
115
|
detail: health !== null ? "ok" : "unreachable"
|
|
113
116
|
});
|
|
114
|
-
const
|
|
115
|
-
|
|
117
|
+
const readControlToken = deps.readControlToken ?? loadControlToken;
|
|
118
|
+
const controlToken = readControlToken(discovery.controlTokenPath);
|
|
119
|
+
const statusHeaders = controlToken ? { Authorization: `Bearer ${controlToken}` } : {};
|
|
120
|
+
const statusProbe = await probeJsonWithStatus(fetchFn, `${base}/status`, statusHeaders);
|
|
121
|
+
if (statusProbe.json === null && statusProbe.unauthorized) {
|
|
122
|
+
checks.push({
|
|
123
|
+
name: "hook-server /status",
|
|
124
|
+
ok: "warn",
|
|
125
|
+
detail: `401 unauthorized \u2014 /status is gated by the control token (0.5.4); could not present a valid bearer from ${discovery.controlTokenPath ?? "~/.kojee/control-token"} (daemon restarted? re-run doctor; file unreadable? check perms)`
|
|
126
|
+
});
|
|
127
|
+
} else if (statusProbe.json === null && statusProbe.routeAbsent) {
|
|
116
128
|
checks.push({
|
|
117
129
|
name: "hook-server /status",
|
|
118
130
|
ok: "unknown",
|
|
@@ -187,14 +199,15 @@ async function probeJson(fetchFn, url) {
|
|
|
187
199
|
return null;
|
|
188
200
|
}
|
|
189
201
|
}
|
|
190
|
-
async function probeJsonWithStatus(fetchFn, url) {
|
|
202
|
+
async function probeJsonWithStatus(fetchFn, url, headers = {}) {
|
|
191
203
|
try {
|
|
192
|
-
const res = await fetchFn(url);
|
|
193
|
-
if (res.ok) return { json: await res.json(), routeAbsent: false };
|
|
204
|
+
const res = await fetchFn(url, { headers });
|
|
205
|
+
if (res.ok) return { json: await res.json(), routeAbsent: false, unauthorized: false };
|
|
194
206
|
const routeAbsent = res.status === 404 || res.status === 405;
|
|
195
|
-
|
|
207
|
+
const unauthorized = res.status === 401;
|
|
208
|
+
return { json: null, routeAbsent, unauthorized };
|
|
196
209
|
} catch {
|
|
197
|
-
return { json: null, routeAbsent: false };
|
|
210
|
+
return { json: null, routeAbsent: false, unauthorized: false };
|
|
198
211
|
}
|
|
199
212
|
}
|
|
200
213
|
function formatDoctorReport(report) {
|
|
@@ -220,7 +233,7 @@ function formatDoctorReport(report) {
|
|
|
220
233
|
async function runDoctor() {
|
|
221
234
|
const { readRecordedRuntime } = await import("./runtime-record-WO4IECM6.js");
|
|
222
235
|
if (readRecordedRuntime() === "codex") {
|
|
223
|
-
const { collectCodexDoctorReport, formatCodexDoctorReport } = await import("./doctor-codex-
|
|
236
|
+
const { collectCodexDoctorReport, formatCodexDoctorReport } = await import("./doctor-codex-NZ53ROQA.js");
|
|
224
237
|
const report2 = collectCodexDoctorReport();
|
|
225
238
|
console.error(formatCodexDoctorReport(report2));
|
|
226
239
|
return report2.verdict === "broken" ? 1 : 0;
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
defaultCodexConfigPath,
|
|
3
3
|
defaultCodexHooksPath
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-64EOLZNI.js";
|
|
5
5
|
import "./chunk-SQL56SEB.js";
|
|
6
|
+
import {
|
|
7
|
+
resolveWebhookConfig
|
|
8
|
+
} from "./chunk-V5VZPYMZ.js";
|
|
6
9
|
import {
|
|
7
10
|
CODEX_LISTEN_CAP_MS
|
|
8
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-X672ZN7V.js";
|
|
9
12
|
import "./chunk-BLEGIR35.js";
|
|
10
|
-
import {
|
|
11
|
-
resolveWebhookConfig
|
|
12
|
-
} from "./chunk-F7L25L2J.js";
|
|
13
13
|
|
|
14
14
|
// src/doctor-codex.ts
|
|
15
15
|
import fs from "fs";
|
|
@@ -84,6 +84,13 @@ function collectCodexDoctorReport(deps = {}) {
|
|
|
84
84
|
ok: true,
|
|
85
85
|
detail: `enabled \u2014 ${resolution.config.redactedSummary}`
|
|
86
86
|
});
|
|
87
|
+
if (resolution.warning) {
|
|
88
|
+
checks.push({
|
|
89
|
+
name: "webhook signature config",
|
|
90
|
+
ok: "warn",
|
|
91
|
+
detail: `${resolution.warning} \u2014 ${WIZARD_RERUN} with valid signature flags`
|
|
92
|
+
});
|
|
93
|
+
}
|
|
87
94
|
} else if (resolution.error) {
|
|
88
95
|
checks.push({
|
|
89
96
|
name: "webhook sink",
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// src/tandem/ensure-join.ts
|
|
2
|
+
var OBJECT_ID_RE = /^[0-9a-f]{24}$/i;
|
|
3
|
+
var DEFAULT_PER_CALL_TIMEOUT_MS = 1e4;
|
|
4
|
+
function parseTandemsConfig(raw) {
|
|
5
|
+
const trimmed = (raw ?? "").trim();
|
|
6
|
+
if (trimmed.length === 0) return { mode: "auto", ids: [], invalid: [] };
|
|
7
|
+
if (trimmed.toLowerCase() === "none") return { mode: "disabled", ids: [], invalid: [] };
|
|
8
|
+
const ids = [];
|
|
9
|
+
const invalid = [];
|
|
10
|
+
for (const entry of trimmed.split(/[\s,]+/)) {
|
|
11
|
+
if (entry.length === 0) continue;
|
|
12
|
+
(OBJECT_ID_RE.test(entry) ? ids : invalid).push(entry);
|
|
13
|
+
}
|
|
14
|
+
return { mode: "explicit", ids, invalid };
|
|
15
|
+
}
|
|
16
|
+
async function ensureJoinTandems(opts) {
|
|
17
|
+
const log = opts.log ?? ((line) => console.error(line));
|
|
18
|
+
const perCallTimeoutMs = opts.perCallTimeoutMs ?? DEFAULT_PER_CALL_TIMEOUT_MS;
|
|
19
|
+
const config = parseTandemsConfig(opts.env);
|
|
20
|
+
const result = { mode: config.mode, joined: [], already: [], failed: [] };
|
|
21
|
+
if (config.mode === "disabled") {
|
|
22
|
+
log("[ensure-join] disabled (KOJEE_TANDEMS=none)");
|
|
23
|
+
return result;
|
|
24
|
+
}
|
|
25
|
+
for (const bad of config.invalid) {
|
|
26
|
+
log(`[ensure-join] skipping invalid tandem id "${bad}" (not a 24-hex ObjectId)`);
|
|
27
|
+
}
|
|
28
|
+
let ids;
|
|
29
|
+
if (config.mode === "explicit") {
|
|
30
|
+
ids = config.ids;
|
|
31
|
+
log(`[ensure-join] mode=explicit n=${ids.length} (KOJEE_TANDEMS)`);
|
|
32
|
+
} else {
|
|
33
|
+
let listed = null;
|
|
34
|
+
try {
|
|
35
|
+
listed = opts.listTandems ? await opts.listTandems() : null;
|
|
36
|
+
} catch (err) {
|
|
37
|
+
log(`[ensure-join] tandem_list threw: ${err.message}`);
|
|
38
|
+
listed = null;
|
|
39
|
+
}
|
|
40
|
+
if (listed === null) {
|
|
41
|
+
log(
|
|
42
|
+
"[ensure-join] tandem_list failed \u2014 cannot auto re-seat this session (set KOJEE_TANDEMS=<ids> to pin, or KOJEE_TANDEMS=none to silence)"
|
|
43
|
+
);
|
|
44
|
+
return result;
|
|
45
|
+
}
|
|
46
|
+
ids = listed;
|
|
47
|
+
log(`[ensure-join] mode=auto n=${ids.length} (KOJEE_TANDEMS unset \u2014 re-seating where this agent already holds a seat)`);
|
|
48
|
+
}
|
|
49
|
+
for (const id of ids) {
|
|
50
|
+
const outcome = await joinOne(opts.gateway, id, perCallTimeoutMs);
|
|
51
|
+
if (outcome.kind === "failed") {
|
|
52
|
+
result.failed.push(id);
|
|
53
|
+
log(`[ensure-join] ${id}: FAILED \u2014 ${outcome.detail} (continuing)`);
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (outcome.kind === "already") {
|
|
57
|
+
result.already.push(id);
|
|
58
|
+
log(`[ensure-join] ${id}: already seated`);
|
|
59
|
+
} else {
|
|
60
|
+
result.joined.push(id);
|
|
61
|
+
log(`[ensure-join] ${id}: joined fresh`);
|
|
62
|
+
}
|
|
63
|
+
try {
|
|
64
|
+
opts.onJoined?.(id);
|
|
65
|
+
} catch {
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return result;
|
|
69
|
+
}
|
|
70
|
+
async function joinOne(gateway, tandemId, perCallTimeoutMs) {
|
|
71
|
+
const ac = new AbortController();
|
|
72
|
+
const timer = setTimeout(() => ac.abort(), perCallTimeoutMs);
|
|
73
|
+
try {
|
|
74
|
+
const result = await gateway.sendRpc(
|
|
75
|
+
"tools/call",
|
|
76
|
+
{ name: "tandem_join", arguments: { tandem_id: tandemId } },
|
|
77
|
+
ac.signal
|
|
78
|
+
);
|
|
79
|
+
const text = (result.content ?? []).map((c) => typeof c?.text === "string" ? c.text : "").filter(Boolean).join("\n");
|
|
80
|
+
if (result.isError) {
|
|
81
|
+
return { kind: "failed", detail: text || "tandem_join returned an error with no text" };
|
|
82
|
+
}
|
|
83
|
+
if (/already[\s_-]?(a[\s_-]?)?(member|seated|joined)/i.test(text)) {
|
|
84
|
+
return { kind: "already" };
|
|
85
|
+
}
|
|
86
|
+
return { kind: "joined" };
|
|
87
|
+
} catch (err) {
|
|
88
|
+
return { kind: "failed", detail: err?.message ?? String(err) };
|
|
89
|
+
} finally {
|
|
90
|
+
clearTimeout(timer);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
export {
|
|
94
|
+
ensureJoinTandems,
|
|
95
|
+
parseTandemsConfig
|
|
96
|
+
};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { KeyLike, JWK } from 'jose';
|
|
2
|
+
|
|
3
|
+
interface ProxyConfig {
|
|
4
|
+
token: string;
|
|
5
|
+
url: string;
|
|
6
|
+
keystorePath: string;
|
|
7
|
+
/**
|
|
8
|
+
* How credentials were resolved at launch: "token" when `--token` was passed
|
|
9
|
+
* on the CLI, "paired" when read from ~/.kojee/config.json. The proxy records
|
|
10
|
+
* this in its session-discovery file so `kojee-mcp doctor` can render the
|
|
11
|
+
* pairing check honestly on a token-mode box (no config.json by design).
|
|
12
|
+
* Defaults to "paired" when unset (back-compat with callers predating the
|
|
13
|
+
* field).
|
|
14
|
+
*/
|
|
15
|
+
authMode?: "token" | "paired";
|
|
16
|
+
}
|
|
17
|
+
interface KeystoreData {
|
|
18
|
+
private_key_jwk: JWK;
|
|
19
|
+
kid: string;
|
|
20
|
+
broker_url: string;
|
|
21
|
+
public_jwk: JWK;
|
|
22
|
+
enrolled_at: string;
|
|
23
|
+
}
|
|
24
|
+
interface LoadedKeyPair {
|
|
25
|
+
privateKey: KeyLike;
|
|
26
|
+
publicJwk: JWK;
|
|
27
|
+
kid: string;
|
|
28
|
+
}
|
|
29
|
+
interface GovernanceMeta {
|
|
30
|
+
decision: "require_approval" | "deny" | "allow";
|
|
31
|
+
approval_id?: string;
|
|
32
|
+
expires_at?: string;
|
|
33
|
+
triggered_guardrails?: string[];
|
|
34
|
+
}
|
|
35
|
+
interface ToolCallContent {
|
|
36
|
+
type: string;
|
|
37
|
+
text: string;
|
|
38
|
+
}
|
|
39
|
+
interface ToolCallResult {
|
|
40
|
+
content: ToolCallContent[];
|
|
41
|
+
isError?: boolean;
|
|
42
|
+
_meta?: {
|
|
43
|
+
governance?: GovernanceMeta;
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
declare class GatewayClient {
|
|
48
|
+
private readonly brokerUrl;
|
|
49
|
+
private readonly token;
|
|
50
|
+
private readonly privateKey;
|
|
51
|
+
private readonly kid;
|
|
52
|
+
private currentNonce;
|
|
53
|
+
private requestCounter;
|
|
54
|
+
private readonly endpoint;
|
|
55
|
+
constructor(brokerUrl: string, token: string, privateKey: KeyLike, kid: string, sessionId: string);
|
|
56
|
+
/**
|
|
57
|
+
* Expose the DPoP signing key so peer modules sharing auth state
|
|
58
|
+
* (e.g. tandem/event-stream.ts) can sign their own requests.
|
|
59
|
+
*/
|
|
60
|
+
getPrivateKey(): KeyLike;
|
|
61
|
+
/**
|
|
62
|
+
* Expose the bot_key_id (kid) for DPoP proof headers. Paired with
|
|
63
|
+
* getPrivateKey() so peer modules can construct proofs without
|
|
64
|
+
* threading the key material through their own constructors.
|
|
65
|
+
*/
|
|
66
|
+
getKid(): string;
|
|
67
|
+
/**
|
|
68
|
+
* Derive a deterministic session ID from the gateway token.
|
|
69
|
+
* session_id = sha256(token + "proxy").slice(0, 16)
|
|
70
|
+
*/
|
|
71
|
+
static deriveSessionId(token: string): string;
|
|
72
|
+
/**
|
|
73
|
+
* Send a JSON-RPC 2.0 request to the gateway, handling DPoP auth and
|
|
74
|
+
* nonce retry transparently. A 403 `step_up_required` (deprecated feature,
|
|
75
|
+
* owner ruling 2026-06-10) is no longer polled — it surfaces immediately as
|
|
76
|
+
* a structured tool error via translateHttpError.
|
|
77
|
+
*
|
|
78
|
+
* `signal` (ROUND-3 MAJOR A) is a REAL AbortSignal threaded into the
|
|
79
|
+
* underlying `fetch` option — NOT placed inside `params`/`arguments`. A
|
|
80
|
+
* caller with a per-call timeout budget (e.g. resubscribeMemberships) passes
|
|
81
|
+
* its controller's signal here so a hung backend aborts at the budget instead
|
|
82
|
+
* of hanging forever. Putting the signal in `arguments` (the round-2 bug) both
|
|
83
|
+
* left fetch un-aborted AND serialized a junk `{}` onto the wire body.
|
|
84
|
+
*/
|
|
85
|
+
sendRpc(method: string, params?: Record<string, unknown>, signal?: AbortSignal): Promise<ToolCallResult>;
|
|
86
|
+
private executeWithRetries;
|
|
87
|
+
private sendHttpRequest;
|
|
88
|
+
private trackNonce;
|
|
89
|
+
private tryParseErrorBody;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export { GatewayClient as G, type KeystoreData as K, type LoadedKeyPair as L, type ProxyConfig as P, type ToolCallResult as T };
|
|
@@ -1,4 +1,13 @@
|
|
|
1
|
+
import {
|
|
2
|
+
executeSend,
|
|
3
|
+
httpStatusForEnvelope,
|
|
4
|
+
parseSendRequest,
|
|
5
|
+
sendFailure
|
|
6
|
+
} from "./chunk-HIZ4NDWN.js";
|
|
7
|
+
import "./chunk-LDZXU3DW.js";
|
|
8
|
+
|
|
1
9
|
// src/tandem/hook-server.ts
|
|
10
|
+
import crypto from "crypto";
|
|
2
11
|
import { createServer } from "http";
|
|
3
12
|
async function startHookServer(opts) {
|
|
4
13
|
const server = createServer((req, res) => {
|
|
@@ -24,6 +33,12 @@ async function handleRequest(req, res, opts) {
|
|
|
24
33
|
if (req.method === "GET" && url.pathname === "/health") {
|
|
25
34
|
return json(res, 200, { ok: true });
|
|
26
35
|
}
|
|
36
|
+
if (req.method === "GET" && (url.pathname === "/status" || url.pathname === "/poll") && opts.controlToken !== void 0 && !bearerMatches(req.headers.authorization, opts.controlToken)) {
|
|
37
|
+
return json(res, 401, {
|
|
38
|
+
error: "unauthorized",
|
|
39
|
+
detail: "GET /poll and /status are gated by the control token (0.5.4) \u2014 read the file named by the discovery file's controlTokenPath and send it as `Authorization: Bearer <token>`"
|
|
40
|
+
});
|
|
41
|
+
}
|
|
27
42
|
if (req.method === "GET" && url.pathname === "/status") {
|
|
28
43
|
return respondWithStatus(res, opts);
|
|
29
44
|
}
|
|
@@ -38,8 +53,84 @@ async function handleRequest(req, res, opts) {
|
|
|
38
53
|
}
|
|
39
54
|
return json(res, 400, { error: "unknown type", detail: "type must be 'stop' or 'user-prompt-submit'" });
|
|
40
55
|
}
|
|
56
|
+
if (req.method === "POST" && url.pathname === "/send") {
|
|
57
|
+
return handleSend(req, res, opts);
|
|
58
|
+
}
|
|
41
59
|
return json(res, 404, { error: "not_found", detail: `${req.method} ${url.pathname}` });
|
|
42
60
|
}
|
|
61
|
+
var MAX_SEND_BODY_BYTES = 256 * 1024;
|
|
62
|
+
async function handleSend(req, res, opts) {
|
|
63
|
+
if (!opts.send) {
|
|
64
|
+
return respondEnvelope(
|
|
65
|
+
res,
|
|
66
|
+
sendFailure(
|
|
67
|
+
"send_unavailable",
|
|
68
|
+
"this daemon started without a send surface (no gateway/control token wired)"
|
|
69
|
+
)
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
if (!bearerMatches(req.headers.authorization, opts.send.authToken)) {
|
|
73
|
+
return respondEnvelope(
|
|
74
|
+
res,
|
|
75
|
+
sendFailure(
|
|
76
|
+
"unauthorized",
|
|
77
|
+
"missing or invalid bearer \u2014 read the control token from the file named by the discovery file's controlTokenPath and send it as `Authorization: Bearer <token>`"
|
|
78
|
+
)
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
let raw;
|
|
82
|
+
try {
|
|
83
|
+
raw = await readBody(req, MAX_SEND_BODY_BYTES);
|
|
84
|
+
} catch (err) {
|
|
85
|
+
return respondEnvelope(
|
|
86
|
+
res,
|
|
87
|
+
sendFailure("bad_request", err.message)
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
let input;
|
|
91
|
+
try {
|
|
92
|
+
input = JSON.parse(raw);
|
|
93
|
+
} catch {
|
|
94
|
+
return respondEnvelope(
|
|
95
|
+
res,
|
|
96
|
+
sendFailure("bad_request", "request body must be valid JSON")
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
const parsed = parseSendRequest(input);
|
|
100
|
+
if (!parsed.ok) {
|
|
101
|
+
return respondEnvelope(res, parsed);
|
|
102
|
+
}
|
|
103
|
+
const envelope = await executeSend(opts.send.gateway, parsed.request);
|
|
104
|
+
respondEnvelope(res, envelope);
|
|
105
|
+
}
|
|
106
|
+
function respondEnvelope(res, envelope) {
|
|
107
|
+
json(res, httpStatusForEnvelope(envelope), envelope);
|
|
108
|
+
}
|
|
109
|
+
function bearerMatches(header, expected) {
|
|
110
|
+
if (!header || !header.startsWith("Bearer ")) return false;
|
|
111
|
+
const presented = header.slice("Bearer ".length).trim();
|
|
112
|
+
if (presented.length === 0) return false;
|
|
113
|
+
const a = crypto.createHash("sha256").update(presented).digest();
|
|
114
|
+
const b = crypto.createHash("sha256").update(expected).digest();
|
|
115
|
+
return crypto.timingSafeEqual(a, b);
|
|
116
|
+
}
|
|
117
|
+
function readBody(req, maxBytes) {
|
|
118
|
+
return new Promise((resolve, reject) => {
|
|
119
|
+
const chunks = [];
|
|
120
|
+
let total = 0;
|
|
121
|
+
req.on("data", (chunk) => {
|
|
122
|
+
total += chunk.length;
|
|
123
|
+
if (total > maxBytes) {
|
|
124
|
+
reject(new Error(`request body exceeds ${maxBytes} bytes`));
|
|
125
|
+
req.destroy();
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
chunks.push(chunk);
|
|
129
|
+
});
|
|
130
|
+
req.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
|
|
131
|
+
req.on("error", reject);
|
|
132
|
+
});
|
|
133
|
+
}
|
|
43
134
|
function respondWithEvents(res, opts) {
|
|
44
135
|
const entries = opts.queue.takeForHook();
|
|
45
136
|
const events = entries.map((entry) => opts.adapter.formatTandemEvent(entry.event));
|
package/dist/index.d.ts
CHANGED
|
@@ -1,18 +1,21 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
url: string;
|
|
4
|
-
keystorePath: string;
|
|
5
|
-
/**
|
|
6
|
-
* How credentials were resolved at launch: "token" when `--token` was passed
|
|
7
|
-
* on the CLI, "paired" when read from ~/.kojee/config.json. The proxy records
|
|
8
|
-
* this in its session-discovery file so `kojee-mcp doctor` can render the
|
|
9
|
-
* pairing check honestly on a token-mode box (no config.json by design).
|
|
10
|
-
* Defaults to "paired" when unset (back-compat with callers predating the
|
|
11
|
-
* field).
|
|
12
|
-
*/
|
|
13
|
-
authMode?: "token" | "paired";
|
|
14
|
-
}
|
|
1
|
+
import { G as GatewayClient, P as ProxyConfig } from './gateway-client-93P1E0CZ.js';
|
|
2
|
+
import 'jose';
|
|
15
3
|
|
|
4
|
+
/**
|
|
5
|
+
* List the tandem_ids where THIS AGENT holds an active seat, via a
|
|
6
|
+
* `tandem_list` tool call. The backend's tandem_list is PRINCIPAL-scoped:
|
|
7
|
+
* rows include rooms where only SIBLING agents of the principal sit
|
|
8
|
+
* (`my_membership: {is_member:false, principal_is_member:true}`). This is
|
|
9
|
+
* the auto ensure-join feed, so object rows are filtered to
|
|
10
|
+
* `my_membership.is_member === true` — FAIL CLOSED: a row missing the flag
|
|
11
|
+
* is excluded, because joining a room the agent was never in is the harm
|
|
12
|
+
* (live-reproduced 2026-06-10: 5 rows listed, only 2 agent seats).
|
|
13
|
+
* Returns the id array, or null when the list could not be determined (tool
|
|
14
|
+
* error or unparseable result) — null is the "unknown" signal callers map to a
|
|
15
|
+
* -1 membership count. MINOR 6: called fresh on every reconnect so the touch
|
|
16
|
+
* set tracks mid-session joins (not boot-frozen).
|
|
17
|
+
*/
|
|
18
|
+
declare function listTandemIds(gateway: GatewayClient): Promise<string[] | null>;
|
|
16
19
|
declare function startProxy(config: ProxyConfig): Promise<void>;
|
|
17
20
|
|
|
18
|
-
export {
|
|
21
|
+
export { ProxyConfig, listTandemIds, startProxy };
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import {
|
|
2
|
+
listTandemIds,
|
|
2
3
|
startProxy
|
|
3
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-2BDAM3TH.js";
|
|
5
|
+
import "./chunk-X672ZN7V.js";
|
|
4
6
|
import "./chunk-BJMASMKX.js";
|
|
5
|
-
import "./chunk-
|
|
6
|
-
import "./chunk-
|
|
7
|
+
import "./chunk-6SK6ITFE.js";
|
|
8
|
+
import "./chunk-3XDJOHMZ.js";
|
|
9
|
+
import "./chunk-UEGQGXPY.js";
|
|
10
|
+
import "./chunk-2MIISF2W.js";
|
|
11
|
+
import "./chunk-LDZXU3DW.js";
|
|
7
12
|
import "./chunk-BLEGIR35.js";
|
|
8
13
|
export {
|
|
14
|
+
listTandemIds,
|
|
9
15
|
startProxy
|
|
10
16
|
};
|