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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CLAUDE.md +95 -0
- package/LICENSE +21 -0
- package/hooks/hooks.json +9 -0
- package/package.json +15 -2
- package/renovate.json5 +6 -0
- package/scripts/map-hook.mjs +88 -7
- package/scripts/map-sidecar.mjs +210 -1
- package/src/__tests__/cascade-client.test.mjs +217 -0
- package/src/__tests__/cascade-diff-server.test.mjs +375 -0
- package/src/__tests__/cascade-watcher.test.mjs +475 -0
- package/src/__tests__/config.test.mjs +23 -0
- package/src/__tests__/loadout-schema-bridge.test.mjs +1 -2
- package/src/__tests__/sidecar-nudge.test.mjs +137 -0
- package/src/bootstrap.mjs +20 -9
- package/src/cascade-client.mjs +334 -0
- package/src/cascade-diff-server.mjs +326 -0
- package/src/cascade-events.mjs +285 -0
- package/src/cascade-watcher.mjs +694 -0
- package/src/config.mjs +7 -0
- package/src/map-connection.mjs +18 -1
- package/src/map-events.mjs +8 -1
- package/src/paths.mjs +12 -0
- package/src/sidecar-server.mjs +62 -0
- package/src/skilltree-client.mjs +1 -1
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",
|
package/src/map-connection.mjs
CHANGED
|
@@ -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,
|
package/src/map-events.mjs
CHANGED
|
@@ -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
|
|
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/.
|
package/src/sidecar-server.mjs
CHANGED
|
@@ -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;
|
package/src/skilltree-client.mjs
CHANGED
|
@@ -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.
|
|
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.
|