let-them-talk 5.2.5 → 5.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/CHANGELOG.md +3 -1
- package/README.md +158 -592
- package/SECURITY.md +3 -3
- package/USAGE.md +151 -0
- package/agent-contracts.js +447 -0
- package/api-agents.js +760 -0
- package/autonomy/decision-v2.js +380 -0
- package/autonomy/watchdog-policy.js +572 -0
- package/cli.js +454 -298
- package/conversation-templates/autonomous-feature.json +83 -22
- package/conversation-templates/code-review.json +69 -21
- package/conversation-templates/debug-squad.json +69 -21
- package/conversation-templates/feature-build.json +69 -21
- package/conversation-templates/research-write.json +69 -21
- package/dashboard.html +3148 -174
- package/dashboard.js +823 -786
- package/data-dir.js +58 -0
- package/docs/architecture/branch-semantics.md +157 -0
- package/docs/architecture/canonical-event-schema.md +88 -0
- package/docs/architecture/markdown-workspace.md +183 -0
- package/docs/architecture/runtime-contract.md +459 -0
- package/docs/architecture/runtime-migration-hardening.md +64 -0
- package/events/hooks.js +154 -0
- package/events/log.js +457 -0
- package/events/replay.js +33 -0
- package/events/schema.js +432 -0
- package/managed-team-integration.js +261 -0
- package/office/agents.js +704 -597
- package/office/animation.js +1 -1
- package/office/assets/arcade-cabinet.js +141 -0
- package/office/assets/archway.js +77 -0
- package/office/assets/bar-counter.js +91 -0
- package/office/assets/bar-stool.js +71 -0
- package/office/assets/beanbag.js +64 -0
- package/office/assets/bench.js +99 -0
- package/office/assets/bollard.js +87 -0
- package/office/assets/cactus.js +100 -0
- package/office/assets/carpet-tile.js +46 -0
- package/office/assets/chair.js +123 -0
- package/office/assets/chandelier.js +107 -0
- package/office/assets/coffee-machine.js +95 -0
- package/office/assets/coffee-table.js +81 -0
- package/office/assets/column.js +95 -0
- package/office/assets/desk-lamp.js +102 -0
- package/office/assets/desk.js +76 -0
- package/office/assets/dining-table.js +105 -0
- package/office/assets/door.js +70 -0
- package/office/assets/dual-monitor.js +72 -0
- package/office/assets/fence.js +76 -0
- package/office/assets/filing-cabinet.js +111 -0
- package/office/assets/floor-lamp.js +69 -0
- package/office/assets/floor-tile.js +54 -0
- package/office/assets/flower-pot.js +76 -0
- package/office/assets/foosball.js +95 -0
- package/office/assets/fridge.js +99 -0
- package/office/assets/gaming-chair.js +154 -0
- package/office/assets/gaming-desk.js +105 -0
- package/office/assets/glass-door.js +72 -0
- package/office/assets/glass-wall.js +64 -0
- package/office/assets/half-wall.js +49 -0
- package/office/assets/hanging-plant.js +112 -0
- package/office/assets/index.js +151 -0
- package/office/assets/indoor-tree.js +90 -0
- package/office/assets/l-sofa.js +153 -0
- package/office/assets/marble-floor.js +64 -0
- package/office/assets/materials.js +40 -0
- package/office/assets/meeting-table.js +88 -0
- package/office/assets/microwave.js +94 -0
- package/office/assets/monitor.js +67 -0
- package/office/assets/neon-strip.js +73 -0
- package/office/assets/painting.js +84 -0
- package/office/assets/palm-tree.js +108 -0
- package/office/assets/pc-tower.js +91 -0
- package/office/assets/pendant-light.js +67 -0
- package/office/assets/ping-pong.js +114 -0
- package/office/assets/plant.js +72 -0
- package/office/assets/planter-box.js +95 -0
- package/office/assets/pool-table.js +94 -0
- package/office/assets/printer.js +113 -0
- package/office/assets/reception-desk.js +133 -0
- package/office/assets/rug.js +78 -0
- package/office/assets/sculpture.js +85 -0
- package/office/assets/server-rack.js +98 -0
- package/office/assets/sink.js +109 -0
- package/office/assets/sofa.js +106 -0
- package/office/assets/speaker.js +83 -0
- package/office/assets/spotlight.js +83 -0
- package/office/assets/street-lamp.js +97 -0
- package/office/assets/trash-can.js +83 -0
- package/office/assets/treadmill.js +126 -0
- package/office/assets/trophy.js +89 -0
- package/office/assets/tv-screen.js +79 -0
- package/office/assets/vase.js +84 -0
- package/office/assets/wall-clock.js +84 -0
- package/office/assets/wall.js +53 -0
- package/office/assets/water-cooler.js +146 -0
- package/office/assets/whiteboard.js +115 -0
- package/office/assets.js +3 -431
- package/office/builder.js +791 -355
- package/office/campus-env.js +1012 -1119
- package/office/environment.js +2 -0
- package/office/gallery.js +997 -0
- package/office/index.js +165 -61
- package/office/navigation.js +173 -152
- package/office/player.js +178 -68
- package/office/robot-character.js +272 -0
- package/office/spectator-camera.js +33 -10
- package/office/state.js +2 -0
- package/office/world-save.js +35 -4
- package/package.json +57 -3
- package/providers/comfyui.js +383 -0
- package/providers/dalle.js +79 -0
- package/providers/gemini.js +181 -0
- package/providers/ollama.js +184 -0
- package/providers/replicate.js +115 -0
- package/providers/zai.js +183 -0
- package/runtime-descriptor.js +270 -0
- package/scripts/check-agent-contract-advisory.js +132 -0
- package/scripts/check-api-agent-parity.js +277 -0
- package/scripts/check-autonomy-v2-decision.js +207 -0
- package/scripts/check-autonomy-v2-execution.js +588 -0
- package/scripts/check-autonomy-v2-watchdog.js +224 -0
- package/scripts/check-branch-fork-snapshot.js +337 -0
- package/scripts/check-branch-isolation.js +787 -0
- package/scripts/check-branch-semantics.js +139 -0
- package/scripts/check-dashboard-control-plane.js +1304 -0
- package/scripts/check-docs-onboarding.js +490 -0
- package/scripts/check-event-schema.js +276 -0
- package/scripts/check-evidence-completion.js +239 -0
- package/scripts/check-invariants.js +992 -0
- package/scripts/check-lifecycle-hooks.js +525 -0
- package/scripts/check-managed-team-integration.js +166 -0
- package/scripts/check-markdown-workspace-export.js +548 -0
- package/scripts/check-markdown-workspace-safety.js +347 -0
- package/scripts/check-markdown-workspace.js +136 -0
- package/scripts/check-message-replay.js +429 -0
- package/scripts/check-migration-hardening.js +300 -0
- package/scripts/check-performance-indexing.js +272 -0
- package/scripts/check-provider-capabilities.js +316 -0
- package/scripts/check-runtime-contract.js +109 -0
- package/scripts/check-session-aware-context.js +172 -0
- package/scripts/check-session-lifecycle.js +210 -0
- package/scripts/export-markdown-workspace.js +84 -0
- package/scripts/fixtures/message-replay/clean.jsonl +2 -0
- package/scripts/fixtures/message-replay/corrupt-correction-payload.jsonl +1 -0
- package/scripts/fixtures/message-replay/corrupt-jsonl.jsonl +1 -0
- package/scripts/fixtures/message-replay/corrupt-payload.jsonl +1 -0
- package/scripts/fixtures/message-replay/out-of-order.jsonl +2 -0
- package/scripts/migrate-legacy-to-canonical.js +201 -0
- package/scripts/run-verification-suite.js +242 -0
- package/scripts/sync-packaged-docs.js +69 -0
- package/server.js +9546 -7214
- package/state/agents.js +161 -0
- package/state/canonical.js +3068 -0
- package/state/dashboard-queries.js +441 -0
- package/state/evidence.js +56 -0
- package/state/io.js +69 -0
- package/state/markdown-workspace.js +951 -0
- package/state/messages.js +669 -0
- package/state/sessions.js +683 -0
- package/state/tasks-workflows.js +92 -0
- package/templates/debate.json +2 -2
- package/templates/managed.json +4 -4
- package/templates/pair.json +2 -2
- package/templates/review.json +2 -2
- package/templates/team.json +3 -3
package/events/schema.js
ADDED
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
const EVENT_STREAMS = Object.freeze({
|
|
2
|
+
RUNTIME: 'runtime',
|
|
3
|
+
BRANCH: 'branch',
|
|
4
|
+
});
|
|
5
|
+
|
|
6
|
+
const CANONICAL_EVENT_SCHEMA_VERSION = 1;
|
|
7
|
+
|
|
8
|
+
const REQUIRED_EVENT_ENVELOPE_FIELDS = Object.freeze([
|
|
9
|
+
'event_id',
|
|
10
|
+
'stream',
|
|
11
|
+
'branch_id',
|
|
12
|
+
'seq',
|
|
13
|
+
'type',
|
|
14
|
+
'occurred_at',
|
|
15
|
+
'schema_version',
|
|
16
|
+
'actor_agent',
|
|
17
|
+
'session_id',
|
|
18
|
+
'command_id',
|
|
19
|
+
'causation_id',
|
|
20
|
+
'correlation_id',
|
|
21
|
+
'payload',
|
|
22
|
+
]);
|
|
23
|
+
|
|
24
|
+
const DEFAULT_COLLABORATION_STREAM = EVENT_STREAMS.BRANCH;
|
|
25
|
+
|
|
26
|
+
const EVENT_SCOPE_SPLIT = Object.freeze({
|
|
27
|
+
defaultStream: DEFAULT_COLLABORATION_STREAM,
|
|
28
|
+
runtimeGlobalFamilies: Object.freeze([
|
|
29
|
+
'agent',
|
|
30
|
+
'profile',
|
|
31
|
+
'lock',
|
|
32
|
+
'branch',
|
|
33
|
+
'migration',
|
|
34
|
+
]),
|
|
35
|
+
branchLocalFamilies: Object.freeze([
|
|
36
|
+
'session',
|
|
37
|
+
'conversation',
|
|
38
|
+
'message',
|
|
39
|
+
'task',
|
|
40
|
+
'workflow',
|
|
41
|
+
'workspace',
|
|
42
|
+
'decision',
|
|
43
|
+
'kb',
|
|
44
|
+
'review',
|
|
45
|
+
'dependency',
|
|
46
|
+
'vote',
|
|
47
|
+
'rule',
|
|
48
|
+
'progress',
|
|
49
|
+
'evidence',
|
|
50
|
+
]),
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
function resolveFamilyStream(family) {
|
|
54
|
+
return EVENT_SCOPE_SPLIT.runtimeGlobalFamilies.includes(family)
|
|
55
|
+
? EVENT_STREAMS.RUNTIME
|
|
56
|
+
: EVENT_SCOPE_SPLIT.defaultStream;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function freezeFamilyDefinition(definition) {
|
|
60
|
+
return Object.freeze({
|
|
61
|
+
family: definition.family,
|
|
62
|
+
scope: definition.scope,
|
|
63
|
+
projectionTargets: Object.freeze([...definition.projectionTargets]),
|
|
64
|
+
types: Object.freeze([...definition.types]),
|
|
65
|
+
summary: definition.summary,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const FAMILY_DEFINITIONS = Object.freeze([
|
|
70
|
+
freezeFamilyDefinition({
|
|
71
|
+
family: 'agent',
|
|
72
|
+
scope: resolveFamilyStream('agent'),
|
|
73
|
+
projectionTargets: ['agents-index', 'sessions-index'],
|
|
74
|
+
types: [
|
|
75
|
+
'agent.registered',
|
|
76
|
+
'agent.unregistered',
|
|
77
|
+
'agent.status_updated',
|
|
78
|
+
'agent.heartbeat_recorded',
|
|
79
|
+
'agent.branch_assigned',
|
|
80
|
+
'agent.listening_updated',
|
|
81
|
+
],
|
|
82
|
+
summary: 'Runtime-global agent identity, liveness, and branch-assignment state.',
|
|
83
|
+
}),
|
|
84
|
+
freezeFamilyDefinition({
|
|
85
|
+
family: 'profile',
|
|
86
|
+
scope: resolveFamilyStream('profile'),
|
|
87
|
+
projectionTargets: ['profiles'],
|
|
88
|
+
types: ['profile.updated'],
|
|
89
|
+
summary: 'Runtime-global agent profile updates.',
|
|
90
|
+
}),
|
|
91
|
+
freezeFamilyDefinition({
|
|
92
|
+
family: 'lock',
|
|
93
|
+
scope: resolveFamilyStream('lock'),
|
|
94
|
+
projectionTargets: ['locks'],
|
|
95
|
+
types: ['lock.acquired', 'lock.released'],
|
|
96
|
+
summary: 'Runtime-global file lock ownership with branch/session context when present.',
|
|
97
|
+
}),
|
|
98
|
+
freezeFamilyDefinition({
|
|
99
|
+
family: 'branch',
|
|
100
|
+
scope: resolveFamilyStream('branch'),
|
|
101
|
+
projectionTargets: ['branch-index'],
|
|
102
|
+
types: ['branch.created'],
|
|
103
|
+
summary: 'Runtime-global branch registry lifecycle.',
|
|
104
|
+
}),
|
|
105
|
+
freezeFamilyDefinition({
|
|
106
|
+
family: 'migration',
|
|
107
|
+
scope: resolveFamilyStream('migration'),
|
|
108
|
+
projectionTargets: ['manifest'],
|
|
109
|
+
types: ['migration.started', 'migration.completed', 'migration.failed'],
|
|
110
|
+
summary: 'Runtime-global storage/runtime migration metadata.',
|
|
111
|
+
}),
|
|
112
|
+
freezeFamilyDefinition({
|
|
113
|
+
family: 'session',
|
|
114
|
+
scope: resolveFamilyStream('session'),
|
|
115
|
+
projectionTargets: ['sessions', 'sessions-index'],
|
|
116
|
+
types: [
|
|
117
|
+
'session.started',
|
|
118
|
+
'session.resumed',
|
|
119
|
+
'session.interrupted',
|
|
120
|
+
'session.completed',
|
|
121
|
+
'session.failed',
|
|
122
|
+
'session.abandoned',
|
|
123
|
+
],
|
|
124
|
+
summary: 'Branch-scoped agent execution lifecycle and recovery state.',
|
|
125
|
+
}),
|
|
126
|
+
freezeFamilyDefinition({
|
|
127
|
+
family: 'conversation',
|
|
128
|
+
scope: resolveFamilyStream('conversation'),
|
|
129
|
+
projectionTargets: ['conversation'],
|
|
130
|
+
types: [
|
|
131
|
+
'conversation.mode_updated',
|
|
132
|
+
'conversation.channel_joined',
|
|
133
|
+
'conversation.channel_left',
|
|
134
|
+
'conversation.manager_claimed',
|
|
135
|
+
'conversation.phase_updated',
|
|
136
|
+
'conversation.floor_yielded',
|
|
137
|
+
],
|
|
138
|
+
summary: 'Branch-local conversation mode, channel, and managed-floor state.',
|
|
139
|
+
}),
|
|
140
|
+
freezeFamilyDefinition({
|
|
141
|
+
family: 'message',
|
|
142
|
+
scope: resolveFamilyStream('message'),
|
|
143
|
+
projectionTargets: ['messages', 'history'],
|
|
144
|
+
types: ['message.sent', 'message.corrected', 'message.redacted'],
|
|
145
|
+
summary: 'Branch-local collaboration message stream and historical corrections.',
|
|
146
|
+
}),
|
|
147
|
+
freezeFamilyDefinition({
|
|
148
|
+
family: 'task',
|
|
149
|
+
scope: resolveFamilyStream('task'),
|
|
150
|
+
projectionTargets: ['tasks'],
|
|
151
|
+
types: ['task.created', 'task.updated', 'task.claimed', 'task.completed'],
|
|
152
|
+
summary: 'Branch-local task planning and status transitions.',
|
|
153
|
+
}),
|
|
154
|
+
freezeFamilyDefinition({
|
|
155
|
+
family: 'workflow',
|
|
156
|
+
scope: resolveFamilyStream('workflow'),
|
|
157
|
+
projectionTargets: ['workflows'],
|
|
158
|
+
types: [
|
|
159
|
+
'workflow.created',
|
|
160
|
+
'workflow.step_started',
|
|
161
|
+
'workflow.step_completed',
|
|
162
|
+
'workflow.step_reassigned',
|
|
163
|
+
'workflow.completed',
|
|
164
|
+
'workflow.paused',
|
|
165
|
+
'workflow.resumed',
|
|
166
|
+
'workflow.stopped',
|
|
167
|
+
],
|
|
168
|
+
summary: 'Branch-local workflow lifecycle and step handoff state.',
|
|
169
|
+
}),
|
|
170
|
+
freezeFamilyDefinition({
|
|
171
|
+
family: 'workspace',
|
|
172
|
+
scope: resolveFamilyStream('workspace'),
|
|
173
|
+
projectionTargets: ['workspaces'],
|
|
174
|
+
types: ['workspace.written'],
|
|
175
|
+
summary: 'Branch-local per-agent workspace and memory materialization.',
|
|
176
|
+
}),
|
|
177
|
+
freezeFamilyDefinition({
|
|
178
|
+
family: 'decision',
|
|
179
|
+
scope: resolveFamilyStream('decision'),
|
|
180
|
+
projectionTargets: ['decisions'],
|
|
181
|
+
types: ['decision.logged'],
|
|
182
|
+
summary: 'Branch-local decision log entries.',
|
|
183
|
+
}),
|
|
184
|
+
freezeFamilyDefinition({
|
|
185
|
+
family: 'kb',
|
|
186
|
+
scope: resolveFamilyStream('kb'),
|
|
187
|
+
projectionTargets: ['kb'],
|
|
188
|
+
types: ['kb.written'],
|
|
189
|
+
summary: 'Branch-local shared knowledge base entries.',
|
|
190
|
+
}),
|
|
191
|
+
freezeFamilyDefinition({
|
|
192
|
+
family: 'review',
|
|
193
|
+
scope: resolveFamilyStream('review'),
|
|
194
|
+
projectionTargets: ['reviews'],
|
|
195
|
+
types: ['review.requested', 'review.submitted'],
|
|
196
|
+
summary: 'Branch-local review requests and review results.',
|
|
197
|
+
}),
|
|
198
|
+
freezeFamilyDefinition({
|
|
199
|
+
family: 'dependency',
|
|
200
|
+
scope: resolveFamilyStream('dependency'),
|
|
201
|
+
projectionTargets: ['dependencies'],
|
|
202
|
+
types: ['dependency.declared', 'dependency.resolved'],
|
|
203
|
+
summary: 'Branch-local blocked-work dependency graph changes.',
|
|
204
|
+
}),
|
|
205
|
+
freezeFamilyDefinition({
|
|
206
|
+
family: 'vote',
|
|
207
|
+
scope: resolveFamilyStream('vote'),
|
|
208
|
+
projectionTargets: ['votes'],
|
|
209
|
+
types: ['vote.called', 'vote.cast', 'vote.resolved'],
|
|
210
|
+
summary: 'Branch-local governance votes and outcomes.',
|
|
211
|
+
}),
|
|
212
|
+
freezeFamilyDefinition({
|
|
213
|
+
family: 'rule',
|
|
214
|
+
scope: resolveFamilyStream('rule'),
|
|
215
|
+
projectionTargets: ['rules'],
|
|
216
|
+
types: ['rule.added', 'rule.toggled', 'rule.removed'],
|
|
217
|
+
summary: 'Branch-local project rule lifecycle.',
|
|
218
|
+
}),
|
|
219
|
+
freezeFamilyDefinition({
|
|
220
|
+
family: 'progress',
|
|
221
|
+
scope: resolveFamilyStream('progress'),
|
|
222
|
+
projectionTargets: ['progress'],
|
|
223
|
+
types: ['progress.updated'],
|
|
224
|
+
summary: 'Branch-local feature progress reporting.',
|
|
225
|
+
}),
|
|
226
|
+
freezeFamilyDefinition({
|
|
227
|
+
family: 'evidence',
|
|
228
|
+
scope: resolveFamilyStream('evidence'),
|
|
229
|
+
projectionTargets: ['evidence'],
|
|
230
|
+
types: ['evidence.recorded'],
|
|
231
|
+
summary: 'Branch-local evidence records backing completion and advancement claims.',
|
|
232
|
+
}),
|
|
233
|
+
]);
|
|
234
|
+
|
|
235
|
+
const EVENT_FAMILY_REGISTRY = Object.freeze(
|
|
236
|
+
Object.fromEntries(FAMILY_DEFINITIONS.map((definition) => [definition.family, definition]))
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
const EVENT_TYPE_REGISTRY = Object.freeze(
|
|
240
|
+
Object.fromEntries(
|
|
241
|
+
FAMILY_DEFINITIONS.flatMap((definition) =>
|
|
242
|
+
definition.types.map((type) => [
|
|
243
|
+
type,
|
|
244
|
+
Object.freeze({
|
|
245
|
+
type,
|
|
246
|
+
family: definition.family,
|
|
247
|
+
scope: definition.scope,
|
|
248
|
+
projectionTargets: definition.projectionTargets,
|
|
249
|
+
}),
|
|
250
|
+
])
|
|
251
|
+
)
|
|
252
|
+
)
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
function getEventFamily(type) {
|
|
256
|
+
if (typeof type !== 'string') return null;
|
|
257
|
+
const separatorIndex = type.indexOf('.');
|
|
258
|
+
return separatorIndex === -1 ? null : type.slice(0, separatorIndex);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function getEventFamilyDefinition(family) {
|
|
262
|
+
return EVENT_FAMILY_REGISTRY[family] || null;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function getEventTypeDefinition(type) {
|
|
266
|
+
return EVENT_TYPE_REGISTRY[type] || null;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function resolveTypeStream(type) {
|
|
270
|
+
const definition = getEventTypeDefinition(type);
|
|
271
|
+
if (definition) return definition.scope;
|
|
272
|
+
const family = getEventFamily(type);
|
|
273
|
+
return family ? resolveFamilyStream(family) : null;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function isPlainObject(value) {
|
|
277
|
+
return !!value && typeof value === 'object' && !Array.isArray(value);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function validateMessageSentPayload(payload) {
|
|
281
|
+
const problems = [];
|
|
282
|
+
if (!isPlainObject(payload)) {
|
|
283
|
+
problems.push('message.sent events require payload to be an object.');
|
|
284
|
+
return problems;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (!isPlainObject(payload.message)) {
|
|
288
|
+
problems.push('message.sent events require payload.message to be an object.');
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return problems;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function validateMessageCorrectedPayload(payload) {
|
|
295
|
+
const problems = [];
|
|
296
|
+
if (!isPlainObject(payload)) {
|
|
297
|
+
problems.push('message.corrected events require payload to be an object.');
|
|
298
|
+
return problems;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (typeof payload.message_id !== 'string' || payload.message_id.length === 0) {
|
|
302
|
+
problems.push('message.corrected events require payload.message_id to be a non-empty string.');
|
|
303
|
+
}
|
|
304
|
+
if (typeof payload.content !== 'string') {
|
|
305
|
+
problems.push('message.corrected events require payload.content to be a string.');
|
|
306
|
+
}
|
|
307
|
+
if ('edited_at' in payload && (typeof payload.edited_at !== 'string' || payload.edited_at.length === 0)) {
|
|
308
|
+
problems.push('message.corrected events require payload.edited_at to be a non-empty string when provided.');
|
|
309
|
+
}
|
|
310
|
+
if ('max_edit_history' in payload && (!Number.isInteger(payload.max_edit_history) || payload.max_edit_history <= 0)) {
|
|
311
|
+
problems.push('message.corrected events require payload.max_edit_history to be a positive integer when provided.');
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return problems;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function validateMessageRedactedPayload(payload) {
|
|
318
|
+
const problems = [];
|
|
319
|
+
if (!isPlainObject(payload)) {
|
|
320
|
+
problems.push('message.redacted events require payload to be an object.');
|
|
321
|
+
return problems;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
if (typeof payload.message_id !== 'string' || payload.message_id.length === 0) {
|
|
325
|
+
problems.push('message.redacted events require payload.message_id to be a non-empty string.');
|
|
326
|
+
}
|
|
327
|
+
if ('redacted_at' in payload && (typeof payload.redacted_at !== 'string' || payload.redacted_at.length === 0)) {
|
|
328
|
+
problems.push('message.redacted events require payload.redacted_at to be a non-empty string when provided.');
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
return problems;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function validateEventPayload(type, payload) {
|
|
335
|
+
switch (type) {
|
|
336
|
+
case 'message.sent':
|
|
337
|
+
return validateMessageSentPayload(payload);
|
|
338
|
+
case 'message.corrected':
|
|
339
|
+
return validateMessageCorrectedPayload(payload);
|
|
340
|
+
case 'message.redacted':
|
|
341
|
+
return validateMessageRedactedPayload(payload);
|
|
342
|
+
default:
|
|
343
|
+
return [];
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* @typedef {'runtime' | 'branch'} CanonicalEventStream
|
|
349
|
+
*/
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* @typedef {Object} CanonicalEvent
|
|
353
|
+
* @property {string} event_id
|
|
354
|
+
* @property {CanonicalEventStream} stream
|
|
355
|
+
* @property {string | null} branch_id
|
|
356
|
+
* @property {number} seq
|
|
357
|
+
* @property {string} type
|
|
358
|
+
* @property {string} occurred_at
|
|
359
|
+
* @property {number} schema_version
|
|
360
|
+
* @property {string} actor_agent
|
|
361
|
+
* @property {string | null} session_id
|
|
362
|
+
* @property {string | null} command_id
|
|
363
|
+
* @property {string | null} causation_id
|
|
364
|
+
* @property {string | null} correlation_id
|
|
365
|
+
* @property {*} payload
|
|
366
|
+
*/
|
|
367
|
+
|
|
368
|
+
function validateCanonicalEvent(event) {
|
|
369
|
+
const problems = [];
|
|
370
|
+
|
|
371
|
+
if (!event || typeof event !== 'object' || Array.isArray(event)) {
|
|
372
|
+
return {
|
|
373
|
+
ok: false,
|
|
374
|
+
problems: ['Canonical event must be an object.'],
|
|
375
|
+
definition: null,
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
for (const field of REQUIRED_EVENT_ENVELOPE_FIELDS) {
|
|
380
|
+
if (!(field in event)) {
|
|
381
|
+
problems.push(`Missing required envelope field: ${field}`);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
if (!Object.values(EVENT_STREAMS).includes(event.stream)) {
|
|
386
|
+
problems.push(`Invalid event stream: ${String(event.stream)}`);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
if (!Number.isInteger(event.seq) || event.seq < 0) {
|
|
390
|
+
problems.push('seq must be a non-negative integer.');
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
const definition = getEventTypeDefinition(event.type);
|
|
394
|
+
if (!definition) {
|
|
395
|
+
problems.push(`Unknown canonical event type: ${String(event.type)}`);
|
|
396
|
+
} else if (event.stream !== definition.scope) {
|
|
397
|
+
problems.push(`Event type ${event.type} belongs on the ${definition.scope} stream, received ${String(event.stream)}.`);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
if (event.stream === EVENT_STREAMS.BRANCH && !event.branch_id) {
|
|
401
|
+
problems.push('Branch stream events must include branch_id.');
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
if (event.schema_version !== CANONICAL_EVENT_SCHEMA_VERSION) {
|
|
405
|
+
problems.push(`schema_version must equal ${CANONICAL_EVENT_SCHEMA_VERSION}.`);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
problems.push(...validateEventPayload(event.type, event.payload));
|
|
409
|
+
|
|
410
|
+
return {
|
|
411
|
+
ok: problems.length === 0,
|
|
412
|
+
problems,
|
|
413
|
+
definition,
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
module.exports = {
|
|
418
|
+
CANONICAL_EVENT_SCHEMA_VERSION,
|
|
419
|
+
DEFAULT_COLLABORATION_STREAM,
|
|
420
|
+
EVENT_FAMILY_REGISTRY,
|
|
421
|
+
EVENT_SCOPE_SPLIT,
|
|
422
|
+
EVENT_STREAMS,
|
|
423
|
+
EVENT_TYPE_REGISTRY,
|
|
424
|
+
FAMILY_DEFINITIONS,
|
|
425
|
+
REQUIRED_EVENT_ENVELOPE_FIELDS,
|
|
426
|
+
getEventFamily,
|
|
427
|
+
getEventFamilyDefinition,
|
|
428
|
+
getEventTypeDefinition,
|
|
429
|
+
resolveFamilyStream,
|
|
430
|
+
resolveTypeStream,
|
|
431
|
+
validateCanonicalEvent,
|
|
432
|
+
};
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
const {
|
|
2
|
+
analyzeContractFit,
|
|
3
|
+
buildRuntimeContractMetadata,
|
|
4
|
+
} = require('./agent-contracts');
|
|
5
|
+
|
|
6
|
+
const MANAGED_TEAM_HOOK_TOPICS = Object.freeze([
|
|
7
|
+
'conversation.mode_updated',
|
|
8
|
+
'conversation.manager_claimed',
|
|
9
|
+
'conversation.floor_yielded',
|
|
10
|
+
'conversation.phase_updated',
|
|
11
|
+
'task.created',
|
|
12
|
+
'task.claimed',
|
|
13
|
+
'task.completed',
|
|
14
|
+
'workflow.created',
|
|
15
|
+
'workflow.step_started',
|
|
16
|
+
'workflow.step_completed',
|
|
17
|
+
'workflow.step_reassigned',
|
|
18
|
+
'workflow.completed',
|
|
19
|
+
'review.requested',
|
|
20
|
+
'review.submitted',
|
|
21
|
+
'dependency.declared',
|
|
22
|
+
'dependency.resolved',
|
|
23
|
+
]);
|
|
24
|
+
|
|
25
|
+
function cloneJsonValue(value) {
|
|
26
|
+
return value == null ? value : JSON.parse(JSON.stringify(value));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function normalizeBranchName(branchName) {
|
|
30
|
+
return typeof branchName === 'string' && branchName.trim() ? branchName.trim() : 'main';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function resolveManagedTeamContractTarget(surface, options = {}) {
|
|
34
|
+
const defaults = {
|
|
35
|
+
claim_manager: {
|
|
36
|
+
work_type: 'managed_manager',
|
|
37
|
+
title: 'Managed mode manager controls',
|
|
38
|
+
description: 'Claim manager control, coordinate the team, delegate work, manage floor control, and set phases.',
|
|
39
|
+
assigned: true,
|
|
40
|
+
},
|
|
41
|
+
manager_briefing: {
|
|
42
|
+
work_type: 'team_coordination',
|
|
43
|
+
title: 'Managed/team coordination briefing',
|
|
44
|
+
description: 'Coordinate the team, track workflow progress, review recent coordination changes, and manage shared execution state.',
|
|
45
|
+
assigned: true,
|
|
46
|
+
},
|
|
47
|
+
manager_listen: {
|
|
48
|
+
work_type: 'team_coordination',
|
|
49
|
+
title: 'Managed/team coordination updates',
|
|
50
|
+
description: 'Process managed/team coordination updates, delegate next actions, and steer the team loop.',
|
|
51
|
+
assigned: true,
|
|
52
|
+
},
|
|
53
|
+
participant_briefing: {
|
|
54
|
+
work_type: 'messages',
|
|
55
|
+
title: 'Managed/team execution briefing',
|
|
56
|
+
description: 'Process team messages, workflow coordination updates, and manager guidance.',
|
|
57
|
+
assigned: true,
|
|
58
|
+
},
|
|
59
|
+
participant_listen: {
|
|
60
|
+
work_type: 'messages',
|
|
61
|
+
title: 'Managed/team execution updates',
|
|
62
|
+
description: 'Process team messages, workflow coordination updates, and manager guidance.',
|
|
63
|
+
assigned: true,
|
|
64
|
+
},
|
|
65
|
+
team_briefing: {
|
|
66
|
+
work_type: 'team_coordination',
|
|
67
|
+
title: 'Team coordination briefing',
|
|
68
|
+
description: 'Review recent workflow coordination, team-wide state changes, and shared execution progress.',
|
|
69
|
+
assigned: true,
|
|
70
|
+
},
|
|
71
|
+
team_listen: {
|
|
72
|
+
work_type: 'messages',
|
|
73
|
+
title: 'Team coordination updates',
|
|
74
|
+
description: 'Process shared team messages and workflow coordination updates.',
|
|
75
|
+
assigned: true,
|
|
76
|
+
},
|
|
77
|
+
yield_floor: {
|
|
78
|
+
work_type: 'team_coordination',
|
|
79
|
+
title: 'Managed mode floor control',
|
|
80
|
+
description: 'Coordinate which teammate should speak next and keep the managed conversation moving.',
|
|
81
|
+
assigned: true,
|
|
82
|
+
},
|
|
83
|
+
set_phase: {
|
|
84
|
+
work_type: 'team_coordination',
|
|
85
|
+
title: 'Managed mode phase control',
|
|
86
|
+
description: 'Set the managed team phase and coordinate how the team should operate next.',
|
|
87
|
+
assigned: true,
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const base = defaults[surface] || defaults.team_listen;
|
|
92
|
+
return Object.assign({}, base, options.target || {});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function buildContractAdvisoryMetadata(contract, advisory, surface) {
|
|
96
|
+
if (!contract || !advisory) return null;
|
|
97
|
+
const metadata = buildRuntimeContractMetadata(contract);
|
|
98
|
+
return Object.assign({
|
|
99
|
+
surface,
|
|
100
|
+
archetype: metadata.contract ? metadata.contract.archetype : null,
|
|
101
|
+
declared_archetype: metadata.archetype || null,
|
|
102
|
+
role: contract.role || '',
|
|
103
|
+
role_token: contract.role_token || null,
|
|
104
|
+
skills: metadata.skills,
|
|
105
|
+
effective_skills: metadata.contract ? metadata.contract.effective_skills : [],
|
|
106
|
+
contract_mode: metadata.contract_mode,
|
|
107
|
+
}, advisory);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function buildManagedTeamContractContext(contract, surface, options = {}) {
|
|
111
|
+
if (!contract) return null;
|
|
112
|
+
|
|
113
|
+
const target = resolveManagedTeamContractTarget(surface, options);
|
|
114
|
+
const advisory = analyzeContractFit(contract, target);
|
|
115
|
+
const contractAdvisory = buildContractAdvisoryMetadata(contract, advisory, surface);
|
|
116
|
+
|
|
117
|
+
const shouldHardBlock = !!(
|
|
118
|
+
surface === 'claim_manager'
|
|
119
|
+
&& contract.contract_mode === 'strict'
|
|
120
|
+
&& contract.has_explicit_contract
|
|
121
|
+
&& advisory
|
|
122
|
+
&& advisory.status === 'mismatch'
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
let contractViolation = null;
|
|
126
|
+
if (advisory && (advisory.status === 'mismatch' || advisory.status === 'partial' || shouldHardBlock)) {
|
|
127
|
+
contractViolation = {
|
|
128
|
+
surface,
|
|
129
|
+
status: shouldHardBlock ? 'blocked' : 'warning',
|
|
130
|
+
advisory_status: advisory.status,
|
|
131
|
+
contract_mode: contract.contract_mode,
|
|
132
|
+
message: shouldHardBlock
|
|
133
|
+
? `Strict contract mismatch: ${advisory.summary}`
|
|
134
|
+
: advisory.summary,
|
|
135
|
+
migration_note: advisory.migration_note || null,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
target,
|
|
141
|
+
advisory,
|
|
142
|
+
contract_advisory: contractAdvisory,
|
|
143
|
+
contract_violation: contractViolation,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function buildHookSummary(hook) {
|
|
148
|
+
const payload = hook && hook.payload && typeof hook.payload === 'object' ? hook.payload : {};
|
|
149
|
+
const topic = hook && hook.topic ? hook.topic : 'unknown';
|
|
150
|
+
let summary = topic;
|
|
151
|
+
let details = {};
|
|
152
|
+
|
|
153
|
+
switch (topic) {
|
|
154
|
+
case 'conversation.mode_updated':
|
|
155
|
+
summary = `Conversation mode changed from ${payload.previous_mode || 'unknown'} to ${payload.mode || 'unknown'}.`;
|
|
156
|
+
details = { mode: payload.mode || null, previous_mode: payload.previous_mode || null };
|
|
157
|
+
break;
|
|
158
|
+
case 'conversation.manager_claimed':
|
|
159
|
+
summary = `${payload.manager || hook.actor_agent || 'An agent'} claimed manager controls.`;
|
|
160
|
+
details = { manager: payload.manager || null, previous_manager: payload.previous_manager || null, phase: payload.phase || null, floor: payload.floor || null };
|
|
161
|
+
break;
|
|
162
|
+
case 'conversation.floor_yielded':
|
|
163
|
+
summary = payload.floor === 'closed'
|
|
164
|
+
? 'The manager closed the floor.'
|
|
165
|
+
: `The floor was set to ${payload.floor || 'unknown'}${payload.to ? ` for ${payload.to}` : ''}.`;
|
|
166
|
+
details = { floor: payload.floor || null, to: payload.to || null, turn_queue: Array.isArray(payload.turn_queue) ? [...payload.turn_queue] : [] };
|
|
167
|
+
break;
|
|
168
|
+
case 'conversation.phase_updated':
|
|
169
|
+
summary = `Managed phase changed from ${payload.previous_phase || 'unknown'} to ${payload.phase || 'unknown'}.`;
|
|
170
|
+
details = { phase: payload.phase || null, previous_phase: payload.previous_phase || null, floor: payload.floor || null };
|
|
171
|
+
break;
|
|
172
|
+
case 'task.created':
|
|
173
|
+
summary = `Task "${payload.title || payload.task_id || 'unknown'}" was created.`;
|
|
174
|
+
details = { task_id: payload.task_id || null, title: payload.title || null, assignee: payload.assignee || null };
|
|
175
|
+
break;
|
|
176
|
+
case 'task.claimed':
|
|
177
|
+
summary = `Task "${payload.title || payload.task_id || 'unknown'}" was claimed by ${payload.assignee || 'unknown'}.`;
|
|
178
|
+
details = { task_id: payload.task_id || null, title: payload.title || null, assignee: payload.assignee || null };
|
|
179
|
+
break;
|
|
180
|
+
case 'task.completed':
|
|
181
|
+
summary = `Task "${payload.title || payload.task_id || 'unknown'}" was completed.`;
|
|
182
|
+
details = { task_id: payload.task_id || null, title: payload.title || null, evidence_ref: cloneJsonValue(payload.evidence_ref || null) };
|
|
183
|
+
break;
|
|
184
|
+
case 'workflow.created':
|
|
185
|
+
summary = `Workflow "${payload.workflow_name || payload.workflow_id || 'unknown'}" was created.`;
|
|
186
|
+
details = { workflow_id: payload.workflow_id || null, workflow_name: payload.workflow_name || null, started_step_ids: Array.isArray(payload.started_step_ids) ? [...payload.started_step_ids] : [] };
|
|
187
|
+
break;
|
|
188
|
+
case 'workflow.step_started':
|
|
189
|
+
summary = `Workflow "${payload.workflow_name || payload.workflow_id || 'unknown'}" step ${payload.step_id || '?'} started.`;
|
|
190
|
+
details = { workflow_id: payload.workflow_id || null, workflow_name: payload.workflow_name || null, step_id: payload.step_id || null, assignee: payload.assignee || null };
|
|
191
|
+
break;
|
|
192
|
+
case 'workflow.step_completed':
|
|
193
|
+
summary = `Workflow "${payload.workflow_name || payload.workflow_id || 'unknown'}" step ${payload.step_id || '?'} completed.`;
|
|
194
|
+
details = { workflow_id: payload.workflow_id || null, workflow_name: payload.workflow_name || null, step_id: payload.step_id || null, evidence_ref: cloneJsonValue(payload.evidence_ref || null), next_step_ids: Array.isArray(payload.next_step_ids) ? [...payload.next_step_ids] : [] };
|
|
195
|
+
break;
|
|
196
|
+
case 'workflow.step_reassigned':
|
|
197
|
+
summary = `Workflow "${payload.workflow_name || payload.workflow_id || 'unknown'}" step ${payload.step_id || '?'} was reassigned to ${payload.new_assignee || 'unknown'}.`;
|
|
198
|
+
details = { workflow_id: payload.workflow_id || null, workflow_name: payload.workflow_name || null, step_id: payload.step_id || null, old_assignee: payload.old_assignee || null, new_assignee: payload.new_assignee || null };
|
|
199
|
+
break;
|
|
200
|
+
case 'workflow.completed':
|
|
201
|
+
summary = `Workflow "${payload.workflow_name || payload.workflow_id || 'unknown'}" completed.`;
|
|
202
|
+
details = { workflow_id: payload.workflow_id || null, workflow_name: payload.workflow_name || null, evidence_ref: cloneJsonValue(payload.evidence_ref || null) };
|
|
203
|
+
break;
|
|
204
|
+
case 'review.requested':
|
|
205
|
+
summary = `Review requested for "${payload.file || payload.review_id || 'unknown'}".`;
|
|
206
|
+
details = { review_id: payload.review_id || null, file: payload.file || null, requested_by: payload.requested_by || null, status: payload.status || null };
|
|
207
|
+
break;
|
|
208
|
+
case 'review.submitted':
|
|
209
|
+
summary = `Review ${payload.status || 'submitted'} for "${payload.file || payload.review_id || 'unknown'}".`;
|
|
210
|
+
details = { review_id: payload.review_id || null, file: payload.file || null, reviewer: payload.reviewer || null, status: payload.status || null };
|
|
211
|
+
break;
|
|
212
|
+
case 'dependency.declared':
|
|
213
|
+
summary = `Dependency declared for task ${payload.task_id || 'unknown'}.`;
|
|
214
|
+
details = { dependency_id: payload.dependency_id || null, task_id: payload.task_id || null, depends_on: payload.depends_on || null, resolved: !!payload.resolved };
|
|
215
|
+
break;
|
|
216
|
+
case 'dependency.resolved':
|
|
217
|
+
summary = `Dependency ${payload.dependency_id || 'unknown'} was resolved.`;
|
|
218
|
+
details = { dependency_id: payload.dependency_id || null, task_id: payload.task_id || null, depends_on: payload.depends_on || null, resolved_by_task_id: payload.resolved_by_task_id || null, reason: payload.reason || null };
|
|
219
|
+
break;
|
|
220
|
+
default:
|
|
221
|
+
details = cloneJsonValue(payload);
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return {
|
|
226
|
+
hook_id: hook.hook_id,
|
|
227
|
+
topic,
|
|
228
|
+
event_id: hook.event_id,
|
|
229
|
+
event_seq: hook.event_seq,
|
|
230
|
+
actor_agent: hook.actor_agent || null,
|
|
231
|
+
published_at: hook.published_at || hook.occurred_at || null,
|
|
232
|
+
summary,
|
|
233
|
+
details,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function readManagedTeamHookDigest(readBranchHooks, branchName, options = {}) {
|
|
238
|
+
if (typeof readBranchHooks !== 'function') return null;
|
|
239
|
+
|
|
240
|
+
const branch = normalizeBranchName(branchName);
|
|
241
|
+
const topics = Array.isArray(options.topics) && options.topics.length > 0
|
|
242
|
+
? options.topics
|
|
243
|
+
: MANAGED_TEAM_HOOK_TOPICS;
|
|
244
|
+
const limit = Number.isInteger(options.limit) && options.limit > 0 ? options.limit : 5;
|
|
245
|
+
const hooks = readBranchHooks(branch, { topics, limit });
|
|
246
|
+
if (!Array.isArray(hooks) || hooks.length === 0) return null;
|
|
247
|
+
|
|
248
|
+
return {
|
|
249
|
+
source: 'derived_post_commit_hooks',
|
|
250
|
+
branch,
|
|
251
|
+
topics: [...new Set(hooks.map((hook) => hook.topic))],
|
|
252
|
+
recent: hooks.map(buildHookSummary),
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
module.exports = {
|
|
257
|
+
MANAGED_TEAM_HOOK_TOPICS,
|
|
258
|
+
buildManagedTeamContractContext,
|
|
259
|
+
readManagedTeamHookDigest,
|
|
260
|
+
resolveManagedTeamContractTarget,
|
|
261
|
+
};
|