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.
Files changed (2) hide show
  1. package/cswarm.cjs +142 -58
  2. 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 discoverContexts(stateDirectory2, isListenerLive = listenerIsLive) {
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
- const statusIsLive = storedStatus === null ? null : await isListenerLive(storedStatus);
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?.paths ?? null,
38563
- status: storedStatus?.status ?? null,
38597
+ instanceDirectory: storedStatus.instanceDirectory,
38598
+ paths: storedStatus.paths,
38599
+ status: storedStatus.status,
38564
38600
  listenerLive: statusIsLive,
38565
38601
  credential,
38566
- credentialReadFailed: credential === null && storedStatus !== 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 contexts = await discoverContexts(
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.39".length > 0) {
38943
- return "0.1.39";
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. hook check has its own 3s ceiling, exits 0 on every
39159
- outcome, and skips network checks made within --cooldown seconds (default 30).
39160
- hook install claude prints the UserPromptSubmit JSON by default and changes the
39161
- project's .claude/settings.json only with --write; uninstall also requires --write.
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 claudeUserPromptHookSnippet() {
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: CLAUDE_HOOK_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 alreadyInstalled = current.some((group) => {
42479
- if (!group || typeof group !== "object" || Array.isArray(group)) return false;
42480
- const commands = group.hooks;
42481
- return Array.isArray(commands) && commands.some(
42482
- (hook) => hook && typeof hook === "object" && !Array.isArray(hook) && hook.type === "command" && hook.command === CLAUDE_HOOK_COMMAND
42483
- );
42484
- });
42485
- if (!alreadyInstalled) {
42486
- const snippetHooks = claudeUserPromptHookSnippet().hooks.UserPromptSubmit;
42487
- current.push(snippetHooks[0]);
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
- hooks.UserPromptSubmit = current;
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 === CLAUDE_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
- try {
42535
- await runListenerHookCheck({
42536
- ...cooldownSeconds === void 0 ? {} : { cooldownSeconds },
42537
- write: async (output) => {
42538
- await new Promise((resolve, reject) => {
42539
- process.stdout.write(`${output}
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
- if (error) reject(error);
42542
- else resolve();
42543
- });
42627
+ if (error) reject(error);
42628
+ else resolve();
42544
42629
  });
42545
- }
42546
- });
42547
- return;
42548
- } finally {
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 snippet = claudeUserPromptHookSnippet();
42563
- if (!args.has("write")) {
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: ${CLAUDE_HOOK_COMMAND}
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
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "commonswarm",
3
- "version": "0.1.39",
3
+ "version": "0.1.40",
4
4
  "description": "CommonSwarm CLI — coordination for teams where people and AI agents work side by side.",
5
5
  "bin": {
6
6
  "cswarm": "cswarm.cjs"