pluriply 0.5.0 → 0.5.2
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 +1 -1
- package/package.json +1 -1
- package/src/connector/tools.js +22 -9
- package/src/setup/clients.js +47 -9
- package/src/setup/run-setup.js +4 -2
package/README.md
CHANGED
|
@@ -32,7 +32,7 @@ Pluriply MCP connector with each of them (idempotent — run it again any time).
|
|
|
32
32
|
- `npx pluriply setup --only claude-code,codex` — limit to specific tools.
|
|
33
33
|
- `npx pluriply setup --remove` — unregister Pluriply from every tool, disable headless workers and stop the hub. Your channels and task history under `~/.pluriply` stay; add `--purge` to delete them too.
|
|
34
34
|
- `npx pluriply setup --remove --hooks-only` — take out only the Stop hooks; MCP registration, headless workers and the hub stay as they are.
|
|
35
|
-
- Codex and Antigravity get a 600 s MCP tool timeout written into their config at registration (their default is 60 s, too short for `ask_agent`/`request_review` waits). If you registered with an earlier version, run `setup
|
|
35
|
+
- Codex and Antigravity get a 600 s MCP tool timeout written into their config at registration (their default is 60 s, too short for `ask_agent`/`request_review` waits). Codex also gets a 30 s MCP startup timeout (its default is 10 s; the connector may have to wait for the hub to start). If you registered with an earlier version, run `setup` again to pick up anything missing — values you already set are kept (the config file is rewritten once, with a `.bak` copy of the original).
|
|
36
36
|
- `setup` also installs a Stop hook for Claude Code, Codex and Antigravity CLI so a live session notices new tasks and finished results at the end of its turn (see _Warm reception_). `--no-hooks` skips it; `setup --remove` takes it out again.
|
|
37
37
|
- After upgrading or cleaning the npx cache, run `npx pluriply@latest setup --hooks-only` — the hook command embeds the installed path, and this refreshes it without rewriting your MCP configuration.
|
|
38
38
|
|
package/package.json
CHANGED
package/src/connector/tools.js
CHANGED
|
@@ -54,7 +54,7 @@ function staleHubMessage(stale) {
|
|
|
54
54
|
/**
|
|
55
55
|
* 허브에 정체성을 알리고 인스턴스 ID를 받는다. 재접속 때는 알고 있는 ID를 실어 그대로 인정받는다.
|
|
56
56
|
* @param {import('./hub-client.js').HubClient} hub
|
|
57
|
-
* @param {{agent: string, worker?: boolean, instanceId?: string, duringReconnect?: boolean}} opts
|
|
57
|
+
* @param {{agent: string, worker?: boolean, instanceId?: string|null, duringReconnect?: boolean}} opts
|
|
58
58
|
* @returns {Promise<string>}
|
|
59
59
|
*/
|
|
60
60
|
export async function hello(
|
|
@@ -63,7 +63,14 @@ export async function hello(
|
|
|
63
63
|
) {
|
|
64
64
|
const r = await hub.request(
|
|
65
65
|
"agent.hello",
|
|
66
|
-
|
|
66
|
+
// null 을 그대로 보내면 허브가 "invalid instanceId: null" 로 거절한다(undefined 만 "새로 발급"이다).
|
|
67
|
+
// 구버전 허브로 시작해 정체성이 없던 커넥터가 재접속 때 새 id 를 받을 수 있게 비운다.
|
|
68
|
+
{
|
|
69
|
+
tool: agent,
|
|
70
|
+
cwd: process.cwd(),
|
|
71
|
+
worker,
|
|
72
|
+
instanceId: instanceId ?? undefined,
|
|
73
|
+
},
|
|
67
74
|
{ duringReconnect },
|
|
68
75
|
);
|
|
69
76
|
return r.instanceId;
|
|
@@ -73,10 +80,11 @@ export async function hello(
|
|
|
73
80
|
* Pluriply MCP 도구를 등록한다.
|
|
74
81
|
* @param {import('@modelcontextprotocol/sdk/server/mcp.js').McpServer} server
|
|
75
82
|
* @param {import('./hub-client.js').HubClient} hub
|
|
76
|
-
* @param {{agent: string, instanceId: string}} identity
|
|
83
|
+
* @param {{agent: string, instanceId: string|null}} identity instanceId 는 구버전 허브로 시작하면 null 이다.
|
|
77
84
|
*/
|
|
78
85
|
export function registerTools(server, hub, { agent, instanceId }) {
|
|
79
|
-
|
|
86
|
+
/** instanceId 는 재접속 hello 가 돌려준 값으로 갱신된다(구버전 허브로 시작해 null 이었던 경우). */
|
|
87
|
+
const state = { currentChannel: null, instanceId };
|
|
80
88
|
const worker = Boolean(process.env.PLURIPLY_WORKER_TASK);
|
|
81
89
|
/** 워커는 자기 태스크 깊이 + 1, 대화형 세션은 0 */
|
|
82
90
|
const delegationDepth = () =>
|
|
@@ -192,7 +200,12 @@ export function registerTools(server, hub, { agent, instanceId }) {
|
|
|
192
200
|
// join_channel이 도구를 다시 시작할 때까지 "say hello first"로 막힌다.
|
|
193
201
|
// (hello는 인증이 필요한 요청이라, 토큰이 틀린 연결은 여기서 unauthorized를
|
|
194
202
|
// 받아 hub-client의 재접속 판정이 그것을 본다.)
|
|
195
|
-
await hello(hub, {
|
|
203
|
+
state.instanceId = await hello(hub, {
|
|
204
|
+
agent,
|
|
205
|
+
worker,
|
|
206
|
+
instanceId: state.instanceId,
|
|
207
|
+
duringReconnect: true,
|
|
208
|
+
});
|
|
196
209
|
if (!state.currentChannel) return;
|
|
197
210
|
await hub.request(
|
|
198
211
|
"channel.join",
|
|
@@ -226,7 +239,7 @@ export function registerTools(server, hub, { agent, instanceId }) {
|
|
|
226
239
|
channelCode: code,
|
|
227
240
|
});
|
|
228
241
|
state.currentChannel = code;
|
|
229
|
-
return ok({ channelCode: code, peers, me: instanceId });
|
|
242
|
+
return ok({ channelCode: code, peers, me: state.instanceId });
|
|
230
243
|
} catch (err) {
|
|
231
244
|
return fail(err.message);
|
|
232
245
|
}
|
|
@@ -260,7 +273,7 @@ export function registerTools(server, hub, { agent, instanceId }) {
|
|
|
260
273
|
});
|
|
261
274
|
const { tasks } = await hub.request("task.list", {
|
|
262
275
|
channelCode: code,
|
|
263
|
-
to: instanceId,
|
|
276
|
+
to: state.instanceId,
|
|
264
277
|
status: "submitted",
|
|
265
278
|
});
|
|
266
279
|
const { running } = await hub.request("worker.status", {
|
|
@@ -591,8 +604,8 @@ export function registerTools(server, hub, { agent, instanceId }) {
|
|
|
591
604
|
({ status, mine_only = true, sent_by_me = false, kind }, code) =>
|
|
592
605
|
hub.request("task.list", {
|
|
593
606
|
channelCode: code,
|
|
594
|
-
to: sent_by_me ? undefined : mine_only ? instanceId : undefined,
|
|
595
|
-
from: sent_by_me ? instanceId : undefined,
|
|
607
|
+
to: sent_by_me ? undefined : mine_only ? state.instanceId : undefined,
|
|
608
|
+
from: sent_by_me ? state.instanceId : undefined,
|
|
596
609
|
status,
|
|
597
610
|
kind,
|
|
598
611
|
}),
|
package/src/setup/clients.js
CHANGED
|
@@ -104,6 +104,13 @@ export function isSkipped(env) {
|
|
|
104
104
|
*/
|
|
105
105
|
export const TOOL_TIMEOUT_SEC = 600;
|
|
106
106
|
|
|
107
|
+
/**
|
|
108
|
+
* Codex MCP 서버 기동 타임아웃(초). Codex 기본값은 10초인데, 커넥터는 MCP 핸드셰이크 전에 허브를
|
|
109
|
+
* 기다린다 — 멎은 허브가 락을 쥐면 인수 유예(10초)를 포함해 spawnHub 가 최대 20초 남짓 걸려 기본값으로는
|
|
110
|
+
* 기동에 실패한다(Plan 4g 최종 리뷰). 스폰 대기보다 길어야 한다(test/hub/timing.test.js 가 고정).
|
|
111
|
+
*/
|
|
112
|
+
export const CODEX_STARTUP_TIMEOUT_SEC = 30;
|
|
113
|
+
|
|
107
114
|
/**
|
|
108
115
|
* @param {"claude-desktop"|"antigravity-ide"} id
|
|
109
116
|
* Antigravity 는 2.x 부터 IDE(`~/.gemini/antigravity-ide`) 와 허브(`~/.gemini/antigravity`) 로
|
|
@@ -297,7 +304,19 @@ function cliClient({
|
|
|
297
304
|
register(env) {
|
|
298
305
|
if (isSkipped(env)) return "skipped";
|
|
299
306
|
const st = this.status(env);
|
|
300
|
-
if (st === "present")
|
|
307
|
+
if (st === "present") {
|
|
308
|
+
// 예전 버전으로 등록돼 타임아웃 키가 빠졌을 수 있다(예: 0.5.1 에 생긴 Codex startup_timeout_sec).
|
|
309
|
+
// 없는 키만 채우고 있는 값은 보존한다. 이미 동작하는 등록이므로 채우지 못해도 되돌리지 않고
|
|
310
|
+
// 안내만 한다 — 되돌리면 멀쩡한 등록을 지우게 된다.
|
|
311
|
+
if (afterAdd) {
|
|
312
|
+
const r = afterAdd(env);
|
|
313
|
+
if (!r.ok)
|
|
314
|
+
env.log(`hint: ${label}: ${r.reason}${r.fix ? ` — ${r.fix}` : ""}`);
|
|
315
|
+
else if (r.changed)
|
|
316
|
+
env.log(`updated ${label}: added missing timeout settings`);
|
|
317
|
+
}
|
|
318
|
+
return "present";
|
|
319
|
+
}
|
|
301
320
|
if (typeof st === "object") {
|
|
302
321
|
env.log(
|
|
303
322
|
`hint: make sure ${label} has pluriply registered: ${hint(env.binPath)}`,
|
|
@@ -475,11 +494,14 @@ export const CLIENTS = [
|
|
|
475
494
|
// codex mcp add 는 타임아웃 플래그가 없어 config.toml 을 직접 편집한다(스펙 §5)
|
|
476
495
|
afterAdd(env) {
|
|
477
496
|
const path = codexConfigPath(env);
|
|
497
|
+
// present 경로(이미 등록됨)에서 채우지 못했을 때 사용자에게 보여 줄 손 수정 방법
|
|
498
|
+
const fix = `add \`tool_timeout_sec = ${TOOL_TIMEOUT_SEC}\` and \`startup_timeout_sec = ${CODEX_STARTUP_TIMEOUT_SEC}\` under [mcp_servers.pluriply] by hand in ${path}`;
|
|
478
499
|
try {
|
|
479
500
|
if (!env.fs.existsSync(path))
|
|
480
501
|
return {
|
|
481
502
|
ok: false,
|
|
482
|
-
|
|
503
|
+
fix,
|
|
504
|
+
reason: `timeout settings not written (${path} missing)`,
|
|
483
505
|
};
|
|
484
506
|
const r = insertTomlKey(
|
|
485
507
|
env.fs.readFileSync(path, "utf8"),
|
|
@@ -490,19 +512,31 @@ export const CLIENTS = [
|
|
|
490
512
|
if (r.reason === "no-header")
|
|
491
513
|
return {
|
|
492
514
|
ok: false,
|
|
493
|
-
|
|
515
|
+
fix,
|
|
516
|
+
reason: `timeout settings not written ([mcp_servers.pluriply] not found in ${path})`,
|
|
494
517
|
};
|
|
495
518
|
if (r.reason === "unsupported")
|
|
496
519
|
return {
|
|
497
520
|
ok: false,
|
|
498
|
-
|
|
521
|
+
fix,
|
|
522
|
+
reason: `timeout settings not written (${path} contains triple-quoted strings)`,
|
|
499
523
|
};
|
|
500
|
-
|
|
501
|
-
|
|
524
|
+
// 헤더·트리플쿼트 판정은 파일 단위라 위에서 통과했으면 두 번째 키도 같은 이유로는 실패하지 않는다.
|
|
525
|
+
// 이미 있는 값(사용자가 고친 값 포함)은 건드리지 않는다.
|
|
526
|
+
const s = insertTomlKey(
|
|
527
|
+
r.text,
|
|
528
|
+
"mcp_servers.pluriply",
|
|
529
|
+
"startup_timeout_sec",
|
|
530
|
+
String(CODEX_STARTUP_TIMEOUT_SEC),
|
|
531
|
+
);
|
|
532
|
+
const changed = r.changed || s.changed;
|
|
533
|
+
if (changed) writeFileAtomic(env, path, s.text);
|
|
534
|
+
return { ok: true, changed };
|
|
502
535
|
} catch (err) {
|
|
503
536
|
return {
|
|
504
537
|
ok: false,
|
|
505
|
-
|
|
538
|
+
fix,
|
|
539
|
+
reason: `timeout settings not written (${err.message})`,
|
|
506
540
|
};
|
|
507
541
|
}
|
|
508
542
|
},
|
|
@@ -535,22 +569,26 @@ export const CLIENTS = [
|
|
|
535
569
|
// agy mcp add 도 타임아웃 플래그가 없다. agy 는 JSONC 를 읽지만 우리는 JSON.parse 만 쓴다(스펙 §11).
|
|
536
570
|
afterAdd(env) {
|
|
537
571
|
const path = agyConfigPath(env);
|
|
572
|
+
const fix = `add \`"timeoutSeconds": ${TOOL_TIMEOUT_SEC}\` to mcpServers.pluriply by hand in ${path}`;
|
|
538
573
|
try {
|
|
539
574
|
const doc = readJsonDoc(env, path);
|
|
540
575
|
const entry = doc.mcpServers?.pluriply;
|
|
541
576
|
if (!entry || typeof entry !== "object")
|
|
542
577
|
return {
|
|
543
578
|
ok: false,
|
|
579
|
+
fix,
|
|
544
580
|
reason: `timeoutSeconds not written (mcpServers.pluriply not found in ${path})`,
|
|
545
581
|
};
|
|
546
|
-
|
|
582
|
+
const changed = entry.timeoutSeconds === undefined;
|
|
583
|
+
if (changed) {
|
|
547
584
|
entry.timeoutSeconds = TOOL_TIMEOUT_SEC;
|
|
548
585
|
writeJsonAtomic(env, path, doc);
|
|
549
586
|
}
|
|
550
|
-
return { ok: true };
|
|
587
|
+
return { ok: true, changed };
|
|
551
588
|
} catch (err) {
|
|
552
589
|
return {
|
|
553
590
|
ok: false,
|
|
591
|
+
fix,
|
|
554
592
|
reason: `timeoutSeconds not written (${err.message})`,
|
|
555
593
|
};
|
|
556
594
|
}
|
package/src/setup/run-setup.js
CHANGED
|
@@ -109,7 +109,8 @@ function walkHooks({ targets, rows, e, dryRun, hooks, remove }) {
|
|
|
109
109
|
* `remove` 면 반대로 등록을 풀고 워커 설정·허브·(purge 시) 데이터까지 정리한다.
|
|
110
110
|
* `--purge` 는 pluriply 홈이 심볼릭 링크면 **링크만 끊고** 링크가 가리키는 디렉터리는 남긴다
|
|
111
111
|
* (그 안의 내용까지 지우려면 실제 경로를 직접 지워야 한다).
|
|
112
|
-
* env.stopHub / env.rm / env.lstat 은 테스트가
|
|
112
|
+
* env.stopHub / env.ensureHub / env.rm / env.lstat 은 테스트가 주입한다(주입하지 않은 ensureHub 는 진짜
|
|
113
|
+
* detached 허브를 띄운다 — 테스트가 넣지 않으면 임시 홈에 허브가 남는다).
|
|
113
114
|
* `hooks`(기본 true)는 Claude Code·Codex 의 Stop 훅 등록 여부다(스펙 §6). `--remove` 는 이 값과
|
|
114
115
|
* 무관하게 항상 훅을 제거한다.
|
|
115
116
|
* `hooksOnly`(스펙 §4)는 MCP 등록·워커·허브를 건너뛰고 Stop 훅만 설치(`remove` 면 제거)한다.
|
|
@@ -153,7 +154,8 @@ export async function runSetup({
|
|
|
153
154
|
};
|
|
154
155
|
if (!dryRun) {
|
|
155
156
|
try {
|
|
156
|
-
|
|
157
|
+
const ensure = e.ensureHub ?? (await hubClient()).ensureHub;
|
|
158
|
+
out.hub = { port: (await ensure({ home })).port };
|
|
157
159
|
} catch (err) {
|
|
158
160
|
out.hubError = err.message;
|
|
159
161
|
}
|