@khorsheed/dsh-room 0.2.2 → 0.2.4

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 (109) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/README.en.md +29 -62
  3. package/README.i18n.yaml +2 -2
  4. package/README.md +28 -61
  5. package/lib/client.js +14620 -5705
  6. package/lib/index.js +4069 -730
  7. package/lib/journal-B4FOGO6S.js +482 -0
  8. package/lib/journal-Rid9AAnD.js +485 -0
  9. package/lib/tool.js +112 -166
  10. package/lib/typert.host.js +2031 -374
  11. package/lib/typert.remote-client.d.ts +34 -10
  12. package/lib/typert.remote-client.js +1599 -210
  13. package/lib/types/agent-setup.d.ts +1 -1
  14. package/lib/types/client/InviteAgentAction.js +4 -16
  15. package/lib/types/client/InviteDialog.d.ts +28 -6
  16. package/lib/types/client/InviteDialog.js +76 -12
  17. package/lib/types/client/MemberCard.d.ts +5 -1
  18. package/lib/types/client/MemberCard.js +4 -4
  19. package/lib/types/client/MembersView.d.ts +1 -1
  20. package/lib/types/client/MembersView.js +58 -22
  21. package/lib/types/client/RemoveMemberConfirm.d.ts +40 -0
  22. package/lib/types/client/RemoveMemberConfirm.js +85 -0
  23. package/lib/types/client/RoomApprovalView.d.ts +11 -0
  24. package/lib/types/client/RoomApprovalView.js +28 -0
  25. package/lib/types/client/RoomComposer.d.ts +1 -1
  26. package/lib/types/client/RoomComposer.js +195 -29
  27. package/lib/types/client/RoomDecisionView.d.ts +22 -0
  28. package/lib/types/client/RoomDecisionView.js +35 -0
  29. package/lib/types/client/RoomHelpView.d.ts +17 -0
  30. package/lib/types/client/RoomHelpView.js +56 -0
  31. package/lib/types/client/RoomInboxView.d.ts +10 -0
  32. package/lib/types/client/RoomInboxView.js +14 -0
  33. package/lib/types/client/RoomQuestionView.d.ts +13 -0
  34. package/lib/types/client/RoomQuestionView.js +61 -0
  35. package/lib/types/client/RoomRelayView.js +1 -1
  36. package/lib/types/client/RoomRunView.d.ts +3 -1
  37. package/lib/types/client/RoomRunView.js +12 -5
  38. package/lib/types/client/RoomSettingsView.d.ts +19 -0
  39. package/lib/types/client/RoomSettingsView.js +30 -0
  40. package/lib/types/client/RoomSpeechView.d.ts +22 -8
  41. package/lib/types/client/RoomSpeechView.js +94 -11
  42. package/lib/types/client/RoomStatsLine.d.ts +26 -5
  43. package/lib/types/client/RoomStatsLine.js +53 -7
  44. package/lib/types/client/RoomTaskCapsule.d.ts +19 -0
  45. package/lib/types/client/RoomTaskCapsule.js +37 -0
  46. package/lib/types/client/TasksTab.d.ts +41 -0
  47. package/lib/types/client/TasksTab.js +43 -0
  48. package/lib/types/client/TasksView.d.ts +44 -0
  49. package/lib/types/client/TasksView.js +149 -0
  50. package/lib/types/client/TeamView.d.ts +33 -0
  51. package/lib/types/client/TeamView.js +210 -0
  52. package/lib/types/client/at-menu.d.ts +127 -0
  53. package/lib/types/client/at-menu.js +173 -0
  54. package/lib/types/client/draft-chips.d.ts +67 -0
  55. package/lib/types/client/draft-chips.js +95 -0
  56. package/lib/types/client/executions.d.ts +0 -30
  57. package/lib/types/client/executions.js +4 -34
  58. package/lib/types/client/icons-local.d.ts +25 -0
  59. package/lib/types/client/icons-local.js +10 -0
  60. package/lib/types/client/icons.d.ts +2 -1
  61. package/lib/types/client/icons.js +1 -0
  62. package/lib/types/client/index.js +276 -72
  63. package/lib/types/client/locales.d.ts +482 -52
  64. package/lib/types/client/locales.js +490 -60
  65. package/lib/types/client/nodes.d.ts +112 -17
  66. package/lib/types/client/nodes.js +197 -26
  67. package/lib/types/client/slots.d.ts +117 -47
  68. package/lib/types/client/task-tag.d.ts +32 -0
  69. package/lib/types/client/task-tag.js +33 -0
  70. package/lib/types/client/tasks.d.ts +50 -0
  71. package/lib/types/client/tasks.js +82 -0
  72. package/lib/types/client/team.d.ts +73 -0
  73. package/lib/types/client/team.js +187 -0
  74. package/lib/types/decisions.d.ts +49 -0
  75. package/lib/types/decisions.js +101 -0
  76. package/lib/types/deliveries.d.ts +17 -1
  77. package/lib/types/deliveries.js +82 -6
  78. package/lib/types/dispatch.d.ts +102 -4
  79. package/lib/types/dispatch.js +498 -109
  80. package/lib/types/help.d.ts +137 -0
  81. package/lib/types/help.js +216 -0
  82. package/lib/types/helps.d.ts +26 -0
  83. package/lib/types/helps.js +47 -0
  84. package/lib/types/index.d.ts +292 -52
  85. package/lib/types/index.js +1421 -199
  86. package/lib/types/jobs.d.ts +22 -0
  87. package/lib/types/jobs.js +77 -0
  88. package/lib/types/journal.d.ts +17 -27
  89. package/lib/types/journal.js +120 -43
  90. package/lib/types/messaging.d.ts +65 -0
  91. package/lib/types/messaging.js +160 -0
  92. package/lib/types/plan-service.js +7 -1
  93. package/lib/types/report-gate.d.ts +81 -0
  94. package/lib/types/report-gate.js +89 -0
  95. package/lib/types/tool-definition.d.ts +43 -20
  96. package/lib/types/tool-definition.js +145 -212
  97. package/lib/types/tool.d.ts +2 -2
  98. package/lib/types/tool.js +3 -2
  99. package/lib/types/types.d.ts +755 -78
  100. package/locale/zh.json +1 -1
  101. package/package.json +3 -3
  102. package/lib/types/client/RoomActivityDock.d.ts +0 -19
  103. package/lib/types/client/RoomActivityDock.js +0 -105
  104. package/lib/types/client/RoomDockCapsules.d.ts +0 -64
  105. package/lib/types/client/RoomDockCapsules.js +0 -277
  106. package/lib/types/client/RoomPlanTab.d.ts +0 -15
  107. package/lib/types/client/RoomPlanTab.js +0 -13
  108. package/lib/types/client/RoomTaskLineView.d.ts +0 -13
  109. package/lib/types/client/RoomTaskLineView.js +0 -21
