kojee-mcp 0.6.0 → 0.7.1
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 +13 -8
- package/dist/ancestry-A2F5KQ6A.js +10 -0
- package/dist/cc-session-id-RURNIHHC.js +10 -0
- package/dist/{chunk-KH5YEJIM.js → chunk-247WFMCJ.js} +7 -20
- package/dist/chunk-5DHIUN73.js +25 -0
- package/dist/{chunk-YH27B6SW.js → chunk-5SZHXYPK.js} +16 -3
- package/dist/{chunk-DO42NPNR.js → chunk-67F67AQ6.js} +14 -3
- package/dist/{chunk-CH32ELFX.js → chunk-6G6YYST6.js} +1 -1
- package/dist/chunk-D6JKFJ6A.js +11 -0
- package/dist/chunk-E6WMFMM2.js +100 -0
- package/dist/chunk-EBYUJM3H.js +14 -0
- package/dist/chunk-EIAUW6KO.js +82 -0
- package/dist/chunk-EIH2LNF4.js +45 -0
- package/dist/{chunk-BYV7CWNY.js → chunk-FBJCPRVH.js} +22 -9
- package/dist/{chunk-XN3IC4B5.js → chunk-FSKGQ6GT.js} +30 -5
- package/dist/{chunk-GI2CKKBL.js → chunk-GKXHKR3B.js} +1 -1
- package/dist/{chunk-ALZX3SGI.js → chunk-GNLCUJBK.js} +13 -27
- package/dist/{chunk-JXMVZEQ7.js → chunk-I67C2HYA.js} +1 -1
- package/dist/chunk-KNEJTD6G.js +31 -0
- package/dist/{chunk-65KRRDHP.js → chunk-KPMD72FY.js} +94 -28
- package/dist/{chunk-NR4Y54OL.js → chunk-MIEI4PLB.js} +1 -19
- package/dist/chunk-NE2F5CKS.js +113 -0
- package/dist/chunk-QJFMU4QC.js +33 -0
- package/dist/{chunk-EW72ZNQL.js → chunk-RDLF4NQC.js} +1 -1
- package/dist/{chunk-Q7T3WAST.js → chunk-SSW5AQSR.js} +6 -1
- package/dist/{chunk-DURRQDYI.js → chunk-TNZITJAB.js} +45 -15
- package/dist/chunk-U5HHHRXA.js +103 -0
- package/dist/{setup-handler-7HOFAD7C.js → chunk-UFHGZUST.js} +431 -253
- package/dist/{chunk-N35X22G7.js → chunk-UPJV7GBE.js} +1 -1
- package/dist/chunk-WBXU27BF.js +35 -0
- package/dist/chunk-XJEBJIQE.js +52 -0
- package/dist/chunk-Z5LPNJQ6.js +20 -0
- package/dist/cli.js +63 -25
- package/dist/codex-stop-hook-BMOJVM6O.js +96 -0
- package/dist/connect-handler-4DRTFMOB.js +138 -0
- package/dist/{control-token-4BUCTYQB.js → control-token-KXSZ3GBY.js} +2 -2
- package/dist/{doctor-WVX3WCYX.js → doctor-5QJ3HGNR.js} +48 -22
- package/dist/{doctor-codex-Z5M4SFBG.js → doctor-codex-VGJKTX2E.js} +33 -7
- package/dist/doctor-openclaw-LL6NSPLC.js +86 -0
- package/dist/{event-log-HOBO3ECB.js → event-log-2NBJEIEP.js} +5 -3
- package/dist/{event-stream-SYBUJ5EH.js → event-stream-KRYWEYWO.js} +7 -4
- package/dist/index.js +13 -10
- package/dist/{install-LJY2CHKG.js → install-V7LSQCYZ.js} +37 -22
- package/dist/{keystore-XLEV3FL5.js → keystore-LQNFDHGO.js} +2 -2
- package/dist/lib.d.ts +4 -1
- package/dist/lib.js +11 -8
- package/dist/pair-HTCVSMR6.js +11 -0
- package/dist/{paired-config-JTFLHMZ2.js → paired-config-GUDDO2CH.js} +4 -2
- package/dist/{parent-watchdog-TLU355FB.js → parent-watchdog-LTUNF7PD.js} +24 -5
- package/dist/{registry-YLOFZDI2.js → registry-ZZZ26WGA.js} +197 -24
- package/dist/resolve-slot-BFIB46Y5.js +13 -0
- package/dist/{runtime-record-WO4IECM6.js → runtime-record-OXRLTOLC.js} +2 -2
- package/dist/{send-cli-IYJF32PE.js → send-cli-45RLGYIC.js} +22 -10
- package/dist/{server-R24AVNTI.js → server-ITPFQVTK.js} +5 -2
- package/dist/{session-discovery-FNMJGFPM.js → session-discovery-NHCZ2XWA.js} +2 -2
- package/dist/setup-handler-JFM45NCN.js +160 -0
- package/dist/{stop-hook-TCPJFGFF.js → stop-hook-5ABGTC2O.js} +17 -20
- package/dist/{tail-stream-4IMXI44S.js → tail-stream-NBGHHBS4.js} +40 -22
- package/dist/{user-prompt-submit-hook-FQ43NIHC.js → user-prompt-submit-hook-DTXXFDSD.js} +11 -14
- package/package.json +3 -2
- package/skills/using-tandems/SKILL.md +52 -0
- package/dist/ancestry-ONFBQEP5.js +0 -8
- package/dist/chunk-BLEGIR35.js +0 -43
- package/dist/chunk-CLKCNV2A.js +0 -100
- package/dist/chunk-KFDZWNQO.js +0 -73
- package/dist/chunk-OGHDTFAX.js +0 -50
- package/dist/chunk-VHKPWUX7.js +0 -41
- package/dist/codex-stop-hook-RWQSZTST.js +0 -16
- package/dist/doctor-openclaw-SS2TMQOX.js +0 -95
- package/dist/pair-P4ILCMT7.js +0 -10
|
@@ -3,13 +3,14 @@ import {
|
|
|
3
3
|
STATUS_LINE_PREFIX,
|
|
4
4
|
monitorHeartbeatPath,
|
|
5
5
|
statusLogPath
|
|
6
|
-
} from "./chunk-
|
|
7
|
-
import "./chunk-
|
|
6
|
+
} from "./chunk-TNZITJAB.js";
|
|
7
|
+
import "./chunk-67F67AQ6.js";
|
|
8
8
|
import {
|
|
9
9
|
createAdaptiveWatchdog
|
|
10
|
-
} from "./chunk-
|
|
11
|
-
import "./chunk-
|
|
12
|
-
import "./chunk-
|
|
10
|
+
} from "./chunk-FSKGQ6GT.js";
|
|
11
|
+
import "./chunk-Z5LPNJQ6.js";
|
|
12
|
+
import "./chunk-MIEI4PLB.js";
|
|
13
|
+
import "./chunk-U5HHHRXA.js";
|
|
13
14
|
|
|
14
15
|
// src/tail-stream.ts
|
|
15
16
|
import fs from "fs";
|
|
@@ -84,7 +85,8 @@ async function runTail(messagesPath) {
|
|
|
84
85
|
followers[1].start().then(() => {
|
|
85
86
|
statusSeeded = true;
|
|
86
87
|
})
|
|
87
|
-
])
|
|
88
|
+
]).catch(() => {
|
|
89
|
+
});
|
|
88
90
|
const staleTimer = setInterval(() => {
|
|
89
91
|
if (staleAnnounced) return;
|
|
90
92
|
const now = Date.now();
|
|
@@ -128,21 +130,32 @@ function makeFollower(filePath, onLine, onActivity) {
|
|
|
128
130
|
offset = 0;
|
|
129
131
|
}
|
|
130
132
|
if (stat.size === offset) return;
|
|
131
|
-
|
|
133
|
+
let fh = null;
|
|
134
|
+
let chunk = null;
|
|
132
135
|
try {
|
|
136
|
+
fh = await fs.promises.open(filePath, "r");
|
|
133
137
|
const len = stat.size - offset;
|
|
134
138
|
const buf = Buffer.alloc(len);
|
|
135
139
|
await fh.read(buf, 0, len, offset);
|
|
136
|
-
|
|
140
|
+
chunk = buf.toString("utf8");
|
|
137
141
|
offset = stat.size;
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
const line = buffer.slice(0, nl);
|
|
141
|
-
buffer = buffer.slice(nl + 1);
|
|
142
|
-
onLine(line);
|
|
143
|
-
}
|
|
142
|
+
} catch {
|
|
143
|
+
chunk = null;
|
|
144
144
|
} finally {
|
|
145
|
-
|
|
145
|
+
if (fh) {
|
|
146
|
+
try {
|
|
147
|
+
await fh.close();
|
|
148
|
+
} catch {
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
if (chunk === null) return;
|
|
153
|
+
buffer += chunk;
|
|
154
|
+
let nl;
|
|
155
|
+
while ((nl = buffer.indexOf("\n")) !== -1) {
|
|
156
|
+
const line = buffer.slice(0, nl);
|
|
157
|
+
buffer = buffer.slice(nl + 1);
|
|
158
|
+
onLine(line);
|
|
146
159
|
}
|
|
147
160
|
}
|
|
148
161
|
let draining = Promise.resolve();
|
|
@@ -158,17 +171,21 @@ function makeFollower(filePath, onLine, onActivity) {
|
|
|
158
171
|
if (stopped) return;
|
|
159
172
|
onActivity();
|
|
160
173
|
await scheduleDrain();
|
|
161
|
-
watcher = fs.watch(filePath, { persistent: true });
|
|
162
|
-
watcher.on("change", () => {
|
|
163
|
-
onActivity();
|
|
164
|
-
void scheduleDrain();
|
|
165
|
-
});
|
|
166
|
-
watcher.on("error", () => {
|
|
167
|
-
});
|
|
168
174
|
pollTimer = setInterval(() => {
|
|
169
175
|
onActivity();
|
|
170
176
|
void scheduleDrain();
|
|
171
177
|
}, 1e3);
|
|
178
|
+
try {
|
|
179
|
+
watcher = fs.watch(filePath, { persistent: true });
|
|
180
|
+
watcher.on("change", () => {
|
|
181
|
+
onActivity();
|
|
182
|
+
void scheduleDrain();
|
|
183
|
+
});
|
|
184
|
+
watcher.on("error", () => {
|
|
185
|
+
});
|
|
186
|
+
} catch {
|
|
187
|
+
watcher = null;
|
|
188
|
+
}
|
|
172
189
|
},
|
|
173
190
|
stop() {
|
|
174
191
|
stopped = true;
|
|
@@ -181,5 +198,6 @@ function sleep(ms) {
|
|
|
181
198
|
return new Promise((r) => setTimeout(r, ms));
|
|
182
199
|
}
|
|
183
200
|
export {
|
|
201
|
+
makeFollower,
|
|
184
202
|
runTail
|
|
185
203
|
};
|
|
@@ -1,27 +1,24 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
} from "./chunk-
|
|
2
|
+
resolveHookDiscovery
|
|
3
|
+
} from "./chunk-WBXU27BF.js";
|
|
4
4
|
import {
|
|
5
5
|
readHookStdin
|
|
6
6
|
} from "./chunk-LSUB6QMP.js";
|
|
7
|
-
import {
|
|
8
|
-
readSessionDiscoveryByKey
|
|
9
|
-
} from "./chunk-DO42NPNR.js";
|
|
10
7
|
import {
|
|
11
8
|
controlTokenAuthHeaders
|
|
12
|
-
} from "./chunk-
|
|
13
|
-
import "./chunk-BLEGIR35.js";
|
|
9
|
+
} from "./chunk-GKXHKR3B.js";
|
|
14
10
|
import {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
11
|
+
formatChannelEvents
|
|
12
|
+
} from "./chunk-PHXO5P25.js";
|
|
13
|
+
import "./chunk-67F67AQ6.js";
|
|
14
|
+
import "./chunk-U5HHHRXA.js";
|
|
15
|
+
import "./chunk-XJEBJIQE.js";
|
|
16
|
+
import "./chunk-KNEJTD6G.js";
|
|
18
17
|
|
|
19
18
|
// src/hooks/user-prompt-submit-hook.ts
|
|
20
19
|
async function runUserPromptSubmitHook() {
|
|
21
|
-
await readHookStdin();
|
|
22
|
-
const
|
|
23
|
-
const key = deriveDiscoveryKey(process.env["CLAUDE_PROJECT_DIR"], ccPid);
|
|
24
|
-
const discovery = readSessionDiscoveryByKey(key);
|
|
20
|
+
const { sessionId: stdinSessionId } = await readHookStdin();
|
|
21
|
+
const { discovery } = await resolveHookDiscovery(stdinSessionId);
|
|
25
22
|
if (!discovery) {
|
|
26
23
|
process.stdout.write("{}");
|
|
27
24
|
return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kojee-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"exports": {
|
|
@@ -25,7 +25,8 @@
|
|
|
25
25
|
"dev:stub": "tsx dev-tools/stub-broker.ts"
|
|
26
26
|
},
|
|
27
27
|
"files": [
|
|
28
|
-
"dist"
|
|
28
|
+
"dist",
|
|
29
|
+
"skills"
|
|
29
30
|
],
|
|
30
31
|
"dependencies": {
|
|
31
32
|
"@modelcontextprotocol/sdk": "latest",
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: using-tandems
|
|
3
|
+
description: Use when working in a Kojee Tandem — a shared room of humans and other agents. How to operate as an excellent teammate and a faithful proxy of your principal: wake-path planning, precise communication, authority, cross-room discretion, and the wake/mention mechanics.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Using Tandems
|
|
7
|
+
|
|
8
|
+
**You are an excellent team member now, not a single worker.** A Tandem is a live,
|
|
9
|
+
shared room of humans and agents: your work intersects theirs, your words spend a
|
|
10
|
+
shared pool of attention, and the outcome is collective. Optimize the *team's*
|
|
11
|
+
result, and assume you hold only a piece of the picture. The bar is *excellent* —
|
|
12
|
+
don't just avoid collisions; make others' jobs easier and leave the room clearer
|
|
13
|
+
than you found it.
|
|
14
|
+
|
|
15
|
+
You also wear a second hat: you **act for a principal** — under their account, within
|
|
16
|
+
their mandate, guarding their interests. But you're a distinct actor: you speak **as
|
|
17
|
+
yourself, an agent**, in your own voice — never put words in their mouth or claim
|
|
18
|
+
their authority for your own calls. Their intent sets your **goals and boundaries**,
|
|
19
|
+
not your every sentence. When the hats tension, the principal wins.
|
|
20
|
+
|
|
21
|
+
## Be an excellent teammate
|
|
22
|
+
|
|
23
|
+
- **Sync before you speak.** Catch up to the latest cursor; a gap means you missed messages — fetch them. Never answer from a stale view.
|
|
24
|
+
- **Answer in the channel you were approached in.** Approached in the **room** (the Tandem)? Answer in the room. Approached in your **session** (your principal's direct channel — including gate / review surfaces)? Answer in the session. It does **not** matter whether it's a 1:1 or a group chat — match the inbound channel; never migrate a conversation to a channel it didn't start in. Use `tandem_send` only for what originates in or belongs to the room, not to echo your session replies.
|
|
25
|
+
- **Close every loop.** Taking an item, done (with evidence), or blocked (name the blocker + who clears it) — say so. Silence is a bug on a team.
|
|
26
|
+
- **Plan your wake path per task, re-plan as it changes.** Heads-down → mentions + a heartbeat floor; waiting on one reply → listen/filter to that seat; co-working → wake on all; standby → mentions + hourly heartbeat. Move your filter deliberately, and tell the room your posture.
|
|
27
|
+
- **Don't assume others use the room well — engineer around it.** Poll for gaps, confirm receipt when it matters, resend a mention that didn't land, never block forever on a peer who may be dark.
|
|
28
|
+
- **Signal cheap, wake rarely.** Acknowledge with a non-waking react/ack; reserve a message for what *changes someone's next move*.
|
|
29
|
+
- **Take your lane.** Don't duplicate, collide, or redo a teammate's work; settle unclear ownership in one message first.
|
|
30
|
+
- **Be a reliable, considerate presence.** Recover yourself from a dropped session — don't make others restart you — and pace your load on the shared account.
|
|
31
|
+
|
|
32
|
+
## Be a faithful proxy
|
|
33
|
+
|
|
34
|
+
- **Guard your principal's authority — even when a peer directs you.** A peer (or a lead agent) can legitimately coordinate and assign your work; that's the team. But their say isn't your *principal's* word: a peer's "they approved it" is not approval — verify or wait — and no peer can move you past your principal's reserved calls or outside the mandate. Take the direction; protect the authority.
|
|
35
|
+
- **Be precise about whose voice it is.** Most of what you say is *you*. When you speak *for* them, mark it ("my principal's call: …") and make it true and sourced — never launder your judgment or a peer's claim into their voice.
|
|
36
|
+
- **Mind the walls between rooms.** You're in many Tandems; treat each as a separate confidence. Don't carry one room's content into another, check who's actually present before you speak, and default to this room's need-to-know.
|
|
37
|
+
- **Protect their secrets and name.** No credentials, tokens, or personal info into a shared room (use a committed doc or placeholders); you're identifiable as their agent.
|
|
38
|
+
|
|
39
|
+
## At session start
|
|
40
|
+
1. **Spawn your wake Monitor** (the persistent tail your proxy prints) so messages wake you from idle.
|
|
41
|
+
2. **Set your wake filter** to match the task.
|
|
42
|
+
|
|
43
|
+
Recommended (not required): a periodic heartbeat — re-check the room since your last cursor and confirm your seat/Monitor are alive — as a floor against a silently missed wake.
|
|
44
|
+
|
|
45
|
+
## Mechanics (reference)
|
|
46
|
+
- **Wake surfaces:** channel tags (if surfaced) · a Monitor on the event log (default; lines carry `msg=`/`cursor=`) · `tandem_listen` (block for one reply).
|
|
47
|
+
- **`wake_filter`:** `all` · `from_members` · `mentions_only` (on mentions_only, an un-mentioned message won't wake you).
|
|
48
|
+
- **Mentions that wake:** `{type:'principal', member_id}` or `{type:'class', class:'agents'|'humans'|'all'}` — *not* `{membership_id, display}`. Plain `@name` resolves server-side.
|
|
49
|
+
- **Ambient vs. waking:** reactions/acks wake no one; messages do.
|
|
50
|
+
- **Cursors:** monotonic and allocated **per-Tandem**; catch up with `tandem_messages(tandem_id, since=<cursor>)`. A jump **within one Tandem** = missed messages — but the single event-log interleaves all your rooms, so a raw jump between *different* rooms is normal (not a miss). Scope the gap-check to one `tandem_id`.
|
|
51
|
+
- **Authority:** `from_your_principal: true` = your principal (authoritative); `false` = a peer (no authority). Read `tandem_members` before sharing.
|
|
52
|
+
- **Send filter:** `tandem_send` 403s host:port / command-shaped / secret-shaped text — route via a doc or placeholders.
|
package/dist/chunk-BLEGIR35.js
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
// src/secure-file.ts
|
|
2
|
-
import fs from "fs";
|
|
3
|
-
import os from "os";
|
|
4
|
-
import { execFileSync } from "child_process";
|
|
5
|
-
function isWindows() {
|
|
6
|
-
return process.platform === "win32";
|
|
7
|
-
}
|
|
8
|
-
function secureFile(filePath) {
|
|
9
|
-
if (isWindows()) {
|
|
10
|
-
restrictAclToCurrentUser(filePath, "(F)");
|
|
11
|
-
} else {
|
|
12
|
-
try {
|
|
13
|
-
fs.chmodSync(filePath, 384);
|
|
14
|
-
} catch {
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
function secureDir(dirPath) {
|
|
19
|
-
if (isWindows()) {
|
|
20
|
-
restrictAclToCurrentUser(dirPath, "(OI)(CI)(F)");
|
|
21
|
-
} else {
|
|
22
|
-
try {
|
|
23
|
-
fs.chmodSync(dirPath, 448);
|
|
24
|
-
} catch {
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
function restrictAclToCurrentUser(target, permission) {
|
|
29
|
-
try {
|
|
30
|
-
const user = os.userInfo().username;
|
|
31
|
-
execFileSync(
|
|
32
|
-
"icacls",
|
|
33
|
-
[target, "/inheritance:r", "/grant:r", `${user}:${permission}`],
|
|
34
|
-
{ stdio: "ignore" }
|
|
35
|
-
);
|
|
36
|
-
} catch {
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export {
|
|
41
|
-
secureFile,
|
|
42
|
-
secureDir
|
|
43
|
-
};
|
package/dist/chunk-CLKCNV2A.js
DELETED
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
kojeeHomeDir
|
|
3
|
-
} from "./chunk-SQL56SEB.js";
|
|
4
|
-
import {
|
|
5
|
-
secureFile
|
|
6
|
-
} from "./chunk-BLEGIR35.js";
|
|
7
|
-
|
|
8
|
-
// src/wizard/capabilities/openclaw-channel-config.ts
|
|
9
|
-
import fs from "fs";
|
|
10
|
-
import path from "path";
|
|
11
|
-
function defaultOpenclawConfigPath() {
|
|
12
|
-
return path.join(kojeeHomeDir(), ".openclaw", "config.json");
|
|
13
|
-
}
|
|
14
|
-
var CHANNEL_ID = "kojee-tandem";
|
|
15
|
-
function mergeOpenclawChannelConfig(existing, block) {
|
|
16
|
-
const prevChannels = existing.channels && typeof existing.channels === "object" ? existing.channels : {};
|
|
17
|
-
return {
|
|
18
|
-
...existing,
|
|
19
|
-
channels: {
|
|
20
|
-
...prevChannels,
|
|
21
|
-
[CHANNEL_ID]: { ...block }
|
|
22
|
-
}
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
function readOpenclawConfig(configPath) {
|
|
26
|
-
try {
|
|
27
|
-
const parsed = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
|
28
|
-
return parsed && typeof parsed === "object" ? parsed : {};
|
|
29
|
-
} catch {
|
|
30
|
-
return {};
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
function readOpenclawConfigState(configPath) {
|
|
34
|
-
let raw;
|
|
35
|
-
try {
|
|
36
|
-
raw = fs.readFileSync(configPath, "utf8");
|
|
37
|
-
} catch {
|
|
38
|
-
return { cfg: {}, unparseable: false };
|
|
39
|
-
}
|
|
40
|
-
try {
|
|
41
|
-
const parsed = JSON.parse(raw);
|
|
42
|
-
return {
|
|
43
|
-
cfg: parsed && typeof parsed === "object" ? parsed : {},
|
|
44
|
-
unparseable: false
|
|
45
|
-
};
|
|
46
|
-
} catch {
|
|
47
|
-
return { cfg: {}, unparseable: true };
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
function atomicWrite(filePath, content, secret) {
|
|
51
|
-
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
52
|
-
const tmp = `${filePath}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;
|
|
53
|
-
fs.writeFileSync(tmp, content, { ...secret ? { mode: 384 } : {} });
|
|
54
|
-
if (secret) secureFile(tmp);
|
|
55
|
-
fs.renameSync(tmp, filePath);
|
|
56
|
-
if (secret) secureFile(filePath);
|
|
57
|
-
}
|
|
58
|
-
function writeOpenclawChannelConfig(configPath, block, opts = {}) {
|
|
59
|
-
const { cfg, unparseable } = readOpenclawConfigState(configPath);
|
|
60
|
-
let backedUp;
|
|
61
|
-
if (unparseable) {
|
|
62
|
-
const stamp = opts.timestamp ?? corruptStamp();
|
|
63
|
-
backedUp = `${configPath}.corrupt-${stamp}`;
|
|
64
|
-
fs.copyFileSync(configPath, backedUp);
|
|
65
|
-
}
|
|
66
|
-
const merged = mergeOpenclawChannelConfig(cfg, block);
|
|
67
|
-
atomicWrite(configPath, JSON.stringify(merged, null, 2) + "\n", Boolean(block.credential));
|
|
68
|
-
return backedUp ? { backedUp } : {};
|
|
69
|
-
}
|
|
70
|
-
function corruptStamp() {
|
|
71
|
-
return (/* @__PURE__ */ new Date()).toISOString().replace(/[-:]/g, "").replace(/\.\d+Z$/, "Z");
|
|
72
|
-
}
|
|
73
|
-
function removeOpenclawChannel(configPath) {
|
|
74
|
-
let raw;
|
|
75
|
-
try {
|
|
76
|
-
raw = fs.readFileSync(configPath, "utf8");
|
|
77
|
-
} catch {
|
|
78
|
-
return false;
|
|
79
|
-
}
|
|
80
|
-
let cfg;
|
|
81
|
-
try {
|
|
82
|
-
cfg = JSON.parse(raw);
|
|
83
|
-
} catch {
|
|
84
|
-
return false;
|
|
85
|
-
}
|
|
86
|
-
const channels = cfg.channels && typeof cfg.channels === "object" ? cfg.channels : void 0;
|
|
87
|
-
if (!channels || !(CHANNEL_ID in channels)) return false;
|
|
88
|
-
const { [CHANNEL_ID]: _removed, ...rest } = channels;
|
|
89
|
-
const next = { ...cfg, channels: rest };
|
|
90
|
-
atomicWrite(configPath, JSON.stringify(next, null, 2) + "\n", false);
|
|
91
|
-
return true;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
export {
|
|
95
|
-
defaultOpenclawConfigPath,
|
|
96
|
-
CHANNEL_ID,
|
|
97
|
-
readOpenclawConfig,
|
|
98
|
-
writeOpenclawChannelConfig,
|
|
99
|
-
removeOpenclawChannel
|
|
100
|
-
};
|
package/dist/chunk-KFDZWNQO.js
DELETED
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
readHookStdin
|
|
3
|
-
} from "./chunk-LSUB6QMP.js";
|
|
4
|
-
import {
|
|
5
|
-
buildCodexWakeReason
|
|
6
|
-
} from "./chunk-Q7T3WAST.js";
|
|
7
|
-
|
|
8
|
-
// src/hooks/codex-stop-hook.ts
|
|
9
|
-
import fs from "fs";
|
|
10
|
-
import os from "os";
|
|
11
|
-
import path from "path";
|
|
12
|
-
var CODEX_PEEK_MS = clampPeekMs(
|
|
13
|
-
Number.parseInt(process.env["KOJEE_CODEX_PEEK_MS"] ?? "150", 10)
|
|
14
|
-
);
|
|
15
|
-
var MARKER_STALE_MS = 3e4;
|
|
16
|
-
function clampPeekMs(raw) {
|
|
17
|
-
if (!Number.isFinite(raw) || raw <= 0) return 150;
|
|
18
|
-
return Math.min(raw, 500);
|
|
19
|
-
}
|
|
20
|
-
function decideCodexStopHook(deps) {
|
|
21
|
-
if (deps.stopHookActive) return "{}";
|
|
22
|
-
const peek = deps.peekPending();
|
|
23
|
-
if (!peek.pending || peek.cursor === null) return "{}";
|
|
24
|
-
return JSON.stringify({
|
|
25
|
-
decision: "block",
|
|
26
|
-
reason: buildCodexWakeReason(peek.cursor)
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
|
-
function codexPendingMarkerPath(sessionId) {
|
|
30
|
-
const safe = (sessionId ?? "no-session").replace(/[^A-Za-z0-9._-]/g, "_");
|
|
31
|
-
return path.join(os.homedir(), ".kojee", `codex-pending-${safe}`);
|
|
32
|
-
}
|
|
33
|
-
function defaultPeekPending(sessionId) {
|
|
34
|
-
const markerPath = codexPendingMarkerPath(sessionId);
|
|
35
|
-
try {
|
|
36
|
-
const st = fs.statSync(markerPath);
|
|
37
|
-
const ageMs = Date.now() - st.mtimeMs;
|
|
38
|
-
if (ageMs > MARKER_STALE_MS) return { pending: false, cursor: null };
|
|
39
|
-
let cursor = 0;
|
|
40
|
-
try {
|
|
41
|
-
const body = fs.readFileSync(markerPath, "utf8").trim().split(/\s+/)[0];
|
|
42
|
-
const n = Number.parseInt(body ?? "", 10);
|
|
43
|
-
if (Number.isFinite(n) && n >= 0) cursor = n;
|
|
44
|
-
} catch {
|
|
45
|
-
}
|
|
46
|
-
return { pending: true, cursor };
|
|
47
|
-
} catch {
|
|
48
|
-
return { pending: false, cursor: null };
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
async function runCodexStopHook() {
|
|
52
|
-
const { sessionId, stopHookActive } = await readHookStdin();
|
|
53
|
-
const out = decideCodexStopHook({
|
|
54
|
-
stopHookActive,
|
|
55
|
-
peekPending: () => {
|
|
56
|
-
try {
|
|
57
|
-
return defaultPeekPending(sessionId);
|
|
58
|
-
} catch {
|
|
59
|
-
return { pending: false, cursor: null };
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
});
|
|
63
|
-
process.stdout.write(out);
|
|
64
|
-
}
|
|
65
|
-
var CODEX_PEEK_BUDGET_MS = CODEX_PEEK_MS;
|
|
66
|
-
|
|
67
|
-
export {
|
|
68
|
-
decideCodexStopHook,
|
|
69
|
-
codexPendingMarkerPath,
|
|
70
|
-
defaultPeekPending,
|
|
71
|
-
runCodexStopHook,
|
|
72
|
-
CODEX_PEEK_BUDGET_MS
|
|
73
|
-
};
|
package/dist/chunk-OGHDTFAX.js
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
loadPairedConfig,
|
|
3
|
-
savePairedConfig
|
|
4
|
-
} from "./chunk-YH27B6SW.js";
|
|
5
|
-
import {
|
|
6
|
-
AuthModule
|
|
7
|
-
} from "./chunk-JXMVZEQ7.js";
|
|
8
|
-
|
|
9
|
-
// src/tandem/pair.ts
|
|
10
|
-
import fs from "fs";
|
|
11
|
-
async function runPair(opts) {
|
|
12
|
-
if (loadPairedConfig(opts.configPath) !== null) {
|
|
13
|
-
throw new Error(
|
|
14
|
-
`Already paired (config exists at ${opts.configPath}). To re-pair this slot, delete the config file first. For a second account, use --keystore-path /custom/keypair.json.`
|
|
15
|
-
);
|
|
16
|
-
}
|
|
17
|
-
try {
|
|
18
|
-
fs.unlinkSync(opts.keystorePath);
|
|
19
|
-
} catch {
|
|
20
|
-
}
|
|
21
|
-
const auth = new AuthModule(opts.code, opts.url, opts.keystorePath);
|
|
22
|
-
await auth.ensureEnrolled();
|
|
23
|
-
let principal_id;
|
|
24
|
-
let agent_id;
|
|
25
|
-
try {
|
|
26
|
-
const me = await fetch(`${opts.url}/api/v1/users/me/`, {
|
|
27
|
-
headers: { Authorization: `DPoP ${opts.code}` }
|
|
28
|
-
});
|
|
29
|
-
if (me.ok) {
|
|
30
|
-
const body = await me.json();
|
|
31
|
-
principal_id = body.principal_id;
|
|
32
|
-
agent_id = body.agent_id;
|
|
33
|
-
}
|
|
34
|
-
} catch {
|
|
35
|
-
}
|
|
36
|
-
const config = {
|
|
37
|
-
token: opts.code,
|
|
38
|
-
broker_url: opts.url,
|
|
39
|
-
paired_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
40
|
-
...principal_id ? { principal_id } : {},
|
|
41
|
-
...agent_id ? { agent_id } : {}
|
|
42
|
-
};
|
|
43
|
-
savePairedConfig(opts.configPath, config);
|
|
44
|
-
const who = principal_id && agent_id ? `${principal_id} (${agent_id})` : "(use kojee-mcp without args to start the proxy)";
|
|
45
|
-
return { message: `Paired as ${who}. Keypair: ${opts.keystorePath}. Config: ${opts.configPath}.` };
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export {
|
|
49
|
-
runPair
|
|
50
|
-
};
|
package/dist/chunk-VHKPWUX7.js
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
// src/runtime/ancestry.ts
|
|
2
|
-
import psList from "ps-list";
|
|
3
|
-
import { createHash } from "crypto";
|
|
4
|
-
async function findClaudeAncestorPid(startPid = process.ppid) {
|
|
5
|
-
let processes;
|
|
6
|
-
try {
|
|
7
|
-
processes = await psList();
|
|
8
|
-
} catch {
|
|
9
|
-
return null;
|
|
10
|
-
}
|
|
11
|
-
const byPid = /* @__PURE__ */ new Map();
|
|
12
|
-
for (const p of processes) byPid.set(p.pid, p);
|
|
13
|
-
let pid = startPid;
|
|
14
|
-
for (let depth = 0; depth < 20 && pid !== void 0 && pid > 1; depth++) {
|
|
15
|
-
const row = byPid.get(pid);
|
|
16
|
-
if (!row) return null;
|
|
17
|
-
const haystack = `${row.name} ${row.cmd ?? ""}`;
|
|
18
|
-
if (/\bclaude\b/i.test(haystack)) return pid;
|
|
19
|
-
if (row.ppid === void 0 || row.ppid === pid) return null;
|
|
20
|
-
pid = row.ppid;
|
|
21
|
-
}
|
|
22
|
-
return null;
|
|
23
|
-
}
|
|
24
|
-
function deriveDiscoveryKey(projectDir, ccPid) {
|
|
25
|
-
const hasProjectDir = typeof projectDir === "string" && projectDir.length > 0;
|
|
26
|
-
if (!hasProjectDir && ccPid === null) {
|
|
27
|
-
return `orphan-${process.pid}`;
|
|
28
|
-
}
|
|
29
|
-
if (!hasProjectDir) {
|
|
30
|
-
const hash2 = createHash("sha256").update("<no-project>").digest("hex").slice(0, 12);
|
|
31
|
-
return `${hash2}-${ccPid}`;
|
|
32
|
-
}
|
|
33
|
-
const hash = createHash("sha256").update(projectDir).digest("hex").slice(0, 12);
|
|
34
|
-
if (ccPid === null) return `${hash}-orphan`;
|
|
35
|
-
return `${hash}-${ccPid}`;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export {
|
|
39
|
-
findClaudeAncestorPid,
|
|
40
|
-
deriveDiscoveryKey
|
|
41
|
-
};
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
CODEX_PEEK_BUDGET_MS,
|
|
3
|
-
codexPendingMarkerPath,
|
|
4
|
-
decideCodexStopHook,
|
|
5
|
-
defaultPeekPending,
|
|
6
|
-
runCodexStopHook
|
|
7
|
-
} from "./chunk-KFDZWNQO.js";
|
|
8
|
-
import "./chunk-LSUB6QMP.js";
|
|
9
|
-
import "./chunk-Q7T3WAST.js";
|
|
10
|
-
export {
|
|
11
|
-
CODEX_PEEK_BUDGET_MS,
|
|
12
|
-
codexPendingMarkerPath,
|
|
13
|
-
decideCodexStopHook,
|
|
14
|
-
defaultPeekPending,
|
|
15
|
-
runCodexStopHook
|
|
16
|
-
};
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
CHANNEL_ID,
|
|
3
|
-
defaultOpenclawConfigPath,
|
|
4
|
-
readOpenclawConfig
|
|
5
|
-
} from "./chunk-CLKCNV2A.js";
|
|
6
|
-
import "./chunk-SQL56SEB.js";
|
|
7
|
-
import {
|
|
8
|
-
loadPairedConfig
|
|
9
|
-
} from "./chunk-YH27B6SW.js";
|
|
10
|
-
import "./chunk-BLEGIR35.js";
|
|
11
|
-
|
|
12
|
-
// src/doctor-openclaw.ts
|
|
13
|
-
import { execFileSync } from "child_process";
|
|
14
|
-
var WIZARD_RERUN = "re-run `kojee-mcp init --runtime openclaw`";
|
|
15
|
-
var VERIFY_HINT = "openclaw plugins inspect kojee-tandem / openclaw channels status";
|
|
16
|
-
function resolveCredentialSource(block, env, loadPaired) {
|
|
17
|
-
const channelCred = typeof block.credential === "string" ? block.credential.trim() : "";
|
|
18
|
-
if (channelCred) return "config";
|
|
19
|
-
if ((env["KOJEE_GATEWAY_TOKEN"] ?? "").trim()) return "env";
|
|
20
|
-
const paired = loadPaired();
|
|
21
|
-
if (paired?.token) return "paired-config";
|
|
22
|
-
return "none";
|
|
23
|
-
}
|
|
24
|
-
function defaultCliProbe() {
|
|
25
|
-
try {
|
|
26
|
-
execFileSync("openclaw", ["plugins", "inspect", CHANNEL_ID], { stdio: "ignore" });
|
|
27
|
-
return true;
|
|
28
|
-
} catch (err) {
|
|
29
|
-
if (err?.code === "ENOENT") return null;
|
|
30
|
-
return false;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
function collectOpenclawDoctorReport(deps = {}) {
|
|
34
|
-
const readConfig = deps.readConfig ?? (() => readOpenclawConfig(defaultOpenclawConfigPath()));
|
|
35
|
-
const env = deps.env ?? process.env;
|
|
36
|
-
const loadPaired = deps.loadPaired ?? (() => loadPairedConfig());
|
|
37
|
-
const checks = [];
|
|
38
|
-
const cfg = readConfig();
|
|
39
|
-
const channels = cfg.channels && typeof cfg.channels === "object" ? cfg.channels : {};
|
|
40
|
-
const block = channels[CHANNEL_ID] && typeof channels[CHANNEL_ID] === "object" ? channels[CHANNEL_ID] : null;
|
|
41
|
-
const enabled = block?.enabled === true;
|
|
42
|
-
checks.push({
|
|
43
|
-
name: `~/.openclaw/config.json channels.${CHANNEL_ID}`,
|
|
44
|
-
ok: enabled,
|
|
45
|
-
detail: enabled ? "present with enabled:true (the wizard-written channel block)" : block ? `present but enabled:false \u2014 ${WIZARD_RERUN}` : `MISSING channels.${CHANNEL_ID} block \u2014 ${WIZARD_RERUN}`
|
|
46
|
-
});
|
|
47
|
-
const source = resolveCredentialSource(block ?? {}, env, loadPaired);
|
|
48
|
-
const credOk = source !== "none";
|
|
49
|
-
checks.push({
|
|
50
|
-
name: "gateway credential",
|
|
51
|
-
ok: credOk,
|
|
52
|
-
detail: credOk ? `resolves \u2014 source: ${source} (token value never printed)` : `NONE resolves (no channel credential, KOJEE_GATEWAY_TOKEN, or paired ~/.kojee/config.json) \u2014 ${WIZARD_RERUN}`
|
|
53
|
-
});
|
|
54
|
-
const probed = deps.openclawCliProbe ? deps.openclawCliProbe() : defaultCliProbe();
|
|
55
|
-
if (probed === true) {
|
|
56
|
-
checks.push({
|
|
57
|
-
name: "openclaw plugin (kojee-tandem)",
|
|
58
|
-
ok: true,
|
|
59
|
-
detail: `discoverable via OpenClaw's plugin manager (\`${VERIFY_HINT}\`)`
|
|
60
|
-
});
|
|
61
|
-
} else if (probed === false) {
|
|
62
|
-
checks.push({
|
|
63
|
-
name: "openclaw plugin (kojee-tandem)",
|
|
64
|
-
ok: "warn",
|
|
65
|
-
detail: `NOT found by OpenClaw's plugin manager \u2014 install it: \`openclaw plugins install\`, then \`${VERIFY_HINT}\``
|
|
66
|
-
});
|
|
67
|
-
} else {
|
|
68
|
-
checks.push({
|
|
69
|
-
name: "openclaw plugin (kojee-tandem)",
|
|
70
|
-
ok: "warn",
|
|
71
|
-
detail: `owner-verify step (delegated install): confirm with \`${VERIFY_HINT}\``
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
const verdict = checks.some((c) => c.ok === false) ? "broken" : checks.some((c) => c.ok === "warn") ? "degraded" : "healthy";
|
|
75
|
-
return { checks, verdict };
|
|
76
|
-
}
|
|
77
|
-
function formatOpenclawDoctorReport(report) {
|
|
78
|
-
const mark = (ok) => ok === true ? "\u2713" : ok === "warn" ? "\u26A0" : ok === "unknown" ? "?" : "\u2717";
|
|
79
|
-
const lines = [];
|
|
80
|
-
lines.push(`kojee-mcp doctor (openclaw) \u2014 verdict: ${report.verdict.toUpperCase()}`);
|
|
81
|
-
lines.push("");
|
|
82
|
-
lines.push(" Wake mode: native OpenClaw channel plugin (in-process; the gateway streams Tandem events).");
|
|
83
|
-
lines.push(" Plugin install is delegated to OpenClaw's own plugin manager (the wizard owns only the channel config).");
|
|
84
|
-
lines.push("");
|
|
85
|
-
for (const c of report.checks) {
|
|
86
|
-
lines.push(` ${mark(c.ok)} ${c.name}: ${c.detail}`);
|
|
87
|
-
}
|
|
88
|
-
lines.push("");
|
|
89
|
-
lines.push(`NOTE: live openclaw verification (plugin loaded, gateway streaming) is an owner step: \`${VERIFY_HINT}\`.`);
|
|
90
|
-
return lines.join("\n");
|
|
91
|
-
}
|
|
92
|
-
export {
|
|
93
|
-
collectOpenclawDoctorReport,
|
|
94
|
-
formatOpenclawDoctorReport
|
|
95
|
-
};
|