kojee-mcp 0.7.5-staging.2 → 0.8.0-beta.0
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 +100 -0
- package/dist/{chunk-67F67AQ6.js → chunk-3N4YYIOJ.js} +28 -0
- package/dist/{chunk-WBXU27BF.js → chunk-7J5CSXGS.js} +1 -1
- package/dist/{chunk-XPIW4N55.js → chunk-BO52SILE.js} +1 -1
- package/dist/{chunk-TCWIXG5C.js → chunk-D5VPUCPY.js} +1 -1
- package/dist/{chunk-CUOJHP2S.js → chunk-GP7XNGUR.js} +10 -2
- package/dist/{chunk-TNZITJAB.js → chunk-HMS4PZLC.js} +1 -1
- package/dist/chunk-JRCHS6CP.js +14 -0
- package/dist/chunk-NMHM6SII.js +118 -0
- package/dist/{chunk-A4IOKD4Z.js → chunk-P2R3KWYK.js} +1 -1
- package/dist/{chunk-FJUAMJHU.js → chunk-QLARIAUJ.js} +26 -2
- package/dist/{chunk-ZSHCHOPL.js → chunk-TXGO2W5I.js} +6 -14
- package/dist/{send-cli-45RLGYIC.js → chunk-VGZ4M3FP.js} +8 -11
- package/dist/{chunk-PKOBR7ZN.js → chunk-WZP7HXXN.js} +9 -0
- package/dist/cli.js +75 -13
- package/dist/{codex-prompt-submit-hook-J5PZEJSK.js → codex-prompt-submit-hook-6QUCHZB2.js} +1 -1
- package/dist/{codex-stop-hook-32W4BIOM.js → codex-stop-hook-5LVFGGFE.js} +1 -1
- package/dist/{connect-handler-NZINEMG3.js → connect-handler-5NACO67R.js} +2 -2
- package/dist/cowork-E46Z2QLU.js +370 -0
- package/dist/cowork-worker-RSXWOYUQ.js +316 -0
- package/dist/{doctor-WUU5BVPT.js → doctor-DK72HJI3.js} +4 -4
- package/dist/{doctor-codex-PJFWIABF.js → doctor-codex-MECEBXJC.js} +1 -1
- package/dist/{event-log-2NBJEIEP.js → event-log-QLWB22KX.js} +2 -2
- package/dist/{event-stream-ATKYXDBV.js → event-stream-L432EP4U.js} +1 -1
- package/dist/{hook-server-CWBH6PFL.js → hook-server-6EVSFLNW.js} +1 -1
- package/dist/index.js +5 -4
- package/dist/lib.d.ts +37 -1
- package/dist/lib.js +1 -1
- package/dist/messages-cli-KKFCKV55.js +24 -0
- package/dist/{registry-XJ67EO22.js → registry-E2DW73RE.js} +14 -14
- package/dist/send-cli-NPFQWSBM.js +18 -0
- package/dist/{server-DU3LFS32.js → server-D5VAMAQW.js} +2 -2
- package/dist/{session-discovery-NHCZ2XWA.js → session-discovery-4LZNLBPN.js} +3 -1
- package/dist/{setup-handler-44ASXYMS.js → setup-handler-ANXNXK6I.js} +2 -2
- package/dist/{stop-hook-N6TX4YQT.js → stop-hook-QZRUUX22.js} +4 -4
- package/dist/{tail-stream-G4WGTXDS.js → tail-stream-EK66YLNF.js} +103 -25
- package/dist/{user-prompt-submit-hook-DTXXFDSD.js → user-prompt-submit-hook-O7KIT3RR.js} +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -555,6 +555,106 @@ literal URL in the body; de-fang or split it; this is **not** an auth failure),
|
|
|
555
555
|
`approval_required`, `not_paired`, `not_enrolled`, `bad_request`,
|
|
556
556
|
`unauthorized`, `send_unavailable`, `send_failed` (raw text preserved).
|
|
557
557
|
|
|
558
|
+
## Claude Co-Work (paste-one-command wake)
|
|
559
|
+
|
|
560
|
+
**Claude Co-Work** is a cloud-sandbox harness with no MCP registration and no
|
|
561
|
+
restart available mid-session, so it can't use the `init`-wired MCP proxy above.
|
|
562
|
+
Instead, one line — generated from the Kojee dashboard and pasted into the
|
|
563
|
+
agent's first turn — enrolls the sandbox and gets it streaming Tandem wakes
|
|
564
|
+
immediately. This is a separate, parallel wake path from the MCP proxy; it does
|
|
565
|
+
not use `startProxy` at all.
|
|
566
|
+
|
|
567
|
+
### The pasted command
|
|
568
|
+
|
|
569
|
+
```bash
|
|
570
|
+
KOJEE_TOKEN=<token> npx -y kojee-mcp@<version> cowork --tandem <tandem_id> --url <broker>
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
The dashboard fills in `<token>`, `<version>` (the currently published
|
|
574
|
+
`kojee-mcp` version), `<tandem_id>`, and `<broker>` when it generates this line
|
|
575
|
+
for you. Flags:
|
|
576
|
+
|
|
577
|
+
| Flag | Required | Description |
|
|
578
|
+
|---|---|---|
|
|
579
|
+
| `--tandem <id>` | yes, to launch | Tandem id to subscribe to. |
|
|
580
|
+
| `--url <url>` | yes, to launch | Broker base URL — the token does not encode it. |
|
|
581
|
+
| `--token <token>` | no (fallback) | Gateway token fallback. The canonical intake is the `KOJEE_TOKEN=` env prefix (keeps the secret off `/proc/<pid>/cmdline` and npm debug logs); `--token` costs a few seconds of argv exposure and is never forwarded to the worker process. |
|
|
582
|
+
| `--force` | no | Override the clobber guard — replace a different token/broker already planted in `~/.kojee/config.json`. Without it, `cowork` refuses to overwrite a different credential (protects against an accidental paste evicting another sandbox's DPoP key — see the warning below). |
|
|
583
|
+
| `--detach` | no | Host the worker as a detached background daemon (hidden `cowork-daemon` subcommand) instead of the default Monitor-hosted worker. |
|
|
584
|
+
|
|
585
|
+
Run once, in the foreground, `cowork` enrolls the token into the paired DPoP
|
|
586
|
+
slot, plants `~/.kojee/config.json` + `~/.kojee/keypair.json`, joins the
|
|
587
|
+
tandem, and runs a startup barrier read — so a bad or revoked token, or a
|
|
588
|
+
token that isn't a member of the tandem, fails loudly at paste time (nonzero
|
|
589
|
+
exit, no Monitor recipe printed) instead of silently starting deaf.
|
|
590
|
+
|
|
591
|
+
### What the agent does with the printed output
|
|
592
|
+
|
|
593
|
+
On success `cowork` prints a recipe similar to:
|
|
594
|
+
|
|
595
|
+
```
|
|
596
|
+
[kojee cowork] enrolled + subscribed to tandem_abc123 (seat cowork-9f2a1c7b4e05).
|
|
597
|
+
[kojee cowork] baseline cursor = 4821. NOTE: this command contains a secret token — don't re-share this transcript.
|
|
598
|
+
|
|
599
|
+
1) Arm the wake watcher ONCE (persistent Monitor):
|
|
600
|
+
Monitor(command=`npx kojee-mcp@<version> cowork-stream --tandem tandem_abc123`, persistent=true, description="kojee Tandem events")
|
|
601
|
+
...
|
|
602
|
+
```
|
|
603
|
+
|
|
604
|
+
The agent should:
|
|
605
|
+
|
|
606
|
+
1. **Arm the Monitor exactly once**, using the printed command — e.g.
|
|
607
|
+
`Monitor(command=\`npx kojee-mcp@<version> cowork-stream --tandem <tandem_id>\`, persistent=true, description="kojee Tandem events")`.
|
|
608
|
+
This spawns `cowork-stream`, which subscribes over SSE, writes the event
|
|
609
|
+
log, and fuses a tail of it onto its own stdout — each line is one Monitor
|
|
610
|
+
wake, carrying `msg=<id>` and `cursor=<n>`. If a watcher is already armed,
|
|
611
|
+
don't arm a second one.
|
|
612
|
+
2. **Catch up once, right after arming** — `npx kojee-mcp@<version> messages <tandem_id> --since <baseline cursor>` — to cover the gap between paste and Monitor arm.
|
|
613
|
+
3. **Reply via the `send` CLI, never MCP** (there are no MCP tools in this
|
|
614
|
+
session) — `npx kojee-mcp@<version> send <tandem_id> --body "..." --reply-to <msg>`.
|
|
615
|
+
One JSON envelope on stdout: `{ok:true, message_id, cursor}` (exit 0) or
|
|
616
|
+
`{ok:false, error, message}` (exit 1). Surface `content_blocked` /
|
|
617
|
+
`rate_limited` / `gateway_auth` distinctly — don't blind-retry.
|
|
618
|
+
4. **Pull full, untruncated context on any gap** with the same `messages
|
|
619
|
+
--since <last cursor you saw>` command — e.g. after a cursor jump or a
|
|
620
|
+
`status=rotated` reset (event lines on the Monitor stream are truncated to
|
|
621
|
+
200 characters).
|
|
622
|
+
|
|
623
|
+
Lines the worker prints that start with `[kojee]` (e.g. `[kojee] log=<path>`,
|
|
624
|
+
`[kojee] WARNING: ...`, `[kojee] WORKER EXITED ...`) are telemetry, not Tandem
|
|
625
|
+
messages — never reply to them.
|
|
626
|
+
|
|
627
|
+
### Stopping it
|
|
628
|
+
|
|
629
|
+
```bash
|
|
630
|
+
npx kojee-mcp@<version> cowork stop --tandem <tandem_id>
|
|
631
|
+
```
|
|
632
|
+
|
|
633
|
+
Scans for live cowork workers on that tandem (omit `--tandem` to stop all live
|
|
634
|
+
cowork workers on the box), SIGTERMs each, and cleans up its discovery file.
|
|
635
|
+
|
|
636
|
+
**Merged (default) vs `--detach`:** by default the persistent Monitor *is* the
|
|
637
|
+
host for the worker (`cowork-stream`), so if the wake stream dies the harness
|
|
638
|
+
sees the Monitor's own process exit. `--detach` instead spawns a headless
|
|
639
|
+
`cowork-daemon` child in the background and has the agent's Monitor tail its
|
|
640
|
+
event-log file — use it only on a sandbox that won't keep a long-lived Monitor
|
|
641
|
+
child alive.
|
|
642
|
+
|
|
643
|
+
> **WARNING — mint a fresh token per sandbox.** Enrolling with `cowork` claims
|
|
644
|
+
> the token's single DPoP key slot: the Kojee backend keeps **exactly one**
|
|
645
|
+
> DPoP key per gateway token, and every enrollment **replaces** it. Paste the
|
|
646
|
+
> *same* token into a second sandbox and the first sandbox's credential is
|
|
647
|
+
> silently evicted — it goes offline. Mint a **fresh** cowork token per
|
|
648
|
+
> sandbox from the dashboard; never reuse one across sandboxes.
|
|
649
|
+
>
|
|
650
|
+
> The pasted command also **contains a secret token in plaintext**. It
|
|
651
|
+
> persists in the agent's transcript and shell history for as long as those
|
|
652
|
+
> are retained — don't re-share the transcript, and treat the token as
|
|
653
|
+
> compromised if it does leak out.
|
|
654
|
+
|
|
655
|
+
Keep the `@<version>` pin — seat coherence relies on a feature that only
|
|
656
|
+
exists in that version; do not drop it.
|
|
657
|
+
|
|
558
658
|
## Development
|
|
559
659
|
|
|
560
660
|
Run tests:
|
|
@@ -73,6 +73,33 @@ function readSessionDiscoveryByKey(key) {
|
|
|
73
73
|
return null;
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
|
+
function scanLiveCoworkWorkers(opts = {}) {
|
|
77
|
+
const dir = opts.dir ?? sessionDiscoveryDir();
|
|
78
|
+
const alive = opts.isAlive ?? isProcessAlive;
|
|
79
|
+
let names;
|
|
80
|
+
try {
|
|
81
|
+
names = fs.readdirSync(dir);
|
|
82
|
+
} catch {
|
|
83
|
+
return [];
|
|
84
|
+
}
|
|
85
|
+
const out = [];
|
|
86
|
+
for (const name of names) {
|
|
87
|
+
if (!name.startsWith("cc-") || !name.endsWith(".json")) continue;
|
|
88
|
+
const filePath = path.join(dir, name);
|
|
89
|
+
let entry = null;
|
|
90
|
+
try {
|
|
91
|
+
entry = JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
92
|
+
} catch {
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (!entry || entry.origin !== "cowork") continue;
|
|
96
|
+
const pid = entry.proxyPid ?? entry.pid;
|
|
97
|
+
if (typeof pid !== "number" || !alive(pid)) continue;
|
|
98
|
+
const key = name.slice("cc-".length, -".json".length);
|
|
99
|
+
out.push({ key, entry, path: filePath, pid });
|
|
100
|
+
}
|
|
101
|
+
return out;
|
|
102
|
+
}
|
|
76
103
|
var DEFAULT_SWEEP_MIN_AGE_MS = 6e4;
|
|
77
104
|
function isProcessAlive(pid) {
|
|
78
105
|
try {
|
|
@@ -143,5 +170,6 @@ export {
|
|
|
143
170
|
writeDiscoveryByKey,
|
|
144
171
|
cleanupDiscoveryByKey,
|
|
145
172
|
readSessionDiscoveryByKey,
|
|
173
|
+
scanLiveCoworkWorkers,
|
|
146
174
|
sweepStaleDiscovery
|
|
147
175
|
};
|
|
@@ -82,6 +82,13 @@ async function startEventStream(opts) {
|
|
|
82
82
|
headersTimeout: UNDICI_DEFAULT_BODY_TIMEOUT_MS
|
|
83
83
|
});
|
|
84
84
|
const cursors = /* @__PURE__ */ new Map();
|
|
85
|
+
if (opts.initialCursors) {
|
|
86
|
+
for (const [tandemId, cursor] of Object.entries(opts.initialCursors)) {
|
|
87
|
+
if (typeof cursor === "number" && Number.isFinite(cursor)) {
|
|
88
|
+
cursors.set(tandemId, cursor);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
85
92
|
let currentController = new AbortController();
|
|
86
93
|
let connectGeneration = 0;
|
|
87
94
|
const state = {
|
|
@@ -273,10 +280,11 @@ async function consumeSse(body, opts, controller, state, onCursor) {
|
|
|
273
280
|
console.error("[event-stream] event-log append failed:", err);
|
|
274
281
|
}
|
|
275
282
|
}
|
|
276
|
-
|
|
283
|
+
const channelServer = opts.server;
|
|
284
|
+
if (opts.adapter.supportsChannels && channelServer) {
|
|
277
285
|
try {
|
|
278
286
|
const channel = opts.adapter.formatTandemEvent(parsed);
|
|
279
|
-
await
|
|
287
|
+
await channelServer.notification({
|
|
280
288
|
method: "notifications/claude/channel",
|
|
281
289
|
params: channel
|
|
282
290
|
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// src/adapters/unknown.ts
|
|
2
|
+
var unknownAdapter = {
|
|
3
|
+
runtime: "unknown",
|
|
4
|
+
supportsChannels: false,
|
|
5
|
+
formatTandemEvent() {
|
|
6
|
+
throw new Error(
|
|
7
|
+
"unknownAdapter.formatTandemEvent() is unreachable \u2014 caller should gate on supportsChannels"
|
|
8
|
+
);
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export {
|
|
13
|
+
unknownAdapter
|
|
14
|
+
};
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createPairedGateway
|
|
3
|
+
} from "./chunk-VGZ4M3FP.js";
|
|
4
|
+
import {
|
|
5
|
+
classifySendFailure,
|
|
6
|
+
ensureJoined,
|
|
7
|
+
sendFailure
|
|
8
|
+
} from "./chunk-WZP7HXXN.js";
|
|
9
|
+
import {
|
|
10
|
+
translateToolCallResult
|
|
11
|
+
} from "./chunk-PPTKGWFF.js";
|
|
12
|
+
|
|
13
|
+
// src/tandem/messages-cli.ts
|
|
14
|
+
import os from "os";
|
|
15
|
+
import path from "path";
|
|
16
|
+
function parseMessagesRequest(input) {
|
|
17
|
+
const tandemId = input.tandem_id;
|
|
18
|
+
if (typeof tandemId !== "string" || tandemId.length === 0) {
|
|
19
|
+
return sendFailure("bad_request", "tandem_id is required and must be a non-empty string");
|
|
20
|
+
}
|
|
21
|
+
const request = { tandem_id: tandemId };
|
|
22
|
+
if (input.since !== void 0) {
|
|
23
|
+
if (typeof input.since !== "string" || input.since.length === 0) {
|
|
24
|
+
return sendFailure("bad_request", "--since must be a non-empty cursor when present");
|
|
25
|
+
}
|
|
26
|
+
request.since = input.since;
|
|
27
|
+
}
|
|
28
|
+
if (input.limit !== void 0) {
|
|
29
|
+
const raw = String(input.limit);
|
|
30
|
+
if (!/^\d+$/.test(raw)) {
|
|
31
|
+
return sendFailure("bad_request", "--limit must be a positive integer");
|
|
32
|
+
}
|
|
33
|
+
const n = Number.parseInt(raw, 10);
|
|
34
|
+
if (!Number.isFinite(n) || n <= 0) {
|
|
35
|
+
return sendFailure("bad_request", "--limit must be a positive integer");
|
|
36
|
+
}
|
|
37
|
+
request.limit = n;
|
|
38
|
+
}
|
|
39
|
+
return { ok: true, request };
|
|
40
|
+
}
|
|
41
|
+
function deriveMaxCursor(messages) {
|
|
42
|
+
let max = null;
|
|
43
|
+
for (const m of messages) {
|
|
44
|
+
if (m !== null && typeof m === "object") {
|
|
45
|
+
const c = m["cursor"];
|
|
46
|
+
if (typeof c === "number" && (max === null || c > max)) max = c;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return max;
|
|
50
|
+
}
|
|
51
|
+
async function executeRead(gateway, request) {
|
|
52
|
+
const sinceArg = request.since !== void 0 ? /^\d+$/.test(request.since) ? Number(request.since) : request.since : void 0;
|
|
53
|
+
const args = {
|
|
54
|
+
tandem_id: request.tandem_id,
|
|
55
|
+
...sinceArg !== void 0 ? { since: sinceArg } : {},
|
|
56
|
+
...request.limit !== void 0 ? { limit: request.limit } : {}
|
|
57
|
+
};
|
|
58
|
+
let result;
|
|
59
|
+
try {
|
|
60
|
+
result = await gateway.sendRpc("tools/call", {
|
|
61
|
+
name: "tandem_messages",
|
|
62
|
+
arguments: args
|
|
63
|
+
});
|
|
64
|
+
} catch (err) {
|
|
65
|
+
return classifySendFailure(
|
|
66
|
+
`tandem_messages failed: ${err?.message ?? String(err)}`
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
const translated = translateToolCallResult(result);
|
|
70
|
+
const text = (translated.content ?? []).map((c) => typeof c?.text === "string" ? c.text : "").filter(Boolean).join("\n");
|
|
71
|
+
if (translated.isError) {
|
|
72
|
+
return classifySendFailure(text || "tandem_messages returned an error with no text");
|
|
73
|
+
}
|
|
74
|
+
let messages = [];
|
|
75
|
+
let cursor = null;
|
|
76
|
+
try {
|
|
77
|
+
const parsed = JSON.parse(text);
|
|
78
|
+
if (Array.isArray(parsed)) {
|
|
79
|
+
messages = parsed;
|
|
80
|
+
} else if (parsed !== null && typeof parsed === "object") {
|
|
81
|
+
const obj = parsed;
|
|
82
|
+
if (Array.isArray(obj["messages"])) messages = obj["messages"];
|
|
83
|
+
const rawCursor = obj["cursor"];
|
|
84
|
+
if (typeof rawCursor === "number") cursor = rawCursor;
|
|
85
|
+
}
|
|
86
|
+
if (cursor === null) cursor = deriveMaxCursor(messages);
|
|
87
|
+
} catch {
|
|
88
|
+
}
|
|
89
|
+
return { ok: true, tandem_id: request.tandem_id, messages, cursor, text };
|
|
90
|
+
}
|
|
91
|
+
async function runMessagesCli(args, deps = {}) {
|
|
92
|
+
const parsed = parseMessagesRequest({
|
|
93
|
+
tandem_id: args.tandemId,
|
|
94
|
+
...args.since !== void 0 ? { since: args.since } : {},
|
|
95
|
+
...args.limit !== void 0 ? { limit: args.limit } : {}
|
|
96
|
+
});
|
|
97
|
+
if (!parsed.ok) {
|
|
98
|
+
return { exitCode: 1, envelope: parsed };
|
|
99
|
+
}
|
|
100
|
+
const kojeeDir = args.kojeeDir ?? path.join(os.homedir(), ".kojee");
|
|
101
|
+
const createGateway = deps.createGateway ?? createPairedGateway;
|
|
102
|
+
const resolution = await createGateway(kojeeDir);
|
|
103
|
+
if (!resolution.ok) {
|
|
104
|
+
return { exitCode: 1, envelope: resolution };
|
|
105
|
+
}
|
|
106
|
+
const guard = await ensureJoined(resolution.gateway, parsed.request.tandem_id);
|
|
107
|
+
if (!guard.ok) {
|
|
108
|
+
return { exitCode: 1, envelope: guard.failure };
|
|
109
|
+
}
|
|
110
|
+
const envelope = await executeRead(resolution.gateway, parsed.request);
|
|
111
|
+
return { exitCode: envelope.ok ? 0 : 1, envelope };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export {
|
|
115
|
+
parseMessagesRequest,
|
|
116
|
+
executeRead,
|
|
117
|
+
runMessagesCli
|
|
118
|
+
};
|
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
// src/tandem/recipe.ts
|
|
2
2
|
var SEND_BODY_PARAM = "body";
|
|
3
|
+
var KOJEE_PACKAGE = "kojee-mcp";
|
|
4
|
+
var MONITOR_DESCRIPTION = "kojee Tandem events";
|
|
5
|
+
function pinnedPackage(version) {
|
|
6
|
+
return `${KOJEE_PACKAGE}@${version}`;
|
|
7
|
+
}
|
|
8
|
+
function buildMonitorSpawnFor(command) {
|
|
9
|
+
return `Monitor(command=\`${command}\`, persistent=true, description="${MONITOR_DESCRIPTION}")`;
|
|
10
|
+
}
|
|
3
11
|
function buildMonitorCommand(logPath) {
|
|
4
|
-
return `npx
|
|
12
|
+
return `npx ${KOJEE_PACKAGE} tail "${logPath}"`;
|
|
5
13
|
}
|
|
6
14
|
function buildMonitorSpawn(logPath) {
|
|
7
|
-
return
|
|
15
|
+
return buildMonitorSpawnFor(buildMonitorCommand(logPath));
|
|
8
16
|
}
|
|
9
17
|
function buildReplyRecipe(event) {
|
|
10
18
|
if (event) {
|
|
@@ -21,6 +29,18 @@ function buildCondensedTandemRules() {
|
|
|
21
29
|
function buildMonitorNudge(logPath) {
|
|
22
30
|
return `[kojee] Tandem events are being logged but no Monitor is reading them \u2014 you may be missing wake notifications. Spawn the watcher once: ${buildMonitorSpawn(logPath)}. Then ${buildReplyRecipe()}.`;
|
|
23
31
|
}
|
|
32
|
+
function buildCoworkMonitorSpawn(tandemId, version) {
|
|
33
|
+
return buildMonitorSpawnFor(
|
|
34
|
+
`npx ${pinnedPackage(version)} cowork-stream --tandem ${tandemId}`
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
function buildCliReplyRecipe(tandemId, version) {
|
|
38
|
+
return `npx ${pinnedPackage(version)} send ${tandemId} --${SEND_BODY_PARAM} "..." --reply-to <msg>`;
|
|
39
|
+
}
|
|
40
|
+
function buildCliCatchUpNote(tandemId, version, sinceCursor) {
|
|
41
|
+
const since = sinceCursor !== void 0 ? String(sinceCursor) : "<cursor>";
|
|
42
|
+
return `npx ${pinnedPackage(version)} messages ${tandemId} --since ${since}`;
|
|
43
|
+
}
|
|
24
44
|
var WEBHOOK_BODY_SHAPE = "{ type, id, tandem_id, cursor, time, from{ member_id, principal, agent_id?, session_id?, displayname }, kind, content{ body, format? }, mentions?, reply_to?, severity? }";
|
|
25
45
|
function buildWebhookReceiverNote(sig) {
|
|
26
46
|
const header = sig?.header ?? "X-Kojee-Signature";
|
|
@@ -32,11 +52,15 @@ var CODEX_LISTEN_CAP_MS = 8e3;
|
|
|
32
52
|
var CODEX_WAKE_BELL = "[kojee] Tandem events may be pending. Call tandem_pending now and drain each room it lists (tandem_messages(id, since=cursor)), then reply in the room with a normal message (kind=message \u2014 never status). If it returns none for you, ignore this.";
|
|
33
53
|
|
|
34
54
|
export {
|
|
55
|
+
pinnedPackage,
|
|
35
56
|
buildMonitorSpawn,
|
|
36
57
|
buildReplyRecipe,
|
|
37
58
|
buildCatchUpNote,
|
|
38
59
|
buildCondensedTandemRules,
|
|
39
60
|
buildMonitorNudge,
|
|
61
|
+
buildCoworkMonitorSpawn,
|
|
62
|
+
buildCliReplyRecipe,
|
|
63
|
+
buildCliCatchUpNote,
|
|
40
64
|
buildWebhookReceiverNote,
|
|
41
65
|
CODEX_LISTEN_CAP_MS,
|
|
42
66
|
CODEX_WAKE_BELL
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import {
|
|
2
|
+
unknownAdapter
|
|
3
|
+
} from "./chunk-JRCHS6CP.js";
|
|
1
4
|
import {
|
|
2
5
|
claudeCodeAdapter
|
|
3
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-D5VPUCPY.js";
|
|
4
7
|
import {
|
|
5
8
|
GatewayClient,
|
|
6
9
|
applyStableSessionId
|
|
@@ -11,7 +14,7 @@ import {
|
|
|
11
14
|
import {
|
|
12
15
|
createMcpServer,
|
|
13
16
|
startMcpServer
|
|
14
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-P2R3KWYK.js";
|
|
15
18
|
import {
|
|
16
19
|
findClaudeAncestorPid
|
|
17
20
|
} from "./chunk-XJEBJIQE.js";
|
|
@@ -181,17 +184,6 @@ var openclawAdapter = {
|
|
|
181
184
|
}
|
|
182
185
|
};
|
|
183
186
|
|
|
184
|
-
// src/adapters/unknown.ts
|
|
185
|
-
var unknownAdapter = {
|
|
186
|
-
runtime: "unknown",
|
|
187
|
-
supportsChannels: false,
|
|
188
|
-
formatTandemEvent() {
|
|
189
|
-
throw new Error(
|
|
190
|
-
"unknownAdapter.formatTandemEvent() is unreachable \u2014 caller should gate on supportsChannels"
|
|
191
|
-
);
|
|
192
|
-
}
|
|
193
|
-
};
|
|
194
|
-
|
|
195
187
|
// src/index.ts
|
|
196
188
|
var DEFAULT_KEYSTORE_PATH = path.join(os.homedir(), ".kojee", "keypair.json");
|
|
197
189
|
function isDPoPEnrollmentError(err) {
|
|
@@ -295,7 +287,7 @@ async function startProxy(config) {
|
|
|
295
287
|
}
|
|
296
288
|
console.error(`[kojee-mcp] Tandem memberships: ${tandemMembershipCount === -1 ? "unknown" : tandemMembershipCount}`);
|
|
297
289
|
let server;
|
|
298
|
-
const { selectDelivery } = await import("./registry-
|
|
290
|
+
const { selectDelivery } = await import("./registry-E2DW73RE.js");
|
|
299
291
|
const delivery = selectDelivery(adapter.runtime, {
|
|
300
292
|
supportsChannels: adapter.supportsChannels,
|
|
301
293
|
// Per-window delivered mirror for the tandem_pending tool (codex only —
|
|
@@ -2,11 +2,11 @@ import {
|
|
|
2
2
|
resolvePairedSlot
|
|
3
3
|
} from "./chunk-EIH2LNF4.js";
|
|
4
4
|
import {
|
|
5
|
-
|
|
5
|
+
ensureJoined,
|
|
6
6
|
executeSend,
|
|
7
7
|
parseSendRequest,
|
|
8
8
|
sendFailure
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-WZP7HXXN.js";
|
|
10
10
|
import {
|
|
11
11
|
loadPairedConfig
|
|
12
12
|
} from "./chunk-5SZHXYPK.js";
|
|
@@ -14,15 +14,10 @@ import {
|
|
|
14
14
|
GatewayClient,
|
|
15
15
|
applyStableSessionId
|
|
16
16
|
} from "./chunk-247WFMCJ.js";
|
|
17
|
-
import "./chunk-Z5LPNJQ6.js";
|
|
18
|
-
import "./chunk-MIEI4PLB.js";
|
|
19
17
|
import {
|
|
20
18
|
deriveKeystorePath,
|
|
21
19
|
loadKeystore
|
|
22
20
|
} from "./chunk-6G6YYST6.js";
|
|
23
|
-
import "./chunk-U5HHHRXA.js";
|
|
24
|
-
import "./chunk-PPTKGWFF.js";
|
|
25
|
-
import "./chunk-KNEJTD6G.js";
|
|
26
21
|
|
|
27
22
|
// src/tandem/send-cli.ts
|
|
28
23
|
import os from "os";
|
|
@@ -42,7 +37,8 @@ async function createPairedGateway(kojeeDir, stableDeps = {}, slot = {}) {
|
|
|
42
37
|
`no paired config at ${configPath} \u2014 run \`kojee-mcp connect <code> --runtime <id>\` first`
|
|
43
38
|
);
|
|
44
39
|
}
|
|
45
|
-
|
|
40
|
+
const seatDeps = paired.default_instance ? { instanceKey: paired.default_instance } : stableDeps;
|
|
41
|
+
await applyStableSessionId(paired.token, seatDeps);
|
|
46
42
|
const brokerUrl = paired.broker_url.replace(/\/+$/, "");
|
|
47
43
|
const keystorePath = resolution.scope === "paired" ? deriveKeystorePath(paired.token) : path.join(kojeeDir, "keypair.json");
|
|
48
44
|
const keystore = await loadKeystore(keystorePath, brokerUrl).catch(() => null);
|
|
@@ -80,13 +76,14 @@ async function runSendCli(args, deps = {}) {
|
|
|
80
76
|
if (!resolution.ok) {
|
|
81
77
|
return { exitCode: 1, envelope: resolution };
|
|
82
78
|
}
|
|
83
|
-
const
|
|
84
|
-
if (!
|
|
85
|
-
return { exitCode: 1, envelope:
|
|
79
|
+
const guard = await ensureJoined(resolution.gateway, parsed.request.tandem_id);
|
|
80
|
+
if (!guard.ok) {
|
|
81
|
+
return { exitCode: 1, envelope: guard.failure };
|
|
86
82
|
}
|
|
87
83
|
const envelope = await executeSend(resolution.gateway, parsed.request);
|
|
88
84
|
return { exitCode: envelope.ok ? 0 : 1, envelope };
|
|
89
85
|
}
|
|
86
|
+
|
|
90
87
|
export {
|
|
91
88
|
createPairedGateway,
|
|
92
89
|
runSendCli
|
|
@@ -129,6 +129,13 @@ async function executeJoin(gateway, tandemId) {
|
|
|
129
129
|
}
|
|
130
130
|
return { ok: true, tandem_id: tandemId, message_id: null, cursor: null, text };
|
|
131
131
|
}
|
|
132
|
+
async function ensureJoined(gateway, tandemId) {
|
|
133
|
+
const joined = await executeJoin(gateway, tandemId);
|
|
134
|
+
if (!joined.ok && (joined.error === "not_member" || joined.error === "gateway_auth")) {
|
|
135
|
+
return { ok: false, failure: joined };
|
|
136
|
+
}
|
|
137
|
+
return { ok: true };
|
|
138
|
+
}
|
|
132
139
|
var HTTP_STATUS_BY_CODE = {
|
|
133
140
|
bad_request: 400,
|
|
134
141
|
unauthorized: 401,
|
|
@@ -155,7 +162,9 @@ function httpStatusForEnvelope(envelope) {
|
|
|
155
162
|
export {
|
|
156
163
|
sendFailure,
|
|
157
164
|
parseSendRequest,
|
|
165
|
+
classifySendFailure,
|
|
158
166
|
executeSend,
|
|
159
167
|
executeJoin,
|
|
168
|
+
ensureJoined,
|
|
160
169
|
httpStatusForEnvelope
|
|
161
170
|
};
|