@@ -1,18 +1,25 @@
1
1
  import roomRemote from '@khorsheed/dsh-room/remote';
2
+ import { presetDisplayText } from '@deepseek-ai/dsh-agent-preset-registry/display';
2
3
  import { RoomRequestIds } from './request-ids.js';
3
4
  import { en, zh } from './locales.js';
4
5
  import { InviteAgentAction } from './InviteAgentAction.js';
5
6
  import { MembersView } from './MembersView.js';
6
7
  import { RoomComposer, selectRoomComposer } from './RoomComposer.js';
7
- import { ROOM_PLAN_KIND, ROOM_PLAN_TAB_ID, RoomPlanTab } from './RoomPlanTab.js';
8
+ import { ROOM_TASKS_KIND, ROOM_TASKS_TAB_ID, RoomTasksTab } from './TasksTab.js';
9
+ import { RoomTaskTags } from './task-tag.js';
8
10
  import { RoomSpeechView } from './RoomSpeechView.js';
9
11
  import { RoomRunView } from './RoomRunView.js';
10
12
  import { RoomEventView } from './RoomEventView.js';
11
- import { roomEventDefinition, roomRelayDefinition, roomRunDefinition, roomSpeechDefinition, roomTaskLineDefinition } from './nodes.js';
13
+ import { roomApprovalDefinition, roomDecisionDefinition, roomHelpDefinition, roomHelpInDefinition, roomHelpRequestDefinition, roomQuestionDefinition, roomEventDefinition, roomRelayDefinition, roomInboxDefinition, roomRunDefinition, roomSpeechDefinition } from './nodes.js';
12
14
  import { mainSessionId, RoomStore } from './room-store.js';
13
15
  import { RoomPresetVisibility, RegistrationToggle } from './preset-visibility.js';
14
16
  import { RoomRelayView } from './RoomRelayView.js';
15
- import { RoomTaskLineView } from './RoomTaskLineView.js';
17
+ import { RoomInboxView } from './RoomInboxView.js';
18
+ import { RoomApprovalView } from './RoomApprovalView.js';
19
+ import { RoomQuestionView } from './RoomQuestionView.js';
20
+ import { RoomDecisionView } from './RoomDecisionView.js';
21
+ import { RoomHelpRequestView, RoomHelpView } from './RoomHelpView.js';
22
+ import { sessionTitle } from './at-menu.js';
16
23
  /** Dictionary namespace owned by this plugin. */
