@khorsheed/dsh-room 0.2.2 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/README.en.md +29 -62
  3. package/README.i18n.yaml +2 -2
  4. package/README.md +28 -61
  5. package/lib/client.js +13097 -4182
  6. package/lib/index.js +4045 -730
  7. package/lib/journal-B4FOGO6S.js +482 -0
  8. package/lib/journal-Rid9AAnD.js +485 -0
  9. package/lib/tool.js +112 -166
  10. package/lib/typert.host.js +2031 -374
  11. package/lib/typert.remote-client.d.ts +34 -10
  12. package/lib/typert.remote-client.js +1599 -210
  13. package/lib/types/agent-setup.d.ts +1 -1
  14. package/lib/types/client/InviteAgentAction.js +4 -16
  15. package/lib/types/client/InviteDialog.d.ts +28 -6
  16. package/lib/types/client/InviteDialog.js +76 -12
  17. package/lib/types/client/MemberCard.d.ts +5 -1
  18. package/lib/types/client/MemberCard.js +4 -4
  19. package/lib/types/client/MembersView.d.ts +1 -1
  20. package/lib/types/client/MembersView.js +58 -22
  21. package/lib/types/client/RemoveMemberConfirm.d.ts +40 -0
  22. package/lib/types/client/RemoveMemberConfirm.js +85 -0
  23. package/lib/types/client/RoomApprovalView.d.ts +11 -0
  24. package/lib/types/client/RoomApprovalView.js +28 -0
  25. package/lib/types/client/RoomComposer.d.ts +1 -1
  26. package/lib/types/client/RoomComposer.js +195 -29
  27. package/lib/types/client/RoomDecisionView.d.ts +22 -0
  28. package/lib/types/client/RoomDecisionView.js +35 -0
  29. package/lib/types/client/RoomHelpView.d.ts +17 -0
  30. package/lib/types/client/RoomHelpView.js +56 -0
  31. package/lib/types/client/RoomInboxView.d.ts +10 -0
  32. package/lib/types/client/RoomInboxView.js +14 -0
  33. package/lib/types/client/RoomQuestionView.d.ts +13 -0
  34. package/lib/types/client/RoomQuestionView.js +61 -0
  35. package/lib/types/client/RoomRelayView.js +1 -1
  36. package/lib/types/client/RoomRunView.d.ts +3 -1
  37. package/lib/types/client/RoomRunView.js +12 -5
  38. package/lib/types/client/RoomSettingsView.d.ts +19 -0
  39. package/lib/types/client/RoomSettingsView.js +30 -0
  40. package/lib/types/client/RoomSpeechView.d.ts +22 -8
  41. package/lib/types/client/RoomSpeechView.js +94 -11
  42. package/lib/types/client/RoomStatsLine.d.ts +26 -5
  43. package/lib/types/client/RoomStatsLine.js +53 -7
  44. package/lib/types/client/RoomTaskCapsule.d.ts +19 -0
  45. package/lib/types/client/RoomTaskCapsule.js +37 -0
  46. package/lib/types/client/TasksTab.d.ts +41 -0
  47. package/lib/types/client/TasksTab.js +43 -0
  48. package/lib/types/client/TasksView.d.ts +44 -0
  49. package/lib/types/client/TasksView.js +149 -0
  50. package/lib/types/client/TeamView.d.ts +33 -0
  51. package/lib/types/client/TeamView.js +210 -0
  52. package/lib/types/client/at-menu.d.ts +127 -0
  53. package/lib/types/client/at-menu.js +173 -0
  54. package/lib/types/client/draft-chips.d.ts +67 -0
  55. package/lib/types/client/draft-chips.js +95 -0
  56. package/lib/types/client/executions.d.ts +0 -30
  57. package/lib/types/client/executions.js +4 -34
  58. package/lib/types/client/icons-local.d.ts +25 -0
  59. package/lib/types/client/icons-local.js +10 -0
  60. package/lib/types/client/icons.d.ts +2 -1
  61. package/lib/types/client/icons.js +1 -0
  62. package/lib/types/client/index.js +276 -72
  63. package/lib/types/client/locales.d.ts +482 -52
  64. package/lib/types/client/locales.js +490 -60
  65. package/lib/types/client/nodes.d.ts +112 -17
  66. package/lib/types/client/nodes.js +197 -26
  67. package/lib/types/client/slots.d.ts +117 -47
  68. package/lib/types/client/task-tag.d.ts +32 -0
  69. package/lib/types/client/task-tag.js +33 -0
  70. package/lib/types/client/tasks.d.ts +50 -0
  71. package/lib/types/client/tasks.js +82 -0
  72. package/lib/types/client/team.d.ts +73 -0
  73. package/lib/types/client/team.js +187 -0
  74. package/lib/types/decisions.d.ts +49 -0
  75. package/lib/types/decisions.js +101 -0
  76. package/lib/types/deliveries.d.ts +17 -1
  77. package/lib/types/deliveries.js +82 -6
  78. package/lib/types/dispatch.d.ts +92 -4
  79. package/lib/types/dispatch.js +477 -109
  80. package/lib/types/help.d.ts +137 -0
  81. package/lib/types/help.js +216 -0
  82. package/lib/types/helps.d.ts +26 -0
  83. package/lib/types/helps.js +47 -0
  84. package/lib/types/index.d.ts +292 -52
  85. package/lib/types/index.js +1415 -199
  86. package/lib/types/jobs.d.ts +22 -0
  87. package/lib/types/jobs.js +77 -0
  88. package/lib/types/journal.d.ts +17 -27
  89. package/lib/types/journal.js +120 -43
  90. package/lib/types/messaging.d.ts +65 -0
  91. package/lib/types/messaging.js +160 -0
  92. package/lib/types/plan-service.js +7 -1
  93. package/lib/types/report-gate.d.ts +81 -0
  94. package/lib/types/report-gate.js +89 -0
  95. package/lib/types/tool-definition.d.ts +43 -20
  96. package/lib/types/tool-definition.js +145 -212
  97. package/lib/types/tool.d.ts +2 -2
  98. package/lib/types/tool.js +3 -2
  99. package/lib/types/types.d.ts +755 -78
  100. package/locale/zh.json +1 -1
  101. package/package.json +3 -3
  102. package/lib/types/client/RoomActivityDock.d.ts +0 -19
  103. package/lib/types/client/RoomActivityDock.js +0 -105
  104. package/lib/types/client/RoomDockCapsules.d.ts +0 -64
  105. package/lib/types/client/RoomDockCapsules.js +0 -277
  106. package/lib/types/client/RoomPlanTab.d.ts +0 -15
  107. package/lib/types/client/RoomPlanTab.js +0 -13
  108. package/lib/types/client/RoomTaskLineView.d.ts +0 -13
  109. package/lib/types/client/RoomTaskLineView.js +0 -21
