@unson/brainbase-mcp 0.44.0 → 0.44.2

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 (76) hide show
  1. package/contracts/canonical-task/operation-schema.sql +58 -0
  2. package/contracts/canonical-task/search-indexes.sql +10 -0
  3. package/contracts/canonical-task/store-schema.sql +77 -0
  4. package/dist/automation-run.d.ts +213 -0
  5. package/dist/automation-run.js +479 -0
  6. package/dist/canonical-task-operations.d.ts +205 -0
  7. package/dist/canonical-task-operations.js +700 -0
  8. package/dist/canonical-task-postgres.d.ts +139 -0
  9. package/dist/canonical-task-postgres.js +554 -0
  10. package/dist/canonical-task-service.d.ts +13 -0
  11. package/dist/canonical-task-service.js +55 -3
  12. package/dist/canonical-task-store-config.d.ts +31 -0
  13. package/dist/canonical-task-store-config.js +53 -0
  14. package/dist/config-parser.d.ts +298 -0
  15. package/dist/config-parser.js +593 -0
  16. package/dist/connected-onboarding.d.ts +57 -0
  17. package/dist/connected-onboarding.js +37 -0
  18. package/dist/durable-wait-http.d.ts +3 -1
  19. package/dist/durable-wait-http.js +73 -6
  20. package/dist/durable-waits.d.ts +93 -3
  21. package/dist/durable-waits.js +320 -8
  22. package/dist/honcho.d.ts +25 -0
  23. package/dist/honcho.js +81 -0
  24. package/dist/judgment-event-adapter.d.ts +72 -0
  25. package/dist/judgment-event-adapter.js +114 -0
  26. package/dist/judgment-event-outbox.d.ts +182 -0
  27. package/dist/judgment-event-outbox.js +568 -0
  28. package/dist/judgment-execution-outcome.d.ts +46 -0
  29. package/dist/judgment-execution-outcome.js +149 -0
  30. package/dist/judgment-host.d.ts +18 -7
  31. package/dist/judgment-host.js +82 -34
  32. package/dist/judgment-lifecycle-core.d.ts +198 -0
  33. package/dist/judgment-lifecycle-core.js +464 -0
  34. package/dist/judgment-node-evidence.d.ts +57 -0
  35. package/dist/judgment-node-evidence.js +297 -0
  36. package/dist/judgment-runtime-resolution.d.ts +264 -0
  37. package/dist/judgment-runtime-resolution.js +1248 -0
  38. package/dist/judgment-runtime-resolution.public.d.ts +255 -0
  39. package/dist/knowledge-event.d.ts +28 -0
  40. package/dist/knowledge-event.js +114 -0
  41. package/dist/mcp-runtime.d.ts +75 -0
  42. package/dist/mcp-runtime.js +188 -0
  43. package/dist/meeting-automation-runtime.d.ts +102 -0
  44. package/dist/meeting-automation-runtime.js +249 -0
  45. package/dist/meeting-automation.d.ts +155 -0
  46. package/dist/meeting-automation.js +341 -0
  47. package/dist/meeting-minutes-context-receipt.d.ts +91 -0
  48. package/dist/meeting-minutes-context-receipt.js +280 -0
  49. package/dist/meeting-source-sync.d.ts +89 -0
  50. package/dist/meeting-source-sync.js +230 -0
  51. package/dist/mesh.d.ts +58 -0
  52. package/dist/mesh.js +134 -0
  53. package/dist/onboarding-runtime.d.ts +47 -0
  54. package/dist/onboarding-runtime.js +4719 -0
  55. package/dist/routine-cycle.d.ts +51 -0
  56. package/dist/routine-cycle.js +153 -0
  57. package/dist/routine-liveness.d.ts +49 -0
  58. package/dist/routine-liveness.js +227 -0
  59. package/dist/routine-runtime.d.ts +101 -0
  60. package/dist/routine-runtime.js +237 -0
  61. package/dist/routines.d.ts +3 -0
  62. package/dist/routines.js +3 -0
  63. package/dist/run-receipt.d.ts +40 -0
  64. package/dist/run-receipt.js +635 -0
  65. package/dist/runtime-host.d.ts +64 -0
  66. package/dist/runtime-host.js +70 -0
  67. package/dist/world-work.d.ts +16 -0
  68. package/package.json +115 -3
  69. package/ui/objective-editor.js +47 -11
  70. package/ui/workspace-kit.css +33 -1
  71. package/ui/workspace-kit.js +13 -2
  72. package/ui/world/world-canvas-ui.css +4 -49
  73. package/ui/world/world-canvas-ui.js +26 -71
  74. package/ui/world/world-district.js +18 -3
  75. package/ui/world/world-placement.js +13 -0
  76. package/ui/world/world-work-rail.js +7 -1
