@khorsheed/dsh-room 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/README.en.md +9 -8
- package/README.i18n.yaml +2 -2
- package/README.md +9 -8
- package/lib/client.js +1007 -340
- package/lib/index.js +88 -12
- package/lib/{journal-CuRnWk66.js → journal-BWL3-i0s.js} +27 -0
- package/lib/tool.js +63 -11
- package/lib/typert.host.js +161 -26
- package/lib/typert.remote-client.d.ts +2 -0
- package/lib/typert.remote-client.js +142 -22
- package/lib/types/client/RoomActivityDock.d.ts +19 -0
- package/lib/types/client/RoomActivityDock.js +105 -0
- package/lib/types/client/RoomComposer.d.ts +1 -1
- package/lib/types/client/RoomComposer.js +3 -3
- package/lib/types/client/RoomPlanTab.d.ts +15 -0
- package/lib/types/client/RoomPlanTab.js +13 -0
- package/lib/types/client/RoomPlanView.d.ts +3 -2
- package/lib/types/client/RoomPlanView.js +5 -6
- package/lib/types/client/executions.d.ts +50 -0
- package/lib/types/client/executions.js +70 -0
- package/lib/types/client/index.d.ts +1 -1
- package/lib/types/client/index.js +33 -1
- package/lib/types/client/locales.d.ts +50 -0
- package/lib/types/client/locales.js +50 -0
- package/lib/types/client/preset-visibility.d.ts +3 -3
- package/lib/types/client/preset-visibility.js +2 -2
- package/lib/types/client/slots.d.ts +6 -1
- package/lib/types/dispatch.js +30 -6
- package/lib/types/index.d.ts +5 -0
- package/lib/types/index.js +42 -7
- package/lib/types/journal.d.ts +1 -1
- package/lib/types/journal.js +27 -1
- package/lib/types/tool-definition.d.ts +56 -0
- package/lib/types/tool-definition.js +393 -0
- package/lib/types/tool.d.ts +37 -49
- package/lib/types/tool.js +47 -381
- package/lib/types/types.d.ts +28 -0
- package/package.json +8 -7
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { RoomExecution, RoomState } from '../types.ts';
|
|
2
|
+
export interface ExecutionRow {
|
|
3
|
+
sessionTotal?: boolean;
|
|
4
|
+
id: string;
|
|
5
|
+
member: string;
|
|
6
|
+
childSessionId?: string;
|
|
7
|
+
title: string;
|
|
8
|
+
status: 'queued' | 'running' | 'done' | 'cancelled' | 'failed' | 'uncertain' | 'submitted' | 'accepted' | 'settled';
|
|
9
|
+
provider?: string;
|
|
10
|
+
model?: string;
|
|
11
|
+
effort?: string;
|
|
12
|
+
tokens?: number;
|
|
13
|
+
startedAt?: number;
|
|
14
|
+
elapsedMs?: number;
|
|
15
|
+
error?: string;
|
|
16
|
+
execution?: RoomExecution;
|
|
17
|
+
}
|
|
18
|
+
/** Public session-list projection; members are deduplicated against durable Room executions. */
|
|
19
|
+
export interface BackgroundSession {
|
|
20
|
+
id: string;
|
|
21
|
+
parentId?: string;
|
|
22
|
+
origin?: string;
|
|
23
|
+
displayTitle?: string;
|
|
24
|
+
running?: boolean;
|
|
25
|
+
projectionValues?: {
|
|
26
|
+
tokenUsage?: {
|
|
27
|
+
uncachedInputTokens: number;
|
|
28
|
+
outputTokens: number;
|
|
29
|
+
cacheReadTokens: number;
|
|
30
|
+
cacheWriteTokens: number;
|
|
31
|
+
};
|
|
32
|
+
subagentTiming?: {
|
|
33
|
+
settledMs: number;
|
|
34
|
+
active?: {
|
|
35
|
+
since: number;
|
|
36
|
+
through: number;
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
export interface BackgroundSessionsStore {
|
|
42
|
+
subscribe(listener: () => void): () => void;
|
|
43
|
+
getSnapshot(): {
|
|
44
|
+
byId: Readonly<Record<string, BackgroundSession>>;
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
export declare function descendantRows(sessions: Readonly<Record<string, BackgroundSession>>, root: string, state: RoomState, now: number, active?: readonly string[]): ExecutionRow[];
|
|
48
|
+
/** One row per delivery/attempt, not one row per persistent member session. */
|
|
49
|
+
export declare function executionRows(state: RoomState): ExecutionRow[];
|
|
50
|
+
//# sourceMappingURL=executions.d.ts.map
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
export function descendantRows(sessions, root, state, now, active = []) {
|
|
2
|
+
const known = new Set([...state.members.map(member => member.childSessionId), ...(state.executions ?? []).map(run => run.childSessionId)]);
|
|
3
|
+
const representedRunning = new Set(executionRows(state).filter(row => row.status === 'running').map(row => row.childSessionId));
|
|
4
|
+
const coordinatorChild = state.members.find(member => state.coordinator === undefined ? member.kind === 'main-agent' : member.id === state.coordinator.memberId)?.childSessionId;
|
|
5
|
+
const result = [];
|
|
6
|
+
const seen = new Set([root]);
|
|
7
|
+
const visit = (parent) => {
|
|
8
|
+
for (const child of Object.values(sessions)) {
|
|
9
|
+
if (child.parentId !== parent || child.origin !== 'subagent' || seen.has(child.id))
|
|
10
|
+
continue;
|
|
11
|
+
seen.add(child.id);
|
|
12
|
+
const running = child.running === true || active.includes(child.id);
|
|
13
|
+
// Direct follow-ups in a member's own composer bypass Room dispatch.
|
|
14
|
+
// Keep that live session visible without duplicating an active Room execution.
|
|
15
|
+
if (child.id !== coordinatorChild && (!known.has(child.id) || (running && !representedRunning.has(child.id)))) {
|
|
16
|
+
const timing = child.projectionValues?.subagentTiming;
|
|
17
|
+
const usage = child.projectionValues?.tokenUsage;
|
|
18
|
+
const elapsedMs = timing === undefined ? undefined : timing.settledMs + (timing.active === undefined ? 0 : Math.max(0, (running ? now : timing.active.through) - timing.active.since));
|
|
19
|
+
const tokens = usage === undefined ? undefined : usage.uncachedInputTokens + usage.outputTokens + usage.cacheReadTokens + usage.cacheWriteTokens;
|
|
20
|
+
result.push({ id: `session:${child.id}`, member: child.displayTitle || child.id, title: child.displayTitle || child.id, childSessionId: child.id,
|
|
21
|
+
status: running ? 'running' : 'settled', sessionTotal: true,
|
|
22
|
+
...elapsedMs === undefined ? {} : { elapsedMs },
|
|
23
|
+
...tokens === undefined || !Number.isFinite(tokens) ? {} : { tokens },
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
visit(child.id);
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
visit(root);
|
|
30
|
+
return result;
|
|
31
|
+
}
|
|
32
|
+
/** One row per delivery/attempt, not one row per persistent member session. */
|
|
33
|
+
export function executionRows(state) {
|
|
34
|
+
const coordinator = state.members.find(member => state.coordinator === undefined ? member.kind === 'main-agent' : member.id === state.coordinator.memberId);
|
|
35
|
+
const history = new Map((state.executions ?? state.runs.map(run => ({ ...run, id: run.runId ?? `${run.member}:${run.startedAt}` }))).map(run => [run.id, run]));
|
|
36
|
+
const rows = [];
|
|
37
|
+
for (const delivery of state.deliveries ?? []) {
|
|
38
|
+
const execution = history.get(delivery.id);
|
|
39
|
+
history.delete(delivery.id);
|
|
40
|
+
const member = state.members.find(member => member.id === delivery.memberId);
|
|
41
|
+
if ((coordinator !== undefined && delivery.memberId === coordinator.id) || delivery.origin === 'report')
|
|
42
|
+
continue;
|
|
43
|
+
const task = delivery.plan?.goalId === state.plan?.id ? state.plan?.tasks.find(task => task.id === delivery.plan?.taskId) : undefined;
|
|
44
|
+
const attempt = task?.attempts.find(attempt => attempt.id === delivery.plan?.attemptId);
|
|
45
|
+
const terminal = delivery.status === 'done' && attempt !== undefined
|
|
46
|
+
? attempt.status === 'accepted' ? 'accepted' : attempt.status === 'submitted' ? 'submitted' : 'done'
|
|
47
|
+
: delivery.status;
|
|
48
|
+
const status = execution?.state === 'failed' && delivery.status === 'running' ? 'uncertain' : terminal;
|
|
49
|
+
const title = task?.title ?? state.tasks.find(task => task.deliveryId === delivery.id)?.title ?? delivery.text;
|
|
50
|
+
rows.push({ title: title.replace(/\s+/g, ' ').trim(),
|
|
51
|
+
...member?.provider === undefined ? {} : { provider: member.provider },
|
|
52
|
+
...execution,
|
|
53
|
+
// Execution identity is immutable; delivery/review state is authoritative.
|
|
54
|
+
id: delivery.id, status, member: member?.name ?? execution?.member ?? delivery.memberId,
|
|
55
|
+
...execution === undefined ? {} : { execution },
|
|
56
|
+
...execution?.childSessionId !== undefined || member?.childSessionId === undefined ? {} : { childSessionId: member.childSessionId },
|
|
57
|
+
...delivery.error === undefined ? {} : { error: delivery.error },
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
for (const execution of history.values()) {
|
|
61
|
+
const member = state.members.find(member => member.id === execution.memberId || (execution.memberId === undefined && member.name === execution.member));
|
|
62
|
+
if (coordinator !== undefined && member === coordinator)
|
|
63
|
+
continue;
|
|
64
|
+
rows.push({ ...execution, execution, title: state.tasks.find(task => task.deliveryId === execution.runId)?.title ?? execution.member,
|
|
65
|
+
status: execution.state, member: member?.name ?? execution.member,
|
|
66
|
+
...execution.childSessionId !== undefined || member?.childSessionId === undefined ? {} : { childSessionId: member.childSessionId },
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
return rows.reverse();
|
|
70
|
+
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* registers the slot entries: the session-header「邀请 agent」action and the
|
|
5
5
|
* 成员 `conversation.view` tab — both self-hiding by the current session's
|
|
6
6
|
* preset composition (M3': visible exactly when the composition names the
|
|
7
|
-
* `@khorsheed/dsh-room
|
|
7
|
+
* `@khorsheed/dsh-room/tool` row, fail-open on every unreadable path, and
|
|
8
8
|
* always visible inside an actual room) — plus the `conversation.composer`
|
|
9
9
|
* chain takeover (claims the composer exactly when the current session is a
|
|
10
10
|
* cached room — and renders the dock capsules plus the session stats row
|
|
@@ -4,6 +4,7 @@ import { en, zh } from './locales.js';
|
|
|
4
4
|
import { InviteAgentAction } from './InviteAgentAction.js';
|
|
5
5
|
import { MembersView } from './MembersView.js';
|
|
6
6
|
import { RoomComposer, selectRoomComposer } from './RoomComposer.js';
|
|
7
|
+
import { ROOM_PLAN_KIND, ROOM_PLAN_TAB_ID, RoomPlanTab } from './RoomPlanTab.js';
|
|
7
8
|
import { RoomSpeechView } from './RoomSpeechView.js';
|
|
8
9
|
import { RoomRunView } from './RoomRunView.js';
|
|
9
10
|
import { RoomEventView } from './RoomEventView.js';
|
|
@@ -360,6 +361,8 @@ export async function apply(ctx) {
|
|
|
360
361
|
// main-agent turn Stop: the takeover renders the dock
|
|
361
362
|
// surfaces and the Stop button itself, because their seats hide with the
|
|
362
363
|
// official fallback.
|
|
364
|
+
const sidebar = ctx.get('sidebarRight');
|
|
365
|
+
const sidebarTabs = ctx.get('sidebarRightTabs');
|
|
363
366
|
const composerEntry = {
|
|
364
367
|
name: 'conversation.composer',
|
|
365
368
|
priority: -10,
|
|
@@ -379,10 +382,26 @@ export async function apply(ctx) {
|
|
|
379
382
|
return result?.ok && result.value.ok ? { ok: true } : { ok: false, message: result?.ok && !result.value.ok ? result.value.message : t('composer.error.generic') };
|
|
380
383
|
},
|
|
381
384
|
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') };
|
|
400
|
+
},
|
|
382
401
|
submit,
|
|
383
402
|
renderMemberConfiguration: ctx.get('localAgentUi')?.renderMemberConfiguration,
|
|
384
403
|
renderMemberInbox: ctx.get('localAgentUi')?.renderMemberInbox,
|
|
385
|
-
stopMember: name => { void remote?.cancel({ sessionId, name }).then(() => roomStore.refresh(sessionId)); },
|
|
404
|
+
stopMember: (name, expectedRunId) => { void remote?.cancel({ sessionId, name, ...expectedRunId === undefined ? {} : { expectedRunId } }).then(() => roomStore.refresh(sessionId)); },
|
|
386
405
|
modelDirectory: modelDirectoryFor(sessionId),
|
|
387
406
|
// The hidden official bar's Stop: the runtime session face's cancel
|
|
388
407
|
// (the same verb ui-conversation's own Stop injects). A torn-down
|
|
@@ -394,6 +413,19 @@ export async function apply(ctx) {
|
|
|
394
413
|
},
|
|
395
414
|
}),
|
|
396
415
|
};
|
|
416
|
+
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); });
|
|
421
|
+
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));
|
|
428
|
+
}
|
|
397
429
|
let composerDispose;
|
|
398
430
|
ctx.slots.inject('conversation.composer', () => {
|
|
399
431
|
composerDispose = ctx.slots.register(composerEntry, RoomComposer);
|
|
@@ -1,6 +1,31 @@
|
|
|
1
1
|
/** `room` namespace dictionaries. */
|
|
2
2
|
/** Simplified Chinese dictionary (the key-set source of truth). */
|
|
3
3
|
export declare const zh: {
|
|
4
|
+
'activity.status.settled': string;
|
|
5
|
+
'activity.sessionTotal': string;
|
|
6
|
+
"activity.agents": string;
|
|
7
|
+
"activity.plan": string;
|
|
8
|
+
"activity.planDetails": string;
|
|
9
|
+
"activity.limits": string;
|
|
10
|
+
"activity.session": string;
|
|
11
|
+
"activity.stop": string;
|
|
12
|
+
"activity.stopChanged": string;
|
|
13
|
+
"activity.runningHint": string;
|
|
14
|
+
"activity.notStarted": string;
|
|
15
|
+
"activity.empty": string;
|
|
16
|
+
"activity.planning": string;
|
|
17
|
+
"activity.filter.recent": string;
|
|
18
|
+
"activity.filter.running": string;
|
|
19
|
+
"activity.filter.attention": string;
|
|
20
|
+
"activity.filter.all": string;
|
|
21
|
+
"activity.status.queued": string;
|
|
22
|
+
"activity.status.running": string;
|
|
23
|
+
"activity.status.done": string;
|
|
24
|
+
"activity.status.cancelled": string;
|
|
25
|
+
"activity.status.failed": string;
|
|
26
|
+
"activity.status.uncertain": string;
|
|
27
|
+
"activity.status.submitted": string;
|
|
28
|
+
"activity.status.accepted": string;
|
|
4
29
|
'recovery.title': string;
|
|
5
30
|
'recovery.help': string;
|
|
6
31
|
'recovery.done': string;
|
|
@@ -215,6 +240,31 @@ declare module '@deepseek-ai/dsh-client-ui-slots' {
|
|
|
215
240
|
}
|
|
216
241
|
/** English dictionary, checked complete against the zh key set. */
|
|
217
242
|
export declare const en: {
|
|
243
|
+
'activity.status.settled': string;
|
|
244
|
+
'activity.sessionTotal': string;
|
|
245
|
+
"activity.agents": string;
|
|
246
|
+
"activity.plan": string;
|
|
247
|
+
"activity.planDetails": string;
|
|
248
|
+
"activity.limits": string;
|
|
249
|
+
"activity.session": string;
|
|
250
|
+
"activity.stop": string;
|
|
251
|
+
"activity.stopChanged": string;
|
|
252
|
+
"activity.runningHint": string;
|
|
253
|
+
"activity.notStarted": string;
|
|
254
|
+
"activity.empty": string;
|
|
255
|
+
"activity.planning": string;
|
|
256
|
+
"activity.filter.recent": string;
|
|
257
|
+
"activity.filter.running": string;
|
|
258
|
+
"activity.filter.attention": string;
|
|
259
|
+
"activity.filter.all": string;
|
|
260
|
+
"activity.status.queued": string;
|
|
261
|
+
"activity.status.running": string;
|
|
262
|
+
"activity.status.done": string;
|
|
263
|
+
"activity.status.cancelled": string;
|
|
264
|
+
"activity.status.failed": string;
|
|
265
|
+
"activity.status.uncertain": string;
|
|
266
|
+
"activity.status.submitted": string;
|
|
267
|
+
"activity.status.accepted": string;
|
|
218
268
|
'recovery.title': string;
|
|
219
269
|
'recovery.help': string;
|
|
220
270
|
'recovery.done': string;
|
|
@@ -1,6 +1,31 @@
|
|
|
1
1
|
/** `room` namespace dictionaries. */
|
|
2
2
|
/** Simplified Chinese dictionary (the key-set source of truth). */
|
|
3
3
|
export const zh = {
|
|
4
|
+
'activity.status.settled': '已结束',
|
|
5
|
+
'activity.sessionTotal': '会话累计',
|
|
6
|
+
"activity.agents": "后台 Agent",
|
|
7
|
+
"activity.plan": "当前计划",
|
|
8
|
+
"activity.planDetails": "计划详情与运行限制",
|
|
9
|
+
"activity.limits": "运行限制",
|
|
10
|
+
"activity.session": "查看会话",
|
|
11
|
+
"activity.stop": "中止",
|
|
12
|
+
"activity.stopChanged": "这次执行已结束或已发生变化,请查看最新状态。",
|
|
13
|
+
"activity.runningHint": "{count} 个 Agent 正在运行",
|
|
14
|
+
"activity.notStarted": "尚未开始",
|
|
15
|
+
"activity.empty": "当前没有这类执行记录",
|
|
16
|
+
"activity.planning": "协调者正在整理计划",
|
|
17
|
+
"activity.filter.recent": "最近",
|
|
18
|
+
"activity.filter.running": "进行中",
|
|
19
|
+
"activity.filter.attention": "待处理",
|
|
20
|
+
"activity.filter.all": "全部",
|
|
21
|
+
"activity.status.queued": "排队中",
|
|
22
|
+
"activity.status.running": "运行中",
|
|
23
|
+
"activity.status.done": "已完成",
|
|
24
|
+
"activity.status.cancelled": "已中止",
|
|
25
|
+
"activity.status.failed": "失败",
|
|
26
|
+
"activity.status.uncertain": "待核对",
|
|
27
|
+
"activity.status.submitted": "待验收",
|
|
28
|
+
"activity.status.accepted": "已验收",
|
|
4
29
|
'recovery.title': '中断的对话需要对账',
|
|
5
30
|
'recovery.help': '重启前的结果尚未确认,不会自动重试。请查看成员会话并填写核对结论;对账后,已排队的普通对话可以继续,正式目标仍遵守暂停状态。',
|
|
6
31
|
'recovery.done': '确认已完成',
|
|
@@ -207,6 +232,31 @@ export const zh = {
|
|
|
207
232
|
};
|
|
208
233
|
/** English dictionary, checked complete against the zh key set. */
|
|
209
234
|
export const en = {
|
|
235
|
+
'activity.status.settled': 'Finished',
|
|
236
|
+
'activity.sessionTotal': 'Session total',
|
|
237
|
+
"activity.agents": "Background agents",
|
|
238
|
+
"activity.plan": "Current plan",
|
|
239
|
+
"activity.planDetails": "Plan details and limits",
|
|
240
|
+
"activity.limits": "Run limits",
|
|
241
|
+
"activity.session": "View conversation",
|
|
242
|
+
"activity.stop": "Stop",
|
|
243
|
+
"activity.stopChanged": "This execution has ended or changed. Check its latest state.",
|
|
244
|
+
"activity.runningHint": "{count} agents are running",
|
|
245
|
+
"activity.notStarted": "Not started",
|
|
246
|
+
"activity.empty": "No matching executions",
|
|
247
|
+
"activity.planning": "The coordinator is preparing the plan",
|
|
248
|
+
"activity.filter.recent": "Recent",
|
|
249
|
+
"activity.filter.running": "Running",
|
|
250
|
+
"activity.filter.attention": "Needs attention",
|
|
251
|
+
"activity.filter.all": "All",
|
|
252
|
+
"activity.status.queued": "Queued",
|
|
253
|
+
"activity.status.running": "Running",
|
|
254
|
+
"activity.status.done": "Completed",
|
|
255
|
+
"activity.status.cancelled": "Stopped",
|
|
256
|
+
"activity.status.failed": "Failed",
|
|
257
|
+
"activity.status.uncertain": "Needs reconciliation",
|
|
258
|
+
"activity.status.submitted": "Awaiting review",
|
|
259
|
+
"activity.status.accepted": "Accepted",
|
|
210
260
|
'recovery.title': 'Interrupted conversations need reconciliation',
|
|
211
261
|
'recovery.help': 'The pre-restart outcome is unknown and will not be retried automatically. Inspect the member session and record your finding. Reconciliation releases queued ordinary conversations; paused goals remain paused.',
|
|
212
262
|
'recovery.done': 'Confirm completed',
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* The preset-composition visibility of room's SESSION chrome (M3' self-hide,
|
|
3
3
|
* the mode-switcher proposal's "组合里有我的行"): the invite chip and the
|
|
4
4
|
* members tab show exactly when the CURRENT session's preset composition
|
|
5
|
-
* names the `@khorsheed/dsh-room
|
|
5
|
+
* names the `@khorsheed/dsh-room/tool` row — the preset composition file is
|
|
6
6
|
* the single source of truth, read from the official `pluginInventory`
|
|
7
7
|
* Remote (the same criterion the worktrees badge implements; inlined here
|
|
8
8
|
* rather than extracted into a helper package, per the proposal's M3'
|
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import type { Context } from '@deepseek-ai/cordis';
|
|
18
18
|
import type { SessionId } from '@deepseek-ai/dsh-session/types';
|
|
19
|
-
/** The composition criterion's row:
|
|
20
|
-
export declare const ROOM_TOOL_ROW_MODULE = "@khorsheed/dsh-room
|
|
19
|
+
/** The composition criterion's row: this package's own `./tool` composition entry. */
|
|
20
|
+
export declare const ROOM_TOOL_ROW_MODULE = "@khorsheed/dsh-room/tool";
|
|
21
21
|
/**
|
|
22
22
|
* Minimal structural mirror of the official pluginInventory snapshot — only
|
|
23
23
|
* the agentPresets slice the criterion reads (duck-typed, host-version
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
/** The composition criterion's row:
|
|
2
|
-
export const ROOM_TOOL_ROW_MODULE = '@khorsheed/dsh-room
|
|
1
|
+
/** The composition criterion's row: this package's own `./tool` composition entry. */
|
|
2
|
+
export const ROOM_TOOL_ROW_MODULE = '@khorsheed/dsh-room/tool';
|
|
3
3
|
/** The current session's agent preset, the badge's dual read per host line. */
|
|
4
4
|
function presetOf(row) {
|
|
5
5
|
const value = row
|
|
@@ -160,12 +160,17 @@ export interface RoomModelDirectory {
|
|
|
160
160
|
* ui-model-selection — the picker simply does not render.
|
|
161
161
|
*/
|
|
162
162
|
export interface RoomComposerInjected extends RoomTasksInjected, RoomInviteInjected {
|
|
163
|
+
readonly backgroundSessions?: import('./executions.ts').BackgroundSessionsStore | undefined;
|
|
164
|
+
readonly activeChildren?: (() => Promise<readonly string[] | undefined>) | undefined;
|
|
165
|
+
readonly stopChild?: ((childSessionId: string) => Promise<RoomMutationOutcome>) | undefined;
|
|
166
|
+
readonly openPlan?: (() => void) | undefined;
|
|
167
|
+
readonly stopExecution?: ((name: string, run: import('../types.ts').RoomMemberRun) => Promise<RoomMutationOutcome>) | undefined;
|
|
163
168
|
readonly reconcileDelivery?: ((deliveryId: string, outcome: 'done' | 'cancelled', evidence: string) => Promise<RoomMutationOutcome>) | undefined;
|
|
164
169
|
readonly planCommand?: ((command: string) => Promise<RoomMutationOutcome>) | undefined;
|
|
165
170
|
readonly openPlanSession?: ((sessionId: string) => void) | undefined;
|
|
166
171
|
readonly renderMemberInbox?: LocalAgentUi['renderMemberInbox'] | undefined;
|
|
167
172
|
readonly renderMemberConfiguration?: LocalAgentUi['renderMemberConfiguration'] | undefined;
|
|
168
|
-
readonly stopMember?: ((name: string) => void) | undefined;
|
|
173
|
+
readonly stopMember?: ((name: string, expectedRunId?: string) => void) | undefined;
|
|
169
174
|
/**
|
|
170
175
|
* Dispatch an @-message into the room and refresh the store on success.
|
|
171
176
|
* Bare messages never reach here — the composer releases them to the
|
package/lib/types/dispatch.js
CHANGED
|
@@ -191,6 +191,12 @@ export class DispatchEngine {
|
|
|
191
191
|
const error = 'Execution outcome is unknown after restart; reconcile before retrying';
|
|
192
192
|
return {
|
|
193
193
|
...state,
|
|
194
|
+
...state.executions === undefined ? {} : { executions: state.executions.map(execution => {
|
|
195
|
+
const active = execution.runId === undefined
|
|
196
|
+
? [...this.inFlight.values()].includes(`${sessionId} ${execution.memberId}`)
|
|
197
|
+
: this.scheduled.has(`${sessionId}:${execution.runId}`);
|
|
198
|
+
return execution.state === 'running' && !active ? { ...execution, state: 'failed', error } : execution;
|
|
199
|
+
}) },
|
|
194
200
|
...state.deliveries === undefined ? {} : { deliveries: state.deliveries.map(row => row.status === 'queued' && closedGoal(state, row.plan?.goalId)
|
|
195
201
|
? { ...row, status: 'cancelled' } : row.status === 'running' && !this.scheduled.has(`${sessionId}:${row.id}`)
|
|
196
202
|
? { ...row, status: 'uncertain', error } : row) },
|
|
@@ -269,7 +275,9 @@ export class DispatchEngine {
|
|
|
269
275
|
if (deliveryId !== undefined)
|
|
270
276
|
room.append('room/delivery-state', { id: deliveryId, dispatchSeq: options.dispatchSeq, memberId: identity, state: 'running' });
|
|
271
277
|
memberName = replay(room.snapshotEvents()).members.find(entry => memberId(room.snapshotEvents(), entry) === identity)?.name ?? memberName;
|
|
272
|
-
room.append('room/run-state', { member: memberName, state: 'running', startedAt, runId
|
|
278
|
+
room.append('room/run-state', { member: memberName, state: 'running', startedAt, runId,
|
|
279
|
+
...member.kind === 'main-agent' ? { childSessionId: room.id } : member.childSessionId === undefined ? {} : { childSessionId: member.childSessionId },
|
|
280
|
+
});
|
|
273
281
|
await this.hooks.admitted?.(room, options.dispatchSeq);
|
|
274
282
|
await this.ctx.sessions.flush(room);
|
|
275
283
|
return startedAt;
|
|
@@ -278,7 +286,7 @@ export class DispatchEngine {
|
|
|
278
286
|
try {
|
|
279
287
|
outcome = member.kind === 'main-agent'
|
|
280
288
|
? await this.runMainAgent(room, member, cursor, text, await admitted(), options.relayIds ?? [])
|
|
281
|
-
: await this.runCliMember(room, member, cursor, text, admitted, options.relayIds ?? [], submitted);
|
|
289
|
+
: await this.runCliMember(room, member, cursor, text, admitted, options.relayIds ?? [], submitted, runId);
|
|
282
290
|
}
|
|
283
291
|
catch (error) {
|
|
284
292
|
this.ctx.logger.warn(`room: dispatch to "${memberName}" failed: ${String(error)}`);
|
|
@@ -351,7 +359,7 @@ export class DispatchEngine {
|
|
|
351
359
|
return { state: 'done' };
|
|
352
360
|
}
|
|
353
361
|
/** CLI member turn: facade start (first round) or resume, then the speech mirror. */
|
|
354
|
-
async runCliMember(room, member, cursor, text, admitted, relayIds, submitted) {
|
|
362
|
+
async runCliMember(room, member, cursor, text, admitted, relayIds, submitted, runId) {
|
|
355
363
|
const facade = probeLocalAgent(this.ctx);
|
|
356
364
|
if (facade === undefined) {
|
|
357
365
|
this.ctx.logger.warn(`room: cannot dispatch to "${member.name}": local-agent facade unavailable`);
|
|
@@ -363,7 +371,23 @@ export class DispatchEngine {
|
|
|
363
371
|
// configuration admit the request. Persistence must finish before native work.
|
|
364
372
|
let startedAt;
|
|
365
373
|
const controlled = facade.supportsMemberConfiguration?.(provider) === true;
|
|
366
|
-
const admission =
|
|
374
|
+
const admission = {
|
|
375
|
+
...controlled ? { onAdmitted: async () => { startedAt = await admitted(); } } : {},
|
|
376
|
+
onProgress: (event) => {
|
|
377
|
+
if (event.kind !== 'settled')
|
|
378
|
+
return;
|
|
379
|
+
const previous = room.snapshotEvents().filter(row => row.type === 'room/run-state' && row.data.runId === runId).at(-1);
|
|
380
|
+
if (previous?.type !== 'room/run-state')
|
|
381
|
+
return;
|
|
382
|
+
const total = event.usage?.totalTokens ?? (event.usage === undefined ? undefined : event.usage.inputTokens + event.usage.outputTokens);
|
|
383
|
+
room.append('room/execution-metadata', { runId,
|
|
384
|
+
...event.observedModel === undefined ? {} : { model: event.observedModel },
|
|
385
|
+
...event.observedEffort === undefined ? {} : { effort: event.observedEffort },
|
|
386
|
+
...total === undefined || !Number.isFinite(total) || total < 0 ? {} : { tokens: total },
|
|
387
|
+
});
|
|
388
|
+
void this.ctx.sessions.flush(room).catch(error => this.ctx.logger.warn(`room: execution metadata flush failed: ${String(error)}`));
|
|
389
|
+
},
|
|
390
|
+
};
|
|
367
391
|
if (!controlled)
|
|
368
392
|
startedAt = await admitted();
|
|
369
393
|
const { prompt, carried } = assemblePrompt(room, member, cursor, text, relayIds);
|
|
@@ -374,8 +398,8 @@ export class DispatchEngine {
|
|
|
374
398
|
// The invite-time model lands as the delegation's own model: the facade
|
|
375
399
|
// records it with the first start and every later resume re-requests it
|
|
376
400
|
// (providers bind it at spawn for exec and live alike).
|
|
377
|
-
? facade.start(room.id, provider, [{ type: 'text', text: prompt }],
|
|
378
|
-
: facade.resume(room.id, provider, member.childSessionId, [{ type: 'text', text: prompt }],
|
|
401
|
+
? facade.start(room.id, provider, [{ type: 'text', text: prompt }], { ...admission, ...member.model === undefined ? {} : { model: member.model }, ...member.cwd === undefined ? {} : { cwd: member.cwd } })
|
|
402
|
+
: facade.resume(room.id, provider, member.childSessionId, [{ type: 'text', text: prompt }], { ...admission, ...member.cwd === undefined ? {} : { cwd: member.cwd } });
|
|
379
403
|
// Existing members can enter core's queue immediately. A fresh/prepared
|
|
380
404
|
// identity must publish its first handle before the next submission resumes it.
|
|
381
405
|
if (controlled && member.childSessionId !== undefined && !prepared)
|
package/lib/types/index.d.ts
CHANGED
|
@@ -296,6 +296,11 @@ export declare class RoomService extends TypertRemoteService {
|
|
|
296
296
|
* @returns the cancellation receipt, or a rejection.
|
|
297
297
|
*/
|
|
298
298
|
cancel(request: RoomCancelRequest): Promise<RoomCancelResult>;
|
|
299
|
+
/** Native descendant controls share the host's continuation and cancellation channels. */
|
|
300
|
+
cancelChild(request: {
|
|
301
|
+
sessionId: SessionId;
|
|
302
|
+
childSessionId: SessionId;
|
|
303
|
+
}): Promise<RoomCancelResult>;
|
|
299
304
|
}
|
|
300
305
|
export default RoomService;
|
|
301
306
|
//# sourceMappingURL=index.d.ts.map
|
package/lib/types/index.js
CHANGED
|
@@ -44,8 +44,8 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
|
|
|
44
44
|
* room host half: the `room` Typert Remote service. (The `room_invite` /
|
|
45
45
|
* `room_task` / `room_message` model tools left this package's profile-root
|
|
46
46
|
* registration in the M4' tool-row split — their definition factories live
|
|
47
|
-
* in ./tool.ts and the
|
|
48
|
-
* inside agent-preset compositions.) The room's entire state is the session's `room/*` custom-event
|
|
47
|
+
* in ./tool-definition.ts and the package's own `./tool` composition entry
|
|
48
|
+
* mounts them inside agent-preset compositions.) The room's entire state is the session's `room/*` custom-event
|
|
49
49
|
* journal (log-only events — persistence and reload-replay come free, the
|
|
50
50
|
* model never sees them, and harnesses without this plugin replay the session
|
|
51
51
|
* safely); every read folds the journal through the pure replay, and every
|
|
@@ -126,6 +126,7 @@ let RoomService = (() => {
|
|
|
126
126
|
let _closeTask_decorators;
|
|
127
127
|
let _setGoal_decorators;
|
|
128
128
|
let _cancel_decorators;
|
|
129
|
+
let _cancelChild_decorators;
|
|
129
130
|
return class RoomService extends _classSuper {
|
|
130
131
|
static {
|
|
131
132
|
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
|
|
@@ -146,6 +147,7 @@ let RoomService = (() => {
|
|
|
146
147
|
_closeTask_decorators = [Remote('closeTask')];
|
|
147
148
|
_setGoal_decorators = [Remote('setGoal')];
|
|
148
149
|
_cancel_decorators = [Remote('cancel')];
|
|
150
|
+
_cancelChild_decorators = [Remote('cancelChild')];
|
|
149
151
|
__esDecorate(this, null, _isRoom_decorators, { kind: "method", name: "isRoom", static: false, private: false, access: { has: obj => "isRoom" in obj, get: obj => obj.isRoom }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
150
152
|
__esDecorate(this, null, _listProviders_decorators, { kind: "method", name: "listProviders", static: false, private: false, access: { has: obj => "listProviders" in obj, get: obj => obj.listProviders }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
151
153
|
__esDecorate(this, null, _getState_decorators, { kind: "method", name: "getState", static: false, private: false, access: { has: obj => "getState" in obj, get: obj => obj.getState }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
@@ -163,6 +165,7 @@ let RoomService = (() => {
|
|
|
163
165
|
__esDecorate(this, null, _closeTask_decorators, { kind: "method", name: "closeTask", static: false, private: false, access: { has: obj => "closeTask" in obj, get: obj => obj.closeTask }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
164
166
|
__esDecorate(this, null, _setGoal_decorators, { kind: "method", name: "setGoal", static: false, private: false, access: { has: obj => "setGoal" in obj, get: obj => obj.setGoal }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
165
167
|
__esDecorate(this, null, _cancel_decorators, { kind: "method", name: "cancel", static: false, private: false, access: { has: obj => "cancel" in obj, get: obj => obj.cancel }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
168
|
+
__esDecorate(this, null, _cancelChild_decorators, { kind: "method", name: "cancelChild", static: false, private: false, access: { has: obj => "cancelChild" in obj, get: obj => obj.cancelChild }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
166
169
|
if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
167
170
|
}
|
|
168
171
|
static inject = ['sessions', 'agents'];
|
|
@@ -232,9 +235,10 @@ let RoomService = (() => {
|
|
|
232
235
|
return { kind: 'enter', messages: [] };
|
|
233
236
|
});
|
|
234
237
|
// The model-facing room tools are deliberately NOT registered here: they
|
|
235
|
-
//
|
|
236
|
-
// tool row inside agent-preset compositions
|
|
237
|
-
// definition factories
|
|
238
|
+
// belong to this package's `./tool` composition entry (src/tool.ts),
|
|
239
|
+
// which mounts the tool row inside agent-preset compositions
|
|
240
|
+
// (session-granted). The definition factories live in
|
|
241
|
+
// ./tool-definition.ts for that row.
|
|
238
242
|
}
|
|
239
243
|
/** Resolve a LIVE session as a room: presence, marker, replayed state. */
|
|
240
244
|
load(sessionId) {
|
|
@@ -1169,16 +1173,25 @@ let RoomService = (() => {
|
|
|
1169
1173
|
const member = loaded.state.members.find(entry => entry.name === request.name);
|
|
1170
1174
|
if (member === undefined)
|
|
1171
1175
|
return { ok: false, error: { code: 'member-not-found' } };
|
|
1176
|
+
const expected = loaded.state.runs.find(entry => entry.member === request.name && entry.state === 'running');
|
|
1177
|
+
if ((request.expectedRunId !== undefined && expected?.runId !== request.expectedRunId)
|
|
1178
|
+
|| (request.expectedStartedAt !== undefined && expected?.startedAt !== request.expectedStartedAt)) {
|
|
1179
|
+
return { ok: true, value: { cancelled: false } };
|
|
1180
|
+
}
|
|
1172
1181
|
const facade = probeLocalAgent(this.ctx);
|
|
1173
|
-
const
|
|
1182
|
+
const native = member.kind === 'main-agent' && expected !== undefined ? this.ctx.agents.get(loaded.session.id) : undefined;
|
|
1183
|
+
if (native !== undefined)
|
|
1184
|
+
native.cancel({ kind: 'user' });
|
|
1185
|
+
const hit = native !== undefined || (facade !== undefined && member.childSessionId !== undefined
|
|
1174
1186
|
? facade.cancel(member.childSessionId)
|
|
1175
|
-
: false;
|
|
1187
|
+
: false);
|
|
1176
1188
|
if (hit) {
|
|
1177
1189
|
const running = loaded.state.runs.find(entry => entry.member === request.name && entry.state === 'running');
|
|
1178
1190
|
if (running === undefined)
|
|
1179
1191
|
return { ok: true, value: { cancelled: true } };
|
|
1180
1192
|
loaded.session.append('room/run-state', {
|
|
1181
1193
|
member: request.name, state: 'cancelled', startedAt: running.startedAt,
|
|
1194
|
+
elapsedMs: Math.max(0, Date.now() - running.startedAt),
|
|
1182
1195
|
...running.runId === undefined ? {} : { runId: running.runId },
|
|
1183
1196
|
});
|
|
1184
1197
|
// The engine's settle no-ops behind this edge, so the task closing the
|
|
@@ -1194,6 +1207,28 @@ let RoomService = (() => {
|
|
|
1194
1207
|
}
|
|
1195
1208
|
return { ok: true, value: { cancelled: false } };
|
|
1196
1209
|
}
|
|
1210
|
+
/** Native descendant controls share the host's continuation and cancellation channels. */
|
|
1211
|
+
async cancelChild(request) {
|
|
1212
|
+
const loaded = await this.ensureLive(request.sessionId);
|
|
1213
|
+
if (!loaded.ok)
|
|
1214
|
+
return { ok: false, error: loaded.error };
|
|
1215
|
+
const target = this.ctx.sessions.get(request.childSessionId);
|
|
1216
|
+
const seen = new Set([request.childSessionId]);
|
|
1217
|
+
let parent = target?.header.parentSession;
|
|
1218
|
+
while (parent !== undefined && parent !== request.sessionId && !seen.has(parent)) {
|
|
1219
|
+
seen.add(parent);
|
|
1220
|
+
parent = this.ctx.sessions.get(parent)?.header.parentSession;
|
|
1221
|
+
}
|
|
1222
|
+
if (!target || target.id === request.sessionId || parent !== request.sessionId)
|
|
1223
|
+
return { ok: true, value: { cancelled: false } };
|
|
1224
|
+
const facadeHit = probeLocalAgent(this.ctx)?.cancel(target.id) ?? false;
|
|
1225
|
+
const agent = this.ctx.agents.get(target.id);
|
|
1226
|
+
if (agent) {
|
|
1227
|
+
this.ctx.get('subagents')?.interrupt(target.id, { kind: 'user', parentSessionId: target.header.parentSession });
|
|
1228
|
+
agent.cancel({ kind: 'user' });
|
|
1229
|
+
}
|
|
1230
|
+
return { ok: true, value: { cancelled: facadeHit || agent !== undefined } };
|
|
1231
|
+
}
|
|
1197
1232
|
};
|
|
1198
1233
|
})();
|
|
1199
1234
|
export { RoomService };
|
package/lib/types/journal.d.ts
CHANGED
|
@@ -18,7 +18,7 @@ export declare const MAIN_AGENT_MEMBER = "dsh";
|
|
|
18
18
|
* in-place add is that surface's temporary form — migrate to the official
|
|
19
19
|
* one when it lands upstream.
|
|
20
20
|
*/
|
|
21
|
-
export declare const ROOM_EVENT_TYPES: readonly ['room/created', 'room/plan-state', 'room/coordinator', 'room/delivery-state', 'room/member-added', 'room/member-updated', 'room/member-removed', 'room/dispatch', 'room/speech', 'room/run-state', 'room/relay', 'room/relay-resolved', 'room/task-added', 'room/task-updated', 'room/task-edited', 'room/goal'];
|
|
21
|
+
export declare const ROOM_EVENT_TYPES: readonly ['room/created', 'room/plan-state', 'room/coordinator', 'room/delivery-state', 'room/member-added', 'room/member-updated', 'room/member-removed', 'room/dispatch', 'room/speech', 'room/run-state', 'room/execution-metadata', 'room/relay', 'room/relay-resolved', 'room/task-added', 'room/task-updated', 'room/task-edited', 'room/goal'];
|
|
22
22
|
/** Whether an event log carries the room identity marker. */
|
|
23
23
|
export declare function isRoomLog(events: readonly SessionEvent[]): boolean;
|
|
24
24
|
/**
|