create-metaclaw 3.3.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.
Files changed (92) hide show
  1. package/LICENSE +44 -0
  2. package/README.md +282 -0
  3. package/docs/assets/favicon.png +0 -0
  4. package/docs/assets/metaclaw-banner.svg +86 -0
  5. package/docs/assets/qis-logo.png +0 -0
  6. package/docs/assets/yz-favicon.png +0 -0
  7. package/docs/assets/yz-logo.png +0 -0
  8. package/docs/index.html +895 -0
  9. package/installer/assets/favicon.png +0 -0
  10. package/installer/auto-start.ts +330 -0
  11. package/installer/brand.ts +115 -0
  12. package/installer/core-scaffold.ts +448 -0
  13. package/installer/dashboard-generator.ts +657 -0
  14. package/installer/detect.ts +129 -0
  15. package/installer/index.ts +355 -0
  16. package/installer/module-loader.ts +412 -0
  17. package/installer/modules/boardroom/boardroom/client.ts.txt +201 -0
  18. package/installer/modules/boardroom/boardroom/db.ts.txt +322 -0
  19. package/installer/modules/boardroom/boardroom/meeting-agent.ts.txt +129 -0
  20. package/installer/modules/boardroom/boardroom/meeting-scheduler.ts.txt +194 -0
  21. package/installer/modules/boardroom/boardroom/server.ts.txt +473 -0
  22. package/installer/modules/boardroom/boardroom/start-boardroom.bat.txt +26 -0
  23. package/installer/modules/boardroom/boardroom/summons.ts.txt +76 -0
  24. package/installer/modules/boardroom/boardroom/turn-v2.ts.txt +172 -0
  25. package/installer/modules/boardroom/boardroom/turn.ts.txt +208 -0
  26. package/installer/modules/boardroom/boardroom/types.ts.txt +100 -0
  27. package/installer/modules/boardroom/metaclaw-module.json +35 -0
  28. package/installer/modules/boardroom/scripts/meeting-check.bat.txt +38 -0
  29. package/installer/modules/core/metaclaw-module.json +51 -0
  30. package/installer/modules/core/src/db.ts.txt +277 -0
  31. package/installer/modules/core/src/health-check.ts.txt +128 -0
  32. package/installer/modules/core/src/observability.ts.txt +20 -0
  33. package/installer/modules/core/src/safety.ts.txt +26 -0
  34. package/installer/modules/core/src/scan-capabilities.ts.txt +196 -0
  35. package/installer/modules/core/src/self-improve.ts.txt +48 -0
  36. package/installer/modules/core/src/self-update.ts.txt +345 -0
  37. package/installer/modules/core/src/sync-context.ts.txt +133 -0
  38. package/installer/modules/core/src/tasks.ts.txt +159 -0
  39. package/installer/modules/custom/metaclaw-module.json +15 -0
  40. package/installer/modules/custom/src/agent-custom.ts.txt +100 -0
  41. package/installer/modules/dashboard/metaclaw-module.json +23 -0
  42. package/installer/modules/dashboard/scripts/build-dashboard.cjs.txt +51 -0
  43. package/installer/modules/dashboard/src/update-dashboard.ts.txt +126 -0
  44. package/installer/modules/outreach/metaclaw-module.json +29 -0
  45. package/installer/modules/outreach/src/agent-outreach.ts.txt +193 -0
  46. package/installer/modules/outreach/src/inbox-agent.ts.txt +283 -0
  47. package/installer/modules/outreach/src/morning-report.ts.txt +124 -0
  48. package/installer/modules/research/metaclaw-module.json +15 -0
  49. package/installer/modules/research/src/agent-research.ts.txt +127 -0
  50. package/installer/modules/scheduler/metaclaw-module.json +27 -0
  51. package/installer/modules/scheduler/scripts/agent-cycle.bat.txt +85 -0
  52. package/installer/modules/scheduler/scripts/detect-session.bat.txt +41 -0
  53. package/installer/modules/scheduler/scripts/launch.bat.txt +120 -0
  54. package/installer/modules/scheduler/src/cron-manager.ts.txt +273 -0
  55. package/installer/modules/social/metaclaw-module.json +15 -0
  56. package/installer/modules/social/src/agent-social.ts.txt +110 -0
  57. package/installer/modules/support/metaclaw-module.json +15 -0
  58. package/installer/modules/support/src/agent-support.ts.txt +60 -0
  59. package/installer/modules/swarm/metaclaw-module.json +25 -0
  60. package/installer/modules/swarm/swarm/dht-client.ts.txt +376 -0
  61. package/installer/modules/swarm/swarm/relay-server.ts.txt +348 -0
  62. package/installer/modules/swarm/swarm/swarm-client.ts.txt +303 -0
  63. package/installer/modules/swarm/swarm/types.ts.txt +51 -0
  64. package/installer/modules/voice/metaclaw-module.json +16 -0
  65. package/installer/questionnaire.ts +277 -0
  66. package/installer/research.ts +258 -0
  67. package/installer/scaffold-from-config.ts +270 -0
  68. package/installer/task-generator.ts +324 -0
  69. package/installer/templates/agent-custom.ts.txt +100 -0
  70. package/installer/templates/agent-cycle.bat.txt +19 -0
  71. package/installer/templates/agent-outreach.ts.txt +193 -0
  72. package/installer/templates/agent-research.ts.txt +127 -0
  73. package/installer/templates/agent-social.ts.txt +110 -0
  74. package/installer/templates/agent-support.ts.txt +60 -0
  75. package/installer/templates/build-dashboard.cjs.txt +51 -0
  76. package/installer/templates/cron-manager.ts.txt +273 -0
  77. package/installer/templates/dashboard.html.txt +450 -0
  78. package/installer/templates/db.ts.txt +277 -0
  79. package/installer/templates/detect-session.bat.txt +41 -0
  80. package/installer/templates/health-check.ts.txt +128 -0
  81. package/installer/templates/inbox-agent.ts.txt +283 -0
  82. package/installer/templates/launch.bat.txt +120 -0
  83. package/installer/templates/morning-report.ts.txt +124 -0
  84. package/installer/templates/observability.ts.txt +20 -0
  85. package/installer/templates/safety.ts.txt +26 -0
  86. package/installer/templates/self-improve.ts.txt +48 -0
  87. package/installer/templates/self-update.ts.txt +345 -0
  88. package/installer/templates/state.json.txt +33 -0
  89. package/installer/templates/system-context.json.txt +33 -0
  90. package/installer/templates/update-dashboard.ts.txt +126 -0
  91. package/package.json +31 -0
  92. package/setup.bat +178 -0
