@teamclaws/teamclaw 2026.3.25 → 2026.3.26-1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/types.ts CHANGED
@@ -106,8 +106,11 @@ export type TaskInfo = {
106
106
  startedAt?: number;
107
107
  completedAt?: number;
108
108
  progress?: string;
109
+ progressContract?: WorkerProgressContract;
109
110
  result?: string;
111
+ resultContract?: WorkerTaskResultContract;
110
112
  error?: string;
113
+ lastHandoff?: TaskHandoffContract;
111
114
  clarificationRequestId?: string;
112
115
  execution?: TaskExecution;
113
116
  };
@@ -130,11 +133,102 @@ export type TaskAssignmentPayload = {
130
133
  description: string;
131
134
  priority?: TaskPriority;
132
135
  recommendedSkills?: string[];
136
+ executionSessionKey?: string;
137
+ executionIdempotencyKey?: string;
133
138
  repo?: RepoSyncInfo;
134
139
  };
135
140
 
136
141
  export type ControllerRunSource = "human" | "task_follow_up";
137
142
 
143
+ export type WorkerTaskResultOutcome = "completed" | "blocked" | "failed";
144
+
145
+ export type WorkerTaskResultDeliverable = {
146
+ kind: "file" | "directory" | "command" | "artifact" | "note";
147
+ value: string;
148
+ summary?: string;
149
+ };
150
+
151
+ export type WorkerTaskResultFollowUp = {
152
+ type: "review" | "handoff" | "clarification" | "downstream-task";
153
+ targetRole?: RoleId;
154
+ reason: string;
155
+ };
156
+
157
+ export type WorkerTaskResultContract = {
158
+ version: string;
159
+ outcome: WorkerTaskResultOutcome;
160
+ summary: string;
161
+ deliverables: WorkerTaskResultDeliverable[];
162
+ keyPoints: string[];
163
+ blockers: string[];
164
+ followUps: WorkerTaskResultFollowUp[];
165
+ questions: string[];
166
+ notes?: string;
167
+ };
168
+
169
+ export type WorkerProgressContract = {
170
+ version: string;
171
+ summary: string;
172
+ status: "in_progress" | "review";
173
+ currentStep?: string;
174
+ nextStep?: string;
175
+ blockers: string[];
176
+ };
177
+
178
+ export type TaskHandoffContract = {
179
+ version: string;
180
+ summary: string;
181
+ reason: string;
182
+ targetRole?: RoleId;
183
+ expectedNextStep?: string;
184
+ artifacts: string[];
185
+ };
186
+
187
+ export type TeamMessageIntent =
188
+ | "question"
189
+ | "announcement"
190
+ | "handoff"
191
+ | "review-request"
192
+ | "review-response"
193
+ | "update"
194
+ | "coordination";
195
+
196
+ export type TeamMessageContract = {
197
+ version: string;
198
+ intent: TeamMessageIntent;
199
+ summary: string;
200
+ details?: string;
201
+ requestedAction?: string;
202
+ requestedRole?: RoleId;
203
+ needsResponse: boolean;
204
+ references: string[];
205
+ };
206
+
207
+ export type ControllerManifestCreatedTask = {
208
+ title: string;
209
+ assignedRole?: RoleId;
210
+ expectedOutcome: string;
211
+ };
212
+
213
+ export type ControllerManifestDeferredTask = {
214
+ title: string;
215
+ assignedRole?: RoleId;
216
+ blockedBy: string;
217
+ whenReady: string;
218
+ };
219
+
220
+ export type ControllerOrchestrationManifest = {
221
+ version: string;
222
+ requirementSummary: string;
223
+ requiredRoles: RoleId[];
224
+ clarificationsNeeded: boolean;
225
+ clarificationQuestions: string[];
226
+ createdTasks: ControllerManifestCreatedTask[];
227
+ deferredTasks: ControllerManifestDeferredTask[];
228
+ handoffPlan?: string;
229
+ notes?: string;
230
+ };
231
+
138
232
  export type ControllerRunInfo = {
139
233
  id: string;
140
234
  title: string;
@@ -147,6 +241,7 @@ export type ControllerRunInfo = {
147
241
  reply?: string;
148
242
  error?: string;
149
243
  createdTaskIds: string[];
244
+ manifest?: ControllerOrchestrationManifest;
150
245
  status: TaskExecutionStatus;
151
246
  createdAt: number;
152
247
  updatedAt: number;
@@ -216,6 +311,7 @@ export type TeamMessage = {
216
311
  toRole?: RoleId;
217
312
  type: "direct" | "broadcast" | "review-request";
218
313
  content: string;
314
+ contract?: TeamMessageContract;
219
315
  taskId?: string;
220
316
  createdAt: number;
221
317
  };
package/src/ui/app.js CHANGED
@@ -292,6 +292,243 @@
292
292
  return '<div class="task-detail-card markdown-body">' + renderMarkdownContent(content) + "</div>";
293
293
  }
294
294
 
295
+ function normalizeTextValue(value) {
296
+ return String(value == null ? "" : value).replace(/\s+/g, " ").trim();
297
+ }
298
+
299
+ function renderContractChips(items, className) {
300
+ const normalized = Array.isArray(items)
301
+ ? items.map(function (item) { return normalizeTextValue(item); }).filter(Boolean)
302
+ : [];
303
+ if (normalized.length === 0) {
304
+ return "";
305
+ }
306
+ return '<div class="' + escapeHtml(className || "contract-chip-row") + '">' + normalized.map(function (item) {
307
+ return '<span class="contract-chip">' + escapeHtml(item) + "</span>";
308
+ }).join("") + "</div>";
309
+ }
310
+
311
+ function renderContractList(items, formatter, className) {
312
+ const values = Array.isArray(items) ? items.filter(Boolean) : [];
313
+ if (values.length === 0) {
314
+ return "";
315
+ }
316
+ return '<ul class="' + escapeHtml(className || "contract-list") + '">' + values.map(function (item) {
317
+ return "<li>" + formatter(item) + "</li>";
318
+ }).join("") + "</ul>";
319
+ }
320
+
321
+ function renderContractMetaRows(rows) {
322
+ const items = (rows || []).filter(function (row) {
323
+ return row && normalizeTextValue(row.label) && normalizeTextValue(row.value);
324
+ });
325
+ if (items.length === 0) {
326
+ return "";
327
+ }
328
+ return '<div class="contract-meta-grid">' + items.map(function (row) {
329
+ return (
330
+ '<div class="contract-meta-item">' +
331
+ ' <div class="contract-meta-label">' + escapeHtml(row.label) + "</div>" +
332
+ ' <div class="contract-meta-value">' + escapeHtml(row.value) + "</div>" +
333
+ "</div>"
334
+ );
335
+ }).join("") + "</div>";
336
+ }
337
+
338
+ function renderContractSection(title, bodyHtml) {
339
+ if (!normalizeTextValue(bodyHtml)) {
340
+ return "";
341
+ }
342
+ return (
343
+ '<div class="contract-section">' +
344
+ ' <div class="contract-section-title">' + escapeHtml(title) + "</div>" +
345
+ bodyHtml +
346
+ "</div>"
347
+ );
348
+ }
349
+
350
+ function renderContractMarkdownBody(content) {
351
+ if (!normalizeTextValue(content)) {
352
+ return "";
353
+ }
354
+ return '<div class="contract-markdown markdown-body">' + renderMarkdownContent(content) + "</div>";
355
+ }
356
+
357
+ function renderContractCard(options) {
358
+ const tone = options && options.tone ? " contract-card-" + options.tone : "";
359
+ const kicker = options && options.kicker ? '<div class="contract-card-kicker">' + escapeHtml(options.kicker) + "</div>" : "";
360
+ const title = options && options.title ? '<h4 class="contract-card-title">' + escapeHtml(options.title) + "</h4>" : "";
361
+ const meta = options && options.metaHtml ? options.metaHtml : "";
362
+ const sections = Array.isArray(options && options.sections)
363
+ ? options.sections.filter(function (section) { return normalizeTextValue(section); }).join("")
364
+ : "";
365
+ const footer = options && options.footerHtml ? options.footerHtml : "";
366
+
367
+ if (!kicker && !title && !meta && !sections && !footer) {
368
+ return "";
369
+ }
370
+
371
+ return (
372
+ '<div class="contract-card' + tone + '">' +
373
+ ' <div class="contract-card-header">' +
374
+ kicker +
375
+ title +
376
+ meta +
377
+ " </div>" +
378
+ sections +
379
+ footer +
380
+ "</div>"
381
+ );
382
+ }
383
+
384
+ function renderResultContractCard(contract) {
385
+ if (!contract || !normalizeTextValue(contract.summary)) {
386
+ return "";
387
+ }
388
+ const deliverables = Array.isArray(contract.deliverables) ? contract.deliverables : [];
389
+ const followUps = Array.isArray(contract.followUps) ? contract.followUps : [];
390
+ return renderContractCard({
391
+ tone: contract.outcome || "completed",
392
+ kicker: "Structured Result Contract",
393
+ title: contract.summary,
394
+ metaHtml: renderContractMetaRows([
395
+ { label: "Outcome", value: humanizeStatus(contract.outcome || "completed") },
396
+ { label: "Deliverables", value: String(deliverables.length) },
397
+ { label: "Follow-ups", value: String(followUps.length) },
398
+ ]),
399
+ sections: [
400
+ renderContractSection("Deliverables", renderContractList(deliverables, function (item) {
401
+ const prefix = escapeHtml(item.kind || "artifact") + ": " + escapeHtml(item.value || "");
402
+ return prefix + (item.summary ? ' <span class="contract-inline-note">— ' + escapeHtml(item.summary) + "</span>" : "");
403
+ })),
404
+ renderContractSection("Key Points", renderContractList(contract.keyPoints, function (item) {
405
+ return renderMarkdownInline(item);
406
+ })),
407
+ renderContractSection("Blockers", renderContractList(contract.blockers, function (item) {
408
+ return renderMarkdownInline(item);
409
+ })),
410
+ renderContractSection("Follow-ups", renderContractList(followUps, function (item) {
411
+ const prefix = escapeHtml(item.type || "follow-up") + (item.targetRole ? " (" + escapeHtml(item.targetRole) + ")" : "");
412
+ return prefix + ": " + renderMarkdownInline(item.reason || "");
413
+ })),
414
+ renderContractSection("Open Questions", renderContractList(contract.questions, function (item) {
415
+ return renderMarkdownInline(item);
416
+ })),
417
+ renderContractSection("Notes", renderContractMarkdownBody(contract.notes)),
418
+ ],
419
+ });
420
+ }
421
+
422
+ function renderProgressContractCard(contract) {
423
+ if (!contract || !normalizeTextValue(contract.summary)) {
424
+ return "";
425
+ }
426
+ return renderContractCard({
427
+ tone: contract.status || "in_progress",
428
+ kicker: "Structured Progress Contract",
429
+ title: contract.summary,
430
+ metaHtml: renderContractMetaRows([
431
+ { label: "Status", value: humanizeStatus(contract.status || "in_progress") },
432
+ { label: "Blockers", value: String((contract.blockers || []).length) },
433
+ ]),
434
+ sections: [
435
+ renderContractSection("Current Step", renderContractMarkdownBody(contract.currentStep)),
436
+ renderContractSection("Next Step", renderContractMarkdownBody(contract.nextStep)),
437
+ renderContractSection("Blockers", renderContractList(contract.blockers, function (item) {
438
+ return renderMarkdownInline(item);
439
+ })),
440
+ ],
441
+ });
442
+ }
443
+
444
+ function renderHandoffContractCard(contract) {
445
+ if (!contract || !normalizeTextValue(contract.summary)) {
446
+ return "";
447
+ }
448
+ return renderContractCard({
449
+ tone: "handoff",
450
+ kicker: "Structured Handoff Contract",
451
+ title: contract.summary,
452
+ metaHtml: renderContractMetaRows([
453
+ { label: "Target Role", value: contract.targetRole || "—" },
454
+ { label: "Artifacts", value: String((contract.artifacts || []).length) },
455
+ ]),
456
+ sections: [
457
+ renderContractSection("Reason", renderContractMarkdownBody(contract.reason)),
458
+ renderContractSection("Expected Next Step", renderContractMarkdownBody(contract.expectedNextStep)),
459
+ ],
460
+ footerHtml: renderContractChips(contract.artifacts, "contract-chip-row contract-reference-row"),
461
+ });
462
+ }
463
+
464
+ function renderTeamMessageContractCard(contract) {
465
+ if (!contract || !normalizeTextValue(contract.summary)) {
466
+ return "";
467
+ }
468
+ return renderContractCard({
469
+ tone: contract.intent || "update",
470
+ kicker: "Structured Message Contract",
471
+ title: contract.summary,
472
+ metaHtml: renderContractMetaRows([
473
+ { label: "Intent", value: humanizeStatus(contract.intent || "update") },
474
+ { label: "Needs Response", value: contract.needsResponse ? "Yes" : "No" },
475
+ { label: "Requested Role", value: contract.requestedRole || "—" },
476
+ ]),
477
+ sections: [
478
+ renderContractSection("Details", renderContractMarkdownBody(contract.details)),
479
+ renderContractSection("Requested Action", renderContractMarkdownBody(contract.requestedAction)),
480
+ ],
481
+ footerHtml: renderContractChips(contract.references, "contract-chip-row contract-reference-row"),
482
+ });
483
+ }
484
+
485
+ function renderControllerManifestCard(manifest) {
486
+ if (!manifest || !normalizeTextValue(manifest.requirementSummary)) {
487
+ return "";
488
+ }
489
+ const createdTasks = Array.isArray(manifest.createdTasks) ? manifest.createdTasks : [];
490
+ const deferredTasks = Array.isArray(manifest.deferredTasks) ? manifest.deferredTasks : [];
491
+ return renderContractCard({
492
+ tone: "manifest",
493
+ kicker: "Structured Manifest",
494
+ title: manifest.requirementSummary,
495
+ metaHtml: renderContractMetaRows([
496
+ { label: "Required Roles", value: String((manifest.requiredRoles || []).length) },
497
+ { label: "Created Tasks", value: String(createdTasks.length) },
498
+ { label: "Deferred Tasks", value: String(deferredTasks.length) },
499
+ ]),
500
+ sections: [
501
+ renderContractSection("Required Roles", renderContractChips(manifest.requiredRoles, "contract-chip-row contract-role-row")),
502
+ renderContractSection("Created Tasks", renderContractList(createdTasks, function (item) {
503
+ const roleLabel = item.assignedRole ? " (" + escapeHtml(item.assignedRole) + ")" : "";
504
+ return '<strong>' + escapeHtml(item.title || "Task") + roleLabel + "</strong>: " + renderMarkdownInline(item.expectedOutcome || "");
505
+ })),
506
+ renderContractSection("Deferred Tasks", renderContractList(deferredTasks, function (item) {
507
+ const roleLabel = item.assignedRole ? " (" + escapeHtml(item.assignedRole) + ")" : "";
508
+ return '<strong>' + escapeHtml(item.title || "Task") + roleLabel + "</strong>: " + renderMarkdownInline(item.blockedBy || "") + " — create when " + renderMarkdownInline(item.whenReady || "");
509
+ })),
510
+ renderContractSection("Clarifications", renderContractList(manifest.clarificationQuestions, function (item) {
511
+ return renderMarkdownInline(item);
512
+ })),
513
+ renderContractSection("Handoff Plan", renderContractMarkdownBody(manifest.handoffPlan)),
514
+ renderContractSection("Notes", renderContractMarkdownBody(manifest.notes)),
515
+ ],
516
+ });
517
+ }
518
+
519
+ function buildMessageDisplayContent(message) {
520
+ const content = normalizeTextValue(message && message.content);
521
+ const contract = message && message.contract ? message.contract : null;
522
+ if (!contract || !normalizeTextValue(contract.summary)) {
523
+ return renderMarkdownContent(content);
524
+ }
525
+ const detailContent = normalizeTextValue(contract.details || "");
526
+ if (!content || content === normalizeTextValue(contract.summary) || content === detailContent) {
527
+ return "";
528
+ }
529
+ return renderMarkdownContent(message.content || "");
530
+ }
531
+
295
532
  function findWorkspaceNodeByPath(nodes, relativePath) {
296
533
  for (let index = 0; index < nodes.length; index += 1) {
297
534
  const node = nodes[index];
@@ -735,6 +972,13 @@
735
972
  const creatorBadge = task.createdBy
736
973
  ? '<span class="task-origin-badge">' + escapeHtml(task.createdBy) + "</span>"
737
974
  : "";
975
+ const contractSummary = task.resultContract
976
+ ? '<div class="task-contract-summary"><strong>Result:</strong> ' + escapeHtml(task.resultContract.summary || "") + "</div>"
977
+ : task.lastHandoff
978
+ ? '<div class="task-contract-summary"><strong>Handoff:</strong> ' + escapeHtml(task.lastHandoff.summary || "") + "</div>"
979
+ : task.progressContract
980
+ ? '<div class="task-contract-summary"><strong>Progress:</strong> ' + escapeHtml(task.progressContract.summary || "") + "</div>"
981
+ : "";
738
982
  const note = task.progress
739
983
  ? '<div class="task-note">' + escapeHtml(task.progress).slice(0, 220) + "</div>"
740
984
  : "";
@@ -750,6 +994,7 @@
750
994
  ' <div class="task-title-row"><div class="task-title">' + escapeHtml(task.title) + "</div>" + creatorBadge + "</div>" +
751
995
  (task.description ? '<div class="task-desc">' + escapeHtml(task.description).slice(0, 220) + "</div>" : "") +
752
996
  renderSkillPills(recommendedSkills, "skill-pills task-skill-pills") +
997
+ contractSummary +
753
998
  note +
754
999
  ' <div class="task-meta">' +
755
1000
  ' <span class="task-status-badge ' + escapeHtml(status) + '">' + escapeHtml(humanizeStatus(status)) + "</span>" +
@@ -790,6 +1035,9 @@
790
1035
  return '<button type="button" class="controller-run-task-link" data-open-task-id="' + escapeHtml(taskId) + '">' + escapeHtml(taskId) + "</button>";
791
1036
  }).join("") + "</div>"
792
1037
  : "";
1038
+ const manifestBlock = run.manifest
1039
+ ? '<div class="controller-run-section"><div class="controller-run-section-title">Manifest</div>' + renderControllerManifestCard(run.manifest) + "</div>"
1040
+ : "";
793
1041
  const replyBlock = run.reply
794
1042
  ? '<div class="controller-run-section"><div class="controller-run-section-title">Reply</div><div class="markdown-body">' + renderMarkdownContent(run.reply) + "</div></div>"
795
1043
  : "";
@@ -822,6 +1070,7 @@
822
1070
  ' <span class="controller-run-status ' + escapeHtml(status) + '">' + escapeHtml(humanizeStatus(status)) + "</span>" +
823
1071
  " </div>" +
824
1072
  ' <div class="controller-run-section"><div class="controller-run-section-title">Request</div><div class="markdown-body">' + renderMarkdownContent(run.request || "") + "</div></div>" +
1073
+ manifestBlock +
825
1074
  replyBlock +
826
1075
  errorBlock +
827
1076
  (createdTaskButtons
@@ -978,14 +1227,44 @@
978
1227
  (task.progress
979
1228
  ? '<div class="task-detail-section"><h3>Latest Progress</h3>' + renderMarkdownCard(task.progress) + "</div>"
980
1229
  : "") +
1230
+ (task.progressContract
1231
+ ? '<div class="task-detail-section"><h3>Structured Progress</h3>' + renderProgressContractCard(task.progressContract) + "</div>"
1232
+ : "") +
981
1233
  (task.result
982
1234
  ? '<div class="task-detail-section"><h3>Result</h3>' + renderMarkdownCard(task.result) + "</div>"
983
1235
  : "") +
1236
+ (task.resultContract
1237
+ ? '<div class="task-detail-section"><h3>Structured Result</h3>' + renderResultContractCard(task.resultContract) + "</div>"
1238
+ : "") +
1239
+ (task.lastHandoff
1240
+ ? '<div class="task-detail-section"><h3>Last Handoff</h3>' + renderHandoffContractCard(task.lastHandoff) + "</div>"
1241
+ : "") +
984
1242
  (task.error
985
1243
  ? '<div class="task-detail-section"><h3>Error</h3>' + renderMarkdownCard(task.error) + "</div>"
986
1244
  : "");
987
1245
  }
988
1246
 
1247
+ function buildTimelineMessageBody(message) {
1248
+ const contractBlock = renderTeamMessageContractCard(message && message.contract);
1249
+ const rawContent = buildMessageDisplayContent(message);
1250
+ return (contractBlock || "") + (rawContent ? '<div class="timeline-raw-markdown markdown-body">' + rawContent + "</div>" : "");
1251
+ }
1252
+
1253
+ function buildTimelineClarificationBody(item) {
1254
+ const answerBlock = item && item.answer
1255
+ ? '<div class="timeline-contract-note"><strong>Answer:</strong> ' + renderMarkdownInline(item.answer) + "</div>"
1256
+ : "";
1257
+ const contextBlock = item && item.context
1258
+ ? '<div class="timeline-contract-note"><strong>Context:</strong> ' + renderMarkdownInline(item.context) + "</div>"
1259
+ : "";
1260
+ return (
1261
+ '<div class="timeline-contract-note"><strong>Question:</strong> ' + renderMarkdownInline(item.question || "") + "</div>" +
1262
+ '<div class="timeline-contract-note"><strong>Blocking Reason:</strong> ' + renderMarkdownInline(item.blockingReason || "") + "</div>" +
1263
+ contextBlock +
1264
+ answerBlock
1265
+ );
1266
+ }
1267
+
989
1268
  function buildTimelineEntries(task) {
990
1269
  if (!task || !selectedTaskDetail) {
991
1270
  return [];
@@ -997,7 +1276,7 @@
997
1276
  createdAt: event.createdAt || 0,
998
1277
  label: humanizeStatus(event.phase || event.type),
999
1278
  meta: [event.source || "execution", event.workerId || event.role || event.stream].filter(Boolean).join(" • "),
1000
- body: event.message || "",
1279
+ bodyHtml: renderMarkdownContent(event.message || ""),
1001
1280
  };
1002
1281
  });