@@ -0,0 +1,160 @@
1
+ /**
2
+ * Member messaging: one path for every agent-originated message in a room —
3
+ * the coordinator's delegations, free member-to-member messages (`task`
4
+ * gets the recipient's reply reported back, `notify` is FYI), and delivery
5
+ * reports. Each message is either journaled as a `room/dispatch` and handed
6
+ * to the engine, or HELD as a pending relay carrying its would-be dispatch,
7
+ * because the room is paused, the causal chain reached the hop limit, or the
8
+ * room asks the human to confirm member messages. Releasing a held message
9
+ * creates exactly the dispatch it would have been.
10
+ *
11
+ * Every dispatch knows its `hop` (position in the chain that a human message
12
+ * starts) and renders an envelope line on delivery — who sent it, its
13
+ * `#m<seq>` id, and whether a reply is expected — so a member can tell
14
+ * concurrent requests apart and the report names the message it answers.
15
+ * @module @khorsheed/dsh-room/messaging
16
+ */
17
+ import { randomUUID } from 'node:crypto';
18
+ import { coordinatorMember, memberId, replay } from './journal.js';
19
+ /** `RoomHeldDispatch.task` marker: this send starts a task rooted at the dispatch itself. */
20
+ export const SELF_TASK = '@self';
21
+ /** The id members and the UI use for a dispatch. */
22
+ export function messageId(seq) {
23
+ return `m${seq}`;
24
+ }
25
+ /** Parse `m12` / `#m12` / `12` back to a dispatch seq. */
26
+ export function parseMessageRef(ref) {
27
+ const match = ref === undefined ? null : /^#?m?(\d+)$/.exec(ref.trim());
28
+ return match === null ? undefined : Number(match[1]);
29
+ }
30
+ /** The hop a dispatch sits at (legacy dispatches count as a chain start). */
31
+ export function dispatchHop(events, seq) {
32
+ const event = events.find(row => row.seq === seq);
33
+ return event?.type === 'room/dispatch' ? event.data.hop ?? 1 : 1;
34
+ }
35
+ /**
36
+ * The hop of whatever a member is working on right now: its running
37
+ * delivery's hop, or 0 when it is not inside a room delivery (a human
38
+ * chatting with the room's agent directly) so its sends start a chain.
39
+ */
40
+ export function workingHop(events, state, member) {
41
+ const run = state.runs.find(entry => entry.member === member.name && entry.state === 'running');
42
+ const seq = run?.runId === undefined ? undefined : Number(run.runId.split(':', 1)[0]);
43
+ return seq === undefined || !Number.isInteger(seq) ? 0 : dispatchHop(events, seq);
44
+ }
45
+ /**
46
+ * Why a message must wait for the human, if it must. A paused room holds
47
+ * everything agents send; a chain past the limit stops; `confirm` holds
48
+ * member-to-member messages only (never the coordinator's delegations or
49
+ * reports, which the human already asked for).
50
+ */
51
+ export function holdReason(state, hop, origin) {
52
+ const settings = state.settings;
53
+ if (settings === undefined)
54
+ return undefined;
55
+ if (settings.paused)
56
+ return 'paused';
57
+ if (hop > settings.hopLimit)
58
+ return 'hop-limit';
59
+ if (origin === 'member' && settings.memberMessages === 'confirm')
60
+ return 'confirm';
61
+ return undefined;
62
+ }
63
+ /**
64
+ * Journal one agent message: a dispatch or a held relay with its intent. The caller
65
+ * flushes and, on delivery, hands `seq` to the engine.
66
+ * @param session - the room session.
67
+ * @param message - recipient, text and the would-be dispatch.
68
+ * @param skipHold - release path: deliver regardless of the hold rules.
69
+ * @returns the dispatch seq, or the hold.
70
+ */
71
+ export function sendOrHold(session, message, skipHold = false) {
72
+ const events = session.snapshotEvents();
73
+ const state = replay(events);
74
+ const reason = skipHold ? undefined : holdReason(state, message.intent.hop, message.intent.origin);
75
+ if (reason !== undefined) {
76
+ const relayId = randomUUID();
77
+ session.append('room/relay', {
78
+ id: relayId, from: message.fromName, to: message.to.name, content: message.text, reason, intent: message.intent,
79
+ });
80
+ return { delivered: false, reason, relayId };
81
+ }
82
+ const targetId = memberId(events, message.to);
83
+ const intent = message.intent;
84
+ const id = randomUUID();
85
+ // A send that starts a task of its own (no human message behind it) roots the task at itself.
86
+ const task = intent.task === SELF_TASK ? id : intent.task;
87
+ const dispatch = session.append('room/dispatch', {
88
+ id, origin: intent.origin,
89
+ ...task === undefined ? {} : { task },
90
+ ...intent.parent === undefined ? {} : { parent: intent.parent }, targets: [message.to.name], targetIds: [targetId], text: message.text, hop: intent.hop,
91
+ ...intent.from === undefined ? {} : { from: intent.from },
92
+ ...intent.kind === undefined ? {} : { kind: intent.kind },
93
+ ...intent.inReplyTo === undefined ? {} : { inReplyTo: intent.inReplyTo },
94
+ ...intent.note === undefined ? {} : { note: intent.note },
95
+ ...intent.replyTo === undefined ? {} : { replyTo: intent.replyTo },
96
+ ...intent.reportFor === undefined ? {} : { reportFor: intent.reportFor },
97
+ ...intent.plan === undefined ? {} : { plan: intent.plan },
98
+ ...intent.help === undefined ? {} : { help: intent.help },
99
+ });
100
+ return { delivered: true, seq: dispatch.seq };
101
+ }
102
+ /**
103
+ * The would-be dispatch of a held relay, re-anchored for release: a chain
104
+ * the human lets continue past the hop limit starts a fresh budget.
105
+ */
106
+ export function releaseIntent(relay) {
107
+ if (relay.intent === undefined)
108
+ return undefined;
109
+ return relay.reason === 'hop-limit' ? { ...relay.intent, hop: 1 } : relay.intent;
110
+ }
111
+ const OUTCOME_LABEL = {
112
+ done: '完成', cancelled: '已取消', failed: '失败', uncertain: '结果不确定',
113
+ };
114
+ /**
115
+ * The envelope line a delivered message opens with, or undefined for a
116
+ * dispatch without one (legacy journals). Names resolve against the CURRENT
117
+ * roster, falling back to the last known name of a member who left.
118
+ * @param events - the room log.
119
+ * @param seq - the dispatch being delivered.
120
+ * @returns the envelope line.
121
+ */
122
+ export function envelopeLine(events, seq) {
123
+ const event = events.find(row => row.seq === seq);
124
+ if (event?.type !== 'room/dispatch')
125
+ return undefined;
126
+ const dispatch = event.data;
127
+ const state = replay(events);
128
+ const nameOf = (id) => id === undefined ? undefined
129
+ : state.members.find(member => memberId(events, member) === id)?.name
130
+ ?? events.reduce((name, row) => row.type === 'room/member-added' && row.data.id === id ? row.data.name : name, undefined);
131
+ const reply = dispatch.inReplyTo === undefined ? '' : ` · 回复 #${messageId(dispatch.inReplyTo)}`;
132
+ switch (dispatch.origin) {
133
+ case 'human':
134
+ case undefined:
135
+ // The human's own words need no envelope: an unmarked message is theirs.
136
+ return undefined;
137
+ case 'report': {
138
+ // Plan bookkeeping reports (goal-start:/reconcile:) carry their own text.
139
+ if (dispatch.reportFor === undefined || !/^\d+:/.test(dispatch.reportFor))
140
+ return undefined;
141
+ const source = Number(dispatch.reportFor.split(':', 1)[0]);
142
+ const sender = nameOf(dispatch.reportFor?.split(':').slice(1).join(':'));
143
+ const outcome = events.filter(row => row.type === 'room/delivery-state' && row.data.id === dispatch.reportFor).at(-1);
144
+ const label = outcome?.type === 'room/delivery-state' ? OUTCOME_LABEL[outcome.data.state] ?? outcome.data.state : '';
145
+ return `【#${messageId(source)} 的回报${sender === undefined ? '' : ` · 来自 ${sender}`}${label === '' ? '' : ` · ${label}`}】(这条回复用户已在房间里看到,不要复述)`;
146
+ }
147
+ case 'relay':
148
+ return undefined;
149
+ case 'help':
150
+ return `【来自协作间「${dispatch.help?.roomTitle ?? ''}」· ${dispatch.help?.from ?? ''} 的求助 · #${messageId(seq)}】(对方在另一个会话里,看不到这个房间;你这一轮的最终回复会自动送回对方,写成给对方看的答复,不用另外回复)`;
151
+ default: {
152
+ const sender = nameOf(dispatch.from)
153
+ ?? (dispatch.origin === 'coordinator' ? coordinatorMember(state, events)?.name : undefined) ?? '成员';
154
+ if (dispatch.kind === 'notify')
155
+ return `【来自 ${sender} · #${messageId(seq)} · 知会${reply}】`;
156
+ const back = dispatch.replyTo === undefined && dispatch.plan === undefined ? '' : '(你这一轮的最终回复会自动回报给对方,不用另外回复)';
157
+ return `【来自 ${sender} · #${messageId(seq)} · 派活${reply}】${back}`;
158
+ }
159
+ }
160
+ }
@@ -1,6 +1,7 @@
1
1
  import { randomUUID } from 'node:crypto';