@@ -0,0 +1,172 @@
1
+ /**
2
+ * MetaClaw Boardroom — Turn-Taking v2
3
+ *
4
+ * Dynamic priority queue with 5 signals:
5
+ * 1. Self-assessment (25%) — agent's own relevance judgment
6
+ * 2. Direct address (25%) — were they called on by name
7
+ * 3. Freshness (20%) — are they responding to recent conversation
8
+ * 4. Equity (15%) — balance participation
9
+ * 5. Urgency (15%) — critical > high > normal
10
+ * × Staleness decay — old hand-raises lose weight
11
+ *
12
+ * After every message, ALL waiting agents re-evaluate.
13
+ * The queue reorders dynamically based on current conversation state.
14
+ */
15
+
16
+ import type Database from "better-sqlite3";
17
+ import { getRecentMessages, getMessageCount, getTurnStates, getParticipants, getPendingHandRaises, expireStaleHandRaises } from "./db.js";
18
+
19
+ const V2_WEIGHTS = {
20
+ self_score: 0.25,
21
+ address: 0.25,
22
+ freshness: 0.20,
23
+ equity: 0.15,
24
+ urgency: 0.15,
25
+ };
26
+
27
+ const THRESHOLD = 20;
28
+
29
+ export interface QueueEntry {
30
+ agent_name: string;
31
+ final_score: number;
32
+ components: {
33
+ self_score: number;
34
+ address_score: number;
35
+ freshness_score: number;
36
+ equity_score: number;
37
+ urgency_bonus: number;
38
+ staleness_decay: number;
39
+ };
40
+ reason: string;
41
+ should_speak: boolean;
42
+ intent_hash: string;
43
+ in_response_to: number | null;
44
+ }
45
+
46
+ export function computePriorityQueue(meetingId: string, db: Database.Database): QueueEntry[] {
47
+ // Expire old hand-raises
48
+ expireStaleHandRaises(db, meetingId, 300);
49
+
50
+ const handRaises = getPendingHandRaises(db, meetingId);
51
+ if (handRaises.length === 0) return [];
52
+
53
+ const recentMessages = getRecentMessages(db, meetingId, 5);
54
+ const currentMsgCount = getMessageCount(db, meetingId);
55
+ const turnStates = getTurnStates(db, meetingId);
56
+ const maxSpeakCount = Math.max(1, ...turnStates.map((t: any) => t.speak_count || 0));
57
+ const now = Date.now();
58
+
59
+ const queue: QueueEntry[] = [];
60
+
61
+ for (const hr of handRaises as any[]) {
62
+ // 1. SELF_SCORE — agent's own assessment
63
+ const selfScore = (hr.self_score || 0.5) * 100;
64
+
65
+ // 2. ADDRESS_SCORE — were they called on
66
+ const addressScore = scoreDirectAddress(hr.agent_name, recentMessages);
67
+
68
+ // 3. FRESHNESS_SCORE — responding to recent conversation?
69
+ let freshnessScore = 100;
70
+ if (hr.in_response_to) {
71
+ const pos = db.prepare("SELECT COUNT(*) as pos FROM messages WHERE meeting_id = ? AND id <= ?")
72
+ .get(meetingId, hr.in_response_to) as any;
73
+ const gap = currentMsgCount - (pos?.pos || 0);
74
+ freshnessScore = Math.max(0, 100 - gap * 30);
75
+ }
76
+
77
+ // 4. EQUITY_SCORE — balance participation
78
+ const myState = turnStates.find((t: any) => t.agent_name === hr.agent_name);
79
+ const mySpeakCount = myState?.speak_count || 0;
80
+ const equityScore = Math.max(10, 100 - (mySpeakCount / maxSpeakCount) * 90);
81
+
82
+ // 5. URGENCY_BONUS
83
+ const urgencyMap: Record<string, number> = { critical: 100, high: 70, normal: 30 };
84
+ const urgencyBonus = urgencyMap[hr.urgency] || 30;
85
+
86
+ // STALENESS_DECAY — old hand-raises lose weight
87
+ const raisedAt = new Date(hr.raised_at + "Z").getTime();
88
+ const secondsSinceRaise = (now - raisedAt) / 1000;
89
+ const stalenessDecay = Math.max(0.3, 1.0 - secondsSinceRaise / 300);
90
+
91
+ // COMPOSITE
92
+ const rawScore =
93
+ selfScore * V2_WEIGHTS.self_score +
94
+ addressScore * V2_WEIGHTS.address +
95
+ freshnessScore * V2_WEIGHTS.freshness +
96
+ equityScore * V2_WEIGHTS.equity +
97
+ urgencyBonus * V2_WEIGHTS.urgency;
98
+
99
+ const finalScore = Math.round(rawScore * stalenessDecay);
100
+
101
+ // Build reason
102
+ const reasons: string[] = [];
103
+ if (selfScore >= 70) reasons.push("high self-assessment");
104
+ if (addressScore >= 70) reasons.push("directly addressed");
105
+ if (freshnessScore >= 70) reasons.push("responding to recent");
106
+ if (equityScore >= 80) reasons.push("hasn't spoken much");
107
+ if (hr.urgency === "critical") reasons.push("CRITICAL");
108
+ else if (hr.urgency === "high") reasons.push("high urgency");
109
+ if (stalenessDecay < 0.7) reasons.push("hand-raise aging");
110
+
111
+ queue.push({
112
+ agent_name: hr.agent_name,
113
+ final_score: finalScore,
114
+ components: { self_score: selfScore, address_score: addressScore, freshness_score: freshnessScore, equity_score: equityScore, urgency_bonus: urgencyBonus, staleness_decay: stalenessDecay },
115
+ reason: reasons.join(", ") || "in queue",
116
+ should_speak: finalScore >= THRESHOLD,
117
+ intent_hash: hr.intent_hash || "",
118
+ in_response_to: hr.in_response_to,
119
+ });
120
+ }
121
+
122
+ // Sort: critical urgency always first, then by score, then by equity (less talkative wins ties)
123
+ queue.sort((a, b) => {
124
+ // Critical urgency trumps everything
125
+ if (a.components.urgency_bonus === 100 && b.components.urgency_bonus !== 100) return -1;
126
+ if (b.components.urgency_bonus === 100 && a.components.urgency_bonus !== 100) return 1;
127
+ // Then by score
128
+ if (b.final_score !== a.final_score) return b.final_score - a.final_score;
129
+ // Tie-break: less talkative goes first
130
+ return a.components.equity_score - b.components.equity_score;
131
+ });
132
+
133
+ return queue;
134
+ }
135
+
136
+ export function getNextSpeakerV2(meetingId: string, db: Database.Database): QueueEntry | null {
137
+ const queue = computePriorityQueue(meetingId, db);
138
+ return queue.length > 0 && queue[0].should_speak ? queue[0] : null;
139
+ }
140
+
141
+ export function scoreForAgentV2(meetingId: string, agentName: string, db: Database.Database): QueueEntry | null {
142
+ const queue = computePriorityQueue(meetingId, db);
143
+ return queue.find(q => q.agent_name === agentName) || null;
144
+ }
145
+
146
+ // Check if another agent's message overlaps with a pending hand-raise
147
+ export function checkCoverage(intentHash: string, messageContent: string): number {
148
+ if (!intentHash || !messageContent) return 0;
149
+ const intentWords = new Set(intentHash.toLowerCase().split(/\s+/).filter(w => w.length > 3));
150
+ const msgWords = new Set(messageContent.toLowerCase().split(/\s+/).filter(w => w.length > 3));
151
+ let overlap = 0;
152
+ for (const w of intentWords) {
153
+ if (msgWords.has(w)) overlap++;
154
+ }
155
+ return intentWords.size > 0 ? overlap / intentWords.size : 0;
156
+ }
157
+
158
+ // --- Helper ---
159
+ function scoreDirectAddress(agentName: string, recentMessages: any[]): number {
160
+ if (recentMessages.length === 0) return 0;
161
+ const nameLower = agentName.toLowerCase();
162
+ let best = 0;
163
+
164
+ for (const msg of recentMessages) {
165
+ let addresses: string[] = [];
166
+ try { addresses = typeof msg.addresses === "string" ? JSON.parse(msg.addresses) : (msg.addresses || []); } catch {}
167
+ if (addresses.some((a: string) => a.toLowerCase() === nameLower)) return 100;
168
+ if (msg.content.toLowerCase().includes(nameLower)) best = Math.max(best, 70);
169
+ if (msg.msg_type === "question" && msg.agent_name !== agentName) best = Math.max(best, 30);
170
+ }
171
+ return best;
172
+ }
@@ -0,0 +1,208 @@
1
+ /**
2
+ * MetaClaw Boardroom — Intelligent Turn-Taking Algorithm
3
+ * 4-signal scoring: direct address (40%), expertise (30%), recency (20%), pending (10%)
4
+ */
5
+
6
+ import type Database from "better-sqlite3";
7
+ import type { TurnScore } from "./types.js";
8
+ import { getRecentMessages, getTurnStates, getParticipants } from "./db.js";
9
+
10
+ const WEIGHTS = {
11
+ direct_address: 0.40,
12
+ expertise: 0.30,
13
+ recency: 0.20,
14
+ pending_flag: 0.10,
15
+ };
16
+
17
+ const THRESHOLD = 30;
18
+
19
+ // Common words to skip when matching expertise
20
+ const STOP_WORDS = new Set([
21
+ "the", "and", "for", "are", "but", "not", "you", "all", "can", "had",
22
+ "her", "was", "one", "our", "out", "has", "have", "this", "that", "with",
23
+ "they", "been", "from", "will", "what", "when", "make", "like", "just",
24
+ "over", "such", "take", "than", "them", "very", "some", "could", "other",
25
+ "into", "more", "also", "back", "after", "should", "would", "about",
26
+ "think", "know", "going", "want", "need", "meeting", "agent", "yeah",
27
+ ]);
28
+
29
+ export function scoreTurns(
30
+ meetingId: string,
31
+ db: Database.Database,
32
+ excludeAgent?: string
33
+ ): TurnScore[] {
34
+ const participants = getParticipants(db, meetingId);
35
+ const activeParticipants = participants.filter((p: any) => p.status === "active");
36
+ const recentMessages = getRecentMessages(db, meetingId, 3);
37
+ const turnStates = getTurnStates(db, meetingId);
38
+ const turnMap = new Map(turnStates.map((t: any) => [t.agent_name, t]));
39
+
40
+ const scores: TurnScore[] = [];
41
+
42
+ for (const p of activeParticipants) {
43
+ const name = p.agent_name;
44
+ if (name === excludeAgent) continue;
45
+
46
+ const ts = turnMap.get(name) || { wants_turn: 0, last_spoke_at: null, speak_count: 0 };
47
+ const expertise: string[] = safeParseJson(p.expertise, []);
48
+
49
+ // Signal 1: Direct Address
50
+ const addressScore = scoreDirectAddress(name, recentMessages);
51
+
52
+ // Signal 2: Expertise Relevance
53
+ const expertiseScore = scoreExpertise(expertise, recentMessages);
54
+
55
+ // Signal 3: Recency
56
+ const recencyScore = scoreRecency(ts, turnStates);
57
+
58
+ // Signal 4: Pending Flag
59
+ const pendingScore = ts.wants_turn ? 100 : 0;
60
+
61
+ const composite = Math.round(
62
+ addressScore * WEIGHTS.direct_address +
63
+ expertiseScore * WEIGHTS.expertise +
64
+ recencyScore * WEIGHTS.recency +
65
+ pendingScore * WEIGHTS.pending_flag
66
+ );
67
+
68
+ const reasons: string[] = [];
69
+ if (addressScore > 50) reasons.push("addressed directly");
70
+ if (expertiseScore > 50) reasons.push("expertise match");
71
+ if (recencyScore > 70) reasons.push("hasn't spoken recently");
72
+ if (ts.wants_turn) reasons.push("requested turn");
73
+
74
+ scores.push({
75
+ agent_name: name,
76
+ score: composite,
77
+ reason: reasons.length > 0 ? reasons.join(", ") : "waiting",
78
+ should_speak: composite >= THRESHOLD,
79
+ });
80
+ }
81
+
82
+ // Sort by score descending, then by speak_count ascending (less talkative wins ties)
83
+ scores.sort((a, b) => {
84
+ if (b.score !== a.score) return b.score - a.score;
85
+ const tsA = turnMap.get(a.agent_name);
86
+ const tsB = turnMap.get(b.agent_name);
87
+ return (tsA?.speak_count || 0) - (tsB?.speak_count || 0);
88
+ });
89
+
90
+ return scores;
91
+ }
92
+
93
+ export function getNextSpeaker(
94
+ meetingId: string,
95
+ db: Database.Database,
96
+ lastSpeaker?: string
97
+ ): TurnScore | null {
98
+ const scores = scoreTurns(meetingId, db, lastSpeaker);
99
+ return scores.length > 0 && scores[0].should_speak ? scores[0] : null;
100
+ }
101
+
102
+ export function scoreForAgent(
103
+ meetingId: string,
104
+ agentName: string,
105
+ db: Database.Database
106
+ ): TurnScore {
107
+ const all = scoreTurns(meetingId, db);
108
+ return all.find(s => s.agent_name === agentName) || {
109
+ agent_name: agentName,
110
+ score: 0,
111
+ reason: "not in meeting",
112
+ should_speak: false,
113
+ };
114
+ }
115
+
116
+ // --- Signal Scoring Functions ---
117
+
118
+ function scoreDirectAddress(agentName: string, recentMessages: any[]): number {
119
+ if (recentMessages.length === 0) return 0;
120
+
121
+ const nameLower = agentName.toLowerCase();
122
+ let best = 0;
123
+
124
+ for (const msg of recentMessages) {
125
+ // Check explicit addresses field
126
+ const addresses: string[] = safeParseJson(msg.addresses, []);
127
+ if (addresses.some((a: string) => a.toLowerCase() === nameLower)) {
128
+ return 100; // Explicitly addressed
129
+ }
130
+
131
+ // Check content for name mentions
132
+ const contentLower = msg.content.toLowerCase();
133
+ if (contentLower.includes(nameLower)) {
134
+ best = Math.max(best, 70);
135
+ }
136
+
137
+ // Check if it's a question (increases likelihood someone should respond)
138
+ if (msg.msg_type === "question" && msg.agent_name !== agentName) {
139
+ best = Math.max(best, 30);
140
+ }
141
+ }
142
+
143
+ return best;
144
+ }
145
+
146
+ function scoreExpertise(expertise: string[], recentMessages: any[]): number {
147
+ if (expertise.length === 0 || recentMessages.length === 0) return 0;
148
+
149
+ const expertiseLower = new Set(expertise.map(e => e.toLowerCase()));
150
+
151
+ // Extract meaningful words from recent messages
152
+ const messageWords = new Set<string>();
153
+ for (const msg of recentMessages) {
154
+ const words = msg.content.toLowerCase()
155
+ .replace(/[^a-z0-9\s-]/g, " ")
156
+ .split(/\s+/)
157
+ .filter((w: string) => w.length > 3 && !STOP_WORDS.has(w));
158
+ words.forEach((w: string) => messageWords.add(w));
159
+ }
160
+
161
+ // Count matches
162
+ let matches = 0;
163
+ for (const tag of expertiseLower) {
164
+ // Check exact match and partial match (tag "pricing" matches word "pricing")
165
+ for (const word of messageWords) {
166
+ if (word.includes(tag) || tag.includes(word)) {
167
+ matches++;
168
+ break;
169
+ }
170
+ }
171
+ }
172
+
173
+ return Math.min(100, Math.round((matches / expertise.length) * 100));
174
+ }
175
+
176
+ function scoreRecency(turnState: any, allTurnStates: any[]): number {
177
+ // Never spoken = maximum recency score
178
+ if (!turnState.last_spoke_at) return 100;
179
+
180
+ const lastSpoke = new Date(turnState.last_spoke_at + "Z").getTime();
181
+ const now = Date.now();
182
+
183
+ // Find the most recent speaker's time
184
+ let mostRecent = 0;
185
+ let oldest = Infinity;
186
+ for (const ts of allTurnStates) {
187
+ if (ts.last_spoke_at) {
188
+ const t = new Date(ts.last_spoke_at + "Z").getTime();
189
+ mostRecent = Math.max(mostRecent, t);
190
+ oldest = Math.min(oldest, t);
191
+ }
192
+ }
193
+
194
+ if (mostRecent === oldest) return 50; // Everyone spoke at the same time
195
+
196
+ // Scale: most recent speaker = 0, oldest speaker = 100
197
+ const range = mostRecent - oldest;
198
+ if (range === 0) return 50;
199
+ return Math.round(((mostRecent - lastSpoke) / range) * 100);
200
+ }
201
+
202
+ function safeParseJson(val: any, fallback: any): any {
203
+ if (Array.isArray(val)) return val;
204
+ if (typeof val === "string") {
205
+ try { return JSON.parse(val); } catch { return fallback; }
206
+ }
207
+ return fallback;
208
+ }
@@ -0,0 +1,100 @@
1
+ /**
2
+ * MetaClaw Boardroom — Shared Types
3
+ * Multi-agent meeting system with intelligent turn-taking
4
+ */
5
+
6
+ export interface Meeting {
7
+ id: string;
8
+ topic: string;
9
+ created_by: string;
10
+ status: "active" | "paused" | "ended";
11
+ max_participants: number;
12
+ created_at: string;
13
+ ended_at: string | null;
14
+ summary: string | null;
15
+ }
16
+
17
+ export interface Participant {
18
+ id: number;
19
+ meeting_id: string;
20
+ agent_name: string;
21
+ expertise: string[];
22
+ status: "active" | "left" | "idle";
23
+ joined_at: string;
24
+ last_poll_at: string | null;
25
+ }
26
+
27
+ export interface Message {
28
+ id: number;
29
+ meeting_id: string;
30
+ agent_name: string;
31
+ content: string;
32
+ addresses: string[] | null;
33
+ msg_type: "statement" | "question" | "proposal" | "decision" | "summary";
34
+ created_at: string;
35
+ }
36
+
37
+ export interface TurnState {
38
+ meeting_id: string;
39
+ agent_name: string;
40
+ wants_turn: boolean;
41
+ last_spoke_at: string | null;
42
+ speak_count: number;
43
+ }
44
+
45
+ export interface TurnScore {
46
+ agent_name: string;
47
+ score: number;
48
+ reason: string;
49
+ should_speak: boolean;
50
+ }
51
+
52
+ export interface ContextPackage {
53
+ meeting_topic: string;
54
+ meeting_id: string;
55
+ participants: string[];
56
+ recent_messages: Message[];
57
+ running_summary: string | null;
58
+ your_turn: TurnScore;
59
+ total_messages: number;
60
+ }
61
+
62
+ export interface PollResult {
63
+ messages: Message[];
64
+ turn_signal: TurnScore;
65
+ context: ContextPackage | null;
66
+ }
67
+
68
+ export interface CreateMeetingRequest {
69
+ topic: string;
70
+ created_by: string;
71
+ expertise?: string[];
72
+ }
73
+
74
+ export interface JoinMeetingRequest {
75
+ agent_name: string;
76
+ expertise?: string[];
77
+ }
78
+
79
+ export interface SpeakRequest {
80
+ agent_name: string;
81
+ content: string;
82
+ addresses?: string[];
83
+ msg_type?: Message["msg_type"];
84
+ }
85
+
86
+ export interface MeetingSummons {
87
+ id: string;
88
+ from: string;
89
+ to: string;
90
+ timestamp: string;
91
+ subject: string;
92
+ body: string;
93
+ type: "request";
94
+ priority: "high";
95
+ boardroom: {
96
+ meeting_id: string;
97
+ coordinator_url: string;
98
+ topic: string;
99
+ };
100
+ }
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "boardroom",
3
+ "displayName": "Boardroom — Agent Meetings",
4
+ "version": "1.0.0",
5
+ "description": "Multi-agent meeting system: scheduling, intelligent turn-taking, persistent attendance, post-meeting artifacts",
6
+ "category": "collaboration",
7
+ "alwaysInstall": true,
8
+ "engines": { "metaclaw": ">=3.3.0" },
9
+ "requires": { "modules": ["core"] },
10
+ "contributes": {
11
+ "boardroom": {
12
+ "types.ts.txt": "boardroom/types.ts",
13
+ "db.ts.txt": "boardroom/db.ts",
14
+ "turn.ts.txt": "boardroom/turn.ts",
15
+ "turn-v2.ts.txt": "boardroom/turn-v2.ts",
16
+ "server.ts.txt": "boardroom/server.ts",
17
+ "client.ts.txt": "boardroom/client.ts",
18
+ "summons.ts.txt": "boardroom/summons.ts",
19
+ "meeting-agent.ts.txt": "boardroom/meeting-agent.ts",
20
+ "meeting-scheduler.ts.txt": "boardroom/meeting-scheduler.ts",
21
+ "start-boardroom.bat.txt": "boardroom/start-boardroom.bat"
22
+ },
23
+ "scripts": {
24
+ "meeting-check.bat.txt": "scripts/meeting-check.bat"
25
+ },
26
+ "npmScripts": {
27
+ "boardroom": "tsx boardroom/server.ts",
28
+ "boardroom-start": "tsx boardroom/server.ts --port 7890",
29
+ "meeting-check": "tsx boardroom/meeting-scheduler.ts"
30
+ },
31
+ "claudeMd": ["boardroom"],
32
+ "directories": ["boardroom"]
33
+ },
34
+ "placeholders": ["__PROJECT_DIR__", "__COORDINATOR_URL__"]
35
+ }
@@ -0,0 +1,38 @@
1
+ @echo off
2
+ REM MetaClaw Boardroom — Meeting Check
3
+ REM Called at the start of every agent cycle.
4
+ REM Checks if this agent has an active meeting to attend.
5
+ REM Returns ERRORLEVEL 0 = meeting found (enter meeting mode)
6
+ REM Returns ERRORLEVEL 1 = no meeting (proceed with normal cycle)
7
+
8
+ setlocal enabledelayedexpansion
9
+ set "AGENT_NAME=%~1"
10
+ set "COORDINATOR=__COORDINATOR_URL__"
11
+ set "INBOX=__INBOX_ROOT__\%AGENT_NAME%"
12
+ set "MEETING_ID="
13
+
14
+ REM Check 1: MEETING_READY sentinel file in inbox
15
+ for %%f in ("%INBOX%\MEETING_READY_*.json") do (
16
+ if exist "%%f" (
17
+ echo [MEETING] Found meeting-ready sentinel: %%~nf
18
+ REM Extract meeting_id from the JSON file
19
+ for /f "usebackq tokens=*" %%j in (`node -e "const d=JSON.parse(require('fs').readFileSync('%%f','utf-8'));console.log(d.meeting_id)"`) do (
20
+ set "MEETING_ID=%%j"
21
+ )
22
+ if defined MEETING_ID (
23
+ echo !MEETING_ID!> "data\.meeting-id"
24
+ exit /b 0
25
+ )
26
+ )
27
+ )
28
+
29
+ REM Check 2: Active meeting where this agent is a participant
30
+ for /f "usebackq delims=" %%r in (`node -e "fetch('%COORDINATOR%/meetings').then(r=>r.json()).then(d=>{const m=d.meetings.find(m=>m.status==='active');if(m)console.log(m.id);else console.log('NONE')}).catch(()=>console.log('NONE'))"`) do (
31
+ if not "%%r"=="NONE" (
32
+ echo [MEETING] Active meeting found: %%r
33
+ echo %%r> "data\.meeting-id"
34
+ exit /b 0
35
+ )
36
+ )
37
+
38
+ exit /b 1
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "core",
3
+ "displayName": "Core — Database, Safety & Self-Improvement",
4
+ "version": "3.3.0",
5
+ "description": "Foundation: SQLite database, safety guardrails, observability, self-improvement, health checks, state management, hooks system",
6
+ "category": "foundation",
7
+ "alwaysInstall": true,
8
+ "engines": { "metaclaw": ">=3.3.0" },
9
+ "requires": { "modules": [], "env": [], "bins": ["node"] },
10
+ "contributes": {
11
+ "src": {
12
+ "db.ts.txt": "src/db.ts",
13
+ "safety.ts.txt": "src/safety.ts",
14
+ "observability.ts.txt": "src/observability.ts",
15
+ "self-improve.ts.txt": "src/self-improve.ts",
16
+ "self-update.ts.txt": "src/self-update.ts",
17
+ "health-check.ts.txt": "src/health-check.ts",
18
+ "tasks.ts.txt": "src/tasks.ts",
19
+ "scan-capabilities.ts.txt": "src/scan-capabilities.ts",
20
+ "sync-context.ts.txt": "src/sync-context.ts"
21
+ },
22
+ "data": {
23
+ "state.json.txt": "data/state.json",
24
+ "system-context.json.txt": "data/system-context.json"
25
+ },
26
+ "memory": {
27
+ "CAPABILITIES.md.txt": "memory/CAPABILITIES.md",
28
+ "stuck-patterns.md.txt": "memory/stuck-patterns.md",
29
+ "logic-log.jsonl.txt": "memory/logic-log.jsonl",
30
+ "reflections.jsonl.txt": "memory/reflections.jsonl",
31
+ "curiosity.md.txt": "memory/curiosity.md"
32
+ },
33
+ "npmScripts": {
34
+ "status": "tsx src/agent.ts --status",
35
+ "self-update": "tsx src/self-update.ts",
36
+ "health-check": "tsx src/health-check.ts",
37
+ "scan": "tsx src/scan-capabilities.ts",
38
+ "sync-context": "tsx src/sync-context.ts"
39
+ },
40
+ "claudeMd": ["identity", "first-launch", "meta-cognition", "state-management", "tasks", "commands"],
41
+ "directories": ["src", "src/tools", "data", "data/logs", "scripts", "memory", "memory/capabilities"],
42
+ "dependencies": {
43
+ "@anthropic-ai/claude-agent-sdk": "latest",
44
+ "@types/better-sqlite3": "latest",
45
+ "better-sqlite3": "latest",
46
+ "tsx": "latest",
47
+ "typescript": "latest"
48
+ }
49
+ },
50
+ "placeholders": ["__AGENT_NAME__", "__SESSION_NAME__", "__CLAW_TYPE__", "__TIMESTAMP__", "__SAFETY_CONFIG__", "__SYSTEM_PROMPT__"]
51
+ }