@cotal-ai/connector-claude-code 0.3.1 → 0.4.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 +11 -0
- package/dist/extension.d.ts.map +1 -1
- package/dist/extension.js +49 -11
- package/dist/extension.js.map +1 -1
- package/dist/hook.cjs +87 -8
- package/dist/mcp.cjs +615 -186
- package/package.json +4 -3
package/README.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# @cotal-ai/connector-claude-code
|
|
2
|
+
|
|
3
|
+
The Claude Code adapter: a bundled, installed plugin plus `claude/channel` push that turns a
|
|
4
|
+
real `claude` session into a Cotal mesh peer. A thin client over
|
|
5
|
+
[`@cotal-ai/connector-core`](../connector-core).
|
|
6
|
+
|
|
7
|
+
**Tier:** `extensions/`. Peer-depends [`@cotal-ai/core`](../../packages/core); self-registers on
|
|
8
|
+
import.
|
|
9
|
+
|
|
10
|
+
See [docs/claude-code-integration.md](../../docs/claude-code-integration.md) for the full
|
|
11
|
+
integration, and the [root AGENTS.md](../../AGENTS.md) for the tier rules.
|
package/dist/extension.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"extension.d.ts","sourceRoot":"","sources":["../src/extension.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"extension.d.ts","sourceRoot":"","sources":["../src/extension.ts"],"names":[],"mappings":"AAIA,OAAO,EAA2B,KAAK,SAAS,EAAoC,MAAM,gBAAgB,CAAC;AAoB3G;;;;;GAKG;AACH,eAAO,MAAM,eAAe,EAAE,SA2F7B,CAAC"}
|
package/dist/extension.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { mkdtempSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join, resolve } from "node:path";
|
|
2
4
|
import { fileURLToPath } from "node:url";
|
|
3
5
|
import { loadAgentFile, registry } from "@cotal-ai/core";
|
|
6
|
+
import { launchEnv, mcpServerEnvKeys } from "@cotal-ai/connector-core";
|
|
4
7
|
/** Name the cotal MCP server is registered under via --mcp-config (see buildLaunch). */
|
|
5
8
|
const MCP_SERVER_NAME = "cotal";
|
|
6
9
|
/** Channel ref for `--dangerously-load-development-channels`, which turns on the cotal MCP server's
|
|
@@ -27,16 +30,25 @@ export const claudeConnector = {
|
|
|
27
30
|
name: "claude",
|
|
28
31
|
pluginRoot: PLUGIN_ROOT,
|
|
29
32
|
buildLaunch(opts) {
|
|
33
|
+
// Operator MCP servers shared with this agent (default none — see the --mcp-config block).
|
|
34
|
+
const shared = opts.mcpServers ?? {};
|
|
35
|
+
// claude auths via macOS Keychain / an OAuth token, not an env key → forward NO provider key.
|
|
36
|
+
// The OS allow-list (PATH/HOME/TERM/…) is the only thing inherited from the manager env, plus
|
|
37
|
+
// — only when a shared server declares them via `${VAR}` — the named secrets it needs (mcpKeys,
|
|
38
|
+
// by name). The operator's unrelated secrets don't reach the child (P3).
|
|
30
39
|
const env = {
|
|
40
|
+
...launchEnv({ mcpKeys: mcpServerEnvKeys(shared) }),
|
|
31
41
|
COTAL_SPACE: opts.space,
|
|
32
42
|
COTAL_NAME: opts.name,
|
|
33
43
|
// Force the connector to emit channel wake-nudges: Claude doesn't advertise the
|
|
34
44
|
// `claude/channel` capability back over MCP, so auto-detection would see it "off".
|
|
35
45
|
COTAL_CHANNEL: "1",
|
|
36
|
-
// Managed sessions mirror their own transcript to `tr-<name>` so peers can read
|
|
37
|
-
// what the agent actually did. Personal sessions (no buildLaunch) never mirror.
|
|
38
|
-
COTAL_TRANSCRIPT: "1",
|
|
39
46
|
};
|
|
47
|
+
// A session can mirror its own transcript to `tr-<name>` so peers can read what the
|
|
48
|
+
// agent actually did — OFF by default (transcripts are verbose and may carry sensitive
|
|
49
|
+
// content); `--transcript` (opts.transcript === true) opts in. Personal sessions never mirror.
|
|
50
|
+
if (opts.transcript === true)
|
|
51
|
+
env.COTAL_TRANSCRIPT = "1";
|
|
40
52
|
if (opts.role)
|
|
41
53
|
env.COTAL_ROLE = opts.role;
|
|
42
54
|
if (opts.id)
|
|
@@ -54,13 +66,39 @@ export const claudeConnector = {
|
|
|
54
66
|
// can look something up under `npx` (no repo on disk) without prompting the operator
|
|
55
67
|
// mid-demo. Additive under the default permission mode — leaves other tools as-is.
|
|
56
68
|
args.push("--allowedTools", "WebFetch(domain:github.com),WebFetch(domain:raw.githubusercontent.com)");
|
|
57
|
-
// Isolate the spawned session's MCP
|
|
58
|
-
//
|
|
59
|
-
//
|
|
60
|
-
//
|
|
61
|
-
//
|
|
62
|
-
//
|
|
63
|
-
|
|
69
|
+
// Isolate the spawned session's MCP. --strict-mcp-config drops every ambient MCP source —
|
|
70
|
+
// including the operator's personal ~/.claude.json servers (e.g. a headless Chromium, a DB
|
|
71
|
+
// server) that a meshed teammate never needs and that, multiplied across several spawns on a
|
|
72
|
+
// busy machine, starve memory and kill the session before it registers presence — so the ONLY
|
|
73
|
+
// servers that load are the ones we name in --mcp-config: cotal (always, for its tools +
|
|
74
|
+
// presence) plus any the operator explicitly opted to share (`shared`, from the cotal config).
|
|
75
|
+
// The plugin itself stays enabled (its hooks + the dev-channels wake path are unaffected).
|
|
76
|
+
// cotal is spread LAST so a shared server can never shadow the mesh server by reusing its name.
|
|
77
|
+
const mcpServers = { ...shared, [MCP_SERVER_NAME]: { command: "node", args: [MCP_CJS] } };
|
|
78
|
+
// Default (no shared servers): pass the config inline, unchanged. With shared servers, write it
|
|
79
|
+
// to a file instead and pass the path. Either way the secret stays a `${VAR}` reference (Claude
|
|
80
|
+
// expands it from the child env at launch — see the mcpKeys forwarding above), never the resolved
|
|
81
|
+
// value, so nothing secret reaches disk or argv. We prefer the file when sharing because env
|
|
82
|
+
// expansion is only *documented* for --mcp-config files (inline expansion does work today, but
|
|
83
|
+
// isn't contracted), and a file keeps a potentially multi-server config off the process argv.
|
|
84
|
+
// Verified end-to-end on claude 2.1.183: ${VAR} expands in the --mcp-config file and the value
|
|
85
|
+
// is handed to the shared server. This is host-version behavior — if a future claude stops
|
|
86
|
+
// expanding here, a shared server would receive a literal `${VAR}`; re-check on host upgrades.
|
|
87
|
+
let mcpConfig;
|
|
88
|
+
if (Object.keys(shared).length === 0) {
|
|
89
|
+
mcpConfig = JSON.stringify({ mcpServers });
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
// A private 0700 temp dir (unique per spawn) holds the 0600 config. mkdtemp can't be raced
|
|
93
|
+
// by a pre-created or symlinked path the way a predictable name in the world-writable tmpdir
|
|
94
|
+
// could, and a fresh file guarantees the 0600 mode applies on creation (mode is ignored on an
|
|
95
|
+
// overwrite). Left for the OS to reap: the file must outlive this call (Claude reads it at
|
|
96
|
+
// startup and on /mcp reconnect), and buildLaunch doesn't own the child's lifecycle.
|
|
97
|
+
const dir = mkdtempSync(join(tmpdir(), "cotal-mcp-"));
|
|
98
|
+
mcpConfig = join(dir, "mcp.json");
|
|
99
|
+
writeFileSync(mcpConfig, JSON.stringify({ mcpServers }, null, 2), { mode: 0o600 });
|
|
100
|
+
}
|
|
101
|
+
args.push("--strict-mcp-config", "--mcp-config", mcpConfig);
|
|
64
102
|
// An agent file carries identity (read in-session via COTAL_AGENT_FILE) plus
|
|
65
103
|
// persona + model, which can only be applied to a `claude` session at launch.
|
|
66
104
|
if (opts.configPath) {
|
package/dist/extension.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"extension.js","sourceRoot":"","sources":["../src/extension.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"extension.js","sourceRoot":"","sources":["../src/extension.ts"],"names":[],"mappings":"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"}
|
package/dist/hook.cjs
CHANGED
|
@@ -14708,6 +14708,53 @@ var require_mod6 = __commonJS({
|
|
|
14708
14708
|
var import_node_os = require("node:os");
|
|
14709
14709
|
var import_node_fs2 = require("node:fs");
|
|
14710
14710
|
|
|
14711
|
+
// ../../packages/core/dist/subjects.js
|
|
14712
|
+
function subjectMatches(pattern, subject) {
|
|
14713
|
+
const p = pattern.split(".");
|
|
14714
|
+
const s = subject.split(".");
|
|
14715
|
+
for (let i = 0; i < p.length; i++) {
|
|
14716
|
+
if (p[i] === ">")
|
|
14717
|
+
return i < s.length;
|
|
14718
|
+
if (i >= s.length)
|
|
14719
|
+
return false;
|
|
14720
|
+
if (p[i] === "*")
|
|
14721
|
+
continue;
|
|
14722
|
+
if (p[i] !== s[i])
|
|
14723
|
+
return false;
|
|
14724
|
+
}
|
|
14725
|
+
return p.length === s.length;
|
|
14726
|
+
}
|
|
14727
|
+
function assertValidChannel(channel) {
|
|
14728
|
+
const segs = channel.split(".");
|
|
14729
|
+
if (!channel.length || segs.some((s) => s.length === 0))
|
|
14730
|
+
throw new Error(`invalid channel "${channel}": empty segment (no leading/trailing/double dots)`);
|
|
14731
|
+
segs.forEach((s, i) => {
|
|
14732
|
+
if (s === ">") {
|
|
14733
|
+
if (i !== segs.length - 1)
|
|
14734
|
+
throw new Error(`invalid channel "${channel}": '>' is only valid as the last segment`);
|
|
14735
|
+
return;
|
|
14736
|
+
}
|
|
14737
|
+
if (s === "*")
|
|
14738
|
+
return;
|
|
14739
|
+
if (!/^[A-Za-z0-9_-]+$/.test(s))
|
|
14740
|
+
throw new Error(`invalid channel "${channel}": segment "${s}" must be a NATS-safe token ([A-Za-z0-9_-]), '*', or '>' \u2014 policy channel names can't contain characters the wire layer would rewrite`);
|
|
14741
|
+
});
|
|
14742
|
+
return channel;
|
|
14743
|
+
}
|
|
14744
|
+
function channelInAllow(allow, channel) {
|
|
14745
|
+
return allow.some((a) => subjectMatches(a, channel));
|
|
14746
|
+
}
|
|
14747
|
+
|
|
14748
|
+
// ../../packages/core/dist/resolve.js
|
|
14749
|
+
function assertValidName(name) {
|
|
14750
|
+
if (name.length === 0 || name !== name.trim())
|
|
14751
|
+
throw new Error(`invalid name ${JSON.stringify(name)}: must be non-empty with no surrounding whitespace`);
|
|
14752
|
+
if (/[\r\n]/.test(name))
|
|
14753
|
+
throw new Error(`invalid name ${JSON.stringify(name)}: must be a single line`);
|
|
14754
|
+
if (name.includes("/"))
|
|
14755
|
+
throw new Error(`invalid name ${JSON.stringify(name)}: "/" is reserved (the owner/name separator)`);
|
|
14756
|
+
}
|
|
14757
|
+
|
|
14711
14758
|
// ../../packages/core/dist/link.js
|
|
14712
14759
|
function parseJoinLink(link) {
|
|
14713
14760
|
const tls = link.startsWith("cotals://");
|
|
@@ -16419,10 +16466,28 @@ function loadAgentFile(path) {
|
|
|
16419
16466
|
const name = str("name");
|
|
16420
16467
|
if (!name)
|
|
16421
16468
|
throw new Error(`agent file ${path}: "name" is required`);
|
|
16469
|
+
assertValidName(name);
|
|
16422
16470
|
const kind = str("kind");
|
|
16423
16471
|
if (kind && kind !== "agent" && kind !== "endpoint")
|
|
16424
16472
|
throw new Error(`agent file ${path}: "kind" must be "agent" or "endpoint"`);
|
|
16425
|
-
const
|
|
16473
|
+
for (const old of ["channels", "publish"])
|
|
16474
|
+
if (old in fm)
|
|
16475
|
+
throw new Error(`agent file ${path}: "${old}" was renamed \u2014 use "subscribe"/"allowSubscribe" (read) and "allowPublish" (post)`);
|
|
16476
|
+
const subscribe = list("subscribe");
|
|
16477
|
+
const allowSubscribe = list("allowSubscribe");
|
|
16478
|
+
const allowPublish = list("allowPublish");
|
|
16479
|
+
for (const ch of [...subscribe ?? [], ...allowSubscribe ?? [], ...allowPublish ?? []])
|
|
16480
|
+
try {
|
|
16481
|
+
assertValidChannel(ch);
|
|
16482
|
+
} catch (e) {
|
|
16483
|
+
throw new Error(`agent file ${path}: ${e.message}`);
|
|
16484
|
+
}
|
|
16485
|
+
const effSubscribe = subscribe?.length ? subscribe : ["general"];
|
|
16486
|
+
const effAllow = allowSubscribe?.length ? allowSubscribe : effSubscribe;
|
|
16487
|
+
for (const ch of effSubscribe)
|
|
16488
|
+
if (!channelInAllow(effAllow, ch))
|
|
16489
|
+
throw new Error(`agent file ${path}: subscribe channel "${ch}" is not within allowSubscribe [${effAllow.join(", ")}]`);
|
|
16490
|
+
const known = /* @__PURE__ */ new Set(["name", "role", "kind", "description", "tags", "subscribe", "allowSubscribe", "allowPublish", "model", "capabilities", "owner"]);
|
|
16426
16491
|
const meta = {};
|
|
16427
16492
|
for (const [k, v] of Object.entries(fm))
|
|
16428
16493
|
if (!known.has(k) && typeof v === "string")
|
|
@@ -16433,9 +16498,12 @@ function loadAgentFile(path) {
|
|
|
16433
16498
|
kind,
|
|
16434
16499
|
description: str("description"),
|
|
16435
16500
|
tags: list("tags"),
|
|
16436
|
-
|
|
16437
|
-
|
|
16501
|
+
subscribe,
|
|
16502
|
+
allowSubscribe,
|
|
16503
|
+
allowPublish,
|
|
16438
16504
|
model: str("model"),
|
|
16505
|
+
capabilities: list("capabilities"),
|
|
16506
|
+
owner: str("owner"),
|
|
16439
16507
|
meta: Object.keys(meta).length ? meta : void 0,
|
|
16440
16508
|
persona: persona || void 0
|
|
16441
16509
|
};
|
|
@@ -16493,9 +16561,17 @@ function configFromEnv(env = process.env) {
|
|
|
16493
16561
|
const name = env.COTAL_NAME?.trim() || def?.name || (link ? (0, import_node_os.userInfo)().username : void 0);
|
|
16494
16562
|
if (!name)
|
|
16495
16563
|
throw new Error("COTAL_NAME, COTAL_AGENT_FILE or COTAL_LINK is required \u2014 a Cotal session needs an explicit identity from its launcher");
|
|
16496
|
-
const
|
|
16497
|
-
const
|
|
16498
|
-
const
|
|
16564
|
+
const subscribe = splitList(env.COTAL_SUBSCRIBE);
|
|
16565
|
+
const resolvedSubscribe = subscribe.length ? subscribe : def?.subscribe ?? link?.channels ?? ["general"];
|
|
16566
|
+
const allowSub = splitList(env.COTAL_ALLOW_SUBSCRIBE);
|
|
16567
|
+
const resolvedAllowSub = allowSub.length ? allowSub : def?.allowSubscribe ?? resolvedSubscribe;
|
|
16568
|
+
for (const ch of resolvedSubscribe)
|
|
16569
|
+
if (!channelInAllow(resolvedAllowSub, ch))
|
|
16570
|
+
throw new Error(`COTAL config: subscribe channel "${ch}" is not within allowSubscribe [${resolvedAllowSub.join(", ")}]`);
|
|
16571
|
+
const allowPub = splitList(env.COTAL_ALLOW_PUBLISH);
|
|
16572
|
+
const resolvedAllowPub = allowPub.length ? allowPub : def?.allowPublish ?? [];
|
|
16573
|
+
for (const ch of [...resolvedSubscribe, ...resolvedAllowSub, ...resolvedAllowPub])
|
|
16574
|
+
assertValidChannel(ch);
|
|
16499
16575
|
const credsPath = env.COTAL_CREDS?.trim();
|
|
16500
16576
|
return {
|
|
16501
16577
|
space: env.COTAL_SPACE?.trim() || link?.space || "demo",
|
|
@@ -16506,8 +16582,11 @@ function configFromEnv(env = process.env) {
|
|
|
16506
16582
|
description: def?.description,
|
|
16507
16583
|
tags: def?.tags,
|
|
16508
16584
|
servers: env.COTAL_SERVERS?.trim() || link?.servers || DEFAULT_SERVER,
|
|
16509
|
-
|
|
16510
|
-
|
|
16585
|
+
subscribe: resolvedSubscribe,
|
|
16586
|
+
allowSubscribe: resolvedAllowSub,
|
|
16587
|
+
// Post ACL is default-DENY: only what's explicitly declared (env > agent-file). The broker
|
|
16588
|
+
// enforces it under auth; in open mode posting is unrestricted regardless (see laneLine).
|
|
16589
|
+
allowPublish: resolvedAllowPub,
|
|
16511
16590
|
kind: env.COTAL_KIND?.trim() || def?.kind || "agent",
|
|
16512
16591
|
token: env.COTAL_TOKEN?.trim() || link?.token,
|
|
16513
16592
|
user: link?.user,
|