1003
1282
 
@@ -1005,13 +1284,30 @@
1005
1284
  return {
1006
1285
  kind: "message",
1007
1286
  createdAt: message.createdAt || 0,
1008
- label: humanizeStatus(message.type || "message"),
1009
- meta: [message.fromRole || message.from || "unknown", message.toRole ? ("to " + message.toRole) : null].filter(Boolean).join(" • "),
1010
- body: message.content || "",
1287
+ label: humanizeStatus((message.contract && message.contract.intent) || message.type || "message"),
1288
+ meta: [
1289
+ message.fromRole || message.from || "unknown",
1290
+ message.toRole ? ("to " + message.toRole) : null,
1291
+ message.contract && message.contract.needsResponse ? "response expected" : null,
1292
+ ].filter(Boolean).join(" • "),
1293
+ bodyHtml: buildTimelineMessageBody(message),
1294
+ };
1295
+ });
1296
+
1297
+ const clarifications = (selectedTaskDetail.clarifications || []).map(function (item) {
1298
+ return {
1299
+ kind: "clarification",
1300
+ createdAt: item.updatedAt || item.createdAt || 0,
1301
+ label: item.status === "answered" ? "clarification answered" : "clarification requested",
1302
+ meta: [
1303
+ item.requestedByRole || "unknown role",
1304
+ item.requestedByWorkerId || item.requestedBy || null,
1305
+ ].filter(Boolean).join(" • "),
1306
+ bodyHtml: buildTimelineClarificationBody(item),
1011
1307
  };
1012
1308
  });