2
2
  import { coordinatorMember, memberId, replay } from './journal.js';
3
3
  import { activePlanMs, dependencyResults, admitPlanAttempt, changePlan, parsePlanCommand, planCommandSignature, queuePlanAttempt, readyPlanTasks, recoverPlan, settlePlanAttempt } from './plan.js';
4
+ import { runChildSessionId, runChildTurn } from './dispatch.js';
4
5
  export function readPlan(room) {
5
6
  const event = room.snapshotEvents().filter(event => event.type === 'room/plan-state').at(-1);
6
7
  return event?.type === 'room/plan-state' ? structuredClone(event.data) : undefined;
@@ -192,9 +193,14 @@ export class PlanService {
192
193
  const deliveryId = `${dispatch.seq}:${target}`;
193
194
  const member = state.members.find(member => memberId(events, member) === target);
194
195
  const run = state.runs.find(run => run.member === member?.name);
195
- if (run?.runId === deliveryId && ['running', 'failed'].includes(run.state))
196
+ if (run?.runId === deliveryId && ['running', 'failed'].includes(run.state)) {
197
+ const childSessionId = runChildSessionId(room.id, member);
198
+ const childTurn = runChildTurn(childSessionId === undefined ? undefined : this.ctx.sessions.get(childSessionId));
196
199
  room.append('room/run-state', { member: run.member, runId: run.runId, startedAt: run.startedAt, state: attempt.status === 'submitted' ? 'done' : 'failed',
200
+ ...childSessionId === undefined ? {} : { childSessionId },
201
+ ...childTurn === undefined ? {} : { childTurn },
197
202
  ...attempt.status === 'failed' ? { error: attempt.submission?.summary ?? 'Human reconciled the attempt as failed' } : {} });
203
+ }
198
204
  room.append('room/delivery-state', { id: deliveryId, dispatchSeq: dispatch.seq, memberId: target,
199
205
  state: attempt.status === 'submitted' ? 'done' : 'failed', text: attempt.submission?.summary ?? 'Human reconciliation' });
200
206
  for (const oldReport of events) {
@@ -0,0 +1,81 @@
1
+ /**
2
+ * The report gate: decides only WHEN a delivery's report is handed to the
3
+ * member waiting on it — now (a round that wakes it), or held in its inbox
4
+ * until more results arrive. What the recipient then says or does next is
5
+ * its own call; the gate never edits, drops or summarizes a report, and a
6
+ * held report still reaches the recipient (a native agent's inbox, or carried
7
+ * by a CLI member's next round).
8
+ *
9
+ * Rules answer first: failures and uncertain outcomes, the last outstanding
10
+ * result, the `each` setting, and an unavailable decision model all hand the
11
+ * report over now. Otherwise the decision model answers the `report-timing`
12
+ * scenario question (decisions.ts) from what the recipient said it waits for,
13
+ * how long the human has been waiting, and the progress of everything still
14
+ * out; the room's threshold turns its probability into now / later.
15
+ * @module @khorsheed/dsh-room/report-gate
16
+ */
17
+ import type { Context } from '@deepseek-ai/cordis';
18
+ import type { SessionEvent } from '@deepseek-ai/dsh-session/types';
19
+ import type { RoomDecisionCall, RoomDelivery, RoomMember } from './types.ts';
20
+ /** The gate's verdict. */
21
+ export interface GateVerdict {
22
+ readonly hold: boolean;
23
+ readonly reason: string;
24
+ readonly probability?: number;
25
+ /** The decision-model call behind it, when one went out. */
26
+ readonly call?: RoomDecisionCall;
27
+ }
28
+ /** One arrived report, as the gate sees it. */
29
+ export interface ArrivedReport {
30
+ readonly from: string;
31
+ readonly outcome: string;
32
+ readonly text: string;
33
+ }
34
+ /** Deliveries still out that report to this member (queued or running), newest first. */
35
+ export declare function outstandingFor(deliveries: readonly RoomDelivery[], recipientId: string, except: string): readonly RoomDelivery[];
36
+ /**
37
+ * The state the decision model judges.
38
+ * @param events - the room log.
39
+ * @param recipient - the member waiting on the report.
40
+ * @param arrived - this report plus the ones held for the same member.
41
+ * @param now - the clock.
42
+ * @returns a JSON-safe state.
43
+ */
44
+ export declare function gateState(events: readonly SessionEvent[], recipient: RoomMember, arrived: readonly ArrivedReport[], now: number): unknown;
45
+ /**
46
+ * Decide whether to hand a report over now.
47
+ * @param ctx - host context (probes `typesafe`).
48
+ * @param input - what the rules and the model read.
49
+ * @returns the verdict with its reason.
50
+ */
51
+ export declare function judgeReport(ctx: Context, input: {
52
+ readonly timing: 'judge' | 'each';
53
+ readonly outcome: string;
54
+ readonly outstanding: number;
55
+ /** The "hand it over now" probability at or above which the report is delivered (default 0.5). */
56
+ readonly threshold?: number;
57
+ readonly state: () => unknown;
58
+ }): Promise<GateVerdict>;
59
+ /** One open task offered to the attribution question. */
60
+ export interface OpenTaskSummary {
61
+ readonly number: number;
62
+ readonly title: string;
63
+ readonly running: readonly string[];
64
+ }
65
+ /**
66
+ * Suggest which open task a new human message continues, or `new`. Only
67
+ * asked while some task is still open; the coordinator sees the suggestion
68
+ * and can override it when it dispatches (`task`).
69
+ * @param ctx - host context (probes `typesafe`).
70
+ * @param message - the new human message text.
71
+ * @param open - the open tasks.
72
+ * @returns the suggested task number (or `new`) with its probability and the call, or `new` by the `unavailable` rule.
73
+ */
74
+ export declare function suggestTask(ctx: Context, message: string, open: readonly OpenTaskSummary[]): Promise<{
75
+ readonly task: number | 'new';
76
+ /** The rule that decided (`unavailable` after a failed or missing model); absent = the model decided. */
77
+ readonly rule?: string;
78
+ readonly probability?: number;
79
+ readonly call?: RoomDecisionCall;
80
+ }>;
81
+ //# sourceMappingURL=report-gate.d.ts.map
@@ -0,0 +1,89 @@
1
+ import { memberId, replay } from './journal.js';
2
+ import { askDecisionModel, DECISION_SCENARIOS, hasDecisionModel } from './decisions.js';
3
+ /** Deliveries still out that report to this member (queued or running), newest first. */
4
+ export function outstandingFor(deliveries, recipientId, except) {
5
+ return deliveries.filter(row => row.id !== except && row.replyTo === recipientId && (row.status === 'queued' || row.status === 'running'));
6
+ }
7
+ /**
8
+ * The state the decision model judges.
9
+ * @param events - the room log.
10
+ * @param recipient - the member waiting on the report.
11
+ * @param arrived - this report plus the ones held for the same member.
12
+ * @param now - the clock.
13
+ * @returns a JSON-safe state.
14
+ */
15
+ export function gateState(events, recipient, arrived, now) {
16
+ const state = replay(events);
17
+ const recipientId = memberId(events, recipient);
18
+ const lastHuman = events.filter(event => event.type === 'user/message' && event.data.source.kind === 'user').at(-1);
19
+ const lastSpoken = events.filter(event => event.type === 'assistant/message' && event.seq > (lastHuman?.seq ?? -1)).at(-1);
20
+ const nameOf = (id) => state.members.find(member => memberId(events, member) === id)?.name ?? id;
21
+ const since = lastHuman?.seq ?? -1;
22
+ const batch = (state.deliveries ?? []).filter(row => row.replyTo === recipientId && row.dispatchSeq > since);
23
+ const running = (state.executions ?? []).filter(row => row.state === 'running');
24
+ const notes = batch.flatMap((row) => {
25
+ const event = events.find(entry => entry.seq === row.dispatchSeq);
26
+ return event?.type === 'room/dispatch' && event.data.note !== undefined ? [event.data.note] : [];
27
+ });
28
+ return {
29
+ userRequest: lastHuman?.type === 'user/message'
30
+ ? lastHuman.data.content.flatMap(block => block.type === 'text' ? [block.text] : []).join('\n').slice(0, 2000) : '',
31
+ coordinatorNotes: [...new Set(notes)],
32
+ userWaitingSeconds: Math.max(0, Math.round((now - (lastSpoken?.time ?? lastHuman?.time ?? now)) / 1000)),
33
+ progress: {
34
+ done: batch.filter(row => !['queued', 'running'].includes(row.status)).length,
35
+ total: batch.length,
36
+ running: batch.filter(row => row.status === 'queued' || row.status === 'running').map((row) => {
37
+ const execution = running.find(entry => entry.id === row.id);
38
+ return { member: nameOf(row.memberId), task: row.text.slice(0, 200), seconds: execution === undefined ? 0 : Math.round((now - execution.startedAt) / 1000) };
39
+ }),
40
+ },
41
+ arrived: arrived.map(report => ({ ...report, text: report.text.slice(0, 1500) })),
42
+ };
43
+ }
44
+ /**
45
+ * Decide whether to hand a report over now.
46
+ * @param ctx - host context (probes `typesafe`).
47
+ * @param input - what the rules and the model read.
48
+ * @returns the verdict with its reason.
49
+ */
50
+ export async function judgeReport(ctx, input) {
51
+ if (input.timing === 'each')
52
+ return { hold: false, reason: 'setting' };
53
+ if (input.outcome !== 'done')
54
+ return { hold: false, reason: 'failure' };
55
+ if (input.outstanding === 0)
56
+ return { hold: false, reason: 'last' };
57
+ if (!hasDecisionModel(ctx))
58
+ return { hold: false, reason: 'unavailable' };
59
+ const asked = await askDecisionModel(ctx, DECISION_SCENARIOS['report-timing'].question, input.state());
60
+ if (!asked.ok || typeof asked.answer !== 'number') {
61
+ return { hold: false, reason: 'unavailable', ...asked.call === undefined ? {} : { call: asked.call } };
62
+ }
63
+ return { hold: asked.answer < (input.threshold ?? 0.5), reason: 'model', probability: asked.answer, call: asked.call };
64
+ }
65
+ /**
66
+ * Suggest which open task a new human message continues, or `new`. Only
67
+ * asked while some task is still open; the coordinator sees the suggestion
68
+ * and can override it when it dispatches (`task`).
69
+ * @param ctx - host context (probes `typesafe`).
70
+ * @param message - the new human message text.
71
+ * @param open - the open tasks.
72
+ * @returns the suggested task number (or `new`) with its probability and the call, or `new` by the `unavailable` rule.
73
+ */
74
+ export async function suggestTask(ctx, message, open) {
75
+ if (!hasDecisionModel(ctx))
76
+ return { task: 'new', rule: 'unavailable' };
77
+ const scenario = DECISION_SCENARIOS['task-filing'].question;
78
+ const criteria = Object.fromEntries(open.map(job => [String(job.number),
79
+ `Continues task ${job.number}: "${job.title.slice(0, 200)}"${job.running.length === 0 ? '' : ` (still running: ${job.running.join(', ')})`}`]));
80
+ criteria['new'] = 'Starts a new, unrelated task';
81
+ const asked = await askDecisionModel(ctx, { ...scenario, criteria }, {
82
+ message: message.slice(0, 2000),
83
+ openTasks: open.map(job => ({ number: job.number, title: job.title.slice(0, 200), running: job.running })),
84
+ });
85
+ if (!asked.ok || typeof asked.answer !== 'string')
86
+ return { task: 'new', rule: 'unavailable', ...asked.call === undefined ? {} : { call: asked.call } };
87
+ const probability = asked.probabilities?.[asked.answer] ?? 0;
88
+ return { task: asked.answer === 'new' ? 'new' : Number(asked.answer), probability, call: asked.call };
89
+ }
@@ -1,4 +1,29 @@
1
- import type { RoomAddTaskRequest, RoomAddTaskResult, RoomCloseTaskRequest, RoomCloseTaskResult, RoomGetStateRequest, RoomGetStateResult, RoomInviteRequest, RoomInviteResult, RoomMessageRequest, RoomMessageResult, RoomUpdateTaskRequest, RoomUpdateTaskResult } from './types.ts';
1
+ /**
2
+ * The room's model-facing tool DEFINITIONS, `room_invite` and
3
+ * `room_message` (plus `room_read` and `room_plan`): the main
4
+ * agent's paths into the room it belongs to. `room_invite` and
5
+ * `room_message` PROMOTE: calling them in a plain session turns it into a
6
+ * room (ensureRoom) instead of rejecting.
7
+ * `room_invite` lands in the same invite path as the human dialog
8
+ * (`invitedBy: 'agent'`), producing an identical member record; `room_message` dispatches
9
+ * a message to a member through the same engine path as the human's
10
+ * `@member text`, minus the user bubble (the caller is the room's own agent).
11
+ *
12
+ * Since the tool-row split (M4') the core never REGISTERS these tools: the
13
+ * factories are consumed by the package's `./tool` subpath row
14
+ * ({@link file://./tool.ts}), which mounts them inside agent-preset
15
+ * compositions (session-granted, never at the profile root). The definitions
16
+ * are therefore returned UNTAGGED — the registering row applies its own
17
+ * tool-origin tag (AGENTS.md § Tool origin tagging — the owner is whichever
18
+ * package mounts the row).
19
+ *
20
+ * Internal module: not on the package's exports map (consumers reach it only
21
+ * through `./src/*`).
22
+ *
23
+ * @module @khorsheed/dsh-room
24
+ */
25
+ import type { Agent } from '@deepseek-ai/dsh-agent';
26
+ import type { RoomGetStateRequest, RoomGetStateResult, RoomInviteRequest, RoomInviteResult, RoomMessageRequest, RoomMessageResult } from './types.ts';
2
27
  /** The slice of RoomService the tool drives. */
3
28
  export interface RoomInviteToolBackend {
4
29
  /** Validate and journal an invitation (see RoomService.inviteMember). */
@@ -10,25 +35,6 @@ export interface RoomInviteToolBackend {
10
35
  * @returns a registry-ready tool definition.
11
36
  */
12
37
  export declare function roomInviteTool(backend: RoomInviteToolBackend): import("@deepseek-ai/dsh-tools").ToolDefinition;
13
- /** The slice of RoomService the room_task tool drives. */
14
- export interface RoomTaskToolBackend {
15
- /** Open a pending task on a member's lane (see RoomService.addTask). */
16
- addTask(request: RoomAddTaskRequest): Promise<RoomAddTaskResult>;
17
- /** Close an open task (see RoomService.closeTask). */
18
- closeTask(request: RoomCloseTaskRequest): Promise<RoomCloseTaskResult>;
19
- /** Edit an open task's title/blockedBy (see RoomService.updateTask). */
20
- updateTask(request: RoomUpdateTaskRequest): Promise<RoomUpdateTaskResult>;
21
- /** Replay the room state (the error texts read the roster/open tasks from it). */
22
- getState(request: RoomGetStateRequest): Promise<RoomGetStateResult>;
23
- }
24
- /**
25
- * Build the room_task tool definition bound to the room service. Every write
26
- * goes through the same host functions as the capsule UI, so a task the agent
27
- * opens is a public commitment indistinguishable from a human-added one.
28
- * @param backend - the room service.
29
- * @returns a registry-ready tool definition.
30
- */
31
- export declare function roomTaskTool(backend: RoomTaskToolBackend): import("@deepseek-ai/dsh-tools").ToolDefinition;
32
38
  /** The slice of RoomService the room_message tool drives. */
33
39
  export interface RoomMessageToolBackend {
34
40
  /** Dispatch one message to one member (see RoomService.messageMember). */
@@ -53,4 +59,21 @@ export declare function roomReadTool(backend: {
53
59
  export declare function roomPlanTool(backend: {
54
60
  commandPlan(sessionId: string, command: string): Promise<string>;
55
61
  }): import("@deepseek-ai/dsh-tools").ToolDefinition;
62
+ /** The slice of RoomService the help tool drives. */
63
+ export interface RoomHelpToolBackend {
64
+ /** Ask another session for help, or write again on a thread (see RoomService.askHelp). */
65
+ askHelp(request: {
66
+ readonly sessionId: string;
67
+ readonly session?: string;
68
+ readonly member?: string;
69
+ readonly help?: string;
70
+ readonly text: string;
71
+ }, caller: Agent): Promise<string>;
72
+ }
73
+ /**
74
+ * Build the room_help tool definition bound to the room service.
75
+ * @param backend - the room service.
76
+ * @returns a registry-ready tool definition.
77
+ */
78
+ export declare function roomHelpTool(backend: RoomHelpToolBackend): import("@deepseek-ai/dsh-tools").ToolDefinition;
56
79
  //# sourceMappingURL=tool-definition.d.ts.map