@khorsheed/dsh-room 0.1.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.i18n.yaml +6 -0
- package/README.md +48 -0
- package/README.zh.md +48 -0
- package/cordis.patch.yml +6 -0
- package/lib/client.js +11481 -0
- package/lib/index.js +3231 -0
- package/lib/invariant.js +26 -0
- package/lib/journal-CuRnWk66.js +435 -0
- package/lib/tool.js +350 -0
- package/lib/typert.host.d.ts +3 -0
- package/lib/typert.host.js +2615 -0
- package/lib/typert.remote-client.d.ts +55 -0
- package/lib/typert.remote-client.js +1711 -0
- package/lib/types/adapter.d.ts +42 -0
- package/lib/types/adapter.js +41 -0
- package/lib/types/agent-setup.d.ts +141 -0
- package/lib/types/agent-setup.js +145 -0
- package/lib/types/client/InviteAgentAction.d.ts +13 -0
- package/lib/types/client/InviteAgentAction.js +45 -0
- package/lib/types/client/InviteDialog.d.ts +90 -0
- package/lib/types/client/InviteDialog.js +216 -0
- package/lib/types/client/MemberCard.d.ts +33 -0
- package/lib/types/client/MemberCard.js +32 -0
- package/lib/types/client/MembersView.d.ts +5 -0
- package/lib/types/client/MembersView.js +168 -0
- package/lib/types/client/RoomComposer.d.ts +21 -0
- package/lib/types/client/RoomComposer.js +211 -0
- package/lib/types/client/RoomDockCapsules.d.ts +64 -0
- package/lib/types/client/RoomDockCapsules.js +277 -0
- package/lib/types/client/RoomEventView.d.ts +11 -0
- package/lib/types/client/RoomEventView.js +17 -0
- package/lib/types/client/RoomModelPicker.d.ts +18 -0
- package/lib/types/client/RoomModelPicker.js +134 -0
- package/lib/types/client/RoomPlanView.d.ts +17 -0
- package/lib/types/client/RoomPlanView.js +88 -0
- package/lib/types/client/RoomQueueStrip.d.ts +26 -0
- package/lib/types/client/RoomQueueStrip.js +28 -0
- package/lib/types/client/RoomRecoveryView.d.ts +11 -0
- package/lib/types/client/RoomRecoveryView.js +38 -0
- package/lib/types/client/RoomRelayView.d.ts +12 -0
- package/lib/types/client/RoomRelayView.js +20 -0
- package/lib/types/client/RoomRunView.d.ts +15 -0
- package/lib/types/client/RoomRunView.js +50 -0
- package/lib/types/client/RoomSpeechView.d.ts +18 -0
- package/lib/types/client/RoomSpeechView.js +62 -0
- package/lib/types/client/RoomStatsLine.d.ts +61 -0
- package/lib/types/client/RoomStatsLine.js +138 -0
- package/lib/types/client/RoomTaskLineView.d.ts +13 -0
- package/lib/types/client/RoomTaskLineView.js +21 -0
- package/lib/types/client/RoomTodoStrip.d.ts +24 -0
- package/lib/types/client/RoomTodoStrip.js +64 -0
- package/lib/types/client/format.d.ts +31 -0
- package/lib/types/client/format.js +48 -0
- package/lib/types/client/icons.d.ts +27 -0
- package/lib/types/client/icons.js +11 -0
- package/lib/types/client/index.d.ts +39 -0
- package/lib/types/client/index.js +471 -0
- package/lib/types/client/locales.d.ts +422 -0
- package/lib/types/client/locales.js +413 -0
- package/lib/types/client/member-color.d.ts +13 -0
- package/lib/types/client/member-color.js +22 -0
- package/lib/types/client/name-pool.d.ts +17 -0
- package/lib/types/client/name-pool.js +25 -0
- package/lib/types/client/nodes.d.ts +136 -0
- package/lib/types/client/nodes.js +212 -0
- package/lib/types/client/preset-visibility.d.ts +91 -0
- package/lib/types/client/preset-visibility.js +113 -0
- package/lib/types/client/request-ids.d.ts +7 -0
- package/lib/types/client/request-ids.js +21 -0
- package/lib/types/client/room-store.d.ts +137 -0
- package/lib/types/client/room-store.js +244 -0
- package/lib/types/client/slots.d.ts +319 -0
- package/lib/types/client/slots.js +1 -0
- package/lib/types/deliveries.d.ts +5 -0
- package/lib/types/deliveries.js +20 -0
- package/lib/types/dispatch.d.ts +105 -0
- package/lib/types/dispatch.js +467 -0
- package/lib/types/index.d.ts +301 -0
- package/lib/types/index.js +1200 -0
- package/lib/types/invariant.d.ts +16 -0
- package/lib/types/invariant.js +24 -0
- package/lib/types/journal.d.ts +117 -0
- package/lib/types/journal.js +434 -0
- package/lib/types/plan-service.d.ts +34 -0
- package/lib/types/plan-service.js +266 -0
- package/lib/types/plan.d.ts +168 -0
- package/lib/types/plan.js +494 -0
- package/lib/types/tool.d.ts +56 -0
- package/lib/types/tool.js +389 -0
- package/lib/types/types.d.ts +813 -0
- package/lib/types/types.js +1 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +131 -0
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { coordinatorMember, memberId, replay } from './journal.js';
|
|
3
|
+
import { activePlanMs, dependencyResults, admitPlanAttempt, changePlan, parsePlanCommand, planCommandSignature, queuePlanAttempt, readyPlanTasks, recoverPlan, settlePlanAttempt } from './plan.js';
|
|
4
|
+
export function readPlan(room) {
|
|
5
|
+
const event = room.snapshotEvents().filter(event => event.type === 'room/plan-state').at(-1);
|
|
6
|
+
return event?.type === 'room/plan-state' ? structuredClone(event.data) : undefined;
|
|
7
|
+
}
|
|
8
|
+
/** One serialized writer for durable goal transitions and automatic leaf dispatch. */
|
|
9
|
+
export class PlanService {
|
|
10
|
+
ctx;
|
|
11
|
+
engine;
|
|
12
|
+
tails = new Map();
|
|
13
|
+
recovered = new Set();
|
|
14
|
+
timers = new Map();
|
|
15
|
+
constructor(ctx, engine) {
|
|
16
|
+
this.ctx = ctx;
|
|
17
|
+
this.engine = engine;
|
|
18
|
+
}
|
|
19
|
+
locked(room, action) {
|
|
20
|
+
const previous = this.tails.get(room.id) ?? Promise.resolve();
|
|
21
|
+
const next = previous.catch(() => { }).then(action);
|
|
22
|
+
this.tails.set(room.id, next);
|
|
23
|
+
void next.finally(() => { if (this.tails.get(room.id) === next)
|
|
24
|
+
this.tails.delete(room.id); }).catch(() => { });
|
|
25
|
+
return next;
|
|
26
|
+
}
|
|
27
|
+
/** Cold inspection projects the restart pause without writing or starting agents. */
|
|
28
|
+
view(sessionId, view) {
|
|
29
|
+
if (this.recovered.has(sessionId))
|
|
30
|
+
return view;
|
|
31
|
+
const { requests: _requests, ...recovered } = recoverPlan({ ...view, requests: [] }, Date.now());
|
|
32
|
+
return recovered;
|
|
33
|
+
}
|
|
34
|
+
async recover(room) {
|
|
35
|
+
return this.locked(room, () => this.recoverUnlocked(room));
|
|
36
|
+
}
|
|
37
|
+
async recoverUnlocked(room) {
|
|
38
|
+
if (this.recovered.has(room.id))
|
|
39
|
+
return;
|
|
40
|
+
const current = readPlan(room);
|
|
41
|
+
if (current !== undefined) {
|
|
42
|
+
const recovered = recoverPlan(current, Date.now());
|
|
43
|
+
const events = room.snapshotEvents();
|
|
44
|
+
for (const task of recovered.tasks) {
|
|
45
|
+
const attempt = task.attempts.at(-1);
|
|
46
|
+
if (attempt?.status !== 'queued')
|
|
47
|
+
continue;
|
|
48
|
+
const dispatch = events.find(event => event.type === 'room/dispatch' && event.data.id === attempt.deliveryId);
|
|
49
|
+
if (dispatch?.type !== 'room/dispatch')
|
|
50
|
+
continue;
|
|
51
|
+
const edge = events.filter(event => event.type === 'room/delivery-state' && event.data.dispatchSeq === dispatch.seq).at(-1);
|
|
52
|
+
if (edge?.type === 'room/delivery-state' && ['running', 'uncertain'].includes(edge.data.state)) {
|
|
53
|
+
attempt.status = 'uncertain';
|
|
54
|
+
attempt.error = 'Restart interrupted the durable admission boundary';
|
|
55
|
+
task.status = 'failed';
|
|
56
|
+
recovered.status = 'paused';
|
|
57
|
+
recovered.reason = 'Reconcile uncertain attempts before continuing';
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
room.append('room/plan-state', recovered);
|
|
61
|
+
await this.ctx.sessions.flush(room);
|
|
62
|
+
}
|
|
63
|
+
this.recovered.add(room.id);
|
|
64
|
+
}
|
|
65
|
+
async write(room, plan) {
|
|
66
|
+
room.append('room/plan-state', plan);
|
|
67
|
+
await this.ctx.sessions.flush(room);
|
|
68
|
+
this.armDeadline(room, plan);
|
|
69
|
+
}
|
|
70
|
+
armDeadline(room, plan) {
|
|
71
|
+
clearTimeout(this.timers.get(room.id));
|
|
72
|
+
this.timers.delete(room.id);
|
|
73
|
+
if (plan.status !== 'running')
|
|
74
|
+
return;
|
|
75
|
+
const remaining = Math.max(1, plan.budget.maxActiveMs - activePlanMs(plan, Date.now()));
|
|
76
|
+
const timer = setTimeout(() => {
|
|
77
|
+
void this.locked(room, async () => {
|
|
78
|
+
const current = readPlan(room);
|
|
79
|
+
if (current?.status !== 'running')
|
|
80
|
+
return;
|
|
81
|
+
if (activePlanMs(current, Date.now()) < current.budget.maxActiveMs) {
|
|
82
|
+
this.armDeadline(room, current);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const paused = changePlan(current, { action: 'pause', reason: 'Active-time budget exhausted', requestId: randomUUID(), expectedRevision: current.revision }, { actor: { kind: 'coordinator', memberId: 'budget-timer' }, memberIds: new Set(), now: Date.now() });
|
|
86
|
+
await this.write(room, paused);
|
|
87
|
+
}).catch(error => this.ctx.logger.warn(`room: goal deadline persistence failed: ${String(error)}`));
|
|
88
|
+
}, Math.min(remaining, 2147483647));
|
|
89
|
+
timer.unref?.();
|
|
90
|
+
this.timers.set(room.id, timer);
|
|
91
|
+
}
|
|
92
|
+
async command(room, json, actor) {
|
|
93
|
+
const command = parsePlanCommand(json);
|
|
94
|
+
return this.locked(room, async () => {
|
|
95
|
+
await this.recoverUnlocked(room);
|
|
96
|
+
const events = room.snapshotEvents();
|
|
97
|
+
const state = replay(events);
|
|
98
|
+
const members = new Set(state.members.map(member => memberId(events, member)));
|
|
99
|
+
if (actor.kind !== 'human' && !members.has(actor.memberId))
|
|
100
|
+
throw new Error('Calling member left the room');
|
|
101
|
+
const coordinator = coordinatorMember(state, events);
|
|
102
|
+
if (actor.kind === 'coordinator' && (coordinator === undefined || memberId(events, coordinator) !== actor.memberId))
|
|
103
|
+
throw new Error('Coordinator changed before command admission');
|
|
104
|
+
// Check all revisions, including a previous closed goal, before reusing an identity.
|
|
105
|
+
const earlier = events.filter(event => event.type === 'room/plan-state').flatMap(event => event.type === 'room/plan-state' ? event.data.requests : []).find(receipt => receipt.id === command.requestId);
|
|
106
|
+
if (earlier !== undefined) {
|
|
107
|
+
if (earlier.signature !== planCommandSignature(command))
|
|
108
|
+
throw new Error('Plan request identity was reused for different input');
|
|
109
|
+
await this.ctx.sessions.flush(room);
|
|
110
|
+
await this.pump(room);
|
|
111
|
+
await this.engine().recover(room);
|
|
112
|
+
return readPlan(room);
|
|
113
|
+
}
|
|
114
|
+
const current = readPlan(room);
|
|
115
|
+
const next = changePlan(current, command, { actor, memberIds: members, now: Date.now() });
|
|
116
|
+
const wake = actor.kind === 'human' && coordinator !== undefined && next.status === 'running'
|
|
117
|
+
&& ((command.action === 'create' && command.mode === 'execute') || (command.action === 'resume' && (current?.status === 'draft' || next.tasks.length === 0)))
|
|
118
|
+
? room.append('room/dispatch', { id: randomUUID(), origin: 'report', reportFor: `goal-start:${next.id}:${command.requestId}`,
|
|
119
|
+
targets: [coordinator.name], targetIds: [memberId(events, coordinator)], plan: { goalId: next.id },
|
|
120
|
+
text: `The human authorized execution of goal ${next.id}: ${next.objective}. Read room_read, organize appropriate stages and tasks with room_plan, invite members when useful, and dispatch within the recorded budget. Small work can remain direct. Review submitted evidence before accepting tasks; wait for correlated reports instead of polling.`,
|
|
121
|
+
}) : undefined;
|
|
122
|
+
if (command.action === 'reconcile')
|
|
123
|
+
this.reconcileDelivery(room, next, command.taskId, command.attemptId, command.requestId);
|
|
124
|
+
await this.write(room, next);
|
|
125
|
+
await this.pump(room);
|
|
126
|
+
if (wake !== undefined && coordinator !== undefined)
|
|
127
|
+
this.engine().dispatch(room, coordinator.name, wake.data.text, { dispatchSeq: wake.seq, targetId: memberId(events, coordinator) });
|
|
128
|
+
// Resumption can release durable reports and reservations that survived a pause.
|
|
129
|
+
if (command.action === 'resume' || command.action === 'cancel' || command.action === 'complete')
|
|
130
|
+
await this.engine().recover(room);
|
|
131
|
+
return readPlan(room);
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
allows(room, dispatchSeq) {
|
|
135
|
+
if (dispatchSeq === undefined)
|
|
136
|
+
return true;
|
|
137
|
+
const event = room.snapshotEvents().find(event => event.seq === dispatchSeq);
|
|
138
|
+
if (event?.type !== 'room/dispatch' || event.data.plan === undefined)
|
|
139
|
+
return true;
|
|
140
|
+
const plan = readPlan(room);
|
|
141
|
+
return this.recovered.has(room.id) && plan?.id === event.data.plan.goalId && plan.status === 'running';
|
|
142
|
+
}
|
|
143
|
+
async admitted(room, dispatchSeq) {
|
|
144
|
+
if (dispatchSeq === undefined)
|
|
145
|
+
return;
|
|
146
|
+
const event = room.snapshotEvents().find(event => event.seq === dispatchSeq);
|
|
147
|
+
if (event?.type !== 'room/dispatch' || event.data.plan === undefined)
|
|
148
|
+
return;
|
|
149
|
+
const link = event.data.plan;
|
|
150
|
+
await this.locked(room, async () => {
|
|
151
|
+
await this.recoverUnlocked(room);
|
|
152
|
+
const plan = readPlan(room);
|
|
153
|
+
if (plan?.id !== link.goalId || plan.status !== 'running')
|
|
154
|
+
throw new Error('Goal no longer permits automatic work');
|
|
155
|
+
if (link.taskId !== undefined && link.attemptId !== undefined)
|
|
156
|
+
await this.write(room, admitPlanAttempt(plan, link.taskId, link.attemptId, Date.now()));
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
async settled(room, dispatchSeq, outcome) {
|
|
160
|
+
if (dispatchSeq === undefined)
|
|
161
|
+
return;
|
|
162
|
+
const event = room.snapshotEvents().find(event => event.seq === dispatchSeq);
|
|
163
|
+
if (event?.type !== 'room/dispatch' || event.data.plan?.taskId === undefined || event.data.plan.attemptId === undefined)
|
|
164
|
+
return;
|
|
165
|
+
const { goalId, taskId, attemptId } = event.data.plan;
|
|
166
|
+
await this.locked(room, async () => {
|
|
167
|
+
const current = readPlan(room);
|
|
168
|
+
if (current?.id !== goalId)
|
|
169
|
+
return;
|
|
170
|
+
const next = settlePlanAttempt(current, taskId, attemptId, {
|
|
171
|
+
state: outcome.state,
|
|
172
|
+
...outcome.error === undefined ? {} : { error: outcome.error },
|
|
173
|
+
...outcome.state === 'done' && outcome.text?.trim() ? { evidence: {
|
|
174
|
+
summary: outcome.text.slice(0, 32000), references: [`room-delivery:${room.id}:${dispatchSeq}`], artifacts: [],
|
|
175
|
+
} } : {},
|
|
176
|
+
}, Date.now());
|
|
177
|
+
await this.write(room, next);
|
|
178
|
+
await this.pump(room);
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
/** Human goal reconciliation settles its room delivery and supersedes stale queued reports. */
|
|
182
|
+
reconcileDelivery(room, plan, taskId, attemptId, requestId) {
|
|
183
|
+
const attempt = plan.tasks.find(task => task.id === taskId)?.attempts.find(attempt => attempt.id === attemptId);
|
|
184
|
+
if (attempt === undefined)
|
|
185
|
+
return;
|
|
186
|
+
const events = room.snapshotEvents();
|
|
187
|
+
const dispatch = events.find(event => event.type === 'room/dispatch' && event.data.id === attempt.deliveryId);
|
|
188
|
+
if (dispatch?.type !== 'room/dispatch')
|
|
189
|
+
return;
|
|
190
|
+
const state = replay(events);
|
|
191
|
+
for (const target of dispatch.data.targetIds ?? []) {
|
|
192
|
+
const deliveryId = `${dispatch.seq}:${target}`;
|
|
193
|
+
const member = state.members.find(member => memberId(events, member) === target);
|
|
194
|
+
const run = state.runs.find(run => run.member === member?.name);
|
|
195
|
+
if (run?.runId === deliveryId && ['running', 'failed'].includes(run.state))
|
|
196
|
+
room.append('room/run-state', { member: run.member, runId: run.runId, startedAt: run.startedAt, state: attempt.status === 'submitted' ? 'done' : 'failed',
|
|
197
|
+
...attempt.status === 'failed' ? { error: attempt.submission?.summary ?? 'Human reconciled the attempt as failed' } : {} });
|
|
198
|
+
room.append('room/delivery-state', { id: deliveryId, dispatchSeq: dispatch.seq, memberId: target,
|
|
199
|
+
state: attempt.status === 'submitted' ? 'done' : 'failed', text: attempt.submission?.summary ?? 'Human reconciliation' });
|
|
200
|
+
for (const oldReport of events) {
|
|
201
|
+
if (oldReport.type !== 'room/dispatch' || oldReport.data.reportFor !== deliveryId)
|
|
202
|
+
continue;
|
|
203
|
+
for (const recipient of oldReport.data.targetIds ?? []) {
|
|
204
|
+
const oldId = `${oldReport.seq}:${recipient}`;
|
|
205
|
+
if (state.deliveries?.find(delivery => delivery.id === oldId)?.status === 'queued')
|
|
206
|
+
room.append('room/delivery-state', { id: oldId, dispatchSeq: oldReport.seq, memberId: recipient, state: 'cancelled', error: 'Superseded by human reconciliation' });
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
const coordinator = coordinatorMember(state, events);
|
|
211
|
+
if (coordinator !== undefined)
|
|
212
|
+
room.append('room/dispatch', { id: randomUUID(), origin: 'report', reportFor: `reconcile:${attempt.id}:${requestId}`,
|
|
213
|
+
targets: [coordinator.name], targetIds: [memberId(events, coordinator)], plan: { goalId: plan.id },
|
|
214
|
+
text: `Human reconciled task ${taskId}, attempt ${attemptId} as ${attempt.status}. Evidence: ${JSON.stringify(attempt.submission)}. Read the current plan before reviewing or retrying.`,
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
async pump(room) {
|
|
218
|
+
let plan = readPlan(room);
|
|
219
|
+
if (plan?.status !== 'running')
|
|
220
|
+
return;
|
|
221
|
+
for (const task of readyPlanTasks(plan)) {
|
|
222
|
+
const events = room.snapshotEvents();
|
|
223
|
+
const state = replay(events);
|
|
224
|
+
const owner = state.members.find(member => memberId(events, member) === task.ownerMemberId);
|
|
225
|
+
const coordinator = coordinatorMember(state, events);
|
|
226
|
+
if (owner === undefined || coordinator === undefined) {
|
|
227
|
+
await this.write(room, changePlan(plan, { action: 'pause', reason: 'A task owner or coordinator is unavailable', requestId: randomUUID(), expectedRevision: plan.revision }, { actor: { kind: 'coordinator', memberId: 'scheduler' }, memberIds: new Set(), now: Date.now() }));
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
// Reserve against budgets before appending a dispatch. The dispatch ID is
|
|
231
|
+
// an opaque correlation identity; the execution journal also retains its seq.
|
|
232
|
+
const attemptId = randomUUID();
|
|
233
|
+
const dispatchId = randomUUID();
|
|
234
|
+
let reserved;
|
|
235
|
+
try {
|
|
236
|
+
reserved = queuePlanAttempt(plan, task.id, attemptId, dispatchId, Date.now());
|
|
237
|
+
}
|
|
238
|
+
catch (error) {
|
|
239
|
+
if (String(error).includes('concurrency is full'))
|
|
240
|
+
return;
|
|
241
|
+
throw error;
|
|
242
|
+
}
|
|
243
|
+
if (reserved.status !== 'running') {
|
|
244
|
+
await this.write(room, reserved);
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
const dependencyEvidence = dependencyResults(plan, task);
|
|
248
|
+
const text = [
|
|
249
|
+
`Goal ${plan.id}, task ${task.id}, attempt ${attemptId}.`,
|
|
250
|
+
`Objective: ${plan.objective}`, task.instruction,
|
|
251
|
+
`Acceptance criteria: ${JSON.stringify(task.criteria)}`,
|
|
252
|
+
`Inputs: ${JSON.stringify(task.inputRefs)}. Expected artifacts: ${JSON.stringify(task.artifactPaths)}.`,
|
|
253
|
+
`Accepted dependency results: ${JSON.stringify(dependencyEvidence)}`,
|
|
254
|
+
'Submit your result and evidence with room_plan action submit (taskId, attemptId, evidence: {summary,references,artifacts}, requestId, expectedRevision from room_read). Only the coordinator or human accepts results; do not claim acceptance yourself.',
|
|
255
|
+
].join('\n');
|
|
256
|
+
const dispatch = room.append('room/dispatch', { id: dispatchId, origin: 'coordinator', targets: [owner.name], targetIds: [task.ownerMemberId], text,
|
|
257
|
+
plan: { goalId: plan.id, taskId: task.id, attemptId },
|
|
258
|
+
...memberId(events, coordinator) === task.ownerMemberId ? {} : { replyTo: memberId(events, coordinator) }, });
|
|
259
|
+
await this.write(room, reserved);
|
|
260
|
+
this.engine().dispatch(room, owner.name, text, { dispatchSeq: dispatch.seq, targetId: task.ownerMemberId });
|
|
261
|
+
plan = reserved;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
dispose() { for (const timer of this.timers.values())
|
|
265
|
+
clearTimeout(timer); this.timers.clear(); }
|
|
266
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/** Formal goal state. Ordinary room chat does not create or mutate a plan. */
|
|
2
|
+
export interface PlanBudget {
|
|
3
|
+
maxParallel: number;
|
|
4
|
+
maxAttempts: number;
|
|
5
|
+
maxAttemptsPerTask: number;
|
|
6
|
+
maxActiveMs: number;
|
|
7
|
+
}
|
|
8
|
+
export interface PlanEvidence {
|
|
9
|
+
summary: string;
|
|
10
|
+
references: string[];
|
|
11
|
+
artifacts: string[];
|
|
12
|
+
}
|
|
13
|
+
export interface PlanAttempt {
|
|
14
|
+
id: string;
|
|
15
|
+
deliveryId: string;
|
|
16
|
+
number: number;
|
|
17
|
+
status: 'queued' | 'running' | 'submitted' | 'accepted' | 'rejected' | 'failed' | 'cancelled' | 'uncertain';
|
|
18
|
+
createdAt: number;
|
|
19
|
+
startedAt?: number;
|
|
20
|
+
settledAt?: number;
|
|
21
|
+
submission?: PlanEvidence;
|
|
22
|
+
review?: {
|
|
23
|
+
decision: 'accepted' | 'rework';
|
|
24
|
+
by: string;
|
|
25
|
+
reason: string;
|
|
26
|
+
references: string[];
|
|
27
|
+
time: number;
|
|
28
|
+
};
|
|
29
|
+
error?: string;
|
|
30
|
+
retry?: {
|
|
31
|
+
by: string;
|
|
32
|
+
reason: string;
|
|
33
|
+
time: number;
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
export interface PlanTask {
|
|
37
|
+
id: string;
|
|
38
|
+
title: string;
|
|
39
|
+
stageId: string;
|
|
40
|
+
parentId?: string;
|
|
41
|
+
kind: 'group' | 'task';
|
|
42
|
+
ownerMemberId?: string;
|
|
43
|
+
instruction: string;
|
|
44
|
+
criteria: string[];
|
|
45
|
+
inputRefs: string[];
|
|
46
|
+
artifactPaths: string[];
|
|
47
|
+
dependsOn: string[];
|
|
48
|
+
status: 'pending' | 'running' | 'submitted' | 'accepted' | 'failed' | 'cancelled';
|
|
49
|
+
attempts: PlanAttempt[];
|
|
50
|
+
}
|
|
51
|
+
export interface RoomPlan {
|
|
52
|
+
version: 1;
|
|
53
|
+
id: string;
|
|
54
|
+
revision: number;
|
|
55
|
+
objective: string;
|
|
56
|
+
status: 'draft' | 'running' | 'paused' | 'completed' | 'cancelled';
|
|
57
|
+
reason?: string;
|
|
58
|
+
budget: PlanBudget;
|
|
59
|
+
activeMs: number;
|
|
60
|
+
resumedAt?: number;
|
|
61
|
+
stages: {
|
|
62
|
+
id: string;
|
|
63
|
+
title: string;
|
|
64
|
+
}[];
|
|
65
|
+
tasks: PlanTask[];
|
|
66
|
+
completion?: PlanEvidence;
|
|
67
|
+
requests: {
|
|
68
|
+
id: string;
|
|
69
|
+
signature: string;
|
|
70
|
+
}[];
|
|
71
|
+
}
|
|
72
|
+
export type PlanTaskInput = Omit<PlanTask, 'status' | 'attempts'>;
|
|
73
|
+
interface CommandBase {
|
|
74
|
+
requestId: string;
|
|
75
|
+
expectedRevision: number;
|
|
76
|
+
}
|
|
77
|
+
export type PlanCommand = CommandBase & ({
|
|
78
|
+
action: 'create';
|
|
79
|
+
id: string;
|
|
80
|
+
objective: string;
|
|
81
|
+
mode: 'draft' | 'execute';
|
|
82
|
+
budget: PlanBudget;
|
|
83
|
+
} | {
|
|
84
|
+
action: 'extend';
|
|
85
|
+
stages: {
|
|
86
|
+
id: string;
|
|
87
|
+
title: string;
|
|
88
|
+
}[];
|
|
89
|
+
tasks: PlanTaskInput[];
|
|
90
|
+
} | {
|
|
91
|
+
action: 'pause';
|
|
92
|
+
reason: string;
|
|
93
|
+
goalId?: string;
|
|
94
|
+
} | {
|
|
95
|
+
action: 'resume';
|
|
96
|
+
} | {
|
|
97
|
+
action: 'budget';
|
|
98
|
+
budget: PlanBudget;
|
|
99
|
+
} | {
|
|
100
|
+
action: 'cancel';
|
|
101
|
+
reason: string;
|
|
102
|
+
} | {
|
|
103
|
+
action: 'submit';
|
|
104
|
+
taskId: string;
|
|
105
|
+
attemptId: string;
|
|
106
|
+
evidence: PlanEvidence;
|
|
107
|
+
} | {
|
|
108
|
+
action: 'review';
|
|
109
|
+
taskId: string;
|
|
110
|
+
attemptId: string;
|
|
111
|
+
decision: 'accepted' | 'rework';
|
|
112
|
+
reason: string;
|
|
113
|
+
references: string[];
|
|
114
|
+
} | {
|
|
115
|
+
action: 'retry';
|
|
116
|
+
taskId: string;
|
|
117
|
+
reason: string;
|
|
118
|
+
} | {
|
|
119
|
+
action: 'reconcile';
|
|
120
|
+
taskId: string;
|
|
121
|
+
attemptId: string;
|
|
122
|
+
outcome: 'failed' | 'submitted';
|
|
123
|
+
evidence: PlanEvidence;
|
|
124
|
+
} | {
|
|
125
|
+
action: 'complete';
|
|
126
|
+
evidence: PlanEvidence;
|
|
127
|
+
});
|
|
128
|
+
export interface PlanActor {
|
|
129
|
+
kind: 'human' | 'coordinator' | 'worker';
|
|
130
|
+
memberId: string;
|
|
131
|
+
}
|
|
132
|
+
export interface PlanContext {
|
|
133
|
+
actor: PlanActor;
|
|
134
|
+
memberIds: ReadonlySet<string>;
|
|
135
|
+
now: number;
|
|
136
|
+
}
|
|
137
|
+
export declare function activePlanMs(plan: RoomPlan, now: number): number;
|
|
138
|
+
export declare function latestAttempt(task: PlanTask): PlanAttempt | undefined;
|
|
139
|
+
export declare function acceptedTask(plan: RoomPlan, taskId: string): boolean;
|
|
140
|
+
/** Accepted leaves behind direct and inherited group prerequisites, once each. */
|
|
141
|
+
export declare function dependencyResults(plan: RoomPlan, task: PlanTask): {
|
|
142
|
+
taskId: string;
|
|
143
|
+
submission?: PlanEvidence;
|
|
144
|
+
}[];
|
|
145
|
+
export declare function readyPlanTasks(plan: RoomPlan): PlanTask[];
|
|
146
|
+
export declare function activePlanAttempts(plan: RoomPlan): PlanAttempt[];
|
|
147
|
+
/** Pure transition: the service persists the returned revision before dispatch. */
|
|
148
|
+
export declare function changePlan(current: RoomPlan | undefined, command: PlanCommand, context: PlanContext): RoomPlan;
|
|
149
|
+
/** Reserve a single executable leaf and count the attempt before side effects. */
|
|
150
|
+
export declare function queuePlanAttempt(current: RoomPlan, taskId: string, attemptId: string, deliveryId: string, now: number): RoomPlan;
|
|
151
|
+
/** Durable admission; a pause after reservation prevents the native side effect. */
|
|
152
|
+
export declare function admitPlanAttempt(current: RoomPlan, taskId: string, attemptId: string, now: number): RoomPlan;
|
|
153
|
+
/** A normal native result is a submission to review, never implicit acceptance. */
|
|
154
|
+
export declare function settlePlanAttempt(current: RoomPlan, taskId: string, attemptId: string, result: {
|
|
155
|
+
state: 'done' | 'failed' | 'cancelled';
|
|
156
|
+
evidence?: PlanEvidence;
|
|
157
|
+
error?: string;
|
|
158
|
+
}, now: number): RoomPlan;
|
|
159
|
+
/** Restart always pauses automation and fences native executions with unknown outcomes. */
|
|
160
|
+
export declare function recoverPlan(current: RoomPlan, now: number): RoomPlan;
|
|
161
|
+
/** One bounded, strict wire grammar is shared by native tools, MCP and the UI. */
|
|
162
|
+
export declare function parsePlanCommand(text: string): PlanCommand;
|
|
163
|
+
/** JSON schema supplied through room_read so both harness families see one contract. */
|
|
164
|
+
export declare function planCommandContract(): object;
|
|
165
|
+
/** Object field order is not part of a retry identity; array order remains meaningful. */
|
|
166
|
+
export declare function planCommandSignature(command: PlanCommand): string;
|
|
167
|
+
export {};
|
|
168
|
+
//# sourceMappingURL=plan.d.ts.map
|