1013
1309
 
1014
- return executionEvents.concat(messages).sort(function (left, right) {
1310
+ return executionEvents.concat(messages).concat(clarifications).sort(function (left, right) {
1015
1311
  return (left.createdAt || 0) - (right.createdAt || 0);
1016
1312
  });
1017
1313
  }
@@ -1040,7 +1336,7 @@
1040
1336
  ' <div class="timeline-entry-meta">' + escapeHtml(formatTime(entry.createdAt)) + "</div>" +
1041
1337
  " </div>" +
1042
1338
  (entry.meta ? '<div class="timeline-entry-meta">' + escapeHtml(entry.meta) + "</div>" : "") +
1043
- ' <div class="timeline-entry-body markdown-body">' + renderMarkdownContent(entry.body) + "</div>" +
1339
+ ' <div class="timeline-entry-body markdown-body">' + (entry.bodyHtml || renderMarkdownContent(entry.body || "")) + "</div>" +
1044
1340
  "</article>"
1045
1341
  );
1046
1342
  }).join("") +
@@ -1222,7 +1518,14 @@
1222
1518
 
1223
1519
  container.innerHTML = recent.map(function (message) {
1224
1520
  const from = message.fromRole || message.from || "unknown";
1225
- const type = message.type || "direct";
1521
+ const type = (message.contract && message.contract.intent) || message.type || "direct";
1522
+ const contractBlock = renderTeamMessageContractCard(message.contract);
1523
+ const rawContent = buildMessageDisplayContent(message);
1524
+ const meta = [
1525
+ message.toRole ? ("to " + message.toRole) : null,
1526
+ message.taskId ? ("task " + message.taskId) : null,
1527
+ formatTime(message.createdAt),
1528
+ ].filter(Boolean).join(" • ");
1226
1529
 
1227
1530
  return (
1228
1531
  '<div class="message-card">' +
@@ -1230,7 +1533,9 @@
1230
1533
  ' <span class="message-from">' + escapeHtml(from) + "</span>" +
1231
1534
  ' <span class="message-type ' + escapeHtml(type) + '">' + escapeHtml(humanizeStatus(type)) + "</span>" +
1232
1535
  " </div>" +
1233
- ' <div class="message-content markdown-body">' + renderMarkdownContent(message.content) + "</div>" +
1536
+ (meta ? '<div class="message-meta">' + escapeHtml(meta) + "</div>" : "") +
1537
+ contractBlock +
1538
+ (rawContent ? '<div class="message-content markdown-body">' + rawContent + "</div>" : "") +
1234
1539
  "</div>"
1235
1540
  );
1236
1541
  }).join("");