17
24
  const NS = 'room';
18
25
  /**
@@ -47,6 +54,13 @@ export async function apply(ctx) {
47
54
  const remote = ctx.get('remote.room');
48
55
  ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'room: dictionaries');
49
56
  const t = ctx.locale.bind(NS);
57
+ // The host's agent-preset dictionary (ui-agent-preset) carries the shipped
58
+ // modes' names; an unregistered key reads back as itself — fall back to the id.
59
+ const hostPresetCopy = ctx.locale.bind('settings.agentPreset');
60
+ const presetCopy = (key) => {
61
+ const text = hostPresetCopy(key);
62
+ return text === key ? '' : text;
63
+ };
50
64
  const roomStore = new RoomStore(ctx, remote);
51
65
  ctx.effect(() => roomStore.start(), 'room: store');
52
66
  // The M3' self-hide criterion for room's session chrome (the invite chip
@@ -54,12 +68,13 @@ export async function apply(ctx) {
54
68
  // RoomStore's cached verdict as the actual-room escape.
55
69
  const roomChrome = new RoomPresetVisibility(ctx, sessionId => roomStore.isRoomCached(sessionId) === true);
56
70
  const requestIds = new RoomRequestIds();
57
- const submit = async (sessionId, text, targets) => {
71
+ const submit = async (sessionId, text, targets, task) => {
58
72
  if (remote === undefined)
59
73
  return { ok: false, message: t('composer.error.generic') };
60
74
  const requestId = requestIds.forInput(sessionId, text, targets);
61
75
  const carried = await remote.postMessage({
62
76
  sessionId, text, requestId, ...targets === undefined || targets.length === 0 ? {} : { targets },
77
+ ...task === undefined ? {} : { task },
63
78
  });
64
79
  if (!carried.ok)
65
80
  return { ok: false, message: t('composer.error.generic') };
@@ -137,31 +152,9 @@ export async function apply(ctx) {
137
152
  void roomStore.refresh(sessionId);
138
153
  },
139
154
  });
140
- /** The task-board actions (sessionId binds per entry inject). */
141
- const tasksFace = (sessionId) => ({
155
+ /** The goal action (sessionId binds per entry inject). */
156
+ const goalFace = (sessionId) => ({
142
157
  roomStore,
143
- addTask: async (member, title, blockedBy) => {
144
- if (remote === undefined)
145
- return { ok: false, message: t('invite.error.generic') };
146
- const carried = await remote.addTask({
147
- sessionId, member, title, ...blockedBy === undefined ? {} : { blockedBy },
148
- });
149
- if (!carried.ok || !carried.value.ok)
150
- return { ok: false, message: t('invite.error.generic') };
151
- void roomStore.refresh(sessionId);
152
- return { ok: true };
153
- },
154
- closeTask: async (taskId, status) => {
155
- if (remote === undefined)
156
- return { ok: false, message: t('invite.error.generic') };
157
- const carried = await remote.closeTask({
158
- sessionId, taskId, ...status === undefined ? {} : { status },
159
- });
160
- if (!carried.ok || !carried.value.ok)
161
- return { ok: false, message: t('invite.error.generic') };
162
- void roomStore.refresh(sessionId);
163
- return { ok: true };
164
- },
165
158
  setGoal: async (text) => {
166
159
  if (remote === undefined)
167
160
  return { ok: false, message: t('invite.error.generic') };
@@ -172,6 +165,7 @@ export async function apply(ctx) {
172
165
  return { ok: true };
173
166
  },
174
167
  });
168
+ const taskTags = new RoomTaskTags();
175
169
  /** Map a structured RoomFailure to the dialog's localized copy. */
176
170
  const failureText = (error) => {
177
171
  switch (error.code) {
@@ -184,6 +178,7 @@ export async function apply(ctx) {
184
178
  case 'configuration-owned-by-core': return t('coordinator.configuration');
185
179
  case 'duplicate-name': return t('invite.error.duplicate');
186
180
  case 'invalid-name': return t('invite.error.invalid');
181
+ case 'role-too-long': return t('invite.error.roleTooLong');
187
182
  case 'local-agent-unavailable': return t('invite.error.unavailable');
188
183
  case 'unknown-provider':
189
184
  return t('invite.error.unknownProvider', {
@@ -218,11 +213,22 @@ export async function apply(ctx) {
218
213
  void roomStore.refresh(sessionId);
219
214
  return { ok: true, pendingFirstTask: carried.value.value.pendingFirstTask };
220
215
  },
221
- listProviders: async () => {
216
+ listProviders: async (sessionId) => {
222
217
  if (remote === undefined)
223
218
  return undefined;
224
- const carried = await remote.listProviders({});
225
- return carried.ok ? carried.value : undefined;
219
+ const carried = await remote.listProviders(sessionId === undefined ? {} : { sessionId });
220
+ if (!carried.ok)
221
+ return undefined;
222
+ // Mode names as the host's own mode selector shows them: shipped modes
223
+ // (标准模式, PTC 模式 …) from the host's dictionary, others as declared.
224
+ const value = carried.value;
225
+ return value.presets === undefined ? value : {
226
+ ...value,
227
+ presets: value.presets.map((row) => {
228
+ const shown = presetDisplayText(row, presetCopy);
229
+ return { ...row, name: shown.name || row.name || row.id, ...shown.description ? { description: shown.description } : {} };
230
+ }),
231
+ };
226
232
  },
227
233
  browseDirectory: () => {
228
234
  // Host 0.1.2 hangs the native picker on ui-workspace's service (the
@@ -250,8 +256,20 @@ export async function apply(ctx) {
250
256
  },
251
257
  roomChrome,
252
258
  });
259
+ // The team page lives in the right sidebar; without it the members tab
260
+ // simply shows no 团队 entry.
261
+ const teamSidebar = ctx.get('sidebarRightTabs') === undefined ? undefined : ctx.get('sidebarRight');
262
+ // Outside sessions on the member list (「加入房间成员」) and the third ask's hint.
263
+ const linkFace = (sessionId) => ({
264
+ linkSession: (target, title) => { void remote?.linkSession({ sessionId, target, title }).then(() => roomStore.refresh(sessionId)); },
265
+ unlinkSession: (target) => { void remote?.unlinkSession({ sessionId, target }).then(() => roomStore.refresh(sessionId)); },
266
+ dismissNudge: (target) => { void remote?.dismissHelpNudge({ sessionId, target }).then(() => roomStore.refresh(sessionId)); },
267
+ });
253
268
  const membersFace = (sessionId) => ({
254
269
  roomStore,
270
+ unlinkSession: linkFace(sessionId).unlinkSession,
271
+ // 团队 is the collaboration page's second view (one sidebar page, two switches).
272
+ ...teamSidebar === undefined ? {} : { openTeam: (member) => { teamSidebar.openTab(ROOM_TASKS_KIND, { params: { view: 'team', ...member === undefined ? {} : { member } } }); } },
255
273
  setCoordinator: async (memberId, expectedRevision) => {
256
274
  if (remote === undefined)
257
275
  return { ok: false, message: t('invite.error.generic') };
@@ -304,6 +322,28 @@ export async function apply(ctx) {
304
322
  if (!carried.value.ok)
305
323
  return { ok: false, message: failureText(carried.value.error) };
306
324
  void roomStore.refresh(sessionId);
325
+ return { ok: true, value: carried.value.value };
326
+ },
327
+ setSettings: async (patch) => {
328
+ if (remote === undefined)
329
+ return { ok: false, message: t('invite.error.generic') };
330
+ const carried = await remote.setSettings({ sessionId, ...patch });
331
+ if (!carried.ok)
332
+ return { ok: false, message: t('invite.error.generic') };
333
+ if (!carried.value.ok)
334
+ return { ok: false, message: failureText(carried.value.error) };
335
+ void roomStore.refresh(sessionId);
336
+ return { ok: true };
337
+ },
338
+ restoreMember: async (member) => {
339
+ if (remote === undefined)
340
+ return { ok: false, message: t('invite.error.generic') };
341
+ const carried = await remote.restoreMember({ sessionId, name: member });
342
+ if (!carried.ok)
343
+ return { ok: false, message: t('invite.error.generic') };
344
+ if (!carried.value.ok)
345
+ return { ok: false, message: failureText(carried.value.error) };
346
+ void roomStore.refresh(sessionId);
307
347
  return { ok: true };
308
348
  },
309
349
  updateMember: async (member, patch) => {
@@ -314,8 +354,10 @@ export async function apply(ctx) {
314
354
  name: member,
315
355
  ...patch.rename === undefined ? {} : { rename: patch.rename },
316
356
  ...patch.instructions === undefined ? {} : { instructions: patch.instructions },
357
+ ...patch.role === undefined ? {} : { role: patch.role },
317
358
  ...patch.cwd === undefined ? {} : { cwd: patch.cwd },
318
359
  ...patch.model === undefined ? {} : { model: patch.model },
360
+ ...patch.effort === undefined ? {} : { effort: patch.effort },
319
361
  });
320
362
  if (!carried.ok)
321
363
  return { ok: false, message: t('invite.error.generic') };
@@ -334,7 +376,13 @@ export async function apply(ctx) {
334
376
  lctx.uiConversation.events.register(roomRunDefinition);
335
377
  lctx.uiConversation.events.register(roomEventDefinition);
336
378
  lctx.uiConversation.events.register(roomRelayDefinition);
337
- lctx.uiConversation.events.register(roomTaskLineDefinition);
379
+ lctx.uiConversation.events.register(roomInboxDefinition);
380
+ lctx.uiConversation.events.register(roomApprovalDefinition);
381
+ lctx.uiConversation.events.register(roomQuestionDefinition);
382
+ lctx.uiConversation.events.register(roomDecisionDefinition);
383
+ lctx.uiConversation.events.register(roomHelpDefinition);
384
+ lctx.uiConversation.events.register(roomHelpRequestDefinition);
385
+ lctx.uiConversation.events.register(roomHelpInDefinition);
338
386
  });
339
387
  // The slots are declared by ui-conversation, whose apply order
340
388
  // relative to this plugin is unconstrained: register through slots.inject so
@@ -361,48 +409,134 @@ export async function apply(ctx) {
361
409
  // main-agent turn Stop: the takeover renders the dock
362
410
  // surfaces and the Stop button itself, because their seats hide with the
363
411
  // official fallback.
412
+ const reconcileFor = (sessionId) => async (deliveryId, outcome, evidence) => {
413
+ const result = await remote?.reconcileDelivery({ sessionId, deliveryId, outcome, evidence });
414
+ await roomStore.refresh(sessionId);
415
+ return result?.ok && result.value.ok ? { ok: true } : { ok: false, message: result?.ok && !result.value.ok ? result.value.error.code : t('composer.error.generic') };
416
+ };
417
+ /** The task page's actions (sessionId binds per entry inject). */
418
+ const tasksFace = (sessionId) => ({
419
+ ...goalFace(sessionId),
420
+ linkSession: linkFace(sessionId).linkSession,
421
+ unlinkSession: linkFace(sessionId).unlinkSession,
422
+ taskTags,
423
+ stopJob: async (task) => {
424
+ const carried = await remote?.stopJob({ sessionId, task });
425
+ await roomStore.refresh(sessionId);
426
+ return carried?.ok && carried.value.ok ? { ok: true } : { ok: false, message: t('composer.error.generic') };
427
+ },
428
+ stopDelivery: (member, deliveryId) => {
429
+ void remote?.cancel({ sessionId, name: member, ...deliveryId === undefined ? {} : { expectedRunId: deliveryId } }).then(() => roomStore.refresh(sessionId));
430
+ },
431
+ reconcile: reconcileFor(sessionId),
432
+ confirmRelay: relayFace(sessionId).confirmRelay,
433
+ dismissRelay: relayFace(sessionId).dismissRelay,
434
+ planCommand: async (command) => {
435
+ const result = await remote?.planCommand({ sessionId, command });
436
+ await roomStore.refresh(sessionId);
437
+ return result?.ok && result.value.ok ? { ok: true } : { ok: false, message: result?.ok && !result.value.ok ? result.value.message : t('composer.error.generic') };
438
+ },
439
+ openSession: id => openSession(id),
440
+ });
364
441
  const sidebar = ctx.get('sidebarRight');
365
442
  const sidebarTabs = ctx.get('sidebarRightTabs');
443
+ // The @ menu's other sessions: the host's session-reference candidates
444
+ // (the recent list merged with the query's own lookup), each flagged when
445
+ // it is a room (probed once per session and remembered).
446
+ const roomFlags = new Map();
447
+ const roomFlag = (id) => {
448
+ let flag = roomFlags.get(id);
449
+ if (flag === undefined) {
450
+ flag = (remote?.isRoom({ sessionId: id }) ?? Promise.resolve(undefined))
451
+ .then(carried => carried?.ok === true && carried.value === true)
452
+ .catch(() => false);
453
+ roomFlags.set(id, flag);
454
+ }
455
+ return flag;
456
+ };
457
+ const memberLists = new Map();
458
+ const roomMembers = (id) => {
459
+ const cached = memberLists.get(id);
460
+ if (cached !== undefined && Date.now() - cached.at < 30_000)
461
+ return cached.members;
462
+ const members = (remote?.getState({ sessionId: id }) ?? Promise.resolve(undefined))
463
+ .then(carried => carried?.ok === true && carried.value.ok
464
+ ? carried.value.value.members.map(member => ({ name: member.name, kind: member.kind, ...member.provider === undefined ? {} : { provider: member.provider } }))
465
+ : [])
466
+ .catch(() => []);
467
+ memberLists.set(id, { at: Date.now(), members });
468
+ return members;
469
+ };
470
+ const listSessionsFor = async (sessionId, query, signal) => {
471
+ // A Remote call resolves to a RemoteResult ({ ok, value }), never the bare list.
472
+ const resolver = ctx.get('remote.sessionReferenceResolver');
473
+ if (resolver?.candidates === undefined)
474
+ return [];
475
+ const lookup = (text) => resolver.candidates(sessionId, text, signal)
476
+ .then(result => result.ok && Array.isArray(result.value) ? result.value : []);
477
+ const [recent, matched] = await Promise.all([lookup(''), query === '' ? Promise.resolve([]) : lookup(query)]);
478
+ // Archived sessions cannot be opened or woken: never offer one.
479
+ const workspaces = ctx.get('workspaces');
480
+ const archived = new Set(workspaces?.list?.getSnapshot?.().archivedSessionIds ?? []);
481
+ const merged = [...recent, ...matched].filter(candidate => !archived.has(candidate.sessionId));
482
+ const flags = await Promise.all(merged.map(candidate => roomFlag(candidate.sessionId)));
483
+ // A room carries its members (askable by name): one getState per room, kept 30s.
484
+ return Promise.all(merged.map(async (candidate, index) => flags[index] === true
485
+ ? { ...candidate, room: true, members: await roomMembers(candidate.sessionId) }
486
+ : candidate));
487
+ };
366
488
  const composerEntry = {
367
489
  name: 'conversation.composer',
368
490
  priority: -10,
369
491
  locale: NS,
370
492
  select: (owner) => selectRoomComposer(owner, id => roomStore.isRoomCached(id) === true),
371
493
  inject: (sessionId) => ({
372
- ...tasksFace(sessionId),
494
+ ...goalFace(sessionId),
373
495
  ...inviteFace(sessionId),
374
- reconcileDelivery: async (deliveryId, outcome, evidence) => {
375
- const result = await remote?.reconcileDelivery({ sessionId, deliveryId, outcome, evidence });
376
- await roomStore.refresh(sessionId);
377
- return result?.ok && result.value.ok ? { ok: true } : { ok: false, message: result?.ok && !result.value.ok ? result.value.error.code : t('composer.error.generic') };
378
- },
379
- planCommand: async (command) => {
380
- const result = await remote?.planCommand({ sessionId, command });
381
- await roomStore.refresh(sessionId);
382
- return result?.ok && result.value.ok ? { ok: true } : { ok: false, message: result?.ok && !result.value.ok ? result.value.message : t('composer.error.generic') };
383
- },
496
+ reconcileDelivery: reconcileFor(sessionId),
384
497
  openPlanSession: id => openSession(id),
385
- backgroundSessions: ctx.sessions.list,
386
- activeChildren: async () => {
387
- const gateway = ctx.get('remote.localAgentGateway');
388
- const result = await gateway?.activeDelegations?.();
389
- return result?.ok ? result.value : undefined;
390
- },
391
- stopChild: async (childSessionId) => {
392
- const result = await remote?.cancelChild({ sessionId, childSessionId: childSessionId });
393
- return result?.ok && result.value.ok && result.value.value.cancelled ? { ok: true } : { ok: false, message: t('activity.stopChanged') };
394
- },
395
- ...sidebar === undefined || sidebarTabs === undefined ? {} : { openPlan: () => { sidebar.openTab(ROOM_PLAN_KIND, { params: {} }); } },
396
- stopExecution: async (name, run) => {
397
- const result = await remote?.cancel({ sessionId, name, expectedStartedAt: run.startedAt, ...run.runId === undefined ? {} : { expectedRunId: run.runId } });
398
- await roomStore.refresh(sessionId);
399
- return result?.ok && result.value.ok && result.value.value.cancelled ? { ok: true } : { ok: false, message: t('activity.stopChanged') };
498
+ ...sidebar === undefined || sidebarTabs === undefined ? {} : {
499
+ openTasks: (task) => { sidebar.openTab(ROOM_TASKS_KIND, { params: { view: 'tasks', ...task === undefined ? {} : { task } } }); },
400
500
  },
501
+ taskTags,
401
502
  submit,
402
503
  renderMemberConfiguration: ctx.get('localAgentUi')?.renderMemberConfiguration,
403
504
  renderMemberInbox: ctx.get('localAgentUi')?.renderMemberInbox,
404
505
  stopMember: (name, expectedRunId) => { void remote?.cancel({ sessionId, name, ...expectedRunId === undefined ? {} : { expectedRunId } }).then(() => roomStore.refresh(sessionId)); },
405
506
  modelDirectory: modelDirectoryFor(sessionId),
507
+ // A draft chip's preview: the official editor's chip click routes
508
+ // through the session's input-trigger controller to the chip's owning
509
+ // source — probed at click time, so a composition without the service
510
+ // (or a torn-down scope) just declines.
511
+ ...ctx.get('remote.sessionReferenceResolver') === undefined ? {} : {
512
+ listSessions: (query, signal) => listSessionsFor(sessionId, query, signal),
513
+ insertSessionReference: (candidate, span) => {
514
+ try {
515
+ const scope = ctx.sessions.scope(sessionId);
516
+ if (scope?.bail === undefined)
517
+ return false;
518
+ return scope.bail(scope, 'slash/input-insert-reference', {
519
+ reference: { source: 'reference', ref: candidate.mention, label: sessionTitle(candidate), appearance: 'session', clipboardText: candidate.mention },
520
+ span,
521
+ }) === true;
522
+ }
523
+ catch {
524
+ return false;
525
+ }
526
+ },
527
+ },
528
+ openReference: (source, ref) => {
529
+ try {
530
+ const scope = ctx.sessions.scope(sessionId);
531
+ const triggers = ctx.get('inputTriggers');
532
+ if (scope === undefined || triggers === undefined)
533
+ return false;
534
+ return triggers.sessionOf(scope).openReference(source, { ref });
535
+ }
536
+ catch {
537
+ return false;
538
+ }
539
+ },
406
540
  // The hidden official bar's Stop: the runtime session face's cancel
407
541
  // (the same verb ui-conversation's own Stop injects). A torn-down
408
542
  // binding degrades to a no-op.
@@ -414,17 +548,14 @@ export async function apply(ctx) {
414
548
  }),
415
549
  };
416
550
  if (sidebar !== undefined && sidebarTabs !== undefined) {
417
- const planToggle = new RegistrationToggle(() => sidebarTabs.register({ id: ROOM_PLAN_TAB_ID, kind: ROOM_PLAN_KIND, title: () => t('activity.plan') }), () => roomChrome.show(mainSessionId(ctx.sessions.list.getSnapshot())));
418
- planToggle.setReady(true);
419
- const unsubscribe = roomChrome.subscribe(() => planToggle.sync());
420
- disposers.push(async () => { unsubscribe(); planToggle.setReady(false); });
551
+ const tasksToggle = new RegistrationToggle(() => sidebarTabs.register({ id: ROOM_TASKS_TAB_ID, kind: ROOM_TASKS_KIND, title: () => t('collab.title') }), () => roomChrome.show(mainSessionId(ctx.sessions.list.getSnapshot())));
552
+ tasksToggle.setReady(true);
553
+ const unsubscribe = roomChrome.subscribe(() => tasksToggle.sync());
554
+ disposers.push(async () => { unsubscribe(); tasksToggle.setReady(false); });
421
555
  ctx.slots.inject('sidebar.right.pane.tab', () => ctx.slots.register({
422
- name: 'sidebar.right.pane.tab', key: ROOM_PLAN_TAB_ID, locale: NS,
423
- inject: (sessionId) => {
424
- const { roomStore, planCommand, openPlanSession, stopMember } = composerEntry.inject(sessionId);
425
- return { roomStore, planCommand, openPlanSession, stopMember };
426
- },
427
- }, RoomPlanTab));
556
+ name: 'sidebar.right.pane.tab', key: ROOM_TASKS_TAB_ID, locale: NS,
557
+ inject: (sessionId) => tasksFace(sessionId),
558
+ }, RoomTasksTab));
428
559
  }
429
560
  let composerDispose;
430
561
  ctx.slots.inject('conversation.composer', () => {
@@ -470,7 +601,20 @@ export async function apply(ctx) {
470
601
  name: 'conversation.chat.node',
471
602
  key: 'room-speech',
472
603
  locale: NS,
473
- inject: () => ({ roomStore, openSession }),
604
+ inject: () => ({
605
+ roomStore,
606
+ openSession,
607
+ // Context Lens is another plugin: reach it through the sidebar by its
608
+ // tab kind, only when its namespace is mounted; a refused open is a no-op.
609
+ ...ctx.get('remote.requestInspector') === undefined || ctx.get('sidebarRight') === undefined ? {} : {
610
+ openDetail: (sessionId, messageId) => {
611
+ try {
612
+ ctx.get('sidebarRight').openTab('context-lens', { params: { sessionId, messageId } });
613
+ }
614
+ catch { /* the tab kind is hidden or absent */ }
615
+ },
616
+ },
617
+ }),
474
618
  }, RoomSpeechView));
475
619
  ctx.slots.inject('conversation.chat.node', () => ctx.slots.register({
476
620
  name: 'conversation.chat.node',
@@ -492,10 +636,70 @@ export async function apply(ctx) {
492
636
  }, RoomRelayView));
493
637
  ctx.slots.inject('conversation.chat.node', () => ctx.slots.register({
494
638
  name: 'conversation.chat.node',
495
- key: 'room-task-line',
639
+ key: 'room-inbox',
640
+ locale: NS,
641
+ inject: (sessionId) => ({
642
+ releaseReports: async (member) => {
643
+ if (remote === undefined)
644
+ return;
645
+ const carried = await remote.releaseReports({ sessionId, name: member });
646
+ if (carried.ok)
647
+ void roomStore.refresh(sessionId);
648
+ },
649
+ }),
650
+ }, RoomInboxView));
651
+ ctx.slots.inject('conversation.chat.node', () => ctx.slots.register({
652
+ name: 'conversation.chat.node',
653
+ key: 'room-approval',
654
+ locale: NS,
655
+ inject: (sessionId) => ({
656
+ answerApproval: async (approvalId, outcome) => {
657
+ if (remote === undefined)
658
+ return t('composer.error.generic');
659
+ const carried = await remote.answerApproval({ sessionId, id: approvalId, outcome });
660
+ void roomStore.refresh(sessionId);
661
+ if (!carried.ok)
662
+ return t('composer.error.generic');
663
+ return carried.value.ok ? undefined : t('approval.expired');
664
+ },
665
+ }),
666
+ }, RoomApprovalView));
667
+ ctx.slots.inject('conversation.chat.node', () => ctx.slots.register({
668
+ name: 'conversation.chat.node',
669
+ key: 'room-question',
496
670
  locale: NS,
497
- inject: () => ({ roomStore }),
498
- }, RoomTaskLineView));
671
+ inject: (sessionId) => ({
672
+ answerQuestion: async (questionId, answers) => {
673
+ if (remote === undefined)
674
+ return t('composer.error.generic');
675
+ const carried = await remote.answerQuestion({ sessionId, id: questionId, answers });
676
+ void roomStore.refresh(sessionId);
677
+ if (!carried.ok)
678
+ return t('composer.error.generic');
679
+ return carried.value.ok ? undefined : t('question.expired');
680
+ },
681
+ }),
682
+ }, RoomQuestionView));
683
+ ctx.slots.inject('conversation.chat.node', () => ctx.slots.register({
684
+ name: 'conversation.chat.node',
685
+ key: 'room-decision',
686
+ locale: NS,
687
+ inject: (sessionId) => ({
688
+ // Context Lens is another plugin: opened by its tab kind, only when mounted.
689
+ ...ctx.get('remote.requestInspector') === undefined || ctx.get('sidebarRight') === undefined ? {} : {
690
+ openDecision: (decisionId) => {
691
+ try {
692
+ ctx.get('sidebarRight').openTab('context-lens', { params: { sessionId, roomDecision: decisionId } });
693
+ }
694
+ catch { /* the tab kind is hidden or absent */ }
695
+ },
696
+ },
697
+ }),
698
+ }, RoomDecisionView));
699
+ // Cross-session help: the thread row in the asking room, the request card
700
+ // in the helper's session (any session — the card is how it gets there).
701
+ ctx.slots.inject('conversation.chat.node', () => ctx.slots.register({ name: 'conversation.chat.node', key: 'room-help', locale: NS, inject: (sessionId) => ({ openSession, roomStore, linkSession: linkFace(sessionId).linkSession, dismissNudge: linkFace(sessionId).dismissNudge }) }, RoomHelpView));
702
+ ctx.slots.inject('conversation.chat.node', () => ctx.slots.register({ name: 'conversation.chat.node', key: 'room-help-request', locale: NS, inject: () => ({ openSession }) }, RoomHelpRequestView));
499
703
  return async () => {
500
704
  for (const dispose of disposers.reverse())
501
705
  await dispose();