@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.
- package/CHANGELOG.md +58 -0
- package/README.en.md +29 -62
- package/README.i18n.yaml +2 -2
- package/README.md +28 -61
- package/lib/client.js +14620 -5705
- package/lib/index.js +4069 -730
- package/lib/journal-B4FOGO6S.js +482 -0
- package/lib/journal-Rid9AAnD.js +485 -0
- package/lib/tool.js +112 -166
- package/lib/typert.host.js +2031 -374
- package/lib/typert.remote-client.d.ts +34 -10
- package/lib/typert.remote-client.js +1599 -210
- package/lib/types/agent-setup.d.ts +1 -1
- package/lib/types/client/InviteAgentAction.js +4 -16
- package/lib/types/client/InviteDialog.d.ts +28 -6
- package/lib/types/client/InviteDialog.js +76 -12
- package/lib/types/client/MemberCard.d.ts +5 -1
- package/lib/types/client/MemberCard.js +4 -4
- package/lib/types/client/MembersView.d.ts +1 -1
- package/lib/types/client/MembersView.js +58 -22
- package/lib/types/client/RemoveMemberConfirm.d.ts +40 -0
- package/lib/types/client/RemoveMemberConfirm.js +85 -0
- package/lib/types/client/RoomApprovalView.d.ts +11 -0
- package/lib/types/client/RoomApprovalView.js +28 -0
- package/lib/types/client/RoomComposer.d.ts +1 -1
- package/lib/types/client/RoomComposer.js +195 -29
- package/lib/types/client/RoomDecisionView.d.ts +22 -0
- package/lib/types/client/RoomDecisionView.js +35 -0
- package/lib/types/client/RoomHelpView.d.ts +17 -0
- package/lib/types/client/RoomHelpView.js +56 -0
- package/lib/types/client/RoomInboxView.d.ts +10 -0
- package/lib/types/client/RoomInboxView.js +14 -0
- package/lib/types/client/RoomQuestionView.d.ts +13 -0
- package/lib/types/client/RoomQuestionView.js +61 -0
- package/lib/types/client/RoomRelayView.js +1 -1
- package/lib/types/client/RoomRunView.d.ts +3 -1
- package/lib/types/client/RoomRunView.js +12 -5
- package/lib/types/client/RoomSettingsView.d.ts +19 -0
- package/lib/types/client/RoomSettingsView.js +30 -0
- package/lib/types/client/RoomSpeechView.d.ts +22 -8
- package/lib/types/client/RoomSpeechView.js +94 -11
- package/lib/types/client/RoomStatsLine.d.ts +26 -5
- package/lib/types/client/RoomStatsLine.js +53 -7
- package/lib/types/client/RoomTaskCapsule.d.ts +19 -0
- package/lib/types/client/RoomTaskCapsule.js +37 -0
- package/lib/types/client/TasksTab.d.ts +41 -0
- package/lib/types/client/TasksTab.js +43 -0
- package/lib/types/client/TasksView.d.ts +44 -0
- package/lib/types/client/TasksView.js +149 -0
- package/lib/types/client/TeamView.d.ts +33 -0
- package/lib/types/client/TeamView.js +210 -0
- package/lib/types/client/at-menu.d.ts +127 -0
- package/lib/types/client/at-menu.js +173 -0
- package/lib/types/client/draft-chips.d.ts +67 -0
- package/lib/types/client/draft-chips.js +95 -0
- package/lib/types/client/executions.d.ts +0 -30
- package/lib/types/client/executions.js +4 -34
- package/lib/types/client/icons-local.d.ts +25 -0
- package/lib/types/client/icons-local.js +10 -0
- package/lib/types/client/icons.d.ts +2 -1
- package/lib/types/client/icons.js +1 -0
- package/lib/types/client/index.js +276 -72
- package/lib/types/client/locales.d.ts +482 -52
- package/lib/types/client/locales.js +490 -60
- package/lib/types/client/nodes.d.ts +112 -17
- package/lib/types/client/nodes.js +197 -26
- package/lib/types/client/slots.d.ts +117 -47
- package/lib/types/client/task-tag.d.ts +32 -0
- package/lib/types/client/task-tag.js +33 -0
- package/lib/types/client/tasks.d.ts +50 -0
- package/lib/types/client/tasks.js +82 -0
- package/lib/types/client/team.d.ts +73 -0
- package/lib/types/client/team.js +187 -0
- package/lib/types/decisions.d.ts +49 -0
- package/lib/types/decisions.js +101 -0
- package/lib/types/deliveries.d.ts +17 -1
- package/lib/types/deliveries.js +82 -6
- package/lib/types/dispatch.d.ts +102 -4
- package/lib/types/dispatch.js +498 -109
- package/lib/types/help.d.ts +137 -0
- package/lib/types/help.js +216 -0
- package/lib/types/helps.d.ts +26 -0
- package/lib/types/helps.js +47 -0
- package/lib/types/index.d.ts +292 -52
- package/lib/types/index.js +1421 -199
- package/lib/types/jobs.d.ts +22 -0
- package/lib/types/jobs.js +77 -0
- package/lib/types/journal.d.ts +17 -27
- package/lib/types/journal.js +120 -43
- package/lib/types/messaging.d.ts +65 -0
- package/lib/types/messaging.js +160 -0
- package/lib/types/plan-service.js +7 -1
- package/lib/types/report-gate.d.ts +81 -0
- package/lib/types/report-gate.js +89 -0
- package/lib/types/tool-definition.d.ts +43 -20
- package/lib/types/tool-definition.js +145 -212
- package/lib/types/tool.d.ts +2 -2
- package/lib/types/tool.js +3 -2
- package/lib/types/types.d.ts +755 -78
- package/locale/zh.json +1 -1
- package/package.json +3 -3
- package/lib/types/client/RoomActivityDock.d.ts +0 -19
- package/lib/types/client/RoomActivityDock.js +0 -105
- package/lib/types/client/RoomDockCapsules.d.ts +0 -64
- package/lib/types/client/RoomDockCapsules.js +0 -277
- package/lib/types/client/RoomPlanTab.d.ts +0 -15
- package/lib/types/client/RoomPlanTab.js +0 -13
- package/lib/types/client/RoomTaskLineView.d.ts +0 -13
- 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 {
|
|
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
|
|
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 {
|
|
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
|
|
141
|
-
const
|
|
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
|
-
|
|
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(
|
|
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
|
-
...
|
|
494
|
+
...goalFace(sessionId),
|
|
373
495
|
...inviteFace(sessionId),
|
|
374
|
-
reconcileDelivery:
|
|
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
|
-
|
|
386
|
-
|
|
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
|
|
418
|
-
|
|
419
|
-
const unsubscribe = roomChrome.subscribe(() =>
|
|
420
|
-
disposers.push(async () => { unsubscribe();
|
|
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:
|
|
423
|
-
inject: (sessionId) =>
|
|
424
|
-
|
|
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: () => ({
|
|
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-
|
|
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: () => ({
|
|
498
|
-
|
|
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();
|