dsh-live-teams 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/LICENSE +176 -0
- package/NOTICE +11 -0
- package/README.md +85 -0
- package/cordis.patch.yml +25 -0
- package/lib/binding.d.ts +18 -0
- package/lib/binding.js +42 -0
- package/lib/changed-paths.d.ts +26 -0
- package/lib/changed-paths.js +69 -0
- package/lib/client.js +6753 -0
- package/lib/command-queue.d.ts +60 -0
- package/lib/command-queue.js +185 -0
- package/lib/compatibility.js +109 -0
- package/lib/context-provider.d.ts +110 -0
- package/lib/context-provider.js +249 -0
- package/lib/dispatch.d.ts +174 -0
- package/lib/dispatch.js +624 -0
- package/lib/errors.d.ts +36 -0
- package/lib/errors.js +103 -0
- package/lib/git-artifacts.d.ts +50 -0
- package/lib/git-artifacts.js +242 -0
- package/lib/index.d.ts +14 -0
- package/lib/index.js +14 -0
- package/lib/mailbox.d.ts +274 -0
- package/lib/mailbox.js +721 -0
- package/lib/member-tools.d.ts +57 -0
- package/lib/member-tools.js +1265 -0
- package/lib/migrations.d.ts +17 -0
- package/lib/migrations.js +47 -0
- package/lib/plugin.d.ts +106 -0
- package/lib/plugin.js +1003 -0
- package/lib/roles.d.ts +35 -0
- package/lib/roles.js +284 -0
- package/lib/routes.d.ts +586 -0
- package/lib/routes.js +2816 -0
- package/lib/scope.d.ts +62 -0
- package/lib/scope.js +133 -0
- package/lib/session-bridge.d.ts +76 -0
- package/lib/session-bridge.js +147 -0
- package/lib/session-title.js +35 -0
- package/lib/storage.d.ts +9 -0
- package/lib/storage.js +65 -0
- package/lib/task-store.d.ts +729 -0
- package/lib/task-store.js +2205 -0
- package/lib/team-store.d.ts +216 -0
- package/lib/team-store.js +765 -0
- package/lib/tree-snapshot.d.ts +28 -0
- package/lib/tree-snapshot.js +80 -0
- package/lib/types/client/TeamView.d.ts +26 -0
- package/lib/types/client/TeamView.dom.test.d.ts +1 -0
- package/lib/types/client/api.d.ts +522 -0
- package/lib/types/client/api.test.d.ts +1 -0
- package/lib/types/client/attention.d.ts +65 -0
- package/lib/types/client/attention.test.d.ts +1 -0
- package/lib/types/client/index.d.ts +31 -0
- package/lib/types/client/locales.d.ts +577 -0
- package/lib/types/client/member-name.d.ts +14 -0
- package/lib/types/client/member-name.test.d.ts +1 -0
- package/lib/types/client/roster.d.ts +26 -0
- package/lib/types/client/roster.test.d.ts +1 -0
- package/lib/types/client/styles.d.ts +3 -0
- package/package.json +104 -0
- package/roles/builder.md +40 -0
- package/roles/delegate.md +36 -0
- package/roles/lead.md +46 -0
- package/roles/oracle.md +36 -0
- package/roles/researcher.md +37 -0
- package/roles/reviewer.md +45 -0
- package/roles/scout.md +36 -0
- package/roles/verifier.md +36 -0
|
@@ -0,0 +1,1265 @@
|
|
|
1
|
+
import { resolveLeadMemberId } from "./team-store.js";
|
|
2
|
+
import { messageDeliveryOutcome } from "./mailbox.js";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
5
|
+
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
6
|
+
//#region src/member-tools.ts
|
|
7
|
+
const MEMBER_TOOL_NAMES = Object.freeze([
|
|
8
|
+
"live_team_send",
|
|
9
|
+
"live_team_ack_message",
|
|
10
|
+
"live_team_context",
|
|
11
|
+
"live_team_status",
|
|
12
|
+
"live_team_brief",
|
|
13
|
+
"live_team_member_note",
|
|
14
|
+
"live_team_grant",
|
|
15
|
+
"live_team_dispatch",
|
|
16
|
+
"live_team_create_task",
|
|
17
|
+
"live_team_read_task",
|
|
18
|
+
"live_team_list_tasks",
|
|
19
|
+
"live_team_claim_task",
|
|
20
|
+
"live_team_ack_task",
|
|
21
|
+
"live_team_submit",
|
|
22
|
+
"live_team_review",
|
|
23
|
+
"live_team_update_task",
|
|
24
|
+
"live_team_accept_task",
|
|
25
|
+
"live_team_revise_task",
|
|
26
|
+
"live_team_revoke_task"
|
|
27
|
+
]);
|
|
28
|
+
/**
|
|
29
|
+
* Which team tools belong to whom.
|
|
30
|
+
*
|
|
31
|
+
* The host registers tools globally and gives a plugin no way to hide one from a Session, so a
|
|
32
|
+
* builder is *offered* the lead's tools and learns by refusal. The membership context is rendered on
|
|
33
|
+
* every turn, so it can say what belongs to the role — and that is what this is for: the context
|
|
34
|
+
* names the member's own tools and the ones addressed to somebody else.
|
|
35
|
+
*
|
|
36
|
+
* One list, not a second copy of the authority rules: `assertToolRolesComplete` (a test) fails if a
|
|
37
|
+
* tool is added without a home here, and the runtime guards in `execute` stay exactly as they are.
|
|
38
|
+
* Visibility is a hint; authority is still decided per call.
|
|
39
|
+
*/
|
|
40
|
+
const MEMBER_BASE_TOOLS = [
|
|
41
|
+
"live_team_send",
|
|
42
|
+
"live_team_ack_message",
|
|
43
|
+
"live_team_context",
|
|
44
|
+
"live_team_status",
|
|
45
|
+
"live_team_brief",
|
|
46
|
+
"live_team_read_task",
|
|
47
|
+
"live_team_list_tasks",
|
|
48
|
+
"live_team_claim_task",
|
|
49
|
+
"live_team_ack_task",
|
|
50
|
+
"live_team_submit"
|
|
51
|
+
];
|
|
52
|
+
const LEAD_TOOLS = [
|
|
53
|
+
"live_team_create_task",
|
|
54
|
+
"live_team_update_task",
|
|
55
|
+
"live_team_revoke_task",
|
|
56
|
+
"live_team_accept_task",
|
|
57
|
+
"live_team_revise_task",
|
|
58
|
+
"live_team_grant",
|
|
59
|
+
"live_team_dispatch",
|
|
60
|
+
"live_team_member_note"
|
|
61
|
+
];
|
|
62
|
+
const REVIEW_TOOLS = ["live_team_review"];
|
|
63
|
+
/** Reviewer and verifier both judge work; the role files are craft, the capability is this. */
|
|
64
|
+
const REVIEW_ROLES = ["reviewer", "verifier"];
|
|
65
|
+
function toolsForRole(role) {
|
|
66
|
+
const mine = [
|
|
67
|
+
...MEMBER_BASE_TOOLS,
|
|
68
|
+
...role === "lead" ? LEAD_TOOLS : [],
|
|
69
|
+
...REVIEW_ROLES.includes(role) ? REVIEW_TOOLS : []
|
|
70
|
+
];
|
|
71
|
+
return {
|
|
72
|
+
mine,
|
|
73
|
+
notMine: MEMBER_TOOL_NAMES.filter((name) => !mine.includes(name))
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
function sessionIdOf(exec) {
|
|
77
|
+
const value = exec.agent?.session?.id;
|
|
78
|
+
if (typeof value !== "string" || value.length === 0) throw new Error("calling Session has no identity");
|
|
79
|
+
return value;
|
|
80
|
+
}
|
|
81
|
+
function memberName(binding, recipient) {
|
|
82
|
+
if (recipient === "human") return "human";
|
|
83
|
+
return binding.team.members.find((member) => member.memberId === recipient)?.displayName ?? recipient;
|
|
84
|
+
}
|
|
85
|
+
function sendResult(binding, messageId, deliveries) {
|
|
86
|
+
const projected = deliveries.map((delivery) => ({
|
|
87
|
+
recipient: delivery.recipient,
|
|
88
|
+
name: memberName(binding, delivery.recipient),
|
|
89
|
+
state: delivery.state,
|
|
90
|
+
outcome: messageDeliveryOutcome(delivery),
|
|
91
|
+
...delivery.state === "failed" && delivery.failure !== void 0 ? { failure: delivery.failure } : {}
|
|
92
|
+
}));
|
|
93
|
+
return {
|
|
94
|
+
accepted: true,
|
|
95
|
+
messageId,
|
|
96
|
+
delivered: projected.filter((delivery) => delivery.outcome === "delivered").length,
|
|
97
|
+
inbox: projected.filter((delivery) => delivery.outcome === "inbox").length,
|
|
98
|
+
pending: projected.filter((delivery) => delivery.outcome === "pending").length,
|
|
99
|
+
failed: projected.filter((delivery) => delivery.outcome === "failed").length,
|
|
100
|
+
deliveries: projected
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
function memberContext(binding) {
|
|
104
|
+
return {
|
|
105
|
+
teamName: binding.teamName,
|
|
106
|
+
memberName: binding.member.displayName,
|
|
107
|
+
role: binding.member.role,
|
|
108
|
+
availability: binding.member.availability,
|
|
109
|
+
members: binding.team.members.map((member) => ({
|
|
110
|
+
memberId: member.memberId,
|
|
111
|
+
name: member.displayName,
|
|
112
|
+
role: member.role,
|
|
113
|
+
availability: member.availability,
|
|
114
|
+
live: binding.live(member)
|
|
115
|
+
}))
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
function memberStatus(binding) {
|
|
119
|
+
return {
|
|
120
|
+
memberId: binding.member.memberId,
|
|
121
|
+
teamId: binding.team.teamId,
|
|
122
|
+
role: binding.member.role,
|
|
123
|
+
availability: binding.member.availability,
|
|
124
|
+
bindingGeneration: binding.member.bindingGeneration,
|
|
125
|
+
contractRevision: binding.member.contractRevision,
|
|
126
|
+
live: binding.live(binding.member)
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
function renderOutput(value) {
|
|
130
|
+
return [{
|
|
131
|
+
type: "text",
|
|
132
|
+
text: JSON.stringify(value)
|
|
133
|
+
}];
|
|
134
|
+
}
|
|
135
|
+
function jsonRecord(value) {
|
|
136
|
+
return JSON.parse(JSON.stringify(value));
|
|
137
|
+
}
|
|
138
|
+
function jsonCreateResult(value) {
|
|
139
|
+
const candidate = jsonRecord(value);
|
|
140
|
+
return {
|
|
141
|
+
task: jsonRecord(candidate.task),
|
|
142
|
+
documentPath: String(candidate.documentPath)
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
function jsonReadResult(value) {
|
|
146
|
+
const candidate = jsonRecord(value);
|
|
147
|
+
return {
|
|
148
|
+
task: jsonRecord(candidate.task),
|
|
149
|
+
document: String(candidate.document),
|
|
150
|
+
submissions: Array.isArray(candidate.submissions) ? candidate.submissions : [],
|
|
151
|
+
rounds: Array.isArray(candidate.rounds) ? candidate.rounds : []
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
function jsonListedTasks(value) {
|
|
155
|
+
return JSON.parse(JSON.stringify(value));
|
|
156
|
+
}
|
|
157
|
+
function normalizeOptions(options, b18, b19, b20) {
|
|
158
|
+
if (typeof options === "object" && options !== null) return {
|
|
159
|
+
includeContext: options.includeContext === true,
|
|
160
|
+
includeContact: options.includeContact === true,
|
|
161
|
+
includeTasks: options.includeTasks === true,
|
|
162
|
+
includeSubmissions: options.includeSubmissions === true,
|
|
163
|
+
includeReview: options.includeReview === true,
|
|
164
|
+
includeAcceptance: options.includeAcceptance === true,
|
|
165
|
+
includeDispatch: options.includeDispatch === true
|
|
166
|
+
};
|
|
167
|
+
return {
|
|
168
|
+
includeContext: options === true,
|
|
169
|
+
includeContact: b18 === true,
|
|
170
|
+
includeTasks: b19 === true,
|
|
171
|
+
includeSubmissions: b20 === true,
|
|
172
|
+
includeReview: false,
|
|
173
|
+
includeAcceptance: false,
|
|
174
|
+
includeDispatch: false
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
/** Mount B13 only when the optional tools service is present. */
|
|
178
|
+
function registerMemberToolsIfAvailable(registry, resolve, options = {}, legacyB18 = false, legacyB19 = false, legacyB20 = false) {
|
|
179
|
+
if (registry === void 0) return false;
|
|
180
|
+
registerMemberTools(registry, resolve, typeof options === "boolean" ? {
|
|
181
|
+
includeContext: true,
|
|
182
|
+
includeContact: legacyB18,
|
|
183
|
+
includeTasks: legacyB19,
|
|
184
|
+
includeSubmissions: legacyB20
|
|
185
|
+
} : {
|
|
186
|
+
includeContext: true,
|
|
187
|
+
...options
|
|
188
|
+
});
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
191
|
+
/** Register the member-tool surface. Object options prevent positional flag drift as gates grow. */
|
|
192
|
+
function registerMemberTools(registry, resolve, options = false, legacyB18, legacyB19, legacyB20) {
|
|
193
|
+
const flags = normalizeOptions(options, legacyB18, legacyB19, legacyB20);
|
|
194
|
+
const includeContext = flags.includeContext;
|
|
195
|
+
const includeContact = flags.includeContact;
|
|
196
|
+
const includeTasks = flags.includeTasks;
|
|
197
|
+
const includeSubmissions = flags.includeSubmissions;
|
|
198
|
+
const includeReview = flags.includeReview;
|
|
199
|
+
const includeAcceptance = flags.includeAcceptance;
|
|
200
|
+
const includeDispatch = flags.includeDispatch;
|
|
201
|
+
registry.register(defineTool({
|
|
202
|
+
name: "live_team_send",
|
|
203
|
+
description: "Send a message, direction, or comment to a member id or display name, the team, or the human.",
|
|
204
|
+
parameters: {
|
|
205
|
+
to: {
|
|
206
|
+
type: "string",
|
|
207
|
+
required: true,
|
|
208
|
+
description: "A member id, display name, 'team', or 'human'."
|
|
209
|
+
},
|
|
210
|
+
kind: {
|
|
211
|
+
type: "string",
|
|
212
|
+
enum: [
|
|
213
|
+
"message",
|
|
214
|
+
"direction",
|
|
215
|
+
"comment",
|
|
216
|
+
"report"
|
|
217
|
+
],
|
|
218
|
+
description: "The message kind: `message` (the default), `direction` (the lead directing a member), `comment` (a note on someone else's work). `report` is accepted as a synonym for `message`."
|
|
219
|
+
},
|
|
220
|
+
content: {
|
|
221
|
+
type: "string",
|
|
222
|
+
required: true,
|
|
223
|
+
description: "The message body."
|
|
224
|
+
},
|
|
225
|
+
threadId: {
|
|
226
|
+
type: "string",
|
|
227
|
+
description: "Optional thread to continue."
|
|
228
|
+
}
|
|
229
|
+
},
|
|
230
|
+
output: {
|
|
231
|
+
schema: {
|
|
232
|
+
type: "object",
|
|
233
|
+
additionalProperties: false,
|
|
234
|
+
properties: {
|
|
235
|
+
accepted: {
|
|
236
|
+
type: "boolean",
|
|
237
|
+
required: true
|
|
238
|
+
},
|
|
239
|
+
messageId: {
|
|
240
|
+
type: "string",
|
|
241
|
+
required: true
|
|
242
|
+
},
|
|
243
|
+
delivered: {
|
|
244
|
+
type: "number",
|
|
245
|
+
required: true
|
|
246
|
+
},
|
|
247
|
+
inbox: {
|
|
248
|
+
type: "number",
|
|
249
|
+
required: true
|
|
250
|
+
},
|
|
251
|
+
pending: {
|
|
252
|
+
type: "number",
|
|
253
|
+
required: true
|
|
254
|
+
},
|
|
255
|
+
failed: {
|
|
256
|
+
type: "number",
|
|
257
|
+
required: true
|
|
258
|
+
},
|
|
259
|
+
deliveries: {
|
|
260
|
+
type: "array",
|
|
261
|
+
required: true,
|
|
262
|
+
items: {
|
|
263
|
+
type: "object",
|
|
264
|
+
additionalProperties: false,
|
|
265
|
+
properties: {
|
|
266
|
+
recipient: {
|
|
267
|
+
type: "string",
|
|
268
|
+
required: true
|
|
269
|
+
},
|
|
270
|
+
name: {
|
|
271
|
+
type: "string",
|
|
272
|
+
required: true
|
|
273
|
+
},
|
|
274
|
+
state: {
|
|
275
|
+
type: "string",
|
|
276
|
+
required: true,
|
|
277
|
+
enum: [
|
|
278
|
+
"stored",
|
|
279
|
+
"leased",
|
|
280
|
+
"delivered",
|
|
281
|
+
"acknowledged",
|
|
282
|
+
"failed"
|
|
283
|
+
]
|
|
284
|
+
},
|
|
285
|
+
outcome: {
|
|
286
|
+
type: "string",
|
|
287
|
+
required: true,
|
|
288
|
+
enum: [
|
|
289
|
+
"delivered",
|
|
290
|
+
"inbox",
|
|
291
|
+
"pending",
|
|
292
|
+
"failed"
|
|
293
|
+
]
|
|
294
|
+
},
|
|
295
|
+
failure: { type: "string" }
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
},
|
|
301
|
+
render: (_args, value) => {
|
|
302
|
+
const result = value;
|
|
303
|
+
const statements = result.deliveries.map((delivery) => {
|
|
304
|
+
if (delivery.outcome === "delivered") return `Delivered to ${delivery.name}.`;
|
|
305
|
+
if (delivery.outcome === "inbox") return delivery.name === "human" ? "Stored in the human inbox (waits for the human)." : `Stored for ${delivery.name}'s context.`;
|
|
306
|
+
if (delivery.outcome === "pending") return `Pending delivery to ${delivery.name}.`;
|
|
307
|
+
return `Failed to deliver to ${delivery.name}: ${delivery.failure ?? "delivery failed"}.`;
|
|
308
|
+
});
|
|
309
|
+
const undelivered = result.deliveries.filter((delivery) => delivery.outcome === "failed" || delivery.outcome === "pending");
|
|
310
|
+
const warning = undelivered.length === 0 ? "" : result.delivered === 0 && result.inbox === 0 ? ` Nothing was delivered to: ${undelivered.map((delivery) => `${delivery.name} (${delivery.outcome}${delivery.failure === void 0 ? "" : `: ${delivery.failure}`})`).join(", ")}.` : ` Not delivered to: ${undelivered.map((delivery) => `${delivery.name} (${delivery.outcome}${delivery.failure === void 0 ? "" : `: ${delivery.failure}`})`).join(", ")}.`;
|
|
311
|
+
return [{
|
|
312
|
+
type: "text",
|
|
313
|
+
text: `Accepted. ${statements.join(" ")}${warning}`
|
|
314
|
+
}];
|
|
315
|
+
}
|
|
316
|
+
},
|
|
317
|
+
execute: async (args, exec) => {
|
|
318
|
+
const sessionId = sessionIdOf(exec);
|
|
319
|
+
const binding = await resolve(sessionId);
|
|
320
|
+
if (binding === void 0) throw new Error("calling Session is not an active member of this team");
|
|
321
|
+
const draft = {
|
|
322
|
+
...args,
|
|
323
|
+
kind: args.kind === void 0 || args.kind === "report" ? "message" : args.kind
|
|
324
|
+
};
|
|
325
|
+
const result = await binding.mailbox.sendFromSession(sessionId, draft);
|
|
326
|
+
return sendResult(binding, result.message.id, result.deliveries);
|
|
327
|
+
}
|
|
328
|
+
}));
|
|
329
|
+
registry.register(defineTool({
|
|
330
|
+
name: "live_team_ack_message",
|
|
331
|
+
description: "Acknowledge the calling member's own delivery of a team message.",
|
|
332
|
+
parameters: { messageId: {
|
|
333
|
+
type: "string",
|
|
334
|
+
required: true
|
|
335
|
+
} },
|
|
336
|
+
output: {
|
|
337
|
+
schema: {
|
|
338
|
+
type: "object",
|
|
339
|
+
additionalProperties: false,
|
|
340
|
+
properties: {
|
|
341
|
+
acknowledged: {
|
|
342
|
+
type: "boolean",
|
|
343
|
+
required: true
|
|
344
|
+
},
|
|
345
|
+
messageId: {
|
|
346
|
+
type: "string",
|
|
347
|
+
required: true
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
},
|
|
351
|
+
render: (_args, value) => renderOutput(value)
|
|
352
|
+
},
|
|
353
|
+
execute: async (args, exec) => {
|
|
354
|
+
const binding = await resolve(sessionIdOf(exec));
|
|
355
|
+
if (binding === void 0) throw new Error("calling Session is not an active member of this team");
|
|
356
|
+
await binding.mailbox.acknowledge(args.messageId, binding.member.memberId);
|
|
357
|
+
return {
|
|
358
|
+
acknowledged: true,
|
|
359
|
+
messageId: args.messageId
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
}));
|
|
363
|
+
registry.register(defineTool({
|
|
364
|
+
name: "live_team_context",
|
|
365
|
+
description: "Read the bounded current team context for the calling member.",
|
|
366
|
+
parameters: {},
|
|
367
|
+
output: {
|
|
368
|
+
schema: {
|
|
369
|
+
type: "object",
|
|
370
|
+
additionalProperties: false,
|
|
371
|
+
properties: {
|
|
372
|
+
teamName: {
|
|
373
|
+
type: "string",
|
|
374
|
+
required: true
|
|
375
|
+
},
|
|
376
|
+
memberName: {
|
|
377
|
+
type: "string",
|
|
378
|
+
required: true
|
|
379
|
+
},
|
|
380
|
+
role: {
|
|
381
|
+
type: "string",
|
|
382
|
+
required: true
|
|
383
|
+
},
|
|
384
|
+
availability: {
|
|
385
|
+
type: "string",
|
|
386
|
+
required: true
|
|
387
|
+
},
|
|
388
|
+
members: {
|
|
389
|
+
type: "array",
|
|
390
|
+
required: true,
|
|
391
|
+
items: {
|
|
392
|
+
type: "object",
|
|
393
|
+
additionalProperties: false,
|
|
394
|
+
properties: {
|
|
395
|
+
memberId: {
|
|
396
|
+
type: "string",
|
|
397
|
+
required: true
|
|
398
|
+
},
|
|
399
|
+
name: {
|
|
400
|
+
type: "string",
|
|
401
|
+
required: true
|
|
402
|
+
},
|
|
403
|
+
role: {
|
|
404
|
+
type: "string",
|
|
405
|
+
required: true
|
|
406
|
+
},
|
|
407
|
+
availability: {
|
|
408
|
+
type: "string",
|
|
409
|
+
required: true
|
|
410
|
+
},
|
|
411
|
+
live: {
|
|
412
|
+
type: "boolean",
|
|
413
|
+
required: true
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
},
|
|
420
|
+
render: (_args, value) => renderOutput(value)
|
|
421
|
+
},
|
|
422
|
+
execute: async (_args, exec) => {
|
|
423
|
+
const binding = await resolve(sessionIdOf(exec));
|
|
424
|
+
if (binding === void 0) throw new Error("calling Session is not an active member of this team");
|
|
425
|
+
return memberContext(binding);
|
|
426
|
+
}
|
|
427
|
+
}));
|
|
428
|
+
if (includeContact) registry.register(defineTool({
|
|
429
|
+
name: "live_team_grant",
|
|
430
|
+
description: "List your permitted contacts, or ask the lead to grant/revoke a contact.",
|
|
431
|
+
parameters: {
|
|
432
|
+
action: {
|
|
433
|
+
type: "string",
|
|
434
|
+
required: true,
|
|
435
|
+
enum: [
|
|
436
|
+
"list",
|
|
437
|
+
"add",
|
|
438
|
+
"remove"
|
|
439
|
+
]
|
|
440
|
+
},
|
|
441
|
+
memberId: { type: "string" },
|
|
442
|
+
allow: { type: "string" }
|
|
443
|
+
},
|
|
444
|
+
output: {
|
|
445
|
+
schema: {
|
|
446
|
+
type: "object",
|
|
447
|
+
additionalProperties: false,
|
|
448
|
+
properties: {
|
|
449
|
+
grants: {
|
|
450
|
+
type: "array",
|
|
451
|
+
required: true
|
|
452
|
+
},
|
|
453
|
+
leadMemberId: { type: "string" }
|
|
454
|
+
}
|
|
455
|
+
},
|
|
456
|
+
render: (_args, value) => renderOutput(value)
|
|
457
|
+
},
|
|
458
|
+
execute: async (args, exec) => {
|
|
459
|
+
const binding = await resolve(sessionIdOf(exec));
|
|
460
|
+
if (binding === void 0) throw new Error("calling Session is not an active member of this team");
|
|
461
|
+
const leadId = resolveLeadMemberId(binding.team);
|
|
462
|
+
const ownGrants = (binding.team.contactGrants ?? []).filter((grant) => grant.from === binding.member.memberId);
|
|
463
|
+
if (args.action === "list") return {
|
|
464
|
+
grants: ownGrants,
|
|
465
|
+
...leadId === void 0 ? {} : { leadMemberId: leadId }
|
|
466
|
+
};
|
|
467
|
+
if (binding.member.memberId !== leadId) throw new Error(`only the lead may change contact grants; ask ${leadId ?? "the lead"}`);
|
|
468
|
+
if (typeof args.memberId !== "string" || args.memberId.length === 0 || typeof args.allow !== "string" || args.allow.length === 0) throw new Error("memberId and allow are required");
|
|
469
|
+
if (binding.updateContactGrant === void 0) throw new Error("contact grants are unavailable");
|
|
470
|
+
return binding.updateContactGrant(args.memberId, args.allow, args.action, binding.member.memberId);
|
|
471
|
+
}
|
|
472
|
+
}));
|
|
473
|
+
if (includeDispatch) registry.register(defineTool({
|
|
474
|
+
name: "live_team_dispatch",
|
|
475
|
+
description: "Pause or resume automatic dispatch for this team. Only the lead may change it. Pausing stops new work from starting: it never revokes a running attempt, never moves ownership, and never drops a message already queued for a member.",
|
|
476
|
+
parameters: {
|
|
477
|
+
mode: {
|
|
478
|
+
type: "string",
|
|
479
|
+
required: true,
|
|
480
|
+
enum: ["auto", "paused"]
|
|
481
|
+
},
|
|
482
|
+
reason: { type: "string" }
|
|
483
|
+
},
|
|
484
|
+
output: {
|
|
485
|
+
schema: {
|
|
486
|
+
type: "object",
|
|
487
|
+
additionalProperties: false,
|
|
488
|
+
properties: {
|
|
489
|
+
from: {
|
|
490
|
+
type: "string",
|
|
491
|
+
required: true
|
|
492
|
+
},
|
|
493
|
+
to: {
|
|
494
|
+
type: "string",
|
|
495
|
+
required: true
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
},
|
|
499
|
+
render: (_args, value) => renderOutput(value)
|
|
500
|
+
},
|
|
501
|
+
execute: async (args, exec) => {
|
|
502
|
+
const binding = await resolve(sessionIdOf(exec));
|
|
503
|
+
if (binding === void 0) throw new Error("calling Session is not an active member of this team");
|
|
504
|
+
const leadId = resolveLeadMemberId(binding.team);
|
|
505
|
+
if (binding.member.memberId !== leadId) throw new Error(`only the lead may pause or resume dispatch; ask ${leadId ?? "the lead"}`);
|
|
506
|
+
if (binding.setDispatch === void 0) throw new Error("dispatch is unavailable");
|
|
507
|
+
const result = await binding.setDispatch({
|
|
508
|
+
mode: args.mode,
|
|
509
|
+
actor: binding.member.memberId,
|
|
510
|
+
...typeof args.reason === "string" && args.reason.trim().length > 0 ? { reason: args.reason.trim() } : {}
|
|
511
|
+
});
|
|
512
|
+
return {
|
|
513
|
+
from: result.from,
|
|
514
|
+
to: result.to
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
}));
|
|
518
|
+
if (includeTasks) {
|
|
519
|
+
registry.register(defineTool({
|
|
520
|
+
name: "live_team_create_task",
|
|
521
|
+
description: "Create a durable task. Only the team lead may create tasks.",
|
|
522
|
+
parameters: {
|
|
523
|
+
title: {
|
|
524
|
+
type: "string",
|
|
525
|
+
required: true
|
|
526
|
+
},
|
|
527
|
+
kind: {
|
|
528
|
+
type: "string",
|
|
529
|
+
required: true,
|
|
530
|
+
enum: [
|
|
531
|
+
"work",
|
|
532
|
+
"research",
|
|
533
|
+
"requirements",
|
|
534
|
+
"implementation",
|
|
535
|
+
"verification",
|
|
536
|
+
"review",
|
|
537
|
+
"repair",
|
|
538
|
+
"integration"
|
|
539
|
+
]
|
|
540
|
+
},
|
|
541
|
+
assignee: { type: "string" },
|
|
542
|
+
route: {
|
|
543
|
+
type: "json",
|
|
544
|
+
description: "Where the finished work goes: {\"type\":\"review\",\"reviewer\":\"<member id>\"} or {\"type\":\"report\",\"to\":\"human\"}."
|
|
545
|
+
},
|
|
546
|
+
humanContact: {
|
|
547
|
+
type: "string",
|
|
548
|
+
enum: [
|
|
549
|
+
"none",
|
|
550
|
+
"expected",
|
|
551
|
+
"required"
|
|
552
|
+
]
|
|
553
|
+
},
|
|
554
|
+
acceptance: {
|
|
555
|
+
type: "array",
|
|
556
|
+
required: true,
|
|
557
|
+
items: { type: "string" },
|
|
558
|
+
description: "What \"done\" means, each criterion checkable inside this task declared paths (e.g. \"node --test packages/alpha/test\"). A repository-wide criterion needs a declared dependency: a sibling task in flight makes it unsatisfiable."
|
|
559
|
+
},
|
|
560
|
+
checks: {
|
|
561
|
+
type: "array",
|
|
562
|
+
items: { type: "string" }
|
|
563
|
+
},
|
|
564
|
+
why: { type: "string" },
|
|
565
|
+
body: { type: "string" },
|
|
566
|
+
roundLimit: { type: "integer" },
|
|
567
|
+
dependsOn: {
|
|
568
|
+
type: "array",
|
|
569
|
+
items: { type: "string" }
|
|
570
|
+
},
|
|
571
|
+
paths: {
|
|
572
|
+
type: "array",
|
|
573
|
+
items: { type: "string" },
|
|
574
|
+
description: "Workspace-relative globs this task may touch. Two writing tasks with intersecting paths never run at the same time, so declare them for any task that writes code."
|
|
575
|
+
}
|
|
576
|
+
},
|
|
577
|
+
output: {
|
|
578
|
+
schema: {
|
|
579
|
+
type: "object",
|
|
580
|
+
additionalProperties: false,
|
|
581
|
+
properties: {
|
|
582
|
+
task: {
|
|
583
|
+
type: "json",
|
|
584
|
+
required: true
|
|
585
|
+
},
|
|
586
|
+
documentPath: {
|
|
587
|
+
type: "string",
|
|
588
|
+
required: true
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
},
|
|
592
|
+
render: (_args, value) => renderOutput(value)
|
|
593
|
+
},
|
|
594
|
+
execute: async (args, exec) => {
|
|
595
|
+
const binding = await resolve(sessionIdOf(exec));
|
|
596
|
+
if (binding === void 0) throw new Error("calling Session is not an active member of this team");
|
|
597
|
+
const leadId = resolveLeadMemberId(binding.team);
|
|
598
|
+
if (binding.member.memberId !== leadId) throw new Error(`only the lead may create tasks; ask ${leadId ?? "the lead"}`);
|
|
599
|
+
if (binding.taskStore === void 0) throw new Error("task store is unavailable");
|
|
600
|
+
return jsonCreateResult(await binding.taskStore.createTask({
|
|
601
|
+
...args,
|
|
602
|
+
actor: binding.member.memberId
|
|
603
|
+
}));
|
|
604
|
+
}
|
|
605
|
+
}));
|
|
606
|
+
registry.register(defineTool({
|
|
607
|
+
name: "live_team_update_task",
|
|
608
|
+
description: "Edit the contract of a task you created: title, kind, assignee, route, acceptance, checks, why, body, round limit, human contact, dependencies, declared paths. Only the lead may update tasks, and only while nobody has started them (draft or ready). Absent fields are left alone; assignee, route, dependsOn and paths accept null to clear them.",
|
|
609
|
+
parameters: {
|
|
610
|
+
taskId: {
|
|
611
|
+
type: "string",
|
|
612
|
+
required: true
|
|
613
|
+
},
|
|
614
|
+
expectedRevision: { type: "integer" },
|
|
615
|
+
title: { type: "string" },
|
|
616
|
+
kind: {
|
|
617
|
+
type: "string",
|
|
618
|
+
enum: [
|
|
619
|
+
"work",
|
|
620
|
+
"research",
|
|
621
|
+
"requirements",
|
|
622
|
+
"implementation",
|
|
623
|
+
"verification",
|
|
624
|
+
"review",
|
|
625
|
+
"repair",
|
|
626
|
+
"integration"
|
|
627
|
+
]
|
|
628
|
+
},
|
|
629
|
+
assignee: { type: "string" },
|
|
630
|
+
route: {
|
|
631
|
+
type: "json",
|
|
632
|
+
description: "Where the finished work goes: {\"type\":\"review\",\"reviewer\":\"<member id>\"} or {\"type\":\"report\",\"to\":\"human\"}."
|
|
633
|
+
},
|
|
634
|
+
humanContact: {
|
|
635
|
+
type: "string",
|
|
636
|
+
enum: [
|
|
637
|
+
"none",
|
|
638
|
+
"expected",
|
|
639
|
+
"required"
|
|
640
|
+
]
|
|
641
|
+
},
|
|
642
|
+
acceptance: {
|
|
643
|
+
type: "array",
|
|
644
|
+
items: { type: "string" },
|
|
645
|
+
description: "What \"done\" means, each criterion checkable inside this task declared paths (e.g. \"node --test packages/alpha/test\"). A repository-wide criterion needs a declared dependency: a sibling task in flight makes it unsatisfiable."
|
|
646
|
+
},
|
|
647
|
+
checks: {
|
|
648
|
+
type: "array",
|
|
649
|
+
items: { type: "string" }
|
|
650
|
+
},
|
|
651
|
+
why: { type: "string" },
|
|
652
|
+
body: { type: "string" },
|
|
653
|
+
roundLimit: { type: "integer" },
|
|
654
|
+
dependsOn: {
|
|
655
|
+
type: "array",
|
|
656
|
+
items: { type: "string" }
|
|
657
|
+
},
|
|
658
|
+
paths: {
|
|
659
|
+
type: "array",
|
|
660
|
+
items: { type: "string" },
|
|
661
|
+
description: "Workspace-relative globs this task may touch; null clears the declaration."
|
|
662
|
+
}
|
|
663
|
+
},
|
|
664
|
+
output: {
|
|
665
|
+
schema: {
|
|
666
|
+
type: "object",
|
|
667
|
+
additionalProperties: false,
|
|
668
|
+
properties: {
|
|
669
|
+
task: {
|
|
670
|
+
type: "json",
|
|
671
|
+
required: true
|
|
672
|
+
},
|
|
673
|
+
document: {
|
|
674
|
+
type: "string",
|
|
675
|
+
required: true
|
|
676
|
+
},
|
|
677
|
+
changed: {
|
|
678
|
+
type: "array",
|
|
679
|
+
required: true,
|
|
680
|
+
items: { type: "string" }
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
},
|
|
684
|
+
render: (_args, value) => renderOutput(value)
|
|
685
|
+
},
|
|
686
|
+
execute: async (args, exec) => {
|
|
687
|
+
const binding = await resolve(sessionIdOf(exec));
|
|
688
|
+
if (binding === void 0) throw new Error("calling Session is not an active member of this team");
|
|
689
|
+
if (binding.taskStore === void 0) throw new Error("task store is unavailable");
|
|
690
|
+
const result = await binding.taskStore.updateTask({
|
|
691
|
+
...args,
|
|
692
|
+
actor: binding.member.memberId
|
|
693
|
+
});
|
|
694
|
+
return {
|
|
695
|
+
task: jsonRecord(result.task),
|
|
696
|
+
document: result.document,
|
|
697
|
+
changed: [...result.changed]
|
|
698
|
+
};
|
|
699
|
+
}
|
|
700
|
+
}));
|
|
701
|
+
registry.register(defineTool({
|
|
702
|
+
name: "live_team_revoke_task",
|
|
703
|
+
description: "Take a task away from the member working on it: invalidate their attempt, stop their Session, and return the task to ready (optionally for a new assignee). Only the lead may revoke. If the Session cannot be observed to have stopped, the task goes to needs_human instead of pretending it was revoked.",
|
|
704
|
+
parameters: {
|
|
705
|
+
taskId: {
|
|
706
|
+
type: "string",
|
|
707
|
+
required: true
|
|
708
|
+
},
|
|
709
|
+
reason: {
|
|
710
|
+
type: "string",
|
|
711
|
+
required: true
|
|
712
|
+
},
|
|
713
|
+
nextAssignee: { type: "string" },
|
|
714
|
+
expectedRevision: { type: "integer" }
|
|
715
|
+
},
|
|
716
|
+
output: {
|
|
717
|
+
schema: {
|
|
718
|
+
type: "object",
|
|
719
|
+
additionalProperties: false,
|
|
720
|
+
properties: {
|
|
721
|
+
task: {
|
|
722
|
+
type: "json",
|
|
723
|
+
required: true
|
|
724
|
+
},
|
|
725
|
+
attempt: {
|
|
726
|
+
type: "json",
|
|
727
|
+
required: true
|
|
728
|
+
},
|
|
729
|
+
quiesced: {
|
|
730
|
+
type: "boolean",
|
|
731
|
+
required: true
|
|
732
|
+
},
|
|
733
|
+
reassigned: {
|
|
734
|
+
type: "boolean",
|
|
735
|
+
required: true
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
},
|
|
739
|
+
render: (_args, value) => renderOutput(value)
|
|
740
|
+
},
|
|
741
|
+
execute: async (args, exec) => {
|
|
742
|
+
const binding = await resolve(sessionIdOf(exec));
|
|
743
|
+
if (binding === void 0) throw new Error("calling Session is not an active member of this team");
|
|
744
|
+
if (binding.taskStore === void 0) throw new Error("task store is unavailable");
|
|
745
|
+
const result = await binding.taskStore.revokeTask({
|
|
746
|
+
...args,
|
|
747
|
+
actor: binding.member.memberId
|
|
748
|
+
});
|
|
749
|
+
return {
|
|
750
|
+
task: jsonRecord(result.task),
|
|
751
|
+
attempt: jsonRecord(result.attempt),
|
|
752
|
+
quiesced: result.quiesced,
|
|
753
|
+
reassigned: result.reassigned
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
}));
|
|
757
|
+
registry.register(defineTool({
|
|
758
|
+
name: "live_team_read_task",
|
|
759
|
+
description: "Read a durable task and its authored document.",
|
|
760
|
+
parameters: { taskId: {
|
|
761
|
+
type: "string",
|
|
762
|
+
required: true
|
|
763
|
+
} },
|
|
764
|
+
output: {
|
|
765
|
+
schema: {
|
|
766
|
+
type: "object",
|
|
767
|
+
additionalProperties: false,
|
|
768
|
+
properties: {
|
|
769
|
+
task: {
|
|
770
|
+
type: "json",
|
|
771
|
+
required: true
|
|
772
|
+
},
|
|
773
|
+
document: {
|
|
774
|
+
type: "string",
|
|
775
|
+
required: true
|
|
776
|
+
},
|
|
777
|
+
submissions: {
|
|
778
|
+
type: "array",
|
|
779
|
+
required: true
|
|
780
|
+
},
|
|
781
|
+
rounds: {
|
|
782
|
+
type: "array",
|
|
783
|
+
required: true
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
},
|
|
787
|
+
render: (_args, value) => renderOutput(value)
|
|
788
|
+
},
|
|
789
|
+
execute: async (args, exec) => {
|
|
790
|
+
const binding = await resolve(sessionIdOf(exec));
|
|
791
|
+
if (binding === void 0) throw new Error("calling Session is not an active member of this team");
|
|
792
|
+
if (binding.taskStore === void 0) throw new Error("task store is unavailable");
|
|
793
|
+
return jsonReadResult(await binding.taskStore.readTask(args.taskId));
|
|
794
|
+
}
|
|
795
|
+
}));
|
|
796
|
+
registry.register(defineTool({
|
|
797
|
+
name: "live_team_list_tasks",
|
|
798
|
+
description: "List durable tasks for this team, newest first; archived tasks are hidden unless requested.",
|
|
799
|
+
parameters: { includeArchived: { type: "boolean" } },
|
|
800
|
+
output: {
|
|
801
|
+
schema: {
|
|
802
|
+
type: "object",
|
|
803
|
+
additionalProperties: false,
|
|
804
|
+
properties: { tasks: {
|
|
805
|
+
type: "array",
|
|
806
|
+
required: true,
|
|
807
|
+
items: {
|
|
808
|
+
type: "object",
|
|
809
|
+
additionalProperties: true,
|
|
810
|
+
properties: { documentMissing: { type: "boolean" } }
|
|
811
|
+
}
|
|
812
|
+
} }
|
|
813
|
+
},
|
|
814
|
+
render: (_args, value) => renderOutput(value)
|
|
815
|
+
},
|
|
816
|
+
execute: async (args, exec) => {
|
|
817
|
+
const binding = await resolve(sessionIdOf(exec));
|
|
818
|
+
if (binding === void 0) throw new Error("calling Session is not an active member of this team");
|
|
819
|
+
if (binding.taskStore === void 0) throw new Error("task store is unavailable");
|
|
820
|
+
return jsonListedTasks({ tasks: await binding.taskStore.listTasks({ includeArchived: args.includeArchived === true }) });
|
|
821
|
+
}
|
|
822
|
+
}));
|
|
823
|
+
if (includeSubmissions) {
|
|
824
|
+
registry.register(defineTool({
|
|
825
|
+
name: "live_team_claim_task",
|
|
826
|
+
description: "Claim your assigned ready task and create its active attempt.",
|
|
827
|
+
parameters: { taskId: {
|
|
828
|
+
type: "string",
|
|
829
|
+
required: true
|
|
830
|
+
} },
|
|
831
|
+
output: {
|
|
832
|
+
schema: {
|
|
833
|
+
type: "object",
|
|
834
|
+
additionalProperties: false,
|
|
835
|
+
properties: {
|
|
836
|
+
task: {
|
|
837
|
+
type: "json",
|
|
838
|
+
required: true
|
|
839
|
+
},
|
|
840
|
+
attempt: {
|
|
841
|
+
type: "json",
|
|
842
|
+
required: true
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
},
|
|
846
|
+
render: (_args, value) => renderOutput(value)
|
|
847
|
+
},
|
|
848
|
+
execute: async (args, exec) => {
|
|
849
|
+
const binding = await resolve(sessionIdOf(exec));
|
|
850
|
+
if (binding === void 0) throw new Error("calling Session is not an active member of this team");
|
|
851
|
+
if (binding.taskStore === void 0) throw new Error("task store is unavailable");
|
|
852
|
+
const result = await binding.taskStore.claimTask({
|
|
853
|
+
taskId: args.taskId,
|
|
854
|
+
memberId: binding.member.memberId,
|
|
855
|
+
sessionId: binding.member.sessionId ?? sessionIdOf(exec),
|
|
856
|
+
bindingGeneration: binding.member.bindingGeneration
|
|
857
|
+
});
|
|
858
|
+
return {
|
|
859
|
+
task: jsonRecord(result.task),
|
|
860
|
+
attempt: jsonRecord(result.attempt)
|
|
861
|
+
};
|
|
862
|
+
}
|
|
863
|
+
}));
|
|
864
|
+
registry.register(defineTool({
|
|
865
|
+
name: "live_team_ack_task",
|
|
866
|
+
description: "Acknowledge your claimed task and move it into progress.",
|
|
867
|
+
parameters: { taskId: {
|
|
868
|
+
type: "string",
|
|
869
|
+
required: true
|
|
870
|
+
} },
|
|
871
|
+
output: {
|
|
872
|
+
schema: {
|
|
873
|
+
type: "object",
|
|
874
|
+
additionalProperties: false,
|
|
875
|
+
properties: { task: {
|
|
876
|
+
type: "json",
|
|
877
|
+
required: true
|
|
878
|
+
} }
|
|
879
|
+
},
|
|
880
|
+
render: (_args, value) => renderOutput(value)
|
|
881
|
+
},
|
|
882
|
+
execute: async (args, exec) => {
|
|
883
|
+
const binding = await resolve(sessionIdOf(exec));
|
|
884
|
+
if (binding === void 0) throw new Error("calling Session is not an active member of this team");
|
|
885
|
+
if (binding.taskStore === void 0) throw new Error("task store is unavailable");
|
|
886
|
+
return { task: jsonRecord((await binding.taskStore.acknowledgeTask({
|
|
887
|
+
taskId: args.taskId,
|
|
888
|
+
memberId: binding.member.memberId,
|
|
889
|
+
sessionId: sessionIdOf(exec),
|
|
890
|
+
bindingGeneration: binding.member.bindingGeneration
|
|
891
|
+
})).task) };
|
|
892
|
+
}
|
|
893
|
+
}));
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
if (includeReview) {
|
|
897
|
+
registry.register(defineTool({
|
|
898
|
+
name: "live_team_submit",
|
|
899
|
+
description: "Submit an immutable report or file artifact for the active task attempt. A refusal here changes nothing about the task.",
|
|
900
|
+
parameters: {
|
|
901
|
+
taskId: {
|
|
902
|
+
type: "string",
|
|
903
|
+
required: true
|
|
904
|
+
},
|
|
905
|
+
artifact: {
|
|
906
|
+
type: "json",
|
|
907
|
+
required: true,
|
|
908
|
+
description: "Exactly one of two shapes, with exactly these keys: {\"type\":\"report\",\"bodyRef\":\"<workspace-relative path to the report you wrote>\"} or {\"type\":\"files\",\"paths\":[\"<workspace-relative path>\", \"...\"]}. `type` is required; no other keys are accepted. Write a report body under .dsh-live-teams/reports/<task-id>.md, never in the repository root: the root is the integration tree."
|
|
909
|
+
},
|
|
910
|
+
summary: {
|
|
911
|
+
type: "string",
|
|
912
|
+
required: true,
|
|
913
|
+
description: "One or two sentences: what you changed and what you observed."
|
|
914
|
+
},
|
|
915
|
+
checks: {
|
|
916
|
+
type: "array",
|
|
917
|
+
items: { type: "json" },
|
|
918
|
+
description: "The checks you actually ran: [{\"command\":\"npm test\",\"status\":\"passed\" | \"failed\",\"output\":\"<short tail>\"}]. Each entry needs `command` and `status`."
|
|
919
|
+
}
|
|
920
|
+
},
|
|
921
|
+
output: {
|
|
922
|
+
schema: {
|
|
923
|
+
type: "object",
|
|
924
|
+
additionalProperties: false,
|
|
925
|
+
properties: { submission: {
|
|
926
|
+
type: "json",
|
|
927
|
+
required: true
|
|
928
|
+
} }
|
|
929
|
+
},
|
|
930
|
+
render: (_args, value) => renderOutput(value)
|
|
931
|
+
},
|
|
932
|
+
execute: async (args, exec) => {
|
|
933
|
+
const sessionId = sessionIdOf(exec);
|
|
934
|
+
const binding = await resolve(sessionId);
|
|
935
|
+
if (binding === void 0) throw new Error("calling Session is not an active member of this team");
|
|
936
|
+
if (binding.taskStore === void 0) throw new Error("task store is unavailable");
|
|
937
|
+
const shape = args.artifact === null || typeof args.artifact !== "object" ? String(args.artifact) : Object.keys(args.artifact).join(", ");
|
|
938
|
+
if (args.artifact?.type === "report" && typeof args.artifact.bodyRef !== "string") throw new Error(`artifact {"type":"report"} needs a string "bodyRef"; received keys: ${shape}. Accepted shapes: {"type":"report","bodyRef":"<path>"} or {"type":"files","paths":["<path>", "…"]}`);
|
|
939
|
+
if (args.artifact?.type === "files" && (!Array.isArray(args.artifact.paths) || args.artifact.paths.length === 0 || !args.artifact.paths.every((entry) => typeof entry === "string"))) throw new Error(`artifact {"type":"files"} needs a non-empty "paths" array of strings; received keys: ${shape}. Accepted shapes: {"type":"files","paths":["<path>", "…"]} or {"type":"report","bodyRef":"<path>"}`);
|
|
940
|
+
if (args.artifact?.type !== "report" && args.artifact?.type !== "files") throw new Error(`artifact must declare its shape with "type":"report" or "type":"files"; received keys: ${shape}. Accepted shapes: {"type":"report","bodyRef":"<path>"} or {"type":"files","paths":["<path>", "…"]}`);
|
|
941
|
+
const artifact = args.artifact.type === "report" ? {
|
|
942
|
+
type: "report",
|
|
943
|
+
bodyRef: args.artifact.bodyRef
|
|
944
|
+
} : {
|
|
945
|
+
type: "files",
|
|
946
|
+
paths: args.artifact.paths
|
|
947
|
+
};
|
|
948
|
+
return { submission: jsonRecord((await binding.taskStore.submitTask({
|
|
949
|
+
taskId: args.taskId,
|
|
950
|
+
memberId: binding.member.memberId,
|
|
951
|
+
sessionId,
|
|
952
|
+
bindingGeneration: binding.member.bindingGeneration,
|
|
953
|
+
artifact,
|
|
954
|
+
summary: args.summary,
|
|
955
|
+
checks: args.checks ?? []
|
|
956
|
+
})).submission) };
|
|
957
|
+
}
|
|
958
|
+
}));
|
|
959
|
+
registry.register(defineTool({
|
|
960
|
+
name: "live_team_review",
|
|
961
|
+
description: "Reviewer only. Review the latest immutable task submission with a terminal verdict. A pass must record at least one check — the command you ran and what it printed — because a verdict that names no command cannot be reproduced; an inspection counts, so record what you looked at.",
|
|
962
|
+
parameters: {
|
|
963
|
+
taskId: {
|
|
964
|
+
type: "string",
|
|
965
|
+
required: true
|
|
966
|
+
},
|
|
967
|
+
submissionId: {
|
|
968
|
+
type: "string",
|
|
969
|
+
required: true
|
|
970
|
+
},
|
|
971
|
+
verdict: {
|
|
972
|
+
type: "string",
|
|
973
|
+
required: true,
|
|
974
|
+
enum: [
|
|
975
|
+
"pass",
|
|
976
|
+
"needs_revision",
|
|
977
|
+
"escalate"
|
|
978
|
+
]
|
|
979
|
+
},
|
|
980
|
+
findings: {
|
|
981
|
+
type: "array",
|
|
982
|
+
items: { type: "json" },
|
|
983
|
+
description: "One entry per problem: [{\"severity\":\"low\" | \"medium\" | \"high\" | \"blocker\",\"problem\":\"…\",\"requiredFix\":\"…\",\"artifactLocation\":\"<optional path>\"}]. Required for a verdict other than pass."
|
|
984
|
+
},
|
|
985
|
+
evidence: {
|
|
986
|
+
type: "array",
|
|
987
|
+
items: { type: "json" },
|
|
988
|
+
description: "What you ran while judging: [{\"command\":\"npm test\",\"status\":\"passed\" | \"failed\",\"output\":\"<short tail>\"}]."
|
|
989
|
+
}
|
|
990
|
+
},
|
|
991
|
+
output: {
|
|
992
|
+
schema: {
|
|
993
|
+
type: "object",
|
|
994
|
+
additionalProperties: false,
|
|
995
|
+
properties: {
|
|
996
|
+
round: {
|
|
997
|
+
type: "json",
|
|
998
|
+
required: true
|
|
999
|
+
},
|
|
1000
|
+
task: {
|
|
1001
|
+
type: "json",
|
|
1002
|
+
required: true
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
},
|
|
1006
|
+
render: (_args, value) => renderOutput(value)
|
|
1007
|
+
},
|
|
1008
|
+
execute: async (args, exec) => {
|
|
1009
|
+
const binding = await resolve(sessionIdOf(exec));
|
|
1010
|
+
if (binding === void 0) throw new Error("calling Session is not an active member of this team");
|
|
1011
|
+
if (binding.taskStore === void 0) throw new Error("task store is unavailable");
|
|
1012
|
+
const result = await binding.taskStore.reviewTask({
|
|
1013
|
+
taskId: args.taskId,
|
|
1014
|
+
submissionId: args.submissionId,
|
|
1015
|
+
reviewerMemberId: binding.member.memberId,
|
|
1016
|
+
verdict: args.verdict,
|
|
1017
|
+
findings: args.findings ?? [],
|
|
1018
|
+
evidence: args.evidence ?? []
|
|
1019
|
+
});
|
|
1020
|
+
return {
|
|
1021
|
+
round: jsonRecord(result.round),
|
|
1022
|
+
task: jsonRecord(result.task)
|
|
1023
|
+
};
|
|
1024
|
+
}
|
|
1025
|
+
}));
|
|
1026
|
+
}
|
|
1027
|
+
if (includeAcceptance) {
|
|
1028
|
+
registry.register(defineTool({
|
|
1029
|
+
name: "live_team_accept_task",
|
|
1030
|
+
description: "Accept a waiting task as the team lead and archive its authored document.",
|
|
1031
|
+
parameters: {
|
|
1032
|
+
taskId: {
|
|
1033
|
+
type: "string",
|
|
1034
|
+
required: true
|
|
1035
|
+
},
|
|
1036
|
+
note: { type: "string" }
|
|
1037
|
+
},
|
|
1038
|
+
output: {
|
|
1039
|
+
schema: {
|
|
1040
|
+
type: "object",
|
|
1041
|
+
additionalProperties: false,
|
|
1042
|
+
properties: {
|
|
1043
|
+
task: {
|
|
1044
|
+
type: "json",
|
|
1045
|
+
required: true
|
|
1046
|
+
},
|
|
1047
|
+
archivedPath: {
|
|
1048
|
+
type: "string",
|
|
1049
|
+
required: true
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
},
|
|
1053
|
+
render: (_args, value) => renderOutput(value)
|
|
1054
|
+
},
|
|
1055
|
+
execute: async (args, exec) => {
|
|
1056
|
+
const binding = await resolve(sessionIdOf(exec));
|
|
1057
|
+
if (binding === void 0) throw new Error("calling Session is not an active member of this team");
|
|
1058
|
+
if (binding.taskStore === void 0) throw new Error("task store is unavailable");
|
|
1059
|
+
const leadId = resolveLeadMemberId(binding.team);
|
|
1060
|
+
if (binding.member.memberId !== leadId) throw new Error(`only the lead may accept tasks; ask ${leadId ?? "the lead"}`);
|
|
1061
|
+
const result = await binding.taskStore.acceptTask({
|
|
1062
|
+
taskId: args.taskId,
|
|
1063
|
+
actor: binding.member.memberId,
|
|
1064
|
+
...args.note === void 0 ? {} : { note: args.note }
|
|
1065
|
+
});
|
|
1066
|
+
return {
|
|
1067
|
+
task: jsonRecord(result.task),
|
|
1068
|
+
archivedPath: result.archivedPath
|
|
1069
|
+
};
|
|
1070
|
+
}
|
|
1071
|
+
}));
|
|
1072
|
+
registry.register(defineTool({
|
|
1073
|
+
name: "live_team_revise_task",
|
|
1074
|
+
description: "Return a waiting task to progress with high-priority instructions for its assignee.",
|
|
1075
|
+
parameters: {
|
|
1076
|
+
taskId: {
|
|
1077
|
+
type: "string",
|
|
1078
|
+
required: true
|
|
1079
|
+
},
|
|
1080
|
+
instructions: {
|
|
1081
|
+
type: "string",
|
|
1082
|
+
required: true
|
|
1083
|
+
}
|
|
1084
|
+
},
|
|
1085
|
+
output: {
|
|
1086
|
+
schema: {
|
|
1087
|
+
type: "object",
|
|
1088
|
+
additionalProperties: false,
|
|
1089
|
+
properties: { task: {
|
|
1090
|
+
type: "json",
|
|
1091
|
+
required: true
|
|
1092
|
+
} }
|
|
1093
|
+
},
|
|
1094
|
+
render: (_args, value) => renderOutput(value)
|
|
1095
|
+
},
|
|
1096
|
+
execute: async (args, exec) => {
|
|
1097
|
+
const binding = await resolve(sessionIdOf(exec));
|
|
1098
|
+
if (binding === void 0) throw new Error("calling Session is not an active member of this team");
|
|
1099
|
+
if (binding.taskStore === void 0) throw new Error("task store is unavailable");
|
|
1100
|
+
const leadId = resolveLeadMemberId(binding.team);
|
|
1101
|
+
if (binding.member.memberId !== leadId) throw new Error(`only the lead may revise tasks; ask ${leadId ?? "the lead"}`);
|
|
1102
|
+
return { task: jsonRecord((await binding.taskStore.reviseTask({
|
|
1103
|
+
taskId: args.taskId,
|
|
1104
|
+
actor: binding.member.memberId,
|
|
1105
|
+
instructions: args.instructions
|
|
1106
|
+
})).task) };
|
|
1107
|
+
}
|
|
1108
|
+
}));
|
|
1109
|
+
}
|
|
1110
|
+
if (includeContext) registry.register(defineTool({
|
|
1111
|
+
name: "live_team_brief",
|
|
1112
|
+
description: "Read or update the team briefing. Only the team lead may update it.",
|
|
1113
|
+
parameters: {
|
|
1114
|
+
action: {
|
|
1115
|
+
type: "string",
|
|
1116
|
+
required: true,
|
|
1117
|
+
enum: ["read", "update"]
|
|
1118
|
+
},
|
|
1119
|
+
text: { type: "string" }
|
|
1120
|
+
},
|
|
1121
|
+
output: {
|
|
1122
|
+
schema: {
|
|
1123
|
+
type: "object",
|
|
1124
|
+
additionalProperties: false,
|
|
1125
|
+
properties: {
|
|
1126
|
+
text: { type: "string" },
|
|
1127
|
+
saved: { type: "boolean" },
|
|
1128
|
+
length: { type: "number" }
|
|
1129
|
+
}
|
|
1130
|
+
},
|
|
1131
|
+
render: (_args, value) => renderOutput(value)
|
|
1132
|
+
},
|
|
1133
|
+
execute: async (args, exec) => {
|
|
1134
|
+
const binding = await resolve(sessionIdOf(exec));
|
|
1135
|
+
if (binding === void 0) throw new Error("calling Session is not an active member of this team");
|
|
1136
|
+
const leadId = resolveLeadMemberId(binding.team);
|
|
1137
|
+
if (args.action === "read") {
|
|
1138
|
+
if (binding.workspacePath === void 0) return { text: "" };
|
|
1139
|
+
try {
|
|
1140
|
+
return { text: await readFile(path.join(binding.workspacePath, ".dsh-live-teams", "BRIEF.md"), "utf8") };
|
|
1141
|
+
} catch {
|
|
1142
|
+
return { text: "" };
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
if (binding.member.memberId !== leadId) throw new Error("briefing is owned by the lead; ask the lead to update it");
|
|
1146
|
+
if (typeof args.text !== "string") throw new Error("text is required for update");
|
|
1147
|
+
const next = args.text.trim().slice(0, 8e3);
|
|
1148
|
+
if (binding.workspacePath === void 0) throw new Error("team workspace is unavailable");
|
|
1149
|
+
await mkdir(path.join(binding.workspacePath, ".dsh-live-teams"), { recursive: true });
|
|
1150
|
+
const file = path.join(binding.workspacePath, ".dsh-live-teams", "BRIEF.md");
|
|
1151
|
+
if (next.length === 0) await writeFile(file, "", "utf8");
|
|
1152
|
+
else await writeFile(file, next, "utf8");
|
|
1153
|
+
await binding.appendAudit?.({
|
|
1154
|
+
kind: "briefing/updated",
|
|
1155
|
+
actor: binding.member.memberId,
|
|
1156
|
+
length: next.length,
|
|
1157
|
+
at: Date.now()
|
|
1158
|
+
});
|
|
1159
|
+
return {
|
|
1160
|
+
saved: true,
|
|
1161
|
+
length: next.length
|
|
1162
|
+
};
|
|
1163
|
+
}
|
|
1164
|
+
}));
|
|
1165
|
+
if (includeContext) registry.register(defineTool({
|
|
1166
|
+
name: "live_team_member_note",
|
|
1167
|
+
description: "Set a member note. Only the team lead may update notes.",
|
|
1168
|
+
parameters: {
|
|
1169
|
+
memberId: {
|
|
1170
|
+
type: "string",
|
|
1171
|
+
required: true
|
|
1172
|
+
},
|
|
1173
|
+
note: {
|
|
1174
|
+
type: "string",
|
|
1175
|
+
required: true
|
|
1176
|
+
}
|
|
1177
|
+
},
|
|
1178
|
+
output: {
|
|
1179
|
+
schema: {
|
|
1180
|
+
type: "object",
|
|
1181
|
+
additionalProperties: false,
|
|
1182
|
+
properties: {
|
|
1183
|
+
memberId: {
|
|
1184
|
+
type: "string",
|
|
1185
|
+
required: true
|
|
1186
|
+
},
|
|
1187
|
+
note: {
|
|
1188
|
+
type: "string",
|
|
1189
|
+
required: true
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
},
|
|
1193
|
+
render: (_args, value) => renderOutput(value)
|
|
1194
|
+
},
|
|
1195
|
+
execute: async (args, exec) => {
|
|
1196
|
+
const binding = await resolve(sessionIdOf(exec));
|
|
1197
|
+
if (binding === void 0) throw new Error("calling Session is not an active member of this team");
|
|
1198
|
+
const leadId = resolveLeadMemberId(binding.team);
|
|
1199
|
+
if (binding.member.memberId !== leadId) throw new Error("briefing is owned by the lead; ask the lead to update the member note");
|
|
1200
|
+
if (binding.team.members.find((entry) => entry.memberId === args.memberId) === void 0) throw new Error("member not found");
|
|
1201
|
+
const note = args.note.trim().slice(0, 200);
|
|
1202
|
+
if (binding.setMemberNote !== void 0) await binding.setMemberNote(args.memberId, note, binding.member.memberId);
|
|
1203
|
+
await binding.appendAudit?.({
|
|
1204
|
+
kind: "member/note",
|
|
1205
|
+
memberId: args.memberId,
|
|
1206
|
+
note,
|
|
1207
|
+
actor: binding.member.memberId,
|
|
1208
|
+
at: Date.now()
|
|
1209
|
+
});
|
|
1210
|
+
return {
|
|
1211
|
+
memberId: args.memberId,
|
|
1212
|
+
note
|
|
1213
|
+
};
|
|
1214
|
+
}
|
|
1215
|
+
}));
|
|
1216
|
+
registry.register(defineTool({
|
|
1217
|
+
name: "live_team_status",
|
|
1218
|
+
description: "Read the calling member current binding and contract status.",
|
|
1219
|
+
parameters: {},
|
|
1220
|
+
output: {
|
|
1221
|
+
schema: {
|
|
1222
|
+
type: "object",
|
|
1223
|
+
additionalProperties: false,
|
|
1224
|
+
properties: {
|
|
1225
|
+
memberId: {
|
|
1226
|
+
type: "string",
|
|
1227
|
+
required: true
|
|
1228
|
+
},
|
|
1229
|
+
teamId: {
|
|
1230
|
+
type: "string",
|
|
1231
|
+
required: true
|
|
1232
|
+
},
|
|
1233
|
+
role: {
|
|
1234
|
+
type: "string",
|
|
1235
|
+
required: true
|
|
1236
|
+
},
|
|
1237
|
+
availability: {
|
|
1238
|
+
type: "string",
|
|
1239
|
+
required: true
|
|
1240
|
+
},
|
|
1241
|
+
bindingGeneration: {
|
|
1242
|
+
type: "number",
|
|
1243
|
+
required: true
|
|
1244
|
+
},
|
|
1245
|
+
contractRevision: {
|
|
1246
|
+
type: "number",
|
|
1247
|
+
required: true
|
|
1248
|
+
},
|
|
1249
|
+
live: {
|
|
1250
|
+
type: "boolean",
|
|
1251
|
+
required: true
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
},
|
|
1255
|
+
render: (_args, value) => renderOutput(value)
|
|
1256
|
+
},
|
|
1257
|
+
execute: async (_args, exec) => {
|
|
1258
|
+
const binding = await resolve(sessionIdOf(exec));
|
|
1259
|
+
if (binding === void 0) throw new Error("calling Session is not an active member of this team");
|
|
1260
|
+
return memberStatus(binding);
|
|
1261
|
+
}
|
|
1262
|
+
}));
|
|
1263
|
+
}
|
|
1264
|
+
//#endregion
|
|
1265
|
+
export { MEMBER_TOOL_NAMES, registerMemberTools, registerMemberToolsIfAvailable, toolsForRole };
|