society-protocol 1.0.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/LICENSE +21 -0
- package/README.md +111 -0
- package/dist/adapters.d.ts +101 -0
- package/dist/adapters.d.ts.map +1 -0
- package/dist/adapters.js +764 -0
- package/dist/adapters.js.map +1 -0
- package/dist/agents-md.d.ts +59 -0
- package/dist/agents-md.d.ts.map +1 -0
- package/dist/agents-md.js +204 -0
- package/dist/agents-md.js.map +1 -0
- package/dist/autoconfig.d.ts +137 -0
- package/dist/autoconfig.d.ts.map +1 -0
- package/dist/autoconfig.js +452 -0
- package/dist/autoconfig.js.map +1 -0
- package/dist/bootstrap.d.ts +68 -0
- package/dist/bootstrap.d.ts.map +1 -0
- package/dist/bootstrap.js +304 -0
- package/dist/bootstrap.js.map +1 -0
- package/dist/bridges/a2a-bridge.d.ts +156 -0
- package/dist/bridges/a2a-bridge.d.ts.map +1 -0
- package/dist/bridges/a2a-bridge.js +337 -0
- package/dist/bridges/a2a-bridge.js.map +1 -0
- package/dist/bridges/mcp-bridge.d.ts +87 -0
- package/dist/bridges/mcp-bridge.d.ts.map +1 -0
- package/dist/bridges/mcp-bridge.js +332 -0
- package/dist/bridges/mcp-bridge.js.map +1 -0
- package/dist/cache.d.ts +130 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +257 -0
- package/dist/cache.js.map +1 -0
- package/dist/capsules.d.ts +23 -0
- package/dist/capsules.d.ts.map +1 -0
- package/dist/capsules.js +75 -0
- package/dist/capsules.js.map +1 -0
- package/dist/cli/commands.d.ts +8 -0
- package/dist/cli/commands.d.ts.map +1 -0
- package/dist/cli/commands.js +263 -0
- package/dist/cli/commands.js.map +1 -0
- package/dist/coc.d.ts +121 -0
- package/dist/coc.d.ts.map +1 -0
- package/dist/coc.js +629 -0
- package/dist/coc.js.map +1 -0
- package/dist/coc.test.d.ts +2 -0
- package/dist/coc.test.d.ts.map +1 -0
- package/dist/coc.test.js +80 -0
- package/dist/coc.test.js.map +1 -0
- package/dist/compression.d.ts +125 -0
- package/dist/compression.d.ts.map +1 -0
- package/dist/compression.js +573 -0
- package/dist/compression.js.map +1 -0
- package/dist/cot-stream.d.ts +220 -0
- package/dist/cot-stream.d.ts.map +1 -0
- package/dist/cot-stream.js +673 -0
- package/dist/cot-stream.js.map +1 -0
- package/dist/crypto-wasm.d.ts +100 -0
- package/dist/crypto-wasm.d.ts.map +1 -0
- package/dist/crypto-wasm.js +229 -0
- package/dist/crypto-wasm.js.map +1 -0
- package/dist/federation.d.ts +200 -0
- package/dist/federation.d.ts.map +1 -0
- package/dist/federation.js +691 -0
- package/dist/federation.js.map +1 -0
- package/dist/federation.test.d.ts +2 -0
- package/dist/federation.test.d.ts.map +1 -0
- package/dist/federation.test.js +71 -0
- package/dist/federation.test.js.map +1 -0
- package/dist/gateway/capability-router.d.ts +77 -0
- package/dist/gateway/capability-router.d.ts.map +1 -0
- package/dist/gateway/capability-router.js +222 -0
- package/dist/gateway/capability-router.js.map +1 -0
- package/dist/gateway/demand-spawner.d.ts +155 -0
- package/dist/gateway/demand-spawner.d.ts.map +1 -0
- package/dist/gateway/demand-spawner.js +426 -0
- package/dist/gateway/demand-spawner.js.map +1 -0
- package/dist/identity.d.ts +46 -0
- package/dist/identity.d.ts.map +1 -0
- package/dist/identity.js +102 -0
- package/dist/identity.js.map +1 -0
- package/dist/identity.test.d.ts +2 -0
- package/dist/identity.test.d.ts.map +1 -0
- package/dist/identity.test.js +45 -0
- package/dist/identity.test.js.map +1 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1572 -0
- package/dist/index.js.map +1 -0
- package/dist/integration.d.ts +210 -0
- package/dist/integration.d.ts.map +1 -0
- package/dist/integration.js +1105 -0
- package/dist/integration.js.map +1 -0
- package/dist/integration.test.d.ts +2 -0
- package/dist/integration.test.d.ts.map +1 -0
- package/dist/integration.test.js +155 -0
- package/dist/integration.test.js.map +1 -0
- package/dist/knowledge.d.ts +219 -0
- package/dist/knowledge.d.ts.map +1 -0
- package/dist/knowledge.js +543 -0
- package/dist/knowledge.js.map +1 -0
- package/dist/knowledge.test.d.ts +2 -0
- package/dist/knowledge.test.d.ts.map +1 -0
- package/dist/knowledge.test.js +72 -0
- package/dist/knowledge.test.js.map +1 -0
- package/dist/latent-space.d.ts +178 -0
- package/dist/latent-space.d.ts.map +1 -0
- package/dist/latent-space.js +385 -0
- package/dist/latent-space.js.map +1 -0
- package/dist/lib.d.ts +30 -0
- package/dist/lib.d.ts.map +1 -0
- package/dist/lib.js +30 -0
- package/dist/lib.js.map +1 -0
- package/dist/mcp/server.d.ts +74 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +1392 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/metrics.d.ts +98 -0
- package/dist/metrics.d.ts.map +1 -0
- package/dist/metrics.js +222 -0
- package/dist/metrics.js.map +1 -0
- package/dist/p2p.d.ts +87 -0
- package/dist/p2p.d.ts.map +1 -0
- package/dist/p2p.js +606 -0
- package/dist/p2p.js.map +1 -0
- package/dist/persona/capabilities.d.ts +17 -0
- package/dist/persona/capabilities.d.ts.map +1 -0
- package/dist/persona/capabilities.js +224 -0
- package/dist/persona/capabilities.js.map +1 -0
- package/dist/persona/domains.d.ts +22 -0
- package/dist/persona/domains.d.ts.map +1 -0
- package/dist/persona/domains.js +176 -0
- package/dist/persona/domains.js.map +1 -0
- package/dist/persona/embeddings.d.ts +40 -0
- package/dist/persona/embeddings.d.ts.map +1 -0
- package/dist/persona/embeddings.js +265 -0
- package/dist/persona/embeddings.js.map +1 -0
- package/dist/persona/engine.d.ts +79 -0
- package/dist/persona/engine.d.ts.map +1 -0
- package/dist/persona/engine.js +1087 -0
- package/dist/persona/engine.js.map +1 -0
- package/dist/persona/index.d.ts +11 -0
- package/dist/persona/index.d.ts.map +1 -0
- package/dist/persona/index.js +11 -0
- package/dist/persona/index.js.map +1 -0
- package/dist/persona/lifecycle.d.ts +17 -0
- package/dist/persona/lifecycle.d.ts.map +1 -0
- package/dist/persona/lifecycle.js +36 -0
- package/dist/persona/lifecycle.js.map +1 -0
- package/dist/persona/retrieval.d.ts +6 -0
- package/dist/persona/retrieval.d.ts.map +1 -0
- package/dist/persona/retrieval.js +122 -0
- package/dist/persona/retrieval.js.map +1 -0
- package/dist/persona/sync.d.ts +15 -0
- package/dist/persona/sync.d.ts.map +1 -0
- package/dist/persona/sync.js +92 -0
- package/dist/persona/sync.js.map +1 -0
- package/dist/persona/types.d.ts +283 -0
- package/dist/persona/types.d.ts.map +1 -0
- package/dist/persona/types.js +2 -0
- package/dist/persona/types.js.map +1 -0
- package/dist/persona/zkp/engine.d.ts +26 -0
- package/dist/persona/zkp/engine.d.ts.map +1 -0
- package/dist/persona/zkp/engine.js +370 -0
- package/dist/persona/zkp/engine.js.map +1 -0
- package/dist/persona/zkp/types.d.ts +39 -0
- package/dist/persona/zkp/types.d.ts.map +1 -0
- package/dist/persona/zkp/types.js +2 -0
- package/dist/persona/zkp/types.js.map +1 -0
- package/dist/planner.d.ts +114 -0
- package/dist/planner.d.ts.map +1 -0
- package/dist/planner.js +522 -0
- package/dist/planner.js.map +1 -0
- package/dist/proactive/checkpoints.d.ts +9 -0
- package/dist/proactive/checkpoints.d.ts.map +1 -0
- package/dist/proactive/checkpoints.js +20 -0
- package/dist/proactive/checkpoints.js.map +1 -0
- package/dist/proactive/engine.d.ts +59 -0
- package/dist/proactive/engine.d.ts.map +1 -0
- package/dist/proactive/engine.js +406 -0
- package/dist/proactive/engine.js.map +1 -0
- package/dist/proactive/scheduler.d.ts +11 -0
- package/dist/proactive/scheduler.d.ts.map +1 -0
- package/dist/proactive/scheduler.js +45 -0
- package/dist/proactive/scheduler.js.map +1 -0
- package/dist/proactive/swarm-controller.d.ts +189 -0
- package/dist/proactive/swarm-controller.d.ts.map +1 -0
- package/dist/proactive/swarm-controller.js +477 -0
- package/dist/proactive/swarm-controller.js.map +1 -0
- package/dist/proactive/swarm-registry.d.ts +13 -0
- package/dist/proactive/swarm-registry.d.ts.map +1 -0
- package/dist/proactive/swarm-registry.js +122 -0
- package/dist/proactive/swarm-registry.js.map +1 -0
- package/dist/proactive/types.d.ts +145 -0
- package/dist/proactive/types.d.ts.map +1 -0
- package/dist/proactive/types.js +25 -0
- package/dist/proactive/types.js.map +1 -0
- package/dist/registry.d.ts +35 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +88 -0
- package/dist/registry.js.map +1 -0
- package/dist/reputation.d.ts +123 -0
- package/dist/reputation.d.ts.map +1 -0
- package/dist/reputation.js +366 -0
- package/dist/reputation.js.map +1 -0
- package/dist/reputation.test.d.ts +5 -0
- package/dist/reputation.test.d.ts.map +1 -0
- package/dist/reputation.test.js +265 -0
- package/dist/reputation.test.js.map +1 -0
- package/dist/rooms.d.ts +96 -0
- package/dist/rooms.d.ts.map +1 -0
- package/dist/rooms.js +410 -0
- package/dist/rooms.js.map +1 -0
- package/dist/sdk/client.d.ts +290 -0
- package/dist/sdk/client.d.ts.map +1 -0
- package/dist/sdk/client.js +1287 -0
- package/dist/sdk/client.js.map +1 -0
- package/dist/sdk/index.d.ts +32 -0
- package/dist/sdk/index.d.ts.map +1 -0
- package/dist/sdk/index.js +70 -0
- package/dist/sdk/index.js.map +1 -0
- package/dist/security.d.ts +230 -0
- package/dist/security.d.ts.map +1 -0
- package/dist/security.js +652 -0
- package/dist/security.js.map +1 -0
- package/dist/skills/engine.d.ts +262 -0
- package/dist/skills/engine.d.ts.map +1 -0
- package/dist/skills/engine.js +788 -0
- package/dist/skills/engine.js.map +1 -0
- package/dist/skills/engine.test.d.ts +2 -0
- package/dist/skills/engine.test.d.ts.map +1 -0
- package/dist/skills/engine.test.js +134 -0
- package/dist/skills/engine.test.js.map +1 -0
- package/dist/skills/parser.d.ts +129 -0
- package/dist/skills/parser.d.ts.map +1 -0
- package/dist/skills/parser.js +318 -0
- package/dist/skills/parser.js.map +1 -0
- package/dist/social.d.ts +149 -0
- package/dist/social.d.ts.map +1 -0
- package/dist/social.js +401 -0
- package/dist/social.js.map +1 -0
- package/dist/storage-optimized.d.ts +116 -0
- package/dist/storage-optimized.d.ts.map +1 -0
- package/dist/storage-optimized.js +264 -0
- package/dist/storage-optimized.js.map +1 -0
- package/dist/storage.d.ts +584 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +2703 -0
- package/dist/storage.js.map +1 -0
- package/dist/storage.test.d.ts +2 -0
- package/dist/storage.test.d.ts.map +1 -0
- package/dist/storage.test.js +78 -0
- package/dist/storage.test.js.map +1 -0
- package/dist/swp.d.ts +443 -0
- package/dist/swp.d.ts.map +1 -0
- package/dist/swp.js +223 -0
- package/dist/swp.js.map +1 -0
- package/dist/swp.test.d.ts +5 -0
- package/dist/swp.test.d.ts.map +1 -0
- package/dist/swp.test.js +127 -0
- package/dist/swp.test.js.map +1 -0
- package/dist/templates.d.ts +25 -0
- package/dist/templates.d.ts.map +1 -0
- package/dist/templates.js +1048 -0
- package/dist/templates.js.map +1 -0
- package/dist/test-e2e.d.ts +14 -0
- package/dist/test-e2e.d.ts.map +1 -0
- package/dist/test-e2e.js +266 -0
- package/dist/test-e2e.js.map +1 -0
- package/dist/workers/research-worker.d.ts +19 -0
- package/dist/workers/research-worker.d.ts.map +1 -0
- package/dist/workers/research-worker.js +141 -0
- package/dist/workers/research-worker.js.map +1 -0
- package/package.json +110 -0
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Society Protocol — Proactive Swarm Controller
|
|
3
|
+
*
|
|
4
|
+
* Advanced swarm coordination layer inspired by:
|
|
5
|
+
* - DRAMA (arXiv:2508.04332): Monitor agent + event-driven reallocation
|
|
6
|
+
* - SwarmSys (arXiv:2510.10047): Pheromone-inspired explorer/worker/validator roles
|
|
7
|
+
* - TDAG (arXiv:2402.10178): Dynamic task decomposition with skill libraries
|
|
8
|
+
* - SECP (arXiv:2602.02170): Self-evolving coordination with formal invariants
|
|
9
|
+
* - Google ADK: Bidirectional streaming + session management
|
|
10
|
+
*
|
|
11
|
+
* Provides:
|
|
12
|
+
* - Time-range scheduling (run swarm within specific time windows)
|
|
13
|
+
* - Real-time event-driven coordination (not just timer-based cadence)
|
|
14
|
+
* - Worker health monitoring with heartbeat + auto-reallocation
|
|
15
|
+
* - Dynamic role assignment (explorer/worker/validator from SwarmSys)
|
|
16
|
+
* - Pheromone-inspired affinity scoring for task-agent matching
|
|
17
|
+
* - Mission lifecycle with start/pause/resume/stop + time bounds
|
|
18
|
+
*/
|
|
19
|
+
import { EventEmitter } from 'events';
|
|
20
|
+
import type { Storage } from '../storage.js';
|
|
21
|
+
import type { RoomManager } from '../rooms.js';
|
|
22
|
+
import type { SwarmWorkerProfile } from './types.js';
|
|
23
|
+
import type { P2PSwarmRegistry } from './swarm-registry.js';
|
|
24
|
+
export type SwarmRole = 'explorer' | 'worker' | 'validator';
|
|
25
|
+
export interface TimeWindow {
|
|
26
|
+
/** Start time (ISO 8601 or epoch ms) */
|
|
27
|
+
startAt: number;
|
|
28
|
+
/** End time (ISO 8601 or epoch ms) */
|
|
29
|
+
endAt: number;
|
|
30
|
+
/** Timezone (IANA, e.g. 'America/Sao_Paulo') */
|
|
31
|
+
timezone?: string;
|
|
32
|
+
/** Recurrence pattern */
|
|
33
|
+
recurrence?: RecurrencePattern;
|
|
34
|
+
}
|
|
35
|
+
export interface RecurrencePattern {
|
|
36
|
+
/** Recurrence type */
|
|
37
|
+
type: 'daily' | 'weekly' | 'interval';
|
|
38
|
+
/** For 'interval': repeat every N ms */
|
|
39
|
+
intervalMs?: number;
|
|
40
|
+
/** For 'weekly': day numbers (0=Sun, 1=Mon, ..., 6=Sat) */
|
|
41
|
+
daysOfWeek?: number[];
|
|
42
|
+
/** For 'daily'/'weekly': start time as "HH:MM" */
|
|
43
|
+
startTime?: string;
|
|
44
|
+
/** For 'daily'/'weekly': end time as "HH:MM" */
|
|
45
|
+
endTime?: string;
|
|
46
|
+
/** Maximum number of occurrences (undefined = infinite) */
|
|
47
|
+
maxOccurrences?: number;
|
|
48
|
+
}
|
|
49
|
+
export interface SwarmAgentProfile extends SwarmWorkerProfile {
|
|
50
|
+
/** Current role in the swarm */
|
|
51
|
+
role: SwarmRole;
|
|
52
|
+
/** Affinity scores for different task types (pheromone-inspired) */
|
|
53
|
+
affinityScores: Map<string, number>;
|
|
54
|
+
/** Epsilon for exploration-exploitation balance */
|
|
55
|
+
explorationEpsilon: number;
|
|
56
|
+
/** Consecutive successful tasks */
|
|
57
|
+
successStreak: number;
|
|
58
|
+
/** Availability window */
|
|
59
|
+
availabilityWindow?: TimeWindow;
|
|
60
|
+
}
|
|
61
|
+
export interface SwarmEvent {
|
|
62
|
+
id: string;
|
|
63
|
+
type: SwarmEventType;
|
|
64
|
+
missionId: string;
|
|
65
|
+
roomId: string;
|
|
66
|
+
timestamp: number;
|
|
67
|
+
data: Record<string, unknown>;
|
|
68
|
+
}
|
|
69
|
+
export type SwarmEventType = 'worker:joined' | 'worker:left' | 'worker:failed' | 'worker:overloaded' | 'task:completed' | 'task:failed' | 'task:timeout' | 'mission:window:opened' | 'mission:window:closed' | 'reallocation:triggered' | 'role:changed' | 'consensus:reached';
|
|
70
|
+
export interface SwarmControllerConfig {
|
|
71
|
+
/** Heartbeat interval for liveness checks (ms) */
|
|
72
|
+
heartbeatIntervalMs: number;
|
|
73
|
+
/** Max time without heartbeat before marking unhealthy (ms) */
|
|
74
|
+
heartbeatTimeoutMs: number;
|
|
75
|
+
/** Reallocation cooldown after a reallocation event (ms) */
|
|
76
|
+
reallocationCooldownMs: number;
|
|
77
|
+
/** Min workers for explorer role */
|
|
78
|
+
minExplorers: number;
|
|
79
|
+
/** Min workers for validator role */
|
|
80
|
+
minValidators: number;
|
|
81
|
+
/** Affinity decay factor per tick (0-1) */
|
|
82
|
+
affinityDecay: number;
|
|
83
|
+
/** Affinity boost on successful task completion */
|
|
84
|
+
affinityBoost: number;
|
|
85
|
+
/** Base epsilon for exploration */
|
|
86
|
+
baseEpsilon: number;
|
|
87
|
+
/** Enable time-window scheduling */
|
|
88
|
+
enableTimeWindows: boolean;
|
|
89
|
+
/** Fixed-time consensus bound (ms) — max time to reach consensus */
|
|
90
|
+
consensusBoundMs: number;
|
|
91
|
+
}
|
|
92
|
+
export declare const DEFAULT_SWARM_CONTROLLER_CONFIG: SwarmControllerConfig;
|
|
93
|
+
export declare class SwarmController extends EventEmitter {
|
|
94
|
+
private storage;
|
|
95
|
+
private rooms;
|
|
96
|
+
private registry;
|
|
97
|
+
private config;
|
|
98
|
+
private agents;
|
|
99
|
+
private eventLog;
|
|
100
|
+
private missionWindows;
|
|
101
|
+
private windowTimers;
|
|
102
|
+
private monitorTimer?;
|
|
103
|
+
private lastReallocationAt;
|
|
104
|
+
constructor(storage: Storage, rooms: RoomManager, registry: P2PSwarmRegistry, config?: Partial<SwarmControllerConfig>);
|
|
105
|
+
/**
|
|
106
|
+
* Start the swarm controller's monitor loop.
|
|
107
|
+
* Acts as the DRAMA Monitor agent — detects state changes and triggers reallocation.
|
|
108
|
+
*/
|
|
109
|
+
start(): void;
|
|
110
|
+
/**
|
|
111
|
+
* Stop the swarm controller.
|
|
112
|
+
*/
|
|
113
|
+
stop(): void;
|
|
114
|
+
/**
|
|
115
|
+
* Register a time window for a mission.
|
|
116
|
+
* The swarm will only be active during this window.
|
|
117
|
+
*/
|
|
118
|
+
setMissionTimeWindow(missionId: string, window: TimeWindow): void;
|
|
119
|
+
/**
|
|
120
|
+
* Remove a mission's time window.
|
|
121
|
+
*/
|
|
122
|
+
removeMissionTimeWindow(missionId: string): void;
|
|
123
|
+
/**
|
|
124
|
+
* Check if a mission is currently within its time window.
|
|
125
|
+
*/
|
|
126
|
+
isMissionInWindow(missionId: string): boolean;
|
|
127
|
+
/**
|
|
128
|
+
* Register an agent with the swarm controller.
|
|
129
|
+
*/
|
|
130
|
+
registerAgent(worker: SwarmWorkerProfile, role?: SwarmRole): SwarmAgentProfile;
|
|
131
|
+
/**
|
|
132
|
+
* Update agent affinity after task completion (pheromone-inspired).
|
|
133
|
+
* SwarmSys: Validated contributions reinforce agent-task compatibility.
|
|
134
|
+
*/
|
|
135
|
+
recordTaskOutcome(agentDid: string, taskType: string, success: boolean): void;
|
|
136
|
+
/**
|
|
137
|
+
* Select the best agent for a task using affinity-based matching.
|
|
138
|
+
* Combines SwarmSys pheromone matching with epsilon-greedy exploration.
|
|
139
|
+
*/
|
|
140
|
+
selectAgent(taskType: string, requirements?: {
|
|
141
|
+
capabilities?: string[];
|
|
142
|
+
minReputation?: number;
|
|
143
|
+
}): SwarmAgentProfile | null;
|
|
144
|
+
/**
|
|
145
|
+
* Dynamically reassign roles based on current swarm state.
|
|
146
|
+
* Ensures minimum explorers and validators are maintained.
|
|
147
|
+
*/
|
|
148
|
+
rebalanceRoles(): {
|
|
149
|
+
changes: Array<{
|
|
150
|
+
did: string;
|
|
151
|
+
from: SwarmRole;
|
|
152
|
+
to: SwarmRole;
|
|
153
|
+
}>;
|
|
154
|
+
};
|
|
155
|
+
/**
|
|
156
|
+
* Get the current swarm status with roles and affinity info.
|
|
157
|
+
*/
|
|
158
|
+
getSwarmStatus(): {
|
|
159
|
+
agents: Array<{
|
|
160
|
+
did: string;
|
|
161
|
+
role: SwarmRole;
|
|
162
|
+
health: string;
|
|
163
|
+
load: number;
|
|
164
|
+
affinities: Record<string, number>;
|
|
165
|
+
epsilon: number;
|
|
166
|
+
successStreak: number;
|
|
167
|
+
}>;
|
|
168
|
+
roleDistribution: Record<SwarmRole, number>;
|
|
169
|
+
activeMissions: number;
|
|
170
|
+
activeWindows: Array<{
|
|
171
|
+
missionId: string;
|
|
172
|
+
inWindow: boolean;
|
|
173
|
+
window: TimeWindow;
|
|
174
|
+
}>;
|
|
175
|
+
recentEvents: SwarmEvent[];
|
|
176
|
+
};
|
|
177
|
+
/**
|
|
178
|
+
* Get recent events (useful for real-time UIs).
|
|
179
|
+
*/
|
|
180
|
+
getEvents(since?: number, limit?: number): SwarmEvent[];
|
|
181
|
+
private monitorTick;
|
|
182
|
+
private scheduleWindowEvents;
|
|
183
|
+
private isInRecurringWindow;
|
|
184
|
+
private computeNextWindow;
|
|
185
|
+
private inferRole;
|
|
186
|
+
private emitEvent;
|
|
187
|
+
destroy(): void;
|
|
188
|
+
}
|
|
189
|
+
//# sourceMappingURL=swarm-controller.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"swarm-controller.d.ts","sourceRoot":"","sources":["../../src/proactive/swarm-controller.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,477 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Society Protocol — Proactive Swarm Controller
|
|
3
|
+
*
|
|
4
|
+
* Advanced swarm coordination layer inspired by:
|
|
5
|
+
* - DRAMA (arXiv:2508.04332): Monitor agent + event-driven reallocation
|
|
6
|
+
* - SwarmSys (arXiv:2510.10047): Pheromone-inspired explorer/worker/validator roles
|
|
7
|
+
* - TDAG (arXiv:2402.10178): Dynamic task decomposition with skill libraries
|
|
8
|
+
* - SECP (arXiv:2602.02170): Self-evolving coordination with formal invariants
|
|
9
|
+
* - Google ADK: Bidirectional streaming + session management
|
|
10
|
+
*
|
|
11
|
+
* Provides:
|
|
12
|
+
* - Time-range scheduling (run swarm within specific time windows)
|
|
13
|
+
* - Real-time event-driven coordination (not just timer-based cadence)
|
|
14
|
+
* - Worker health monitoring with heartbeat + auto-reallocation
|
|
15
|
+
* - Dynamic role assignment (explorer/worker/validator from SwarmSys)
|
|
16
|
+
* - Pheromone-inspired affinity scoring for task-agent matching
|
|
17
|
+
* - Mission lifecycle with start/pause/resume/stop + time bounds
|
|
18
|
+
*/
|
|
19
|
+
import { EventEmitter } from 'events';
|
|
20
|
+
import { ulid } from 'ulid';
|
|
21
|
+
export const DEFAULT_SWARM_CONTROLLER_CONFIG = {
|
|
22
|
+
heartbeatIntervalMs: 10_000,
|
|
23
|
+
heartbeatTimeoutMs: 30_000,
|
|
24
|
+
reallocationCooldownMs: 15_000,
|
|
25
|
+
minExplorers: 1,
|
|
26
|
+
minValidators: 1,
|
|
27
|
+
affinityDecay: 0.95,
|
|
28
|
+
affinityBoost: 0.15,
|
|
29
|
+
baseEpsilon: 0.25,
|
|
30
|
+
enableTimeWindows: true,
|
|
31
|
+
consensusBoundMs: 60_000,
|
|
32
|
+
};
|
|
33
|
+
// ─── Swarm Controller ───────────────────────────────────────────
|
|
34
|
+
export class SwarmController extends EventEmitter {
|
|
35
|
+
storage;
|
|
36
|
+
rooms;
|
|
37
|
+
registry;
|
|
38
|
+
config;
|
|
39
|
+
agents = new Map();
|
|
40
|
+
eventLog = [];
|
|
41
|
+
missionWindows = new Map();
|
|
42
|
+
windowTimers = new Map();
|
|
43
|
+
monitorTimer;
|
|
44
|
+
lastReallocationAt = 0;
|
|
45
|
+
constructor(storage, rooms, registry, config = {}) {
|
|
46
|
+
super();
|
|
47
|
+
this.storage = storage;
|
|
48
|
+
this.rooms = rooms;
|
|
49
|
+
this.registry = registry;
|
|
50
|
+
this.config = { ...DEFAULT_SWARM_CONTROLLER_CONFIG, ...config };
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Start the swarm controller's monitor loop.
|
|
54
|
+
* Acts as the DRAMA Monitor agent — detects state changes and triggers reallocation.
|
|
55
|
+
*/
|
|
56
|
+
start() {
|
|
57
|
+
if (this.monitorTimer)
|
|
58
|
+
return;
|
|
59
|
+
this.monitorTimer = setInterval(() => {
|
|
60
|
+
this.monitorTick();
|
|
61
|
+
}, this.config.heartbeatIntervalMs);
|
|
62
|
+
this.monitorTimer.unref?.();
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Stop the swarm controller.
|
|
66
|
+
*/
|
|
67
|
+
stop() {
|
|
68
|
+
if (this.monitorTimer) {
|
|
69
|
+
clearInterval(this.monitorTimer);
|
|
70
|
+
this.monitorTimer = undefined;
|
|
71
|
+
}
|
|
72
|
+
for (const timer of this.windowTimers.values()) {
|
|
73
|
+
clearTimeout(timer);
|
|
74
|
+
}
|
|
75
|
+
this.windowTimers.clear();
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Register a time window for a mission.
|
|
79
|
+
* The swarm will only be active during this window.
|
|
80
|
+
*/
|
|
81
|
+
setMissionTimeWindow(missionId, window) {
|
|
82
|
+
this.missionWindows.set(missionId, window);
|
|
83
|
+
this.scheduleWindowEvents(missionId, window);
|
|
84
|
+
this.emitEvent({
|
|
85
|
+
type: 'mission:window:opened',
|
|
86
|
+
missionId,
|
|
87
|
+
roomId: '',
|
|
88
|
+
data: { startAt: window.startAt, endAt: window.endAt, recurrence: window.recurrence },
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Remove a mission's time window.
|
|
93
|
+
*/
|
|
94
|
+
removeMissionTimeWindow(missionId) {
|
|
95
|
+
this.missionWindows.delete(missionId);
|
|
96
|
+
const timer = this.windowTimers.get(missionId);
|
|
97
|
+
if (timer) {
|
|
98
|
+
clearTimeout(timer);
|
|
99
|
+
this.windowTimers.delete(missionId);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Check if a mission is currently within its time window.
|
|
104
|
+
*/
|
|
105
|
+
isMissionInWindow(missionId) {
|
|
106
|
+
const window = this.missionWindows.get(missionId);
|
|
107
|
+
if (!window)
|
|
108
|
+
return true; // No window = always active
|
|
109
|
+
const now = Date.now();
|
|
110
|
+
if (window.recurrence) {
|
|
111
|
+
return this.isInRecurringWindow(now, window);
|
|
112
|
+
}
|
|
113
|
+
return now >= window.startAt && now <= window.endAt;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Register an agent with the swarm controller.
|
|
117
|
+
*/
|
|
118
|
+
registerAgent(worker, role) {
|
|
119
|
+
const agent = {
|
|
120
|
+
...worker,
|
|
121
|
+
role: role || this.inferRole(worker),
|
|
122
|
+
affinityScores: new Map(),
|
|
123
|
+
explorationEpsilon: this.config.baseEpsilon,
|
|
124
|
+
successStreak: 0,
|
|
125
|
+
};
|
|
126
|
+
this.agents.set(worker.did, agent);
|
|
127
|
+
this.emitEvent({
|
|
128
|
+
type: 'worker:joined',
|
|
129
|
+
missionId: '',
|
|
130
|
+
roomId: worker.roomId,
|
|
131
|
+
data: { did: worker.did, role: agent.role },
|
|
132
|
+
});
|
|
133
|
+
return agent;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Update agent affinity after task completion (pheromone-inspired).
|
|
137
|
+
* SwarmSys: Validated contributions reinforce agent-task compatibility.
|
|
138
|
+
*/
|
|
139
|
+
recordTaskOutcome(agentDid, taskType, success) {
|
|
140
|
+
const agent = this.agents.get(agentDid);
|
|
141
|
+
if (!agent)
|
|
142
|
+
return;
|
|
143
|
+
const currentAffinity = agent.affinityScores.get(taskType) || 0.5;
|
|
144
|
+
if (success) {
|
|
145
|
+
// Boost affinity
|
|
146
|
+
agent.affinityScores.set(taskType, Math.min(1, currentAffinity + this.config.affinityBoost));
|
|
147
|
+
agent.successStreak++;
|
|
148
|
+
// Reduce epsilon for high-performing agents (exploit more)
|
|
149
|
+
agent.explorationEpsilon = Math.max(0.1, this.config.baseEpsilon - agent.successStreak * 0.02);
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
// Reduce affinity
|
|
153
|
+
agent.affinityScores.set(taskType, Math.max(0, currentAffinity - this.config.affinityBoost * 0.5));
|
|
154
|
+
agent.successStreak = 0;
|
|
155
|
+
// Increase epsilon for struggling agents (explore more)
|
|
156
|
+
agent.explorationEpsilon = Math.min(0.4, this.config.baseEpsilon + 0.1);
|
|
157
|
+
}
|
|
158
|
+
this.emitEvent({
|
|
159
|
+
type: success ? 'task:completed' : 'task:failed',
|
|
160
|
+
missionId: '',
|
|
161
|
+
roomId: agent.roomId,
|
|
162
|
+
data: { agentDid, taskType, success, newAffinity: agent.affinityScores.get(taskType) },
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Select the best agent for a task using affinity-based matching.
|
|
167
|
+
* Combines SwarmSys pheromone matching with epsilon-greedy exploration.
|
|
168
|
+
*/
|
|
169
|
+
selectAgent(taskType, requirements = {}) {
|
|
170
|
+
const candidates = [...this.agents.values()].filter((a) => {
|
|
171
|
+
if (a.health === 'unhealthy')
|
|
172
|
+
return false;
|
|
173
|
+
if (a.role === 'validator')
|
|
174
|
+
return false; // Validators don't do tasks
|
|
175
|
+
if (requirements.capabilities?.length) {
|
|
176
|
+
const hasCaps = requirements.capabilities.every((cap) => a.capabilities.includes(cap) || a.specialties.includes(cap));
|
|
177
|
+
if (!hasCaps)
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
return true;
|
|
181
|
+
});
|
|
182
|
+
if (candidates.length === 0)
|
|
183
|
+
return null;
|
|
184
|
+
// Epsilon-greedy: explore with probability epsilon
|
|
185
|
+
const explorer = candidates[0]; // Use first agent's epsilon as reference
|
|
186
|
+
if (Math.random() < (explorer?.explorationEpsilon || this.config.baseEpsilon)) {
|
|
187
|
+
// Random selection (exploration)
|
|
188
|
+
return candidates[Math.floor(Math.random() * candidates.length)];
|
|
189
|
+
}
|
|
190
|
+
// Greedy selection based on affinity + load score
|
|
191
|
+
let best = null;
|
|
192
|
+
let bestScore = -Infinity;
|
|
193
|
+
for (const agent of candidates) {
|
|
194
|
+
const affinity = agent.affinityScores.get(taskType) || 0.5;
|
|
195
|
+
const loadPenalty = agent.load * 30;
|
|
196
|
+
const healthBonus = agent.health === 'healthy' ? 50 : 20;
|
|
197
|
+
const streakBonus = Math.min(agent.successStreak * 2, 20);
|
|
198
|
+
const score = affinity * 100 + healthBonus - loadPenalty + streakBonus;
|
|
199
|
+
if (score > bestScore) {
|
|
200
|
+
bestScore = score;
|
|
201
|
+
best = agent;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return best;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Dynamically reassign roles based on current swarm state.
|
|
208
|
+
* Ensures minimum explorers and validators are maintained.
|
|
209
|
+
*/
|
|
210
|
+
rebalanceRoles() {
|
|
211
|
+
const agents = [...this.agents.values()].filter((a) => a.health !== 'unhealthy');
|
|
212
|
+
const changes = [];
|
|
213
|
+
const explorers = agents.filter((a) => a.role === 'explorer');
|
|
214
|
+
const validators = agents.filter((a) => a.role === 'validator');
|
|
215
|
+
const workers = agents.filter((a) => a.role === 'worker');
|
|
216
|
+
// Ensure minimum explorers
|
|
217
|
+
if (explorers.length < this.config.minExplorers && workers.length > 1) {
|
|
218
|
+
const toPromote = workers
|
|
219
|
+
.sort((a, b) => (b.explorationEpsilon || 0) - (a.explorationEpsilon || 0))
|
|
220
|
+
.slice(0, this.config.minExplorers - explorers.length);
|
|
221
|
+
for (const agent of toPromote) {
|
|
222
|
+
const oldRole = agent.role;
|
|
223
|
+
agent.role = 'explorer';
|
|
224
|
+
changes.push({ did: agent.did, from: oldRole, to: 'explorer' });
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
// Ensure minimum validators
|
|
228
|
+
if (validators.length < this.config.minValidators && workers.length > 1) {
|
|
229
|
+
const toPromote = workers
|
|
230
|
+
.filter((a) => !changes.some((c) => c.did === a.did))
|
|
231
|
+
.sort((a, b) => (b.successRate || 0) - (a.successRate || 0))
|
|
232
|
+
.slice(0, this.config.minValidators - validators.length);
|
|
233
|
+
for (const agent of toPromote) {
|
|
234
|
+
const oldRole = agent.role;
|
|
235
|
+
agent.role = 'validator';
|
|
236
|
+
changes.push({ did: agent.did, from: oldRole, to: 'validator' });
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
for (const change of changes) {
|
|
240
|
+
this.emitEvent({
|
|
241
|
+
type: 'role:changed',
|
|
242
|
+
missionId: '',
|
|
243
|
+
roomId: this.agents.get(change.did)?.roomId || '',
|
|
244
|
+
data: change,
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
return { changes };
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Get the current swarm status with roles and affinity info.
|
|
251
|
+
*/
|
|
252
|
+
getSwarmStatus() {
|
|
253
|
+
const agents = [...this.agents.values()].map((a) => ({
|
|
254
|
+
did: a.did,
|
|
255
|
+
role: a.role,
|
|
256
|
+
health: a.health,
|
|
257
|
+
load: a.load,
|
|
258
|
+
affinities: Object.fromEntries(a.affinityScores),
|
|
259
|
+
epsilon: a.explorationEpsilon,
|
|
260
|
+
successStreak: a.successStreak,
|
|
261
|
+
}));
|
|
262
|
+
const roleDistribution = {
|
|
263
|
+
explorer: agents.filter((a) => a.role === 'explorer').length,
|
|
264
|
+
worker: agents.filter((a) => a.role === 'worker').length,
|
|
265
|
+
validator: agents.filter((a) => a.role === 'validator').length,
|
|
266
|
+
};
|
|
267
|
+
const activeWindows = [...this.missionWindows.entries()].map(([missionId, window]) => ({
|
|
268
|
+
missionId,
|
|
269
|
+
inWindow: this.isMissionInWindow(missionId),
|
|
270
|
+
window,
|
|
271
|
+
}));
|
|
272
|
+
return {
|
|
273
|
+
agents,
|
|
274
|
+
roleDistribution,
|
|
275
|
+
activeMissions: activeWindows.filter((w) => w.inWindow).length,
|
|
276
|
+
activeWindows,
|
|
277
|
+
recentEvents: this.eventLog.slice(-20),
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Get recent events (useful for real-time UIs).
|
|
282
|
+
*/
|
|
283
|
+
getEvents(since, limit = 50) {
|
|
284
|
+
let events = this.eventLog;
|
|
285
|
+
if (since) {
|
|
286
|
+
events = events.filter((e) => e.timestamp > since);
|
|
287
|
+
}
|
|
288
|
+
return events.slice(-limit);
|
|
289
|
+
}
|
|
290
|
+
// ─── Private: Monitor ───────────────────────────────────────
|
|
291
|
+
monitorTick() {
|
|
292
|
+
const now = Date.now();
|
|
293
|
+
let stateChanged = false;
|
|
294
|
+
// Check heartbeats (DRAMA Monitor agent)
|
|
295
|
+
for (const agent of this.agents.values()) {
|
|
296
|
+
const timeSinceLastSeen = now - (agent.lastSeen || 0);
|
|
297
|
+
if (timeSinceLastSeen > this.config.heartbeatTimeoutMs && agent.health !== 'unhealthy') {
|
|
298
|
+
agent.health = 'unhealthy';
|
|
299
|
+
stateChanged = true;
|
|
300
|
+
this.emitEvent({
|
|
301
|
+
type: 'worker:failed',
|
|
302
|
+
missionId: '',
|
|
303
|
+
roomId: agent.roomId,
|
|
304
|
+
data: { did: agent.did, reason: 'heartbeat_timeout', lastSeen: agent.lastSeen },
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
// Check overload
|
|
308
|
+
if (agent.load >= 0.95 && agent.health === 'healthy') {
|
|
309
|
+
this.emitEvent({
|
|
310
|
+
type: 'worker:overloaded',
|
|
311
|
+
missionId: '',
|
|
312
|
+
roomId: agent.roomId,
|
|
313
|
+
data: { did: agent.did, load: agent.load },
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
// Decay affinities
|
|
318
|
+
for (const agent of this.agents.values()) {
|
|
319
|
+
for (const [taskType, affinity] of agent.affinityScores) {
|
|
320
|
+
agent.affinityScores.set(taskType, affinity * this.config.affinityDecay);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
// Check time windows
|
|
324
|
+
if (this.config.enableTimeWindows) {
|
|
325
|
+
for (const [missionId, window] of this.missionWindows) {
|
|
326
|
+
const inWindow = this.isMissionInWindow(missionId);
|
|
327
|
+
// Could trigger mission pause/resume here
|
|
328
|
+
if (!inWindow && now > window.endAt && !window.recurrence) {
|
|
329
|
+
this.emitEvent({
|
|
330
|
+
type: 'mission:window:closed',
|
|
331
|
+
missionId,
|
|
332
|
+
roomId: '',
|
|
333
|
+
data: { endAt: window.endAt },
|
|
334
|
+
});
|
|
335
|
+
this.missionWindows.delete(missionId);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
// DRAMA: Trigger reallocation only on significant state changes
|
|
340
|
+
if (stateChanged && now - this.lastReallocationAt > this.config.reallocationCooldownMs) {
|
|
341
|
+
this.lastReallocationAt = now;
|
|
342
|
+
this.rebalanceRoles();
|
|
343
|
+
this.emitEvent({
|
|
344
|
+
type: 'reallocation:triggered',
|
|
345
|
+
missionId: '',
|
|
346
|
+
roomId: '',
|
|
347
|
+
data: { reason: 'state_change' },
|
|
348
|
+
});
|
|
349
|
+
this.emit('reallocation', this.getSwarmStatus());
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
// ─── Private: Time Windows ──────────────────────────────────
|
|
353
|
+
scheduleWindowEvents(missionId, window) {
|
|
354
|
+
const now = Date.now();
|
|
355
|
+
// Schedule window open
|
|
356
|
+
if (window.startAt > now) {
|
|
357
|
+
const openTimer = setTimeout(() => {
|
|
358
|
+
this.emit('window:opened', missionId);
|
|
359
|
+
this.emitEvent({
|
|
360
|
+
type: 'mission:window:opened',
|
|
361
|
+
missionId,
|
|
362
|
+
roomId: '',
|
|
363
|
+
data: { startAt: window.startAt },
|
|
364
|
+
});
|
|
365
|
+
}, window.startAt - now);
|
|
366
|
+
openTimer.unref?.();
|
|
367
|
+
this.windowTimers.set(`${missionId}:open`, openTimer);
|
|
368
|
+
}
|
|
369
|
+
// Schedule window close
|
|
370
|
+
if (window.endAt > now) {
|
|
371
|
+
const closeTimer = setTimeout(() => {
|
|
372
|
+
this.emit('window:closed', missionId);
|
|
373
|
+
this.emitEvent({
|
|
374
|
+
type: 'mission:window:closed',
|
|
375
|
+
missionId,
|
|
376
|
+
roomId: '',
|
|
377
|
+
data: { endAt: window.endAt },
|
|
378
|
+
});
|
|
379
|
+
// Schedule next occurrence if recurring
|
|
380
|
+
if (window.recurrence) {
|
|
381
|
+
const nextWindow = this.computeNextWindow(window);
|
|
382
|
+
if (nextWindow) {
|
|
383
|
+
this.setMissionTimeWindow(missionId, nextWindow);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}, window.endAt - now);
|
|
387
|
+
closeTimer.unref?.();
|
|
388
|
+
this.windowTimers.set(`${missionId}:close`, closeTimer);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
isInRecurringWindow(now, window) {
|
|
392
|
+
const rec = window.recurrence;
|
|
393
|
+
if (rec.type === 'interval') {
|
|
394
|
+
const elapsed = now - window.startAt;
|
|
395
|
+
const interval = rec.intervalMs || 3_600_000;
|
|
396
|
+
const windowDuration = window.endAt - window.startAt;
|
|
397
|
+
const cyclePosition = elapsed % interval;
|
|
398
|
+
return cyclePosition < windowDuration;
|
|
399
|
+
}
|
|
400
|
+
if (rec.type === 'daily' || rec.type === 'weekly') {
|
|
401
|
+
const date = new Date(now);
|
|
402
|
+
const dayOfWeek = date.getDay();
|
|
403
|
+
if (rec.type === 'weekly' && rec.daysOfWeek) {
|
|
404
|
+
if (!rec.daysOfWeek.includes(dayOfWeek))
|
|
405
|
+
return false;
|
|
406
|
+
}
|
|
407
|
+
if (rec.startTime && rec.endTime) {
|
|
408
|
+
const [startH, startM] = rec.startTime.split(':').map(Number);
|
|
409
|
+
const [endH, endM] = rec.endTime.split(':').map(Number);
|
|
410
|
+
const currentMinutes = date.getHours() * 60 + date.getMinutes();
|
|
411
|
+
const startMinutes = startH * 60 + startM;
|
|
412
|
+
const endMinutes = endH * 60 + endM;
|
|
413
|
+
return currentMinutes >= startMinutes && currentMinutes <= endMinutes;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
// Fallback to simple range check
|
|
417
|
+
return now >= window.startAt && now <= window.endAt;
|
|
418
|
+
}
|
|
419
|
+
computeNextWindow(window) {
|
|
420
|
+
const rec = window.recurrence;
|
|
421
|
+
if (rec.maxOccurrences !== undefined && rec.maxOccurrences <= 1)
|
|
422
|
+
return null;
|
|
423
|
+
const duration = window.endAt - window.startAt;
|
|
424
|
+
let nextStart;
|
|
425
|
+
if (rec.type === 'interval') {
|
|
426
|
+
nextStart = window.startAt + (rec.intervalMs || 3_600_000);
|
|
427
|
+
}
|
|
428
|
+
else if (rec.type === 'daily') {
|
|
429
|
+
nextStart = window.startAt + 86_400_000;
|
|
430
|
+
}
|
|
431
|
+
else if (rec.type === 'weekly') {
|
|
432
|
+
nextStart = window.startAt + 7 * 86_400_000;
|
|
433
|
+
}
|
|
434
|
+
else {
|
|
435
|
+
return null;
|
|
436
|
+
}
|
|
437
|
+
return {
|
|
438
|
+
startAt: nextStart,
|
|
439
|
+
endAt: nextStart + duration,
|
|
440
|
+
timezone: window.timezone,
|
|
441
|
+
recurrence: {
|
|
442
|
+
...rec,
|
|
443
|
+
maxOccurrences: rec.maxOccurrences ? rec.maxOccurrences - 1 : undefined,
|
|
444
|
+
},
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
// ─── Private: Helpers ───────────────────────────────────────
|
|
448
|
+
inferRole(worker) {
|
|
449
|
+
if (worker.kinds.includes('verification') || worker.kinds.includes('review')) {
|
|
450
|
+
return 'validator';
|
|
451
|
+
}
|
|
452
|
+
if (worker.kinds.includes('synthesis') || worker.specialties.includes('planning')) {
|
|
453
|
+
return 'explorer';
|
|
454
|
+
}
|
|
455
|
+
return 'worker';
|
|
456
|
+
}
|
|
457
|
+
emitEvent(event) {
|
|
458
|
+
const full = {
|
|
459
|
+
id: `sevt_${ulid()}`,
|
|
460
|
+
timestamp: Date.now(),
|
|
461
|
+
...event,
|
|
462
|
+
};
|
|
463
|
+
this.eventLog.push(full);
|
|
464
|
+
// Keep last 1000 events
|
|
465
|
+
if (this.eventLog.length > 1000) {
|
|
466
|
+
this.eventLog = this.eventLog.slice(-500);
|
|
467
|
+
}
|
|
468
|
+
this.emit('event', full);
|
|
469
|
+
}
|
|
470
|
+
destroy() {
|
|
471
|
+
this.stop();
|
|
472
|
+
this.agents.clear();
|
|
473
|
+
this.eventLog = [];
|
|
474
|
+
this.removeAllListeners();
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
//# sourceMappingURL=swarm-controller.js.map
|