claude-code-swarm 0.3.25 → 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/src/config.mjs CHANGED
@@ -105,6 +105,13 @@ export function readConfig(configPath = CONFIG_PATH, globalConfigPath = GLOBAL_C
105
105
  trust: project.inbox?.federation?.trust ?? global.inbox?.federation?.trust ?? undefined,
106
106
  },
107
107
  },
108
+ // Cascade integration. Only meaningful when `map` is enabled — the
109
+ // sidecar emits x-cascade/* notifications over the MAP connection.
110
+ // When map is disabled there is no connection to emit on, so cascade
111
+ // becomes an inert no-op (no hard failure — mirrors opentasks/minimem).
112
+ cascade: {
113
+ enabled: envBool("SWARM_CASCADE_ENABLED") ?? Boolean(project.cascade?.enabled ?? global.cascade?.enabled),
114
+ },
108
115
  minimem: {
109
116
  enabled: envBool("SWARM_MINIMEM_ENABLED") ?? Boolean(project.minimem?.enabled ?? global.minimem?.enabled),
110
117
  provider: envStr("SWARM_MINIMEM_PROVIDER") ?? project.minimem?.provider ?? global.minimem?.provider ?? "auto",
@@ -24,7 +24,7 @@ const log = createLogger("map");
24
24
  * authRequired challenge with the server's preferred method + this credential.
25
25
  * When absent, uses the standard SDK connect() for open mode servers.
26
26
  */
27
- export async function connectToMAP({ server, scope, systemId, onMessage, credential, projectContext, inboxEnabled }) {
27
+ export async function connectToMAP({ server, scope, systemId, onMessage, credential, projectContext, inboxEnabled, cascadeEnabled }) {
28
28
  try {
29
29
  const mapSdk = await resolvePackage("@multi-agent-protocol/sdk");
30
30
  if (!mapSdk) throw new Error("@multi-agent-protocol/sdk not available");
@@ -49,6 +49,23 @@ export async function connectToMAP({ server, scope, systemId, onMessage, credent
49
49
  ...(projectContext?.task_graph ? {
50
50
  opentasks: { canQuery: true, canLink: true, canAnnotate: true, canTask: true },
51
51
  } : {}),
52
+ // Cascade capability (CascadeCapability, git-cascade >= 0.0.8) — gated
53
+ // on cascade.enabled. cc-swarm runs cascade in "observed git" mode: it
54
+ // watches git state and serves diffs. It now also probes in-progress
55
+ // *merge* conflict state on every poll tick and emits
56
+ // `x-cascade/stream.conflicted` / `stream.conflict_resolved` on the
57
+ // transitions — so `emitsConflicts: true` ships honestly. Action-layer
58
+ // remains observe-only (`canAct: false`); the watcher does not drive
59
+ // merges / rebases / pauses.
60
+ // canServeDiff: true — the diff server (src/cascade-diff-server.mjs) is wired
61
+ // canAct: false — no cascade action handler
62
+ // emitsConflicts: true — the watcher observes merge-conflict transitions
63
+ // TODO: rebase-conflict observation (`.git/rebase-merge/`,
64
+ // `.git/rebase-apply/`) is a known follow-up. v1 covers `git merge`
65
+ // conflicts only.
66
+ ...(cascadeEnabled ? {
67
+ cascade: { canServeDiff: true, canAct: false, emitsConflicts: true },
68
+ } : {}),
52
69
  },
53
70
  metadata: {
54
71
  systemId,
@@ -63,12 +63,18 @@ export async function emitPayload(config, payload, meta, sessionId) {
63
63
 
64
64
  /**
65
65
  * Build a "spawn" sidecar command for a subagent.
66
+ *
67
+ * The agentId is derived from hookData.agent_id when available (stable,
68
+ * set by the spawning agent) or falls back to a timestamp-based ID.
69
+ * `inboxAgentId` is included in metadata so the hub can correlate MAP
70
+ * and inbox identities.
66
71
  */
67
72
  export function buildSubagentSpawnCommand(hookData, teamName) {
73
+ const agentId = hookData.agent_id || `${teamName}-subagent-${Date.now()}`;
68
74
  return {
69
75
  action: "spawn",
70
76
  agent: {
71
- agentId: hookData.agent_id || `${teamName}-subagent-${Date.now()}`,
77
+ agentId,
72
78
  name: hookData.agent_type || "subagent",
73
79
  role: "subagent",
74
80
  scopes: [`swarm:${teamName}`],
@@ -76,6 +82,7 @@ export function buildSubagentSpawnCommand(hookData, teamName) {
76
82
  agentType: hookData.agent_type || "",
77
83
  sessionId: hookData.session_id || "",
78
84
  isTeamRole: false,
85
+ inboxAgentId: agentId,
79
86
  },
80
87
  },
81
88
  };
package/src/paths.mjs CHANGED
@@ -102,6 +102,10 @@ export const LOGS_DIR = path.join(GLOBAL_CONFIG_DIR, "tmp", "logs");
102
102
  export const OPENTASKS_DIR = path.join(_tmpDir, "opentasks");
103
103
  export const OPENTASKS_SYNC_STATE_PATH = path.join(_tmpDir, "opentasks", "sync-state.json");
104
104
 
105
+ // cascade runtime state — git-cascade tracker DB (local-mode state store)
106
+ export const CASCADE_DIR = path.join(_tmpDir, "cascade");
107
+ export const CASCADE_DB_PATH = path.join(_tmpDir, "cascade", "tracker.db");
108
+
105
109
  /**
106
110
  * Whether paths resolved to global (~/.claude-swarm/tmp/) vs project-level.
107
111
  */
@@ -149,6 +153,14 @@ export function ensureOpentasksDir() {
149
153
  fs.mkdirSync(OPENTASKS_DIR, { recursive: true });
150
154
  }
151
155
 
156
+ /**
157
+ * Ensure the cascade runtime directory exists.
158
+ * Holds the git-cascade tracker DB (local-mode state store).
159
+ */
160
+ export function ensureCascadeDir() {
161
+ fs.mkdirSync(CASCADE_DIR, { recursive: true });
162
+ }
163
+
152
164
  /**
153
165
  * Resolve the plugin root directory.
154
166
  * Works from any file in src/ or scripts/.
@@ -99,6 +99,17 @@ export function createCommandHandler(connection, scope, registeredAgents, opts =
99
99
  const { inboxInstance, meshPeer, transportMode = "websocket" } = opts;
100
100
  const useMeshRegistry = transportMode === "mesh" && inboxInstance;
101
101
 
102
+ // Dispatch thread nudge state — set by x-dispatch/nudge notifications,
103
+ // consumed by the UserPromptSubmit hook via the check-nudge command.
104
+ // Keyed by dispatch_id → { conversation_id, received_at }.
105
+ const _pendingNudges = new Map();
106
+
107
+ // Cascade attribution hint — the single most-recent { agentId, taskRef, ts }
108
+ // sent by the PostToolUse(Bash) hook via the cascade-attribution command.
109
+ // The cascade-watcher reads this (via getCascadeAttribution) to stamp
110
+ // agent_id / task_ref on observed-git events when the hint is fresh.
111
+ let _cascadeAttribution = null;
112
+
102
113
  // Connection-ready gate: commands that need `conn` await this promise.
103
114
  // If connection is already available, resolves immediately.
104
115
  // When connection arrives later (via setConnection), resolves the pending promise.
@@ -474,6 +485,52 @@ export function createCommandHandler(connection, scope, registeredAgents, opts =
474
485
  break;
475
486
  }
476
487
 
488
+ // --- Dispatch thread nudge ---
489
+ // Set by x-dispatch/nudge MAP notifications, consumed by hooks.
490
+
491
+ case "nudge": {
492
+ // Called internally when the notification handler fires.
493
+ const { dispatch_id, conversation_id } = command;
494
+ if (dispatch_id) {
495
+ _pendingNudges.set(dispatch_id, {
496
+ conversation_id,
497
+ received_at: Date.now(),
498
+ });
499
+ }
500
+ respond(client, { ok: true });
501
+ break;
502
+ }
503
+
504
+ case "check-nudge": {
505
+ // Called by UserPromptSubmit hook. Returns and clears all
506
+ // pending nudges so the hook can inject a hint.
507
+ const nudges = [];
508
+ for (const [dispatchId, info] of _pendingNudges) {
509
+ nudges.push({
510
+ dispatch_id: dispatchId,
511
+ conversation_id: info.conversation_id,
512
+ });
513
+ }
514
+ _pendingNudges.clear();
515
+ respond(client, { ok: true, nudges });
516
+ break;
517
+ }
518
+
519
+ // --- Cascade attribution hint ---
520
+ // Sent by the PostToolUse(Bash) hook. Stores the single most-recent
521
+ // attribution hint; the cascade-watcher reads it to attribute
522
+ // observed-git events. Attribution-only — no git detection here.
523
+
524
+ case "cascade-attribution": {
525
+ _cascadeAttribution = {
526
+ agentId: command.agentId || "",
527
+ taskRef: command.taskRef || null,
528
+ ts: typeof command.ts === "number" ? command.ts : Date.now(),
529
+ };
530
+ respond(client, { ok: true });
531
+ break;
532
+ }
533
+
477
534
  default:
478
535
  respond(client, { ok: false, error: `Unknown action: ${action}` });
479
536
  }
@@ -483,6 +540,11 @@ export function createCommandHandler(connection, scope, registeredAgents, opts =
483
540
  }
484
541
  };
485
542
 
543
+ // Expose the latest cascade attribution hint so the sidecar can pass a
544
+ // getter to startCascadeWatcher as `getAttribution`. Returns the single
545
+ // most-recent { agentId, taskRef, ts } hint, or null when none received.
546
+ handler.getCascadeAttribution = () => _cascadeAttribution;
547
+
486
548
  // Allow updating the connection reference (also resolves any pending waitForConn)
487
549
  handler.setConnection = (newConn) => {
488
550
  conn = newConn;
@@ -155,7 +155,7 @@ export function inferProfileFromRole(roleName) {
155
155
  // The bridge between openteams `loadout.skills` (SkillsConfig in the
156
156
  // schema) and skill-tree's LoadoutCriteria. skill-tree is the
157
157
  // *mechanism*; openteams is the *declaration layer* that dispatches
158
- // into it. See openhive's docs/LOADOUT_INTEGRATION.md for the model.
158
+ // into it.
159
159
  //
160
160
  // Bridged fields are locked in by src/__tests__/loadout-schema-bridge.test.mjs
161
161
  // which cross-references this list against openteams' SkillsConfig schema.