@@ -5,11 +5,13 @@
5
5
  * unanchored:true omits the pointer for a real record without a drawable lot.
6
6
  */
7
7
 
8
+ import { workspaceNotificationCard } from '../../workspace-kit.js';
9
+
8
10
  const SKY_MODES = Object.freeze([
9
- { value: 'auto', label: '時刻に合わせる', icon: '◷' },
10
- { value: 'day', label: '昼', icon: '☀' },
11
- { value: 'dusk', label: '夕方', icon: '◒' },
12
- { value: 'night', label: '夜', icon: '☾' },
11
+ { value: 'auto', label: '自動(現在時刻)', icon: '<circle cx="12" cy="12" r="8.5"/><path d="M12 7.5v5l3 1.8"/>' },
12
+ { value: 'day', label: '昼', icon: '<circle cx="12" cy="12" r="5" fill="currentColor" fill-opacity=".12"/><path d="M12 2v2m0 16v2M2 12h2m16 0h2M4.9 4.9l1.4 1.4m11.4 11.4 1.4 1.4M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/><path d="M10 12h.01M14 12h.01m-4 2.5q2 1.6 4 0"/>' },
13
+ { value: 'dusk', label: '夕方', icon: '<path d="M5.5 13a6.5 6.5 0 0 1 13 0" fill="currentColor" fill-opacity=".12"/><path d="M12 2v2M3.5 5.5 5 7m15.5-1.5L19 7M2 13h20M3 17q3-2 6 0t6 0 6 0M6 21q3-2 6 0t6 0"/>' },
14
+ { value: 'night', label: '夜', icon: '<path d="M19.5 15.5A8.5 8.5 0 0 1 8.5 4.5a8.5 8.5 0 1 0 11 11Z" fill="currentColor" fill-opacity=".12"/><path d="m18 3 .7 2.3L21 6l-2.3.7L18 9l-.7-2.3L15 6l2.3-.7ZM22 11h.01"/>' },
13
15
  ]);
14
16
  const CLOSE_DELAY = 220;
15
17
  let instanceId = 0;
