@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
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tasks (jobs): what the human asked for and everything it set in motion.
|
|
3
|
+
* A task starts at a human message that led to work — at least one
|
|
4
|
+
* delivery carries its id — and gathers the follow-up messages attributed to
|
|
5
|
+
* it (`room/task-link`, by the composer's tag, the coordinator or the
|
|
6
|
+
* decision model) and every delivery recorded under it: delegations,
|
|
7
|
+
* members' sub-delegations, reports. Plain chat that dispatched nothing is
|
|
8
|
+
* not a task. Pure: the host replay and the client views share it.
|
|
9
|
+
* @module @khorsheed/dsh-room/jobs
|
|
10
|
+
*/
|
|
11
|
+
import type { SessionEvent } from '@deepseek-ai/dsh-session/types';
|
|
12
|
+
import type { RoomDelivery, RoomJob } from './types.ts';
|
|
13
|
+
/**
|
|
14
|
+
* Fold the room log into its tasks, newest first.
|
|
15
|
+
* @param events - the room log.
|
|
16
|
+
* @param deliveries - the replayed deliveries (their `task` already resolved).
|
|
17
|
+
* @returns the tasks.
|
|
18
|
+
*/
|
|
19
|
+
export declare function replayJobs(events: readonly SessionEvent[], deliveries: readonly RoomDelivery[]): RoomJob[];
|
|
20
|
+
/** Resolve the coordinator's `task` argument (`new`, a 「任务 N」 number, or a root id) against the room's tasks. */
|
|
21
|
+
export declare function resolveTaskRef(jobs: readonly RoomJob[], ref: string): string | 'new' | undefined;
|
|
22
|
+
//# sourceMappingURL=jobs.d.ts.map
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { isHumanMessage } from './deliveries.js';
|
|
2
|
+
/** The text of a message's text blocks. */
|
|
3
|
+
function textOf(content) {
|
|
4
|
+
return content.flatMap(block => block.type === 'text' && typeof block.text === 'string' ? [block.text] : []).join('\n');
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Fold the room log into its tasks, newest first.
|
|
8
|
+
* @param events - the room log.
|
|
9
|
+
* @param deliveries - the replayed deliveries (their `task` already resolved).
|
|
10
|
+
* @returns the tasks.
|
|
11
|
+
*/
|
|
12
|
+
export function replayJobs(events, deliveries) {
|
|
13
|
+
const messages = new Map();
|
|
14
|
+
const links = new Map();
|
|
15
|
+
const stopped = new Set();
|
|
16
|
+
const dispatchById = new Map();
|
|
17
|
+
for (const event of events) {
|
|
18
|
+
if (isHumanMessage(event))
|
|
19
|
+
messages.set(event.data.id, { id: event.data.id, time: event.time, text: textOf(event.data.content) });
|
|
20
|
+
else if (event.type === 'room/task-link') {
|
|
21
|
+
links.set(event.data.message, { task: event.data.task, by: event.data.by, ...event.data.probability === undefined ? {} : { probability: event.data.probability } });
|
|
22
|
+
}
|
|
23
|
+
else if (event.type === 'room/job-stopped')
|
|
24
|
+
stopped.add(event.data.task);
|
|
25
|
+
else if (event.type === 'room/dispatch' && event.data.id !== undefined)
|
|
26
|
+
dispatchById.set(event.data.id, { time: event.time, text: event.data.text });
|
|
27
|
+
}
|
|
28
|
+
const byTask = new Map();
|
|
29
|
+
for (const row of deliveries) {
|
|
30
|
+
if (row.task === undefined)
|
|
31
|
+
continue;
|
|
32
|
+
const list = byTask.get(row.task) ?? [];
|
|
33
|
+
list.push(row);
|
|
34
|
+
byTask.set(row.task, list);
|
|
35
|
+
}
|
|
36
|
+
const roots = [...byTask.keys()]
|
|
37
|
+
.map(id => ({ id, root: messages.get(id) ?? dispatchById.get(id) }))
|
|
38
|
+
.filter((entry) => entry.root !== undefined)
|
|
39
|
+
.sort((a, b) => a.root.time - b.root.time);
|
|
40
|
+
const jobs = roots.map(({ id, root }, index) => {
|
|
41
|
+
const rows = byTask.get(id) ?? [];
|
|
42
|
+
const follow = [...links.entries()]
|
|
43
|
+
.filter(([message, link]) => link.task === id && message !== id)
|
|
44
|
+
.flatMap(([message, link]) => {
|
|
45
|
+
const found = messages.get(message);
|
|
46
|
+
return found === undefined ? [] : [{ ...found, by: link.by, ...link.probability === undefined ? {} : { probability: link.probability } }];
|
|
47
|
+
});
|
|
48
|
+
const open = rows.some(row => row.status === 'queued' || row.status === 'running' || row.status === 'held');
|
|
49
|
+
const attention = rows.some(row => row.status === 'uncertain');
|
|
50
|
+
// A round with an unknown outcome needs the human even in a stopped task.
|
|
51
|
+
const status = attention ? 'attention' : stopped.has(id) && !open ? 'stopped' : open ? 'running' : 'done';
|
|
52
|
+
const ended = Math.max(...rows.map(row => row.endedAt ?? row.queuedAt));
|
|
53
|
+
const goal = rows.find(row => row.plan !== undefined)?.plan?.goalId;
|
|
54
|
+
return {
|
|
55
|
+
id, number: index + 1,
|
|
56
|
+
// Leading @-mentions address members; the request is what follows.
|
|
57
|
+
title: (root.text.replace(/^(?:@\S+\s+)+/, '').split('\n', 1)[0] ?? '').slice(0, 120),
|
|
58
|
+
messages: [{ id, time: root.time, text: root.text }, ...follow].sort((a, b) => a.time - b.time),
|
|
59
|
+
status, startedAt: root.time,
|
|
60
|
+
...open ? {} : { endedAt: ended },
|
|
61
|
+
deliveries: rows.map(row => row.id),
|
|
62
|
+
members: [...new Set(rows.map(row => row.memberId))],
|
|
63
|
+
...goal === undefined ? {} : { goalId: goal },
|
|
64
|
+
};
|
|
65
|
+
});
|
|
66
|
+
return jobs.reverse();
|
|
67
|
+
}
|
|
68
|
+
/** Resolve the coordinator's `task` argument (`new`, a 「任务 N」 number, or a root id) against the room's tasks. */
|
|
69
|
+
export function resolveTaskRef(jobs, ref) {
|
|
70
|
+
const value = ref.trim().replace(/^任务\s*/, '').replace(/^#/, '');
|
|
71
|
+
if (value === 'new' || value === '新')
|
|
72
|
+
return 'new';
|
|
73
|
+
const number = Number(value);
|
|
74
|
+
if (Number.isInteger(number) && number > 0)
|
|
75
|
+
return jobs.find(job => job.number === number)?.id;
|
|
76
|
+
return jobs.find(job => job.id === value)?.id;
|
|
77
|
+
}
|
package/lib/types/journal.d.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import type { SessionEvent } from '@deepseek-ai/dsh-session/types';
|
|
2
|
-
import type { RoomMember, RoomState, RoomTask, RoomTaskProgress } from './types.ts';
|
|
2
|
+
import type { RoomMember, RoomSettings, RoomState, RoomTask, RoomTaskProgress } from './types.ts';
|
|
3
3
|
/**
|
|
4
4
|
* The native DSH member's addressing name in new rooms. It starts as the
|
|
5
5
|
* coordinator; bare messages follow the selected coordinator thereafter.
|
|
6
6
|
* Existing journals retain their recorded names, including legacy main.
|
|
7
7
|
*/
|
|
8
8
|
export declare const MAIN_AGENT_MEMBER = "dsh";
|
|
9
|
+
/** Longest accepted public member role (`RoomMember.role`), in characters. */
|
|
10
|
+
export declare const ROOM_ROLE_MAX = 30;
|
|
9
11
|
/**
|
|
10
12
|
* Every `room/*` session-event type this package introduces. Room registers
|
|
11
13
|
* them into the harness's KNOWN_SESSION_EVENT_TYPES persistence catalog at
|
|
@@ -18,7 +20,9 @@ export declare const MAIN_AGENT_MEMBER = "dsh";
|
|
|
18
20
|
* in-place add is that surface's temporary form — migrate to the official
|
|
19
21
|
* one when it lands upstream.
|
|
20
22
|
*/
|
|
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'];
|
|
23
|
+
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/approval', 'room/approval-resolved', 'room/question', 'room/question-resolved', 'room/task-added', 'room/task-updated', 'room/task-edited', 'room/goal', 'room/settings', 'room/report-gate', 'room/decision', 'room/task-link', 'room/job-stopped', 'room/help', 'room/help-message', 'room/help-state', 'room/help-in', 'room/help-link', 'room/help-unlink', 'room/help-nudge-dismissed'];
|
|
24
|
+
/** Collaboration settings of a room that never changed them. */
|
|
25
|
+
export declare const DEFAULT_ROOM_SETTINGS: RoomSettings;
|
|
22
26
|
/** Whether an event log carries the room identity marker. */
|
|
23
27
|
export declare function isRoomLog(events: readonly SessionEvent[]): boolean;
|
|
24
28
|
/**
|
|
@@ -32,20 +36,6 @@ export declare function parseMentions(raw: string): {
|
|
|
32
36
|
readonly targets: readonly string[];
|
|
33
37
|
readonly text: string;
|
|
34
38
|
};
|
|
35
|
-
/**
|
|
36
|
-
* The fallback notification channel: a member reply's TRAILING own line of
|
|
37
|
-
* the form `@name <content>` (the format the roster injection teaches).
|
|
38
|
-
* Only the last line counts — an `@name` inside prose is a mention, not a
|
|
39
|
-
* notification. Leading whitespace on that line is tolerated (models indent
|
|
40
|
-
* under their own bullet/list style). The caller checks the target against
|
|
41
|
-
* the roster.
|
|
42
|
-
* @param text - the member's reply text.
|
|
43
|
-
* @returns the parsed directive, or undefined.
|
|
44
|
-
*/
|
|
45
|
-
export declare function parseRelayDirective(text: string): {
|
|
46
|
-
readonly to: string;
|
|
47
|
-
readonly content: string;
|
|
48
|
-
} | undefined;
|
|
49
39
|
/**
|
|
50
40
|
* Fold a session's event log into the room state.
|
|
51
41
|
*
|
|
@@ -81,20 +71,20 @@ export declare function taskProgress(tasks: readonly RoomTask[]): RoomTaskProgre
|
|
|
81
71
|
*/
|
|
82
72
|
export declare function previousCursor(events: readonly SessionEvent[], member: string, beforeSeq: number): number | undefined;
|
|
83
73
|
/**
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
* member
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
* never seen the roster, so their first dispatch always counts as stale.
|
|
74
|
+
* What changed in the room since a member's last dispatch, as one-line
|
|
75
|
+
* notes for the next prompt: other members joining or leaving, renames, role
|
|
76
|
+
* changes, the member's own role change, and a goal change. The full roster
|
|
77
|
+
* rides only the first dispatch's briefing; later dispatches carry these
|
|
78
|
+
* deltas instead (the member's own session holds the rest), and a member who
|
|
79
|
+
* wants the whole picture calls `room_read`. Model, cwd and delegation-handle
|
|
80
|
+
* updates never reach a prompt. A member with no cursor gets no deltas — the
|
|
81
|
+
* briefing already covers everything.
|
|
93
82
|
* @param events - the session's event log.
|
|
94
83
|
* @param cursor - the member's pre-dispatch cursor ({@link previousCursor}).
|
|
95
|
-
* @
|
|
84
|
+
* @param self - the member's CURRENT name (its own rename is not news to it).
|
|
85
|
+
* @returns the change notes (empty when nothing changed).
|
|
96
86
|
*/
|
|
97
|
-
export declare function
|
|
87
|
+
export declare function roomChangesSince(events: readonly SessionEvent[], cursor: number | undefined, self: string): readonly string[];
|
|
98
88
|
/**
|
|
99
89
|
* The role-instructions carry for the next dispatch to a member: 'initial'
|
|
100
90
|
* when the member has never been dispatched (their instructions open the
|
package/lib/types/journal.js
CHANGED
|
@@ -11,12 +11,16 @@
|
|
|
11
11
|
* @module @khorsheed/dsh-room/journal
|
|
12
12
|
*/
|
|
13
13
|
import { replayDeliveries } from './deliveries.js';
|
|
14
|
+
import { replayJobs } from './jobs.js';
|
|
15
|
+
import { replayHelps, replayLinks } from './helps.js';
|
|
14
16
|
/**
|
|
15
17
|
* The native DSH member's addressing name in new rooms. It starts as the
|
|
16
18
|
* coordinator; bare messages follow the selected coordinator thereafter.
|
|
17
19
|
* Existing journals retain their recorded names, including legacy main.
|
|
18
20
|
*/
|
|
19
21
|
export const MAIN_AGENT_MEMBER = 'dsh';
|
|
22
|
+
/** Longest accepted public member role (`RoomMember.role`), in characters. */
|
|
23
|
+
export const ROOM_ROLE_MAX = 30;
|
|
20
24
|
/**
|
|
21
25
|
* Every `room/*` session-event type this package introduces. Room registers
|
|
22
26
|
* them into the harness's KNOWN_SESSION_EVENT_TYPES persistence catalog at
|
|
@@ -43,11 +47,29 @@ export const ROOM_EVENT_TYPES = [
|
|
|
43
47
|
'room/execution-metadata',
|
|
44
48
|
'room/relay',
|
|
45
49
|
'room/relay-resolved',
|
|
50
|
+
'room/approval',
|
|
51
|
+
'room/approval-resolved',
|
|
52
|
+
'room/question',
|
|
53
|
+
'room/question-resolved',
|
|
46
54
|
'room/task-added',
|
|
47
55
|
'room/task-updated',
|
|
48
56
|
'room/task-edited',
|
|
49
57
|
'room/goal',
|
|
58
|
+
'room/settings',
|
|
59
|
+
'room/report-gate',
|
|
60
|
+
'room/decision',
|
|
61
|
+
'room/task-link',
|
|
62
|
+
'room/job-stopped',
|
|
63
|
+
'room/help',
|
|
64
|
+
'room/help-message',
|
|
65
|
+
'room/help-state',
|
|
66
|
+
'room/help-in',
|
|
67
|
+
'room/help-link',
|
|
68
|
+
'room/help-unlink',
|
|
69
|
+
'room/help-nudge-dismissed',
|
|
50
70
|
];
|
|
71
|
+
/** Collaboration settings of a room that never changed them. */
|
|
72
|
+
export const DEFAULT_ROOM_SETTINGS = { memberMessages: 'free', hopLimit: 10, paused: false, reportTiming: 'judge', autoTaskFiling: true, reportThreshold: 0.5, reportWaitMinutes: 3, helpReminderMinutes: 30 };
|
|
51
73
|
/** Whether an event log carries the room identity marker. */
|
|
52
74
|
export function isRoomLog(events) {
|
|
53
75
|
return events.some(event => event.type === 'room/created');
|
|
@@ -64,7 +86,9 @@ export function parseMentions(raw) {
|
|
|
64
86
|
let rest = raw.trim();
|
|
65
87
|
for (;;) {
|
|
66
88
|
const match = /^@(\S+)(?:\s+|$)/.exec(rest);
|
|
67
|
-
|
|
89
|
+
// A host reference (`@[title](dsh-session:…)`, the @ menu's session chip)
|
|
90
|
+
// names another session, never a member: it stays in the text.
|
|
91
|
+
if (match === null || match[1].startsWith('['))
|
|
68
92
|
break;
|
|
69
93
|
const name = match[1];
|
|
70
94
|
if (!targets.includes(name))
|
|
@@ -73,26 +97,6 @@ export function parseMentions(raw) {
|
|
|
73
97
|
}
|
|
74
98
|
return { targets, text: rest };
|
|
75
99
|
}
|
|
76
|
-
/**
|
|
77
|
-
* The fallback notification channel: a member reply's TRAILING own line of
|
|
78
|
-
* the form `@name <content>` (the format the roster injection teaches).
|
|
79
|
-
* Only the last line counts — an `@name` inside prose is a mention, not a
|
|
80
|
-
* notification. Leading whitespace on that line is tolerated (models indent
|
|
81
|
-
* under their own bullet/list style). The caller checks the target against
|
|
82
|
-
* the roster.
|
|
83
|
-
* @param text - the member's reply text.
|
|
84
|
-
* @returns the parsed directive, or undefined.
|
|
85
|
-
*/
|
|
86
|
-
export function parseRelayDirective(text) {
|
|
87
|
-
const trimmed = text.trimEnd();
|
|
88
|
-
if (trimmed === '')
|
|
89
|
-
return undefined;
|
|
90
|
-
const last = trimmed.slice(trimmed.lastIndexOf('\n') + 1).trimStart();
|
|
91
|
-
const match = /^@(\S+)\s+(\S[\s\S]*)$/.exec(last);
|
|
92
|
-
if (match === null)
|
|
93
|
-
return undefined;
|
|
94
|
-
return { to: match[1], content: match[2].trim() };
|
|
95
|
-
}
|
|
96
100
|
/**
|
|
97
101
|
* Fold a session's event log into the room state.
|
|
98
102
|
*
|
|
@@ -111,15 +115,26 @@ export function replay(events) {
|
|
|
111
115
|
const members = [];
|
|
112
116
|
const byName = new Map();
|
|
113
117
|
const relays = [];
|
|
118
|
+
const approvals = new Map();
|
|
119
|
+
const questions = new Map();
|
|
114
120
|
const relayById = new Map();
|
|
115
121
|
const tasks = [];
|
|
116
122
|
const taskById = new Map();
|
|
117
123
|
const runs = new Map();
|
|
118
124
|
const executions = new Map();
|
|
119
125
|
let goal;
|
|
126
|
+
let settings = DEFAULT_ROOM_SETTINGS;
|
|
120
127
|
let coordinator;
|
|
128
|
+
let latestReplySeq;
|
|
129
|
+
let latestSpeechSeq;
|
|
121
130
|
for (const event of events) {
|
|
122
131
|
switch (event.type) {
|
|
132
|
+
case 'room/speech':
|
|
133
|
+
latestSpeechSeq = event.seq;
|
|
134
|
+
// A reply to the room (not a report to another member).
|
|
135
|
+
if (event.data.reportedTo === undefined)
|
|
136
|
+
latestReplySeq = event.seq;
|
|
137
|
+
break;
|
|
123
138
|
case 'room/coordinator': {
|
|
124
139
|
if (event.data.revision > (coordinator?.revision ?? 0))
|
|
125
140
|
coordinator = event.data;
|
|
@@ -135,8 +150,11 @@ export function replay(events) {
|
|
|
135
150
|
invitedBy: event.data.invitedBy,
|
|
136
151
|
...event.data.provider === undefined ? {} : { provider: event.data.provider },
|
|
137
152
|
...event.data.instructions === undefined ? {} : { instructions: event.data.instructions },
|
|
153
|
+
...event.data.role === undefined ? {} : { role: event.data.role },
|
|
154
|
+
...event.data.preset === undefined ? {} : { preset: event.data.preset },
|
|
138
155
|
...event.data.cwd === undefined ? {} : { cwd: event.data.cwd },
|
|
139
156
|
...event.data.model === undefined ? {} : { model: event.data.model },
|
|
157
|
+
...event.data.effort === undefined ? {} : { effort: event.data.effort },
|
|
140
158
|
...event.data.childSessionId === undefined ? {} : { childSessionId: event.data.childSessionId },
|
|
141
159
|
};
|
|
142
160
|
byName.set(added.name, added);
|
|
@@ -152,14 +170,20 @@ export function replay(events) {
|
|
|
152
170
|
const withoutInstructions = event.data.instructions === null
|
|
153
171
|
? (({ instructions: _cleared, ...rest }) => rest)(member)
|
|
154
172
|
: { ...member, ...event.data.instructions === undefined ? {} : { instructions: event.data.instructions } };
|
|
173
|
+
const withoutRole = event.data.role === null
|
|
174
|
+
? (({ role: _cleared, ...rest }) => rest)(withoutInstructions)
|
|
175
|
+
: { ...withoutInstructions, ...event.data.role === undefined ? {} : { role: event.data.role } };
|
|
155
176
|
const withoutCwd = event.data.cwd === null
|
|
156
|
-
? (({ cwd: _cleared, ...rest }) => rest)(
|
|
157
|
-
: { ...
|
|
177
|
+
? (({ cwd: _cleared, ...rest }) => rest)(withoutRole)
|
|
178
|
+
: { ...withoutRole, ...event.data.cwd === undefined ? {} : { cwd: event.data.cwd } };
|
|
158
179
|
const withoutModel = event.data.model === null
|
|
159
180
|
? (({ model: _cleared, ...rest }) => rest)(withoutCwd)
|
|
160
181
|
: { ...withoutCwd, ...event.data.model === undefined ? {} : { model: event.data.model } };
|
|
182
|
+
const withoutEffort = event.data.effort === null
|
|
183
|
+
? (({ effort: _cleared, ...rest }) => rest)(withoutModel)
|
|
184
|
+
: { ...withoutModel, ...event.data.effort === undefined ? {} : { effort: event.data.effort } };
|
|
161
185
|
const patched = {
|
|
162
|
-
...
|
|
186
|
+
...withoutEffort,
|
|
163
187
|
...event.data.childSessionId === undefined ? {} : { childSessionId: event.data.childSessionId },
|
|
164
188
|
};
|
|
165
189
|
// First admission can precede publication of the native child handle.
|
|
@@ -233,11 +257,25 @@ export function replay(events) {
|
|
|
233
257
|
content: event.data.content,
|
|
234
258
|
state: 'pending',
|
|
235
259
|
...event.data.provenance === undefined ? {} : { provenance: event.data.provenance },
|
|
260
|
+
...event.data.reason === undefined ? {} : { reason: event.data.reason },
|
|
261
|
+
...event.data.intent === undefined ? {} : { intent: event.data.intent },
|
|
236
262
|
};
|
|
237
263
|
relayById.set(relay.id, relay);
|
|
238
264
|
relays.push(relay);
|
|
239
265
|
break;
|
|
240
266
|
}
|
|
267
|
+
case 'room/approval':
|
|
268
|
+
approvals.set(event.data.id, { id: event.data.id, member: event.data.member, tool: event.data.tool, ...event.data.reason === undefined ? {} : { reason: event.data.reason }, time: event.time });
|
|
269
|
+
break;
|
|
270
|
+
case 'room/question':
|
|
271
|
+
questions.set(event.data.id, { id: event.data.id, member: event.data.member, questions: event.data.questions, time: event.time });
|
|
272
|
+
break;
|
|
273
|
+
case 'room/question-resolved':
|
|
274
|
+
questions.delete(event.data.id);
|
|
275
|
+
break;
|
|
276
|
+
case 'room/approval-resolved':
|
|
277
|
+
approvals.delete(event.data.id);
|
|
278
|
+
break;
|
|
241
279
|
case 'room/relay-resolved': {
|
|
242
280
|
const relay = relayById.get(event.data.id);
|
|
243
281
|
if (relay === undefined)
|
|
@@ -284,12 +322,26 @@ export function replay(events) {
|
|
|
284
322
|
const edited = {
|
|
285
323
|
...base,
|
|
286
324
|
...event.data.title === undefined ? {} : { title: event.data.title },
|
|
325
|
+
...event.data.member === undefined ? {} : { member: event.data.member },
|
|
287
326
|
updatedAt: event.time,
|
|
288
327
|
};
|
|
289
328
|
taskById.set(edited.id, edited);
|
|
290
329
|
tasks[tasks.indexOf(task)] = edited;
|
|
291
330
|
break;
|
|
292
331
|
}
|
|
332
|
+
case 'room/settings': {
|
|
333
|
+
settings = {
|
|
334
|
+
memberMessages: event.data.memberMessages ?? settings.memberMessages,
|
|
335
|
+
hopLimit: event.data.hopLimit ?? settings.hopLimit,
|
|
336
|
+
paused: event.data.paused ?? settings.paused,
|
|
337
|
+
reportTiming: event.data.reportTiming ?? settings.reportTiming,
|
|
338
|
+
autoTaskFiling: event.data.autoTaskFiling ?? settings.autoTaskFiling,
|
|
339
|
+
reportThreshold: event.data.reportThreshold ?? settings.reportThreshold,
|
|
340
|
+
reportWaitMinutes: event.data.reportWaitMinutes ?? settings.reportWaitMinutes,
|
|
341
|
+
helpReminderMinutes: event.data.helpReminderMinutes ?? settings.helpReminderMinutes,
|
|
342
|
+
};
|
|
343
|
+
break;
|
|
344
|
+
}
|
|
293
345
|
case 'room/goal': {
|
|
294
346
|
// Latest goal event wins; a blank text clears the goal.
|
|
295
347
|
const text = event.data.text.trim();
|
|
@@ -337,7 +389,14 @@ export function replay(events) {
|
|
|
337
389
|
const planEvent = events.filter(event => event.type === 'room/plan-state').at(-1);
|
|
338
390
|
const plan = planEvent?.type === 'room/plan-state' ? (({ requests: _requests, ...view }) => view)(planEvent.data) : undefined;
|
|
339
391
|
const deliveries = replayDeliveries(events);
|
|
340
|
-
|
|
392
|
+
const jobs = deliveries.length === 0 ? [] : replayJobs(events, deliveries);
|
|
393
|
+
const helps = replayHelps(events);
|
|
394
|
+
const links = replayLinks(events);
|
|
395
|
+
return {
|
|
396
|
+
...helps.length === 0 ? {} : { helps },
|
|
397
|
+
...links.linked.length === 0 ? {} : { linked: links.linked },
|
|
398
|
+
...links.nudgesDismissed.length === 0 ? {} : { helpNudgesDismissed: links.nudgesDismissed }, members, relays, ...approvals.size === 0 ? {} : { approvals: [...approvals.values()] }, ...questions.size === 0 ? {} : { questions: [...questions.values()] }, tasks, ...executions.size === 0 ? {} : { executions: [...executions.values()] }, ...plan === undefined ? {} : { plan }, ...deliveries.length === 0 ? {} : { deliveries }, runs: [...runs.values()], ...coordinator === undefined ? {} : { coordinator }, ...goal === undefined ? {} : { goal }, ...latestReplySeq === undefined ? {} : { latestReplySeq }, ...latestSpeechSeq === undefined ? {} : { latestSpeechSeq }, settings, ...jobs.length === 0 ? {} : { jobs }
|
|
399
|
+
};
|
|
341
400
|
}
|
|
342
401
|
/**
|
|
343
402
|
* The goal progress pair: done tasks over the COUNTABLE total — cancelled
|
|
@@ -371,32 +430,50 @@ export function previousCursor(events, member, beforeSeq) {
|
|
|
371
430
|
return cursor;
|
|
372
431
|
}
|
|
373
432
|
/**
|
|
374
|
-
*
|
|
375
|
-
*
|
|
376
|
-
* member
|
|
377
|
-
*
|
|
378
|
-
*
|
|
379
|
-
*
|
|
380
|
-
*
|
|
381
|
-
*
|
|
382
|
-
* never seen the roster, so their first dispatch always counts as stale.
|
|
433
|
+
* What changed in the room since a member's last dispatch, as one-line
|
|
434
|
+
* notes for the next prompt: other members joining or leaving, renames, role
|
|
435
|
+
* changes, the member's own role change, and a goal change. The full roster
|
|
436
|
+
* rides only the first dispatch's briefing; later dispatches carry these
|
|
437
|
+
* deltas instead (the member's own session holds the rest), and a member who
|
|
438
|
+
* wants the whole picture calls `room_read`. Model, cwd and delegation-handle
|
|
439
|
+
* updates never reach a prompt. A member with no cursor gets no deltas — the
|
|
440
|
+
* briefing already covers everything.
|
|
383
441
|
* @param events - the session's event log.
|
|
384
442
|
* @param cursor - the member's pre-dispatch cursor ({@link previousCursor}).
|
|
385
|
-
* @
|
|
443
|
+
* @param self - the member's CURRENT name (its own rename is not news to it).
|
|
444
|
+
* @returns the change notes (empty when nothing changed).
|
|
386
445
|
*/
|
|
387
|
-
export function
|
|
446
|
+
export function roomChangesSince(events, cursor, self) {
|
|
388
447
|
if (cursor === undefined)
|
|
389
|
-
return
|
|
448
|
+
return [];
|
|
449
|
+
const notes = [];
|
|
450
|
+
let goal;
|
|
390
451
|
for (const event of events) {
|
|
391
452
|
if (event.seq <= cursor)
|
|
392
453
|
continue;
|
|
393
|
-
if (event.type === 'room/member-added'
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
454
|
+
if (event.type === 'room/member-added' && event.data.name !== self && event.data.kind !== 'main-agent') {
|
|
455
|
+
const detail = [event.data.role, event.data.provider].filter(Boolean).join(' · ');
|
|
456
|
+
notes.push(`${event.data.name} 加入${detail === '' ? '' : `(${detail})`}`);
|
|
457
|
+
}
|
|
458
|
+
else if (event.type === 'room/member-removed' && event.data.name !== self) {
|
|
459
|
+
notes.push(`${event.data.name} 离开`);
|
|
460
|
+
}
|
|
461
|
+
else if (event.type === 'room/member-updated') {
|
|
462
|
+
if (event.data.rename !== undefined && event.data.rename !== self)
|
|
463
|
+
notes.push(`${event.data.name} 改名为 ${event.data.rename}`);
|
|
464
|
+
if (event.data.role !== undefined) {
|
|
465
|
+
const who = event.data.rename ?? event.data.name;
|
|
466
|
+
const subject = who === self ? '你的职责' : `${who} 的职责`;
|
|
467
|
+
notes.push(event.data.role === null ? `${subject}已清除` : `${subject}改为:${event.data.role}`);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
else if (event.type === 'room/goal') {
|
|
471
|
+
goal = { text: event.data.text.trim() };
|
|
472
|
+
}
|
|
398
473
|
}
|
|
399
|
-
|
|
474
|
+
if (goal !== undefined)
|
|
475
|
+
notes.push(goal.text === '' ? '房间目标已清除' : `房间目标更新为:${goal.text}`);
|
|
476
|
+
return notes;
|
|
400
477
|
}
|
|
401
478
|
/**
|
|
402
479
|
* The role-instructions carry for the next dispatch to a member: 'initial'
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { Session } from '@deepseek-ai/dsh-session';
|
|
2
|
+
import type { SessionEvent } from '@deepseek-ai/dsh-session/types';
|
|
3
|
+
import type { RoomHeldDispatch, RoomHoldReason, RoomMember, RoomRelay, RoomState } from './types.ts';
|
|
4
|
+
/** `RoomHeldDispatch.task` marker: this send starts a task rooted at the dispatch itself. */
|
|
5
|
+
export declare const SELF_TASK = "@self";
|
|
6
|
+
/** The id members and the UI use for a dispatch. */
|
|
7
|
+
export declare function messageId(seq: number): string;
|
|
8
|
+
/** Parse `m12` / `#m12` / `12` back to a dispatch seq. */
|
|
9
|
+
export declare function parseMessageRef(ref: string | undefined): number | undefined;
|
|
10
|
+
/** The hop a dispatch sits at (legacy dispatches count as a chain start). */
|
|
11
|
+
export declare function dispatchHop(events: readonly SessionEvent[], seq: number): number;
|
|
12
|
+
/**
|
|
13
|
+
* The hop of whatever a member is working on right now: its running
|
|
14
|
+
* delivery's hop, or 0 when it is not inside a room delivery (a human
|
|
15
|
+
* chatting with the room's agent directly) so its sends start a chain.
|
|
16
|
+
*/
|
|
17
|
+
export declare function workingHop(events: readonly SessionEvent[], state: RoomState, member: RoomMember): number;
|
|
18
|
+
/**
|
|
19
|
+
* Why a message must wait for the human, if it must. A paused room holds
|
|
20
|
+
* everything agents send; a chain past the limit stops; `confirm` holds
|
|
21
|
+
* member-to-member messages only (never the coordinator's delegations or
|
|
22
|
+
* reports, which the human already asked for).
|
|
23
|
+
*/
|
|
24
|
+
export declare function holdReason(state: RoomState, hop: number, origin: RoomHeldDispatch['origin']): RoomHoldReason | undefined;
|
|
25
|
+
/** One outgoing agent message. */
|
|
26
|
+
export interface OutgoingMessage {
|
|
27
|
+
readonly to: RoomMember;
|
|
28
|
+
readonly text: string;
|
|
29
|
+
readonly intent: RoomHeldDispatch;
|
|
30
|
+
/** Sender's display name for a held card (`from`). */
|
|
31
|
+
readonly fromName: string;
|
|
32
|
+
}
|
|
33
|
+
/** What {@link sendOrHold} did. */
|
|
34
|
+
export type SendOutcome = {
|
|
35
|
+
readonly delivered: true;
|
|
36
|
+
readonly seq: number;
|
|
37
|
+
} | {
|
|
38
|
+
readonly delivered: false;
|
|
39
|
+
readonly reason: RoomHoldReason;
|
|
40
|
+
readonly relayId: string;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Journal one agent message: a dispatch or a held relay with its intent. The caller
|
|
44
|
+
* flushes and, on delivery, hands `seq` to the engine.
|
|
45
|
+
* @param session - the room session.
|
|
46
|
+
* @param message - recipient, text and the would-be dispatch.
|
|
47
|
+
* @param skipHold - release path: deliver regardless of the hold rules.
|
|
48
|
+
* @returns the dispatch seq, or the hold.
|
|
49
|
+
*/
|
|
50
|
+
export declare function sendOrHold(session: Session, message: OutgoingMessage, skipHold?: boolean): SendOutcome;
|
|
51
|
+
/**
|
|
52
|
+
* The would-be dispatch of a held relay, re-anchored for release: a chain
|
|
53
|
+
* the human lets continue past the hop limit starts a fresh budget.
|
|
54
|
+
*/
|
|
55
|
+
export declare function releaseIntent(relay: RoomRelay): RoomHeldDispatch | undefined;
|
|
56
|
+
/**
|
|
57
|
+
* The envelope line a delivered message opens with, or undefined for a
|
|
58
|
+
* dispatch without one (legacy journals). Names resolve against the CURRENT
|
|
59
|
+
* roster, falling back to the last known name of a member who left.
|
|
60
|
+
* @param events - the room log.
|
|
61
|
+
* @param seq - the dispatch being delivered.
|
|
62
|
+
* @returns the envelope line.
|
|
63
|
+
*/
|
|
64
|
+
export declare function envelopeLine(events: readonly SessionEvent[], seq: number): string | undefined;
|
|
65
|
+
//# sourceMappingURL=messaging.d.ts.map
|