commonswarm 0.1.39 → 0.1.40
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/cswarm.cjs +142 -58
- package/package.json +1 -1
package/cswarm.cjs
CHANGED
|
@@ -38294,6 +38294,7 @@ var HOOK_CHECK_TIMEOUT_MS = 3e3;
|
|
|
38294
38294
|
var HOOK_DEFAULT_COOLDOWN_SECONDS = 30;
|
|
38295
38295
|
var HOOK_SURFACED_IDS_MAX = 1024;
|
|
38296
38296
|
var HOOK_BODY_PREVIEW_CHARS = 240;
|
|
38297
|
+
var HOOK_MULTI_PRINCIPAL_GUIDANCE = "This host runs multiple agents. The CommonSwarm hook needs --principal-id. Reinstall it for this agent: cswarm hook install claude --principal-id <uuid> --write";
|
|
38297
38298
|
function exactKeys2(row, keys) {
|
|
38298
38299
|
const expected = new Set(keys);
|
|
38299
38300
|
return Object.keys(row).length === expected.size && Object.keys(row).every((key2) => expected.has(key2));
|
|
@@ -38523,7 +38524,7 @@ async function listenerIsLive(context) {
|
|
|
38523
38524
|
return false;
|
|
38524
38525
|
}
|
|
38525
38526
|
}
|
|
38526
|
-
async function
|
|
38527
|
+
async function discoverStoredStatusContexts(stateDirectory2) {
|
|
38527
38528
|
let entries;
|
|
38528
38529
|
try {
|
|
38529
38530
|
entries = await (0, import_promises10.readdir)(stateDirectory2, { withFileTypes: true });
|
|
@@ -38540,11 +38541,46 @@ async function discoverContexts(stateDirectory2, isListenerLive = listenerIsLive
|
|
|
38540
38541
|
entry.name,
|
|
38541
38542
|
instanceDirectory
|
|
38542
38543
|
);
|
|
38543
|
-
|
|
38544
|
-
if (statusIsLive === false) {
|
|
38544
|
+
if (storedStatus !== null) {
|
|
38545
38545
|
contexts.push({
|
|
38546
38546
|
instanceDirectory,
|
|
38547
38547
|
paths: storedStatus.paths,
|
|
38548
|
+
status: storedStatus.status
|
|
38549
|
+
});
|
|
38550
|
+
}
|
|
38551
|
+
}
|
|
38552
|
+
return contexts;
|
|
38553
|
+
}
|
|
38554
|
+
async function discoverListenerHookPrincipalIds(stateDirectory2 = defaultListenerStateDirectory()) {
|
|
38555
|
+
if (!(0, import_node_path19.isAbsolute)(stateDirectory2)) return [];
|
|
38556
|
+
const stored = await discoverStoredStatusContexts(stateDirectory2);
|
|
38557
|
+
return [...new Set(stored.map((context) => context.status.principalId))].sort();
|
|
38558
|
+
}
|
|
38559
|
+
async function discoverContexts(stateDirectory2, principalIds, isListenerLive = listenerIsLive) {
|
|
38560
|
+
const storedContexts = await discoverStoredStatusContexts(stateDirectory2);
|
|
38561
|
+
const availablePrincipals = new Set(
|
|
38562
|
+
storedContexts.map((context) => context.status.principalId)
|
|
38563
|
+
);
|
|
38564
|
+
let selectedPrincipals;
|
|
38565
|
+
if (principalIds === void 0) {
|
|
38566
|
+
if (availablePrincipals.size > 1) {
|
|
38567
|
+
return { contexts: [], requiresPrincipalScope: true };
|
|
38568
|
+
}
|
|
38569
|
+
selectedPrincipals = availablePrincipals;
|
|
38570
|
+
} else {
|
|
38571
|
+
if (principalIds.some((principalId) => !UUID_RE18.test(principalId))) {
|
|
38572
|
+
return { contexts: [], requiresPrincipalScope: false };
|
|
38573
|
+
}
|
|
38574
|
+
selectedPrincipals = new Set(principalIds.map((principalId) => principalId.toLowerCase()));
|
|
38575
|
+
}
|
|
38576
|
+
const contexts = [];
|
|
38577
|
+
for (const storedStatus of storedContexts) {
|
|
38578
|
+
if (!selectedPrincipals.has(storedStatus.status.principalId)) continue;
|
|
38579
|
+
const statusIsLive = await isListenerLive(storedStatus);
|
|
38580
|
+
if (statusIsLive === false) {
|
|
38581
|
+
contexts.push({
|
|
38582
|
+
instanceDirectory: storedStatus.instanceDirectory,
|
|
38583
|
+
paths: storedStatus.paths,
|
|
38548
38584
|
status: storedStatus.status,
|
|
38549
38585
|
listenerLive: false,
|
|
38550
38586
|
credential: null,
|
|
@@ -38553,22 +38589,21 @@ async function discoverContexts(stateDirectory2, isListenerLive = listenerIsLive
|
|
|
38553
38589
|
continue;
|
|
38554
38590
|
}
|
|
38555
38591
|
await deleteSecureJsonFile(
|
|
38556
|
-
(0, import_node_path19.join)(instanceDirectory, RETIRED_HOOK_CREDENTIAL_FILE)
|
|
38592
|
+
(0, import_node_path19.join)(storedStatus.instanceDirectory, RETIRED_HOOK_CREDENTIAL_FILE)
|
|
38557
38593
|
).catch(() => void 0);
|
|
38558
38594
|
try {
|
|
38559
|
-
const credential = await readListenerCredentialState(instanceDirectory);
|
|
38595
|
+
const credential = await readListenerCredentialState(storedStatus.instanceDirectory);
|
|
38560
38596
|
contexts.push({
|
|
38561
|
-
instanceDirectory,
|
|
38562
|
-
paths: storedStatus
|
|
38563
|
-
status: storedStatus
|
|
38597
|
+
instanceDirectory: storedStatus.instanceDirectory,
|
|
38598
|
+
paths: storedStatus.paths,
|
|
38599
|
+
status: storedStatus.status,
|
|
38564
38600
|
listenerLive: statusIsLive,
|
|
38565
38601
|
credential,
|
|
38566
|
-
credentialReadFailed: credential === null
|
|
38602
|
+
credentialReadFailed: credential === null
|
|
38567
38603
|
});
|
|
38568
38604
|
} catch {
|
|
38569
|
-
if (storedStatus === null) continue;
|
|
38570
38605
|
contexts.push({
|
|
38571
|
-
instanceDirectory,
|
|
38606
|
+
instanceDirectory: storedStatus.instanceDirectory,
|
|
38572
38607
|
paths: storedStatus.paths,
|
|
38573
38608
|
status: storedStatus.status,
|
|
38574
38609
|
listenerLive: statusIsLive,
|
|
@@ -38577,7 +38612,7 @@ async function discoverContexts(stateDirectory2, isListenerLive = listenerIsLive
|
|
|
38577
38612
|
});
|
|
38578
38613
|
}
|
|
38579
38614
|
}
|
|
38580
|
-
return contexts;
|
|
38615
|
+
return { contexts, requiresPrincipalScope: false };
|
|
38581
38616
|
}
|
|
38582
38617
|
function entryFromSignal(signal, principalId, directory, now) {
|
|
38583
38618
|
const senderName = signal.from_kind === "agent" ? directory?.agents.find((agent) => agent.principal_id === signal.from)?.name ?? null : directory?.members.find((member) => member.user_id === signal.from)?.display_name ?? null;
|
|
@@ -38710,10 +38745,16 @@ async function checkListenerHooks(options) {
|
|
|
38710
38745
|
if (!Number.isSafeInteger(cooldownSeconds) || cooldownSeconds < 0 || cooldownSeconds > 86400) {
|
|
38711
38746
|
return "";
|
|
38712
38747
|
}
|
|
38713
|
-
const
|
|
38748
|
+
const discovery = await discoverContexts(
|
|
38714
38749
|
stateDirectory2,
|
|
38750
|
+
options.principalIds,
|
|
38715
38751
|
options.isListenerLive ?? listenerIsLive
|
|
38716
38752
|
);
|
|
38753
|
+
if (discovery.requiresPrincipalScope) {
|
|
38754
|
+
await (options.write ?? (() => void 0))(HOOK_MULTI_PRINCIPAL_GUIDANCE);
|
|
38755
|
+
return HOOK_MULTI_PRINCIPAL_GUIDANCE;
|
|
38756
|
+
}
|
|
38757
|
+
const contexts = discovery.contexts;
|
|
38717
38758
|
if (contexts.length === 0) return "";
|
|
38718
38759
|
const networkAllowed = contexts.some((context) => context.listenerLive !== false) ? await reserveCheck(
|
|
38719
38760
|
stateDirectory2,
|
|
@@ -38939,8 +38980,8 @@ var ACCEPTED_AGENT_CREDENTIAL_MESSAGES = [
|
|
|
38939
38980
|
AGENT_CREDENTIAL_MESSAGE_D088
|
|
38940
38981
|
];
|
|
38941
38982
|
function packageVersion() {
|
|
38942
|
-
if ("0.1.
|
|
38943
|
-
return "0.1.
|
|
38983
|
+
if ("0.1.40".length > 0) {
|
|
38984
|
+
return "0.1.40";
|
|
38944
38985
|
}
|
|
38945
38986
|
try {
|
|
38946
38987
|
const value = JSON.parse(
|
|
@@ -39076,8 +39117,8 @@ Usage:
|
|
|
39076
39117
|
cswarm listen start ${requiredAgentCredential} [--url <url> --anon-key <key>] --workspace-id <uuid> --provider grok|opencode|claude|codex [--cwd <absolute-path>] [--model <model>] [--effort <level>] [--permissions deny|allow] [--grok-executable <path>] [--opencode-executable <path>] [--claude-executable <path>] [--codex-executable <path>] [--turn-budget <duration>] [--route worker|main|split] [--defer-over <chars>] [--foreground] [--json]
|
|
39077
39118
|
cswarm listen status [--url <url> --anon-key <key>] --workspace-id <uuid> --principal-id <uuid> [--json]
|
|
39078
39119
|
cswarm listen stop [--url <url> --anon-key <key>] --workspace-id <uuid> --principal-id <uuid> [--json]
|
|
39079
|
-
cswarm hook check [--cooldown <seconds>]
|
|
39080
|
-
cswarm hook install claude [--write]
|
|
39120
|
+
cswarm hook check [--principal-id <uuid> ...] [--cooldown <seconds>]
|
|
39121
|
+
cswarm hook install claude [--principal-id <uuid>] [--write]
|
|
39081
39122
|
cswarm hook uninstall claude --write
|
|
39082
39123
|
cswarm new "<workspace name>" [--url <url> --anon-key <key>] [--json]
|
|
39083
39124
|
cswarm new --name "<workspace name>" [--url <url> --anon-key <key>] [--json]
|
|
@@ -39123,7 +39164,7 @@ Credential selection for command/dogfood:
|
|
|
39123
39164
|
command, dogfood
|
|
39124
39165
|
task protocol commands -- either form
|
|
39125
39166
|
listen start persists durable state, rotates -- needs expires_at
|
|
39126
|
-
hook check reads the listener's owned 0600 credential state;
|
|
39167
|
+
hook check reads only the selected listener's owned 0600 credential state;
|
|
39127
39168
|
never accepts or prints a credential
|
|
39128
39169
|
hook install/uninstall
|
|
39129
39170
|
edits only local Claude Code settings; no credential
|
|
@@ -39155,10 +39196,11 @@ listen start --route worker|main|split chooses where directed messages go. worke
|
|
|
39155
39196
|
is the unchanged default. main queues every ask or note for the interactive session.
|
|
39156
39197
|
split queues messages whose body is longer than --defer-over <chars>; the bound is
|
|
39157
39198
|
1..10000 and an equal-length message stays on the worker path. Run cswarm hook check
|
|
39158
|
-
to surface queued messages.
|
|
39159
|
-
|
|
39160
|
-
|
|
39161
|
-
|
|
39199
|
+
--principal-id <uuid> to surface that agent's queued messages. A bare check works only
|
|
39200
|
+
when the state directory holds one principal. hook check has its own 3s ceiling, exits 0
|
|
39201
|
+
on every outcome, and skips network checks made within --cooldown seconds (default 30).
|
|
39202
|
+
hook install claude prints principal-scoped UserPromptSubmit JSON by default and changes
|
|
39203
|
+
the project's .claude/settings.json only with --write; uninstall also requires --write.
|
|
39162
39204
|
|
|
39163
39205
|
Invite, legacy token accept, principal create/revoke, human token mint/revoke, link, new, and workspace close require a
|
|
39164
39206
|
stored human login. Agent self-surrender of a token uses --agent-token-file or --agent-token-stdin and never takes the secret on argv. Invite-link accept signs in when needed, then accepts and
|
|
@@ -42439,7 +42481,13 @@ async function runListen(args) {
|
|
|
42439
42481
|
throw new UsageError("listen requires start, status, or stop");
|
|
42440
42482
|
}
|
|
42441
42483
|
var CLAUDE_HOOK_COMMAND = "cswarm hook check";
|
|
42442
|
-
function
|
|
42484
|
+
function scopedClaudeHookCommand(principalId) {
|
|
42485
|
+
return `${CLAUDE_HOOK_COMMAND} --principal-id ${listenerUuid(principalId, "principal-id")}`;
|
|
42486
|
+
}
|
|
42487
|
+
function isCommonSwarmClaudeHook(value) {
|
|
42488
|
+
return typeof value === "string" && (value === CLAUDE_HOOK_COMMAND || /^cswarm hook check --principal-id [0-9a-f-]{36}$/.test(value));
|
|
42489
|
+
}
|
|
42490
|
+
function claudeUserPromptHookSnippet(principalId) {
|
|
42443
42491
|
return {
|
|
42444
42492
|
hooks: {
|
|
42445
42493
|
UserPromptSubmit: [
|
|
@@ -42447,7 +42495,7 @@ function claudeUserPromptHookSnippet() {
|
|
|
42447
42495
|
hooks: [
|
|
42448
42496
|
{
|
|
42449
42497
|
type: "command",
|
|
42450
|
-
command:
|
|
42498
|
+
command: scopedClaudeHookCommand(principalId)
|
|
42451
42499
|
}
|
|
42452
42500
|
]
|
|
42453
42501
|
}
|
|
@@ -42472,21 +42520,40 @@ function readProjectSettings(path) {
|
|
|
42472
42520
|
}
|
|
42473
42521
|
return value;
|
|
42474
42522
|
}
|
|
42475
|
-
function installClaudeHook(settings) {
|
|
42523
|
+
function installClaudeHook(settings, principalId) {
|
|
42476
42524
|
const hooks = settings.hooks && typeof settings.hooks === "object" && !Array.isArray(settings.hooks) ? { ...settings.hooks } : {};
|
|
42477
42525
|
const current = Array.isArray(hooks.UserPromptSubmit) ? [...hooks.UserPromptSubmit] : [];
|
|
42478
|
-
const
|
|
42479
|
-
|
|
42480
|
-
|
|
42481
|
-
|
|
42482
|
-
|
|
42483
|
-
|
|
42484
|
-
|
|
42485
|
-
|
|
42486
|
-
const
|
|
42487
|
-
|
|
42526
|
+
const command2 = scopedClaudeHookCommand(principalId);
|
|
42527
|
+
let installed = false;
|
|
42528
|
+
const groups = [];
|
|
42529
|
+
for (const group of current) {
|
|
42530
|
+
if (!group || typeof group !== "object" || Array.isArray(group)) {
|
|
42531
|
+
groups.push(group);
|
|
42532
|
+
continue;
|
|
42533
|
+
}
|
|
42534
|
+
const row = { ...group };
|
|
42535
|
+
if (!Array.isArray(row.hooks)) {
|
|
42536
|
+
groups.push(group);
|
|
42537
|
+
continue;
|
|
42538
|
+
}
|
|
42539
|
+
const commands = [];
|
|
42540
|
+
for (const hook of row.hooks) {
|
|
42541
|
+
if (hook && typeof hook === "object" && !Array.isArray(hook) && hook.type === "command" && isCommonSwarmClaudeHook(hook.command)) {
|
|
42542
|
+
if (!installed) {
|
|
42543
|
+
commands.push({ ...hook, command: command2 });
|
|
42544
|
+
installed = true;
|
|
42545
|
+
}
|
|
42546
|
+
continue;
|
|
42547
|
+
}
|
|
42548
|
+
commands.push(hook);
|
|
42549
|
+
}
|
|
42550
|
+
if (commands.length > 0) groups.push({ ...row, hooks: commands });
|
|
42488
42551
|
}
|
|
42489
|
-
|
|
42552
|
+
if (!installed) {
|
|
42553
|
+
const snippetHooks = claudeUserPromptHookSnippet(principalId).hooks.UserPromptSubmit;
|
|
42554
|
+
groups.push(snippetHooks[0]);
|
|
42555
|
+
}
|
|
42556
|
+
hooks.UserPromptSubmit = groups;
|
|
42490
42557
|
return { ...settings, hooks };
|
|
42491
42558
|
}
|
|
42492
42559
|
function uninstallClaudeHook(settings) {
|
|
@@ -42506,7 +42573,7 @@ function uninstallClaudeHook(settings) {
|
|
|
42506
42573
|
groups.push(group);
|
|
42507
42574
|
continue;
|
|
42508
42575
|
}
|
|
42509
|
-
row.hooks = row.hooks.filter((hook) => !(hook && typeof hook === "object" && !Array.isArray(hook) && hook.type === "command" && hook.command
|
|
42576
|
+
row.hooks = row.hooks.filter((hook) => !(hook && typeof hook === "object" && !Array.isArray(hook) && hook.type === "command" && isCommonSwarmClaudeHook(hook.command)));
|
|
42510
42577
|
if (row.hooks.length > 0) groups.push(row);
|
|
42511
42578
|
}
|
|
42512
42579
|
if (groups.length > 0) hooks.UserPromptSubmit = groups;
|
|
@@ -42518,10 +42585,29 @@ function uninstallClaudeHook(settings) {
|
|
|
42518
42585
|
}
|
|
42519
42586
|
return { ...settings, hooks };
|
|
42520
42587
|
}
|
|
42588
|
+
async function hookInstallPrincipalId(args) {
|
|
42589
|
+
const explicit = args.optional("principal-id");
|
|
42590
|
+
if (explicit !== void 0) return listenerUuid(explicit, "principal-id");
|
|
42591
|
+
const principalIds = await discoverListenerHookPrincipalIds(
|
|
42592
|
+
defaultListenerStateDirectory()
|
|
42593
|
+
);
|
|
42594
|
+
if (principalIds.length === 1) return principalIds[0];
|
|
42595
|
+
if (principalIds.length > 1) {
|
|
42596
|
+
throw new Error(
|
|
42597
|
+
"hook install claude found multiple agents on this host. Choose this agent explicitly: cswarm hook install claude --principal-id <uuid> [--write]"
|
|
42598
|
+
);
|
|
42599
|
+
}
|
|
42600
|
+
throw new Error(
|
|
42601
|
+
"hook install claude could not find a listener principal. Name this agent explicitly: cswarm hook install claude --principal-id <uuid> [--write]"
|
|
42602
|
+
);
|
|
42603
|
+
}
|
|
42521
42604
|
async function runHook(args) {
|
|
42522
42605
|
const command2 = args.positionals[1];
|
|
42523
42606
|
if (command2 === "check") {
|
|
42524
|
-
args.assertShape(["cooldown"], 2);
|
|
42607
|
+
args.assertShape(["cooldown", "principal-id"], 2);
|
|
42608
|
+
const rawPrincipalIds = args.all("principal-id");
|
|
42609
|
+
if (rawPrincipalIds.some((principalId2) => !UUID_RE19.test(principalId2))) return;
|
|
42610
|
+
const principalIds = rawPrincipalIds.map((principalId2) => principalId2.toLowerCase());
|
|
42525
42611
|
const rawCooldown = args.optional("cooldown");
|
|
42526
42612
|
const cooldownSeconds = rawCooldown === void 0 ? void 0 : Number(rawCooldown);
|
|
42527
42613
|
if (cooldownSeconds !== void 0 && (!/^\d+$/.test(rawCooldown) || !Number.isSafeInteger(cooldownSeconds) || cooldownSeconds < 0 || cooldownSeconds > 86400)) {
|
|
@@ -42531,43 +42617,41 @@ async function runHook(args) {
|
|
|
42531
42617
|
process.exit(0);
|
|
42532
42618
|
}, 3e3);
|
|
42533
42619
|
hardExit.unref();
|
|
42534
|
-
|
|
42535
|
-
|
|
42536
|
-
|
|
42537
|
-
|
|
42538
|
-
|
|
42539
|
-
|
|
42620
|
+
await runListenerHookCheck({
|
|
42621
|
+
...cooldownSeconds === void 0 ? {} : { cooldownSeconds },
|
|
42622
|
+
...principalIds.length === 0 ? {} : { principalIds },
|
|
42623
|
+
write: async (output) => {
|
|
42624
|
+
await new Promise((resolve, reject) => {
|
|
42625
|
+
process.stdout.write(`${output}
|
|
42540
42626
|
`, (error) => {
|
|
42541
|
-
|
|
42542
|
-
|
|
42543
|
-
});
|
|
42627
|
+
if (error) reject(error);
|
|
42628
|
+
else resolve();
|
|
42544
42629
|
});
|
|
42545
|
-
}
|
|
42546
|
-
}
|
|
42547
|
-
|
|
42548
|
-
|
|
42549
|
-
clearTimeout(hardExit);
|
|
42550
|
-
}
|
|
42630
|
+
});
|
|
42631
|
+
}
|
|
42632
|
+
});
|
|
42633
|
+
return;
|
|
42551
42634
|
}
|
|
42552
42635
|
if (command2 !== "install" && command2 !== "uninstall") {
|
|
42553
42636
|
throw new UsageError("hook requires check, install, or uninstall");
|
|
42554
42637
|
}
|
|
42555
|
-
args.assertShape(["write"], 3);
|
|
42638
|
+
args.assertShape(command2 === "install" ? ["write", "principal-id"] : ["write"], 3);
|
|
42556
42639
|
if (args.positionals[2] !== "claude") {
|
|
42557
42640
|
throw new Error("hook install/uninstall currently supports claude");
|
|
42558
42641
|
}
|
|
42559
42642
|
if (command2 === "uninstall" && !args.has("write")) {
|
|
42560
42643
|
throw new Error("hook uninstall claude requires --write");
|
|
42561
42644
|
}
|
|
42562
|
-
const
|
|
42563
|
-
|
|
42645
|
+
const principalId = command2 === "install" ? await hookInstallPrincipalId(args) : null;
|
|
42646
|
+
const snippet = principalId === null ? null : claudeUserPromptHookSnippet(principalId);
|
|
42647
|
+
if (command2 === "install" && !args.has("write")) {
|
|
42564
42648
|
process.stdout.write(`${JSON.stringify(snippet, null, 2)}
|
|
42565
42649
|
`);
|
|
42566
42650
|
return;
|
|
42567
42651
|
}
|
|
42568
42652
|
const path = projectClaudeSettingsPath();
|
|
42569
42653
|
const settings = readProjectSettings(path);
|
|
42570
|
-
const updated = command2 === "install" ? installClaudeHook(settings) : uninstallClaudeHook(settings);
|
|
42654
|
+
const updated = command2 === "install" ? installClaudeHook(settings, principalId) : uninstallClaudeHook(settings);
|
|
42571
42655
|
(0, import_node_fs7.mkdirSync)((0, import_node_path20.dirname)(path), { recursive: true });
|
|
42572
42656
|
(0, import_node_fs7.writeFileSync)(path, `${JSON.stringify(updated, null, 2)}
|
|
42573
42657
|
`, {
|
|
@@ -42575,7 +42659,7 @@ async function runHook(args) {
|
|
|
42575
42659
|
mode: 384
|
|
42576
42660
|
});
|
|
42577
42661
|
process.stdout.write(
|
|
42578
|
-
command2 === "install" ? `Installed the Claude Code UserPromptSubmit hook in ${path}. It runs: ${
|
|
42662
|
+
command2 === "install" ? `Installed the Claude Code UserPromptSubmit hook in ${path}. It runs: ${scopedClaudeHookCommand(principalId)}
|
|
42579
42663
|
` : `Removed the CommonSwarm UserPromptSubmit hook from ${path}. Other settings were kept.
|
|
42580
42664
|
`
|
|
42581
42665
|
);
|