@@ -57,7 +59,6 @@ export function createWorldCanvasUI({
57
59
  let taskOpen = false;
58
60
  let taskAnchor = null;
59
61
  let helpOpen = false;
60
- let skyOpen = false;
61
62
  let skyMode = 'auto';
62
63
  let businesses = new Set();
63
64
  let overlayOrder = [];
@@ -138,10 +139,7 @@ export function createWorldCanvasUI({
138
139
  const reset = button('bb-world-canvas-control bb-world-canvas-reset', '世界全体に戻る', '全体に戻る');
139
140
  const city = element('select', 'bb-world-canvas-control bb-world-canvas-city', null, { 'aria-label': '都市を選ぶ' });
140
141
  const sky = button('bb-world-canvas-control bb-world-canvas-sky', '空:時刻に合わせる', '');
141
- sky.setAttribute('aria-haspopup', 'menu');
142
- sky.setAttribute('aria-expanded', 'false');
143
- sky.setAttribute('aria-controls', `${id}-sky`);
144
- const skyIcon = element('span', 'bb-world-canvas-sky-icon', SKY_MODES[0].icon, { 'aria-hidden': 'true' });
142
+ const skyIcon = element('span', 'bb-world-canvas-sky-icon', '', { 'aria-hidden': 'true' });
145
143
  sky.append(skyIcon);
146
144
  toolbar.append(reset, city, sky);
147
145
  const contextRoot = element('div', 'bb-world-canvas-context', null, {
@@ -153,22 +151,7 @@ export function createWorldCanvasUI({
153
151
  const contextNextAction = element('p', 'bb-world-canvas-context-next-action', worldContext.unavailableCity, {
154
152
  'data-world-context': 'next-action',
155
153
  });
156
- contextRoot.append(contextPurpose, contextNextAction);
157
- const skyMenu = element('div', 'bb-world-canvas-sky-menu', null, { id: `${id}-sky`, role: 'menu', 'aria-label': '空の時間帯' });
158
- skyMenu.hidden = true;
159
- const skyOptions = SKY_MODES.map((mode) => {
160
- const option = button('bb-world-canvas-sky-option', mode.label);
161
- option.setAttribute('role', 'menuitemradio');
162
- option.setAttribute('aria-checked', String(mode.value === skyMode));
163
- option.setAttribute('tabindex', '-1');
164
- listen(option, 'click', () => {
165
- setSky(mode.value);
166
- closeSky(true);
167
- onSky?.(mode.value);
168
- });
169
- skyMenu.append(option);
170
- return option;
171
- });
154
+ contextRoot.append(contextNextAction);
172
155
  const quests = element('div', 'bb-world-canvas-quests', null, { 'aria-live': 'polite', 'aria-label': '確認が必要なこと' });
173
156
 
174
157
  const edge = button('bb-world-canvas-edge', '都市の詳細を開く', '');
@@ -203,7 +186,7 @@ export function createWorldCanvasUI({
203
186
  const notes = element('div', 'bb-world-canvas-notes');
204
187
  helpBody.append(helpContent, notes);
205
188
  help.append(helpTop, helpBody);
206
- root.append(toolbar, contextRoot, skyMenu, quests, edge, rail, task, helpButton, help);
189
+ root.append(toolbar, contextRoot, quests, edge, rail, task, helpButton, help);
207
190
  stage.append(root);
208
191
 
209
192
  function setCities(items = [], selectedCode = null) {
@@ -222,6 +205,7 @@ export function createWorldCanvasUI({
222
205
  }
223
206
  city.replaceChildren(...options);
224
207
  city.disabled = businesses.size === 0;
208
+ contextRoot.hidden = businesses.size > 0;
225
209
  contextNextAction.textContent = businesses.size === 0
226
210
  ? worldContext.unavailableCity
227
211
  : worldContext.knownCityAction;
@@ -236,27 +220,12 @@ export function createWorldCanvasUI({
236
220
  const match = SKY_MODES.find((entry) => entry.value === mode);
237
221
  if (!match) return;
238
222
  skyMode = mode;
239
- skyIcon.textContent = match.icon;
240
- sky.setAttribute('aria-label', `空:${match.label}`);
241
- sky.setAttribute('title', `空:${match.label}`);
242
- skyOptions.forEach((option, index) => option.setAttribute('aria-checked', String(SKY_MODES[index].value === mode)));
243
- }
244
- function openSky() {
245
- if (disposed) return;
246
- skyOpen = true;
247
- skyMenu.hidden = false;
248
- sky.setAttribute('aria-expanded', 'true');
249
- activate('sky', skyMenu);
250
- focus(skyOptions[SKY_MODES.findIndex((entry) => entry.value === skyMode)]);
251
- }
252
- function closeSky(restoreFocus = false) {
253
- if (!skyOpen) return;
254
- const shouldRestore = restoreFocus && focusedWithin(skyMenu);
255
- skyOpen = false;
256
- skyMenu.hidden = true;
257
- sky.setAttribute('aria-expanded', 'false');
258
- forget('sky');
259
- if (shouldRestore) focus(sky);
223
+ // Artwork is fixed code, never host data. Native button activation covers Enter/Space.
224
+ skyIcon.innerHTML = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${match.icon}</svg>`;
225
+ const next = SKY_MODES[(SKY_MODES.indexOf(match) + 1) % SKY_MODES.length];
226
+ const label = `空:${match.label}。次は${next.label}`;
227
+ sky.setAttribute('aria-label', label);
228
+ sky.setAttribute('title', label);
260
229
  }
261
230
  function cancelRailTimer() {
262
231
  if (railTimer != null) clearTimeout(railTimer);
@@ -397,10 +366,10 @@ export function createWorldCanvasUI({
397
366
  // A quest is a way down (a link already checked by the caller, or an action in this page) and then the
398
367
  // whole row is pressable; without one it is drawn flat, so it never looks pressable (story-world-clickable-honesty-v1).
399
368
  const way = link?.href ? 'link' : typeof action?.onClick === 'function' ? 'action' : null;
400
- const quest = element('p', `bb-world-canvas-quest ${way ? 'has-way' : 'is-static'}`);
401
- const icon = tone === 'attention' || tone === 'warning' ? '!' : 'i';
369
+ const quest = workspaceNotificationCard(doc, { tag: 'p', tone, className: `bb-world-canvas-quest ${way ? 'has-way' : 'is-static'}` });
370
+ const icon = ['attention', 'warning', 'danger'].includes(tone) ? '!' : 'i';
402
371
  quest.append(
403
- element('span', 'bb-world-canvas-quest-symbol', icon, { 'aria-hidden': 'true' }),
372
+ element('span', 'bb-world-canvas-quest-symbol bb-ws-notification-symbol', icon, { 'aria-hidden': 'true' }),
404
373
  element('span', 'bb-world-canvas-quest-text', summary.trim()),
405
374
  );
406
375
  // The accessible name contains the unabridged summary even when visual text
@@ -428,38 +397,23 @@ export function createWorldCanvasUI({
428
397
  // A selected task outside the camera is not a visible overlay to dismiss.
429
398
  const name = [...overlayOrder].reverse().find((entry) => entry !== 'task' || !task.hidden);
430
399
  if (!name) return false;
431
- if (name === 'sky') closeSky(true);
432
- else if (name === 'help') closeHelp();
400
+ if (name === 'help') closeHelp();
433
401
  else if (name === 'rail') closeRail();
434
402
  else if (name === 'task') dismissTask();
435
403
  return true;
436
404
  }
437
405
 
438
406
  listen(reset, 'click', () => {
439
- closeSky();
440
407
  closeHelp();
441
408
  closeRail();
442
409
  closeTask();
443
410
  onReset?.();
444
411
  });
445
412
  listen(city, 'change', () => { if (businesses.has(city.value)) onCity?.(city.value); });
446
- listen(sky, 'click', () => { if (skyOpen) closeSky(); else openSky(); });
447
- listen(sky, 'keydown', (event) => {
448
- if (event.key === 'ArrowDown' || event.key === 'ArrowUp') { event.preventDefault(); openSky(); }
449
- });
450
- listen(skyMenu, 'keydown', (event) => {
451
- let index = skyOptions.indexOf(doc.activeElement);
452
- if (event.key === 'ArrowDown') index = (index + 1) % skyOptions.length;
453
- else if (event.key === 'ArrowUp') index = (index - 1 + skyOptions.length) % skyOptions.length;
454
- else if (event.key === 'Home') index = 0;
455
- else if (event.key === 'End') index = skyOptions.length - 1;
456
- else if (event.key === 'Tab') { closeSky(true); return; }
457
- else return;
458
- event.preventDefault();
459
- focus(skyOptions[index]);
460
- });
461
- listen(skyMenu, 'focusout', (event) => {
462
- if (!skyMenu.contains(event.relatedTarget) && event.relatedTarget !== sky) closeSky();
413
+ listen(sky, 'click', () => {
414
+ const next = SKY_MODES[(SKY_MODES.findIndex(entry => entry.value === skyMode) + 1) % SKY_MODES.length];
415
+ setSky(next.value);
416
+ onSky?.(next.value);
463
417
  });
464
418
  for (const target of [edge, rail]) {
465
419
  listen(target, 'pointerenter', railEnter);
@@ -493,10 +447,11 @@ export function createWorldCanvasUI({
493
447
  observer?.observe(stage);
494
448
  observer?.observe(taskBody);
495
449
  setCities([]);
450
+ setSky(skyMode);
496
451
 
497
452
  return {
498
453
  setCities, setCity, setSky, showRail, openRail, showTask, closeTask, setTaskAnchor,
499
- setHelp(nodes) { if (!disposed) helpContent.replaceChildren(...nodeList(nodes)); },
454
+ setHelp(nodes) { if (!disposed) helpContent.replaceChildren(contextPurpose, element('p', '', worldContext.knownCityAction), ...nodeList(nodes)); },
500
455
  addNote, clearNotes, dismissOverlay,
501
456
  dispose() {
502
457
  if (disposed) return;
@@ -20,7 +20,7 @@
20
20
  */
21
21
 
22
22
  import { THREE, MapControls } from './world-vendor.js';
23
- import { DISTRICT_STREETS, districtGroundPlan, districtStage, districtStreetLots, skyAt } from './world-placement.js';
23
+ import { DISTRICT_STREETS, districtTaskActors, districtGroundPlan, districtStage, districtStreetLots, skyAt } from './world-placement.js';
24
24
  import { canvasTexture, drawFacade, FACADE_SIZES, FACADE_UNITS, gableRoof, hashUnit, paintVertices, roundedPlate, valueNoise } from './world-scenery.js';
25
25
  import { makeWorkspaceElement as el } from '../../workspace-kit.js';
26
26
  import { attentionSignText } from './world-exits.js';
@@ -46,7 +46,7 @@ export const DISTRICT_LEGEND = Object.freeze([
46
46
  ['is-stakes', '杭と縄の空き地=未着手(記録上)'],
47
47
  ['is-house', '明かりのついた家=完了して、出典・成果の記録がある(本設の建物)'],
48
48
  ['is-prefab', 'プレハブ=完了したが、出典・成果の記録が無い'],
49
- ['is-worker', 'ヘルメットの人=担当欄の担当'],
49
+ ['is-worker', 'ヘルメットの人=記録された担当。緑はAI担当、動きは進行中Taskと現在の稼働証跡が一致したときだけ'],
50
50
  ['is-empty', '誰もいない現場=担当の記録なし'],
51
51
  ['is-outside', '柵の外の人(破線の輪)=本文にだけ名前がある'],
52
52
  ['is-nopath', '通りへの道が無い=出典リンクなし'],
@@ -397,6 +397,7 @@ export function createDistrictView({ doc, stage, reducedMotion = false, focusSel
397
397
  }
398
398
 
399
399
  // --- people (records: worker, person named in the text; atmosphere: walkers) ---------------------
400
+ const taskActors = [];
400
401
  function person(clothes, { helmet = false } = {}) {
401
402
  const group = new THREE.Group();
402
403
  group.add(mesh(new THREE.CapsuleGeometry(0.2, 0.46, 4, 10), material(clothes), { y: 0.5 }));
@@ -527,6 +528,12 @@ export function createDistrictView({ doc, stage, reducedMotion = false, focusSel
527
528
  worker.position.set(0.55, 0.06, 0.55);
528
529
  group.add(worker);
529
530
  }
531
+ districtTaskActors(site).forEach((actor, index) => {
532
+ const worker = person(0x2f9c8b, { helmet: true });
533
+ worker.position.set(-0.5 + index * 0.55, 0.06, 0.35);
534
+ group.add(worker);
535
+ taskActors.push({ worker, x: worker.position.x, moving: actor.moving, heartbeatAt: actor.activity?.heartbeat_at, phase: hash(actor.id + site.task_id) });
536
+ });
530
537
  if (named && status !== 'completed') {
531
538
  const outside = person(0x6b5b95);
532
539
  outside.position.set(-0.95, 0, half + 0.65);
@@ -682,6 +689,7 @@ export function createDistrictView({ doc, stage, reducedMotion = false, focusSel
682
689
  scene.add(root);
683
690
  root.add(selectionRing);
684
691
  const readable = work?.status === 'ok' && (work.reads.tasks.state === 'complete' || work.reads.tasks.state === 'partial');
692
+ taskActors.length = 0;
685
693
  const shown = readable ? work.sites.filter((site) => site.work.status !== 'cancelled') : [];
686
694
  const layout = districtStreetLots(shown.map((site) => ({ task_id: site.task_id, created_at: site.work.created_at, purpose_label: site.purpose_label })));
687
695
  const L = layout.length;
@@ -850,7 +858,8 @@ export function createDistrictView({ doc, stage, reducedMotion = false, focusSel
850
858
  root.add(group);
851
859
  lots.set(site.task_id, group);
852
860
  const title = site.title.length > 15 ? `${site.title.slice(0, 14)}…` : site.title;
853
- addLabel(title, new THREE.Vector3(lot.x, 2.6, lot.z), `is-site${site.gaps.length ? ' is-check' : ''}`, { owner: group, near: 15, priority: 6 });
861
+ const actorNames = districtTaskActors(site).map(actor => actor.name).join('・');
862
+ addLabel(actorNames ? `${title} / ${actorNames}` : title, new THREE.Vector3(lot.x, 2.6, lot.z), `is-site${site.gaps.length ? ' is-check' : ''}`, { owner: group, near: 15, priority: 6 });
854
863
  }
855
864
  // Atmosphere: walkers on the sidewalks and back streets, smoke from chimneys (no record behind them).
856
865
  const routes = [-1.55, 1.55, -DISTRICT_STREETS.backStreet + 0.6, DISTRICT_STREETS.backStreet - 0.6];
@@ -1074,6 +1083,12 @@ export function createDistrictView({ doc, stage, reducedMotion = false, focusSel
1074
1083
  }
1075
1084
  if (!reducedMotion) {
1076
1085
  const seconds = time / 1000;
1086
+ for (const actor of taskActors) {
1087
+ const age = Date.now() - Date.parse(actor.heartbeatAt ?? '');
1088
+ if (!actor.moving || !Number.isFinite(age) || age < 0 || age > 300000) continue;
1089
+ actor.worker.position.set(actor.x + Math.sin(seconds * 1.4 + actor.phase) * 0.12, 0.06 + Math.abs(Math.sin(seconds * 3 + actor.phase)) * 0.025, 0.35);
1090
+ actor.worker.rotation.y = Math.sin(seconds * 1.4 + actor.phase) * 0.3;
1091
+ }
1077
1092
  for (const walker of walkers) {
1078
1093
  const { x, from, to, speed, phase } = walker.userData;
1079
1094
  const span = to - from;
@@ -313,3 +313,16 @@ export function districtStage(sites) {
313
313
  next: following ? { label: following.label, needed: following.min - permanent } : null,
314
314
  };
315
315
  }
316
+
317
+ /** Optional host-supplied actors. Movement needs current host-confirmed runtime evidence and an in-progress Task. */
318
+ export function districtTaskActors(site, now = Date.now()) {
319
+ if (site?.actors_state !== 'confirmed' || !Array.isArray(site.actors)) return [];
320
+ const seen = new Set();
321
+ return site.actors.filter(actor => {
322
+ if (!actor || typeof actor.id !== 'string' || !actor.id || typeof actor.name !== 'string' || !actor.name || actor.task_id !== site.task_id || actor.state !== site.work?.status || seen.has(actor.id)) return false;
323
+ seen.add(actor.id); return true;
324
+ }).map(actor => {
325
+ const age = now - Date.parse(actor.activity?.heartbeat_at ?? '');
326
+ return { ...actor, moving: site.work.status === 'in_progress' && actor.activity?.moving === true && actor.activity?.state === 'running' && Number.isFinite(age) && age >= 0 && age <= 300000 };
327
+ });
328
+ }
@@ -8,6 +8,8 @@
8
8
  * different marks. Nothing here writes; corrections go to the host's own task screen.
9
9
  */
10
10
 
11
+ import { districtTaskActors } from './world-placement.js';
12
+
11
13
  import {
12
14
  makeWorkspaceElement as el,
13
15
  workspaceRailBlock,
@@ -417,10 +419,14 @@ export function workSiteBlocks(doc, { business, site, readAt, taskHref }) {
417
419
  const how = person.link === 'recorded' ? '担当欄' : `${person.stated_as_assignee ? '本文が担当として記載' : '本文に記載'}(${person.basis === 'text_person_id' ? '人物IDつき' : '氏名の一致'})・担当欄には未接続`;
418
420
  people.append(linkLine(doc, person.link, person.name, how));
419
421
  }
422
+ for (const actor of districtTaskActors(site)) {
423
+ people.append(linkLine(doc, 'recorded', actor.name, `${actor.kind === 'agent' ? 'AI担当' : '担当'}・Taskの記録上の状態: ${site.work.label ?? site.work.status}・実行: ${actor.moving ? '稼働証跡確認済み' : '未確認または停止'}`));
424
+ }
425
+ if (site.actors_state === 'unconfirmed') people.append(linkLine(doc, 'unreadable', 'AI担当未確認', '現在の登録とTask委任を確認できませんでした'));
420
426
  for (const mention of site.ambiguous_mentions ?? []) {
421
427
  people.append(linkLine(doc, 'inferred', `「${mention.text}」`, `組織のGraphの${mention.candidates.map((candidate) => candidate.name).join('・')}のどれか一意に決まりません`));
422
428
  }
423
- blocks.push(workspaceRailBlock(doc, { title: '担当・関係者', content: site.people.length || site.ambiguous_mentions?.length ? people : { text: '担当欄も本文も、登録された人物を挙げていません。' } }));
429
+ blocks.push(workspaceRailBlock(doc, { title: '担当・関係者', content: site.people.length || site.ambiguous_mentions?.length || districtTaskActors(site).length || site.actors_state === 'unconfirmed' ? people : { text: '担当欄も本文も、登録された人物を挙げていません。' } }));
424
430
  const refs = el(doc, 'ul', { className: 'bb-world-rail-list' });
425
431
  for (const ref of site.source_refs) {
426
432
  const li = el(doc, 'li');