pi2web 0.2.1 → 0.2.6

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 (51) hide show
  1. package/README.md +44 -5
  2. package/dist/activity.js +178 -0
  3. package/dist/agent-feed.js +133 -0
  4. package/dist/agent-templates.js +144 -0
  5. package/dist/agents.js +518 -141
  6. package/dist/auth.js +3 -1
  7. package/dist/collab/baseline.js +84 -0
  8. package/dist/collab/dispatcher.js +45 -10
  9. package/dist/collab/hub.js +1392 -384
  10. package/dist/collab/pi-extension.js +322 -65
  11. package/dist/collab/review-flow.js +197 -48
  12. package/dist/collab/routes.js +94 -23
  13. package/dist/collab/schemas.js +69 -13
  14. package/dist/collab/scoring-flow.js +145 -39
  15. package/dist/collab/similarity.js +15 -0
  16. package/dist/collab/store.js +467 -112
  17. package/dist/collab/types.js +94 -10
  18. package/dist/collab/validate.js +62 -25
  19. package/dist/drafts.js +26 -6
  20. package/dist/http/agent-routes.js +196 -0
  21. package/dist/http/login-limiter.js +53 -0
  22. package/dist/http/mail-settings-routes.js +53 -0
  23. package/dist/http/system-prompt-routes.js +52 -0
  24. package/dist/http/utils.js +43 -0
  25. package/dist/http/web-assets.js +63 -0
  26. package/dist/http/workspace-routes.js +58 -0
  27. package/dist/mail-notifier.js +63 -17
  28. package/dist/main.js +61 -16
  29. package/dist/mcp.js +24 -5
  30. package/dist/metadata-store.js +190 -56
  31. package/dist/paths.js +10 -0
  32. package/dist/sdk-backend.js +221 -45
  33. package/dist/server.js +444 -620
  34. package/dist/session-namer.js +66 -17
  35. package/dist/terminals.js +94 -27
  36. package/dist/timeline.js +185 -0
  37. package/dist/voice.js +153 -35
  38. package/dist/workspaces.js +91 -26
  39. package/dist/ws/agent-socket.js +256 -0
  40. package/dist/ws/terminal-socket.js +46 -0
  41. package/npm-shrinkwrap.json +3685 -0
  42. package/package.json +11 -1
  43. package/packages/protocol/openapi.yaml +6 -2
  44. package/packages/protocol/websocket.schema.json +58 -15
  45. package/web/activity-view.js +121 -0
  46. package/web/app.js +3098 -743
  47. package/web/backend-state.js +59 -27
  48. package/web/collab.js +668 -255
  49. package/web/hid-keyboard.js +173 -140
  50. package/web/index.html +4 -1
  51. package/web/styles.css +14 -0
@@ -1,31 +1,76 @@
1
1
  import { randomUUID } from 'node:crypto';
2
2
  import { StringEnum } from '@earendil-works/pi-ai';
3
3
  import { Type } from 'typebox';
4
- export const COLLAB_ACTIONS = ['ready', 'findings', 'issue_votes', 'merge_votes', 'issue_discussions', 'nominations', 'votes', 'scores', 'debate_argument', 'debate_arguments', 'escalate', 'withdraw'];
4
+ export const COLLAB_ACTIONS = [
5
+ 'ready',
6
+ 'findings',
7
+ 'issue_votes',
8
+ 'merge_votes',
9
+ 'issue_discussions',
10
+ 'nominations',
11
+ 'votes',
12
+ 'scores',
13
+ 'debate_argument',
14
+ 'debate_arguments',
15
+ 'escalate',
16
+ 'withdraw'
17
+ ];
5
18
  const evidence = Type.Object({
6
19
  path: Type.String({ minLength: 1, description: 'Workspace-relative evidence file' }),
7
- startLine: Type.Optional(Type.Number({ minimum: 1 })), endLine: Type.Optional(Type.Number({ minimum: 1 })), excerpt: Type.Optional(Type.String({ maxLength: 2000 }))
20
+ startLine: Type.Optional(Type.Number({ minimum: 1 })),
21
+ endLine: Type.Optional(Type.Number({ minimum: 1 })),
22
+ excerpt: Type.Optional(Type.String({ maxLength: 2000 }))
8
23
  }, { additionalProperties: false });
9
24
  const location = Type.Object({
10
25
  path: Type.String({ minLength: 1, description: 'Workspace-relative file containing the finding' }),
11
- startLine: Type.Optional(Type.Number({ minimum: 1 })), endLine: Type.Optional(Type.Number({ minimum: 1 }))
26
+ startLine: Type.Optional(Type.Number({ minimum: 1 })),
27
+ endLine: Type.Optional(Type.Number({ minimum: 1 }))
12
28
  }, { additionalProperties: false });
13
29
  const finding = Type.Object({
14
- externalId: Type.Optional(Type.String({ maxLength: 100 })), title: Type.String({ minLength: 8, maxLength: 200 }),
30
+ externalId: Type.Optional(Type.String({ maxLength: 100 })),
31
+ title: Type.String({ minLength: 8, maxLength: 200 }),
15
32
  severity: StringEnum(['blocker', 'critical', 'major', 'minor', 'nit']),
16
- category: StringEnum(['security', 'correctness', 'performance', 'maintainability', 'style', 'test', 'docs', 'process']),
17
- requiredAction: Type.Optional(StringEnum(['must_fix', 'should_fix', 'discuss', 'fyi'])), confidence: Type.Optional(Type.Number({ minimum: 0, maximum: 1 })),
18
- location, evidence: Type.String({ minLength: 10, maxLength: 4000 }), impact: Type.Optional(Type.String({ maxLength: 2000 })), suggestion: Type.Optional(Type.String({ maxLength: 4000 }))
33
+ category: StringEnum([
34
+ 'security',
35
+ 'correctness',
36
+ 'performance',
37
+ 'maintainability',
38
+ 'style',
39
+ 'test',
40
+ 'docs',
41
+ 'process'
42
+ ]),
43
+ requiredAction: Type.Optional(StringEnum(['must_fix', 'should_fix', 'discuss', 'fyi'])),
44
+ confidence: Type.Optional(Type.Number({ minimum: 0, maximum: 1 })),
45
+ location,
46
+ evidence: Type.String({ minLength: 10, maxLength: 4000 }),
47
+ impact: Type.Optional(Type.String({ maxLength: 2000 })),
48
+ suggestion: Type.Optional(Type.String({ maxLength: 4000 }))
19
49
  }, { additionalProperties: false });
20
50
  const TOOL_FOR_TASK = {
21
- implement: 'collab_submit_ready', file_findings: 'collab_submit_findings', file_findings_optional: 'collab_submit_findings', validate_issues: 'collab_submit_issue_votes',
22
- vote_on_merges: 'collab_submit_merge_votes', defend_approved_issues: 'collab_submit_issue_discussions', reconsider_issue_votes: 'collab_submit_issue_votes',
23
- nominate_criteria: 'collab_submit_nominations', vote_on_criteria: 'collab_submit_votes', score_rubric: 'collab_submit_scores', rescore_contested: 'collab_submit_scores',
51
+ implement: 'collab_submit_ready',
52
+ file_findings: 'collab_submit_findings',
53
+ file_findings_optional: 'collab_submit_findings',
54
+ validate_issues: 'collab_submit_issue_votes',
55
+ vote_on_merges: 'collab_submit_merge_votes',
56
+ defend_approved_issues: 'collab_submit_issue_discussions',
57
+ reconsider_issue_votes: 'collab_submit_issue_votes',
58
+ nominate_criteria: 'collab_submit_nominations',
59
+ vote_on_criteria: 'collab_submit_votes',
60
+ score_rubric: 'collab_submit_scores',
61
+ rescore_contested: 'collab_submit_scores',
24
62
  debate_contested_scores: 'collab_submit_debate_arguments'
25
63
  };
26
64
  const TASK_FOR_ACTION = {
27
- ready: ['implement'], findings: ['file_findings', 'file_findings_optional'], issue_votes: ['validate_issues', 'reconsider_issue_votes'], merge_votes: ['vote_on_merges'],
28
- issue_discussions: ['defend_approved_issues'], nominations: ['nominate_criteria'], votes: ['vote_on_criteria'], scores: ['score_rubric', 'rescore_contested'], debate_arguments: ['debate_contested_scores']
65
+ ready: ['implement'],
66
+ findings: ['file_findings', 'file_findings_optional'],
67
+ issue_votes: ['validate_issues', 'reconsider_issue_votes'],
68
+ merge_votes: ['vote_on_merges'],
69
+ issue_discussions: ['defend_approved_issues'],
70
+ nominations: ['nominate_criteria'],
71
+ votes: ['vote_on_criteria'],
72
+ scores: ['score_rubric', 'rescore_contested'],
73
+ debate_arguments: ['debate_contested_scores']
29
74
  };
30
75
  const SUBMIT_TOOLS = [...new Set(Object.values(TOOL_FOR_TASK))];
31
76
  const OPTIONAL_TOOLS = ['collab_escalate', 'collab_withdraw_issue'];
@@ -44,7 +89,19 @@ export function createCollabExtension(agentId, bridge) {
44
89
  const existing = aliases.get(id);
45
90
  if (existing)
46
91
  return existing;
47
- const prefix = kind === 'issue' ? 'issue' : kind === 'criterion' ? 'criterion' : kind === 'proposal' ? 'proposal' : kind === 'debate' ? 'debate' : kind === 'baseline' ? 'base' : kind === 'participant' ? 'reviewer' : 'ref';
92
+ const prefix = kind === 'issue'
93
+ ? 'issue'
94
+ : kind === 'criterion'
95
+ ? 'criterion'
96
+ : kind === 'proposal'
97
+ ? 'proposal'
98
+ : kind === 'debate'
99
+ ? 'debate'
100
+ : kind === 'baseline'
101
+ ? 'base'
102
+ : kind === 'participant'
103
+ ? 'reviewer'
104
+ : 'ref';
48
105
  const value = `${prefix}-${[...aliases.values()].filter(entry => entry.startsWith(`${prefix}-`)).length + 1}`;
49
106
  aliases.set(id, value);
50
107
  return value;
@@ -58,10 +115,27 @@ export function createCollabExtension(agentId, bridge) {
58
115
  for (const [name, entry] of Object.entries(value)) {
59
116
  if (name === 'sessionId')
60
117
  continue;
61
- const ids = typeof entry === 'string' ? [entry] : Array.isArray(entry) && entry.every(item => typeof item === 'string') ? entry : undefined;
118
+ const ids = typeof entry === 'string'
119
+ ? [entry]
120
+ : Array.isArray(entry) && entry.every(item => typeof item === 'string')
121
+ ? entry
122
+ : undefined;
62
123
  if (ids && /(Id|Ids)$/.test(name)) {
63
- const kind = /issue/i.test(name) ? 'issue' : /criterion/i.test(name) ? 'criterion' : /proposal/i.test(name) ? 'proposal' : /debate/i.test(name) ? 'debate' : /baseline/i.test(name) ? 'baseline' : /participant|author|reporter|target|raisedBy/i.test(name) ? 'participant' : 'ref';
64
- output[name.replace(/Id(s)?$/, 'Ref$1')] = typeof entry === 'string' ? reference(entry, kind) : ids.map(item => reference(item, kind));
124
+ const kind = /issue/i.test(name)
125
+ ? 'issue'
126
+ : /criterion/i.test(name)
127
+ ? 'criterion'
128
+ : /proposal/i.test(name)
129
+ ? 'proposal'
130
+ : /debate/i.test(name)
131
+ ? 'debate'
132
+ : /baseline/i.test(name)
133
+ ? 'baseline'
134
+ : /participant|author|reporter|target|raisedBy/i.test(name)
135
+ ? 'participant'
136
+ : 'ref';
137
+ output[name.replace(/Id(s)?$/, 'Ref$1')] =
138
+ typeof entry === 'string' ? reference(entry, kind) : ids.map(item => reference(item, kind));
65
139
  }
66
140
  else
67
141
  output[name] = redact(entry);
@@ -76,8 +150,10 @@ export function createCollabExtension(agentId, bridge) {
76
150
  */
77
151
  const seed = (task) => {
78
152
  const counters = new Map();
79
- const claim = (id, alias) => { if (typeof id === 'string' && id && !aliases.has(id))
80
- aliases.set(id, alias); };
153
+ const claim = (id, alias) => {
154
+ if (typeof id === 'string' && id && !aliases.has(id))
155
+ aliases.set(id, alias);
156
+ };
81
157
  for (const seat of Array.isArray(task.panel) ? task.panel : []) {
82
158
  const role = typeof seat.role === 'string' ? seat.role : 'participant', next = (counters.get(role) ?? 0) + 1;
83
159
  counters.set(role, next);
@@ -116,8 +192,13 @@ export function createCollabExtension(agentId, bridge) {
116
192
  }
117
193
  // Only alias-carrying keys are translated back to hub ids. `codeRef` is a semantic field whose value is an
118
194
  // object (commit/dirtyHash), so renaming it to `codeId` would make the hub reject a valid ready submission.
119
- const isAliasKey = (key, entry) => /Ref(s)?$/.test(key) && (typeof entry === 'string' || (Array.isArray(entry) && entry.every(item => typeof item === 'string')));
120
- return Object.fromEntries(Object.entries(value).map(([key, entry]) => [isAliasKey(key, entry) ? key.replace(/Ref(s)?$/, 'Id$1') : key, restore(entry)]));
195
+ const isAliasKey = (key, entry) => /Ref(s)?$/.test(key) &&
196
+ (typeof entry === 'string' ||
197
+ (Array.isArray(entry) && entry.every(item => typeof item === 'string')));
198
+ return Object.fromEntries(Object.entries(value).map(([key, entry]) => [
199
+ isAliasKey(key, entry) ? key.replace(/Ref(s)?$/, 'Id$1') : key,
200
+ restore(entry)
201
+ ]));
121
202
  };
122
203
  const taskText = (task) => {
123
204
  const safe = redact(task), context = { ...safe };
@@ -140,14 +221,18 @@ export function createCollabExtension(agentId, bridge) {
140
221
  if (Object.keys(context).length)
141
222
  lines.push(`Task context:\n${JSON.stringify(context, null, 2)}`);
142
223
  const submit = TOOL_FOR_TASK[String(safe.task ?? '')];
143
- lines.push(submit ? `Complete every required Ref with ${submit}, then end the turn.` : 'No submission is required; end the turn.');
224
+ lines.push(submit
225
+ ? `Complete every required Ref with ${submit}, then end the turn.`
226
+ : 'No submission is required; end the turn.');
144
227
  return lines.join('\n');
145
228
  };
146
229
  const currentTask = () => String(lastTask?.task ?? '');
147
230
  const normalize = (action, payload) => {
148
231
  const body = restore(payload);
149
232
  if (action === 'findings') {
150
- if (!body.baselineId && lastTask?.baseline && typeof lastTask.baseline.baselineId === 'string')
233
+ if (!body.baselineId &&
234
+ lastTask?.baseline &&
235
+ typeof lastTask.baseline.baselineId === 'string')
151
236
  body.baselineId = lastTask.baseline.baselineId;
152
237
  body.reviewComplete = true;
153
238
  }
@@ -157,16 +242,29 @@ export function createCollabExtension(agentId, bridge) {
157
242
  body.clientRequestId = randomUUID();
158
243
  return body;
159
244
  };
160
- const rememberBaseTools = () => { if (!baseTools)
161
- baseTools = pi.getActiveTools().filter(name => !SUBMIT_TOOLS.includes(name) && !OPTIONAL_TOOLS.includes(name) && name !== 'collab_get_task'); };
245
+ const rememberBaseTools = () => {
246
+ if (!baseTools)
247
+ baseTools = pi
248
+ .getActiveTools()
249
+ .filter(name => !SUBMIT_TOOLS.includes(name) && !OPTIONAL_TOOLS.includes(name) && name !== 'collab_get_task');
250
+ };
162
251
  const activate = (task) => {
163
252
  rememberBaseTools();
164
253
  const taskName = String(task?.task ?? '');
165
254
  // Tool names are not an integrity boundary: bash can write just as readily as edit/write, while edit/write
166
255
  // may still be useful for scratch work outside the reviewed checkout. Keep the normal tools available,
167
256
  // state the non-mutation contract explicitly, and let the baseline comparison enforce workspace identity.
168
- const submit = TOOL_FOR_TASK[taskName], optional = taskName && taskName !== 'wait' ? ['collab_escalate', ...(taskName.includes('issue') || taskName === 'validate_issues' ? ['collab_withdraw_issue'] : [])] : [];
169
- pi.setActiveTools([...new Set([...baseTools, 'collab_get_task', ...(submit ? [submit] : []), ...optional])]);
257
+ const submit = TOOL_FOR_TASK[taskName], optional = taskName && taskName !== 'wait'
258
+ ? [
259
+ 'collab_escalate',
260
+ ...(taskName.includes('issue') || taskName === 'validate_issues'
261
+ ? ['collab_withdraw_issue']
262
+ : [])
263
+ ]
264
+ : [];
265
+ pi.setActiveTools([
266
+ ...new Set([...baseTools, 'collab_get_task', ...(submit ? [submit] : []), ...optional])
267
+ ]);
170
268
  };
171
269
  const assertTask = (action) => {
172
270
  const allowed = TASK_FOR_ACTION[action];
@@ -175,13 +273,23 @@ export function createCollabExtension(agentId, bridge) {
175
273
  };
176
274
  const acceptedText = (result) => `Collaboration submission accepted.${result && typeof result === 'object' ? ` ${JSON.stringify(redact(result))}` : ''}`;
177
275
  const registerSubmit = (definition) => pi.registerTool({
178
- name: definition.name, label: definition.label, description: definition.description, promptSnippet: definition.description,
179
- promptGuidelines: [`Use ${definition.name} only after collab_get_task requests it, and include every required Ref exactly once.`], parameters: definition.parameters,
276
+ name: definition.name,
277
+ label: definition.label,
278
+ description: definition.description,
279
+ promptSnippet: definition.description,
280
+ promptGuidelines: [
281
+ `Use ${definition.name} only after collab_get_task requests it, and include every required Ref exactly once.`
282
+ ],
283
+ parameters: definition.parameters,
180
284
  async execute(_id, input) {
181
285
  try {
182
286
  assertTask(definition.action);
183
287
  const result = await bridge.submit(agentId, definition.action, normalize(definition.action, input));
184
- return { content: [{ type: 'text', text: acceptedText(result) }], details: { result: redact(result) }, terminate: definition.terminate ?? true };
288
+ return {
289
+ content: [{ type: 'text', text: acceptedText(result) }],
290
+ details: { result: redact(result) },
291
+ terminate: definition.terminate ?? true
292
+ };
185
293
  }
186
294
  catch (error) {
187
295
  const fields = error.fieldErrors;
@@ -189,49 +297,198 @@ export function createCollabExtension(agentId, bridge) {
189
297
  }
190
298
  }
191
299
  });
192
- pi.on('session_start', () => { baseTools = undefined; lastTask = undefined; aliases = new Map(); activate(); });
300
+ pi.on('session_start', () => {
301
+ baseTools = undefined;
302
+ lastTask = undefined;
303
+ aliases = new Map();
304
+ activate();
305
+ });
193
306
  pi.on('before_agent_start', event => {
194
307
  lastTask = undefined;
195
308
  aliases = new Map();
196
309
  activate();
197
- return { systemPrompt: `${event.systemPrompt}\n\n## Local collaboration\nThis is a local software-development workflow. Call collab_get_task first after every collaboration wake-up. Use only the collaboration submission tool activated for that task. Review and scoring are strictly non-mutating: bash may inspect with commands such as git diff/log, but must not write anywhere under the working directory—not through redirection, tee, file-management commands, formatters, generators, package installation, build artifacts, caches, or scratch files. Send command output to stdout, or if temporary storage is unavoidable use the OS temporary directory outside the workspace. Only an implement task may modify the workspace. The tools own transport and credentials: never use curl, invent endpoints or identifiers, sleep, or poll.` };
310
+ return {
311
+ systemPrompt: `${event.systemPrompt}\n\n## Local collaboration\nThis is a local software-development workflow. Call collab_get_task first after every collaboration wake-up. Use only the collaboration submission tool activated for that task. Review and scoring are strictly non-mutating: bash may inspect with commands such as git diff/log, but must not write anywhere under the working directory—not through redirection, tee, file-management commands, formatters, generators, package installation, build artifacts, caches, or scratch files. Send command output to stdout, or if temporary storage is unavoidable use the OS temporary directory outside the workspace. Only an implement task may modify the workspace. The tools own transport and credentials: never use curl, invent endpoints or identifiers, sleep, or poll.`
312
+ };
198
313
  });
199
314
  pi.registerTool({
200
- name: 'collab_get_task', label: 'Get Collaboration Task', description: 'Get the complete current collaboration assignment and activate its exact typed submission tool.',
201
- promptSnippet: 'Fetch the current collaboration assignment before doing any collaboration work', promptGuidelines: ['Call collab_get_task first after every pi2web collaboration wake-up.'], parameters: Type.Object({}, { additionalProperties: false }),
202
- async execute() { aliases = new Map(); lastTask = await bridge.getTask(agentId); seed(lastTask); activate(lastTask); return { content: [{ type: 'text', text: taskText(lastTask) }], details: { task: redact(lastTask) } }; }
315
+ name: 'collab_get_task',
316
+ label: 'Get Collaboration Task',
317
+ description: 'Get the complete current collaboration assignment and activate its exact typed submission tool.',
318
+ promptSnippet: 'Fetch the current collaboration assignment before doing any collaboration work',
319
+ promptGuidelines: ['Call collab_get_task first after every pi2web collaboration wake-up.'],
320
+ parameters: Type.Object({}, { additionalProperties: false }),
321
+ async execute() {
322
+ aliases = new Map();
323
+ lastTask = await bridge.getTask(agentId);
324
+ seed(lastTask);
325
+ activate(lastTask);
326
+ return { content: [{ type: 'text', text: taskText(lastTask) }], details: { task: redact(lastTask) } };
327
+ }
203
328
  });
204
- registerSubmit({ name: 'collab_submit_ready', label: 'Submit Implementation', action: 'ready', description: 'Declare the assigned implementation complete with an auditable change summary.', parameters: Type.Object({
205
- summary: Type.String({ minLength: 10, maxLength: 4000 }), changes: Type.Array(Type.Object({ path: Type.String({ minLength: 1 }), summary: Type.String({ minLength: 5 }) }, { additionalProperties: false }), { maxItems: 100 }),
329
+ registerSubmit({
330
+ name: 'collab_submit_ready',
331
+ label: 'Submit Implementation',
332
+ action: 'ready',
333
+ description: 'Declare the assigned implementation complete with an auditable change summary.',
334
+ parameters: Type.Object({
335
+ summary: Type.String({ minLength: 10, maxLength: 4000 }),
336
+ changes: Type.Array(Type.Object({ path: Type.String({ minLength: 1 }), summary: Type.String({ minLength: 5 }) }, { additionalProperties: false }), { maxItems: 100 }),
206
337
  codeRef: Type.Optional(Type.Object({ commit: Type.Optional(Type.String()), dirtyHash: Type.Optional(Type.String()) }, { additionalProperties: false }))
207
- }, { additionalProperties: false }) });
208
- registerSubmit({ name: 'collab_submit_findings', label: 'Submit Review Findings', action: 'findings', description: 'Submit the complete blind review, including every required recheck and all new evidence-backed findings.', parameters: Type.Object({
209
- findings: Type.Array(finding, { maxItems: 50 }), rechecks: Type.Optional(Type.Array(Type.Object({ issueRef: Type.String(), outcome: StringEnum(['resolved', 'still_present']), rationale: Type.String({ minLength: 10, maxLength: 4000 }) }, { additionalProperties: false }), { maxItems: 100 }))
210
- }, { additionalProperties: false }) });
211
- registerSubmit({ name: 'collab_submit_issue_votes', label: 'Submit Issue Votes', action: 'issue_votes', description: 'Vote on every required issue and optionally propose duplicate groups.', parameters: Type.Object({
212
- votes: Type.Array(Type.Object({ issueRef: Type.String(), stance: StringEnum(['approve', 'reject']), rationale: Type.Optional(Type.String({ maxLength: 4000 })) }, { additionalProperties: false }), { maxItems: 200, description: 'One vote per Ref in yourRequiredIssueIds; an empty array is only accepted when you owe none' }),
213
- mergeProposals: Type.Optional(Type.Array(Type.Object({ issueRefs: Type.Array(Type.String(), { minItems: 2, maxItems: 20 }), rationale: Type.String({ minLength: 10, maxLength: 2000 }) }, { additionalProperties: false }), { maxItems: 50 }))
214
- }, { additionalProperties: false }) });
215
- registerSubmit({ name: 'collab_submit_merge_votes', label: 'Submit Merge Votes', action: 'merge_votes', description: 'Vote on every required duplicate-merge proposal.', parameters: Type.Object({ votes: Type.Array(Type.Object({ proposalRef: Type.String(), stance: StringEnum(['approve', 'reject']), rationale: Type.Optional(Type.String({ maxLength: 4000 })) }, { additionalProperties: false }), { minItems: 1, maxItems: 100 }) }, { additionalProperties: false }) });
216
- registerSubmit({ name: 'collab_submit_issue_discussions', label: 'Submit Issue Arguments', action: 'issue_discussions', description: 'Defend every required finding with evidence, and withdraw the ones you no longer stand by.', parameters: Type.Object({
217
- discussions: Type.Optional(Type.Array(Type.Object({ issueRef: Type.String(), argument: Type.String({ minLength: 20, maxLength: 4000 }), respondingTo: Type.Optional(Type.String()) }, { additionalProperties: false }), { maxItems: 200 })),
218
- withdrawals: Type.Optional(Type.Array(Type.Object({ issueRef: Type.String(), rationale: Type.String({ minLength: 10, maxLength: 4000, description: 'Why you no longer stand by your own finding' }) }, { additionalProperties: false }), { maxItems: 200 }))
219
- }, { additionalProperties: false }) });
220
- registerSubmit({ name: 'collab_submit_nominations', label: 'Submit Criteria Nominations', action: 'nominations', description: 'Submit the complete independent set of proposed scoring criteria.', parameters: Type.Object({ nominations: Type.Array(Type.Object({
221
- externalId: Type.Optional(Type.String()), name: Type.String({ minLength: 2, maxLength: 80 }), definition: Type.String({ minLength: 20, maxLength: 2000 }), weightSuggestion: Type.Optional(Type.Number({ minimum: 0, maximum: 1 })), anchors: Type.Optional(Type.Record(Type.String(), Type.String())), rationale: Type.Optional(Type.String({ maxLength: 2000 }))
222
- }, { additionalProperties: false }), { maxItems: 20 }) }, { additionalProperties: false }) });
223
- registerSubmit({ name: 'collab_submit_votes', label: 'Submit Criteria Votes', action: 'votes', description: 'Vote exactly once on every required rubric candidate.', parameters: Type.Object({ votes: Type.Array(Type.Object({
224
- criterionRef: Type.String(), stance: StringEnum(['approve', 'reject', 'abstain']), weight: Type.Optional(Type.Number({ minimum: 0, maximum: 1 })), amendment: Type.Optional(Type.String({ maxLength: 1000 })), rationale: Type.Optional(Type.String({ maxLength: 2000 }))
225
- }, { additionalProperties: false }), { minItems: 1, maxItems: 40 }) }, { additionalProperties: false }) });
226
- registerSubmit({ name: 'collab_submit_scores', label: 'Submit Rubric Scores', action: 'scores', description: 'Score exactly once every required criterion with rationale and file evidence.', parameters: Type.Object({ scores: Type.Array(Type.Object({
227
- criterionRef: Type.String(), score: Type.Number(), rationale: Type.String({ minLength: 20, maxLength: 4000 }), evidence: Type.Array(evidence, { minItems: 1, maxItems: 20 }), confidence: Type.Optional(Type.Number({ minimum: 0, maximum: 1 })), changeReason: Type.Optional(Type.String({ maxLength: 2000 }))
228
- }, { additionalProperties: false }), { minItems: 1, maxItems: 40 }) }, { additionalProperties: false }) });
229
- registerSubmit({ name: 'collab_submit_debate_arguments', label: 'Submit Score Debate', action: 'debate_arguments', description: 'Submit one evidence-backed position for every required contested-criterion debate.', parameters: Type.Object({ arguments: Type.Array(Type.Object({
230
- debateRef: Type.String(), stance: StringEnum(['raise', 'lower', 'hold']), argument: Type.String({ minLength: 20, maxLength: 4000 }), evidence: Type.Optional(Type.Array(evidence, { maxItems: 20 })), respondingTo: Type.Optional(Type.String())
231
- }, { additionalProperties: false }), { minItems: 1, maxItems: 40 }) }, { additionalProperties: false }) });
232
- registerSubmit({ name: 'collab_escalate', label: 'Escalate Collaboration', action: 'escalate', description: 'Escalate a genuine blocker or unresolved judgment to the human operator.', terminate: false, parameters: Type.Object({
233
- kind: StringEnum(['issue_dispute', 'rubric_dispute', 'score_dispute', 'other']), refRef: Type.Optional(Type.String()), summary: Type.String({ minLength: 20, maxLength: 2000 }), question: Type.String({ minLength: 10, maxLength: 1000 }), options: Type.Optional(Type.Array(Type.String(), { maxItems: 10 })), urgency: Type.Optional(StringEnum(['low', 'normal', 'high']))
234
- }, { additionalProperties: false }) });
235
- registerSubmit({ name: 'collab_withdraw_issue', label: 'Withdraw Issue', action: 'withdraw', description: 'Withdraw one of your own findings when you no longer stand by it. Does not end your turn.', terminate: false, parameters: Type.Object({ issueRef: Type.String() }, { additionalProperties: false }) });
338
+ }, { additionalProperties: false })
339
+ });
340
+ registerSubmit({
341
+ name: 'collab_submit_findings',
342
+ label: 'Submit Review Findings',
343
+ action: 'findings',
344
+ description: 'Submit the complete blind review, including every required recheck and all new evidence-backed findings.',
345
+ parameters: Type.Object({
346
+ findings: Type.Array(finding, { maxItems: 50 }),
347
+ rechecks: Type.Optional(Type.Array(Type.Object({
348
+ issueRef: Type.String(),
349
+ outcome: StringEnum(['resolved', 'still_present']),
350
+ rationale: Type.String({ minLength: 10, maxLength: 4000 })
351
+ }, { additionalProperties: false }), { maxItems: 100 }))
352
+ }, { additionalProperties: false })
353
+ });
354
+ registerSubmit({
355
+ name: 'collab_submit_issue_votes',
356
+ label: 'Submit Issue Votes',
357
+ action: 'issue_votes',
358
+ description: 'Vote on every required issue and optionally propose duplicate groups.',
359
+ parameters: Type.Object({
360
+ votes: Type.Array(Type.Object({
361
+ issueRef: Type.String(),
362
+ stance: StringEnum(['approve', 'reject']),
363
+ rationale: Type.Optional(Type.String({ maxLength: 4000 }))
364
+ }, { additionalProperties: false }), {
365
+ maxItems: 200,
366
+ description: 'One vote per Ref in yourRequiredIssueIds; an empty array is only accepted when you owe none'
367
+ }),
368
+ mergeProposals: Type.Optional(Type.Array(Type.Object({
369
+ issueRefs: Type.Array(Type.String(), { minItems: 2, maxItems: 20 }),
370
+ rationale: Type.String({ minLength: 10, maxLength: 2000 })
371
+ }, { additionalProperties: false }), { maxItems: 50 }))
372
+ }, { additionalProperties: false })
373
+ });
374
+ registerSubmit({
375
+ name: 'collab_submit_merge_votes',
376
+ label: 'Submit Merge Votes',
377
+ action: 'merge_votes',
378
+ description: 'Vote on every required duplicate-merge proposal.',
379
+ parameters: Type.Object({
380
+ votes: Type.Array(Type.Object({
381
+ proposalRef: Type.String(),
382
+ stance: StringEnum(['approve', 'reject']),
383
+ rationale: Type.Optional(Type.String({ maxLength: 4000 }))
384
+ }, { additionalProperties: false }), { minItems: 1, maxItems: 100 })
385
+ }, { additionalProperties: false })
386
+ });
387
+ registerSubmit({
388
+ name: 'collab_submit_issue_discussions',
389
+ label: 'Submit Issue Arguments',
390
+ action: 'issue_discussions',
391
+ description: 'Defend every required finding with evidence, and withdraw the ones you no longer stand by.',
392
+ parameters: Type.Object({
393
+ discussions: Type.Optional(Type.Array(Type.Object({
394
+ issueRef: Type.String(),
395
+ argument: Type.String({ minLength: 20, maxLength: 4000 }),
396
+ respondingTo: Type.Optional(Type.String())
397
+ }, { additionalProperties: false }), { maxItems: 200 })),
398
+ withdrawals: Type.Optional(Type.Array(Type.Object({
399
+ issueRef: Type.String(),
400
+ rationale: Type.String({
401
+ minLength: 10,
402
+ maxLength: 4000,
403
+ description: 'Why you no longer stand by your own finding'
404
+ })
405
+ }, { additionalProperties: false }), { maxItems: 200 }))
406
+ }, { additionalProperties: false })
407
+ });
408
+ registerSubmit({
409
+ name: 'collab_submit_nominations',
410
+ label: 'Submit Criteria Nominations',
411
+ action: 'nominations',
412
+ description: 'Submit the complete independent set of proposed scoring criteria.',
413
+ parameters: Type.Object({
414
+ nominations: Type.Array(Type.Object({
415
+ externalId: Type.Optional(Type.String()),
416
+ name: Type.String({ minLength: 2, maxLength: 80 }),
417
+ definition: Type.String({ minLength: 20, maxLength: 2000 }),
418
+ weightSuggestion: Type.Optional(Type.Number({ minimum: 0, maximum: 1 })),
419
+ anchors: Type.Optional(Type.Record(Type.String(), Type.String())),
420
+ rationale: Type.Optional(Type.String({ maxLength: 2000 }))
421
+ }, { additionalProperties: false }), { maxItems: 20 })
422
+ }, { additionalProperties: false })
423
+ });
424
+ registerSubmit({
425
+ name: 'collab_submit_votes',
426
+ label: 'Submit Criteria Votes',
427
+ action: 'votes',
428
+ description: 'Vote exactly once on every required rubric candidate.',
429
+ parameters: Type.Object({
430
+ votes: Type.Array(Type.Object({
431
+ criterionRef: Type.String(),
432
+ stance: StringEnum(['approve', 'reject', 'abstain']),
433
+ weight: Type.Optional(Type.Number({ minimum: 0, maximum: 1 })),
434
+ amendment: Type.Optional(Type.String({ maxLength: 1000 })),
435
+ rationale: Type.Optional(Type.String({ maxLength: 2000 }))
436
+ }, { additionalProperties: false }), { minItems: 1, maxItems: 40 })
437
+ }, { additionalProperties: false })
438
+ });
439
+ registerSubmit({
440
+ name: 'collab_submit_scores',
441
+ label: 'Submit Rubric Scores',
442
+ action: 'scores',
443
+ description: 'Score exactly once every required criterion with rationale and file evidence.',
444
+ parameters: Type.Object({
445
+ scores: Type.Array(Type.Object({
446
+ criterionRef: Type.String(),
447
+ score: Type.Number(),
448
+ rationale: Type.String({ minLength: 20, maxLength: 4000 }),
449
+ evidence: Type.Array(evidence, { minItems: 1, maxItems: 20 }),
450
+ confidence: Type.Optional(Type.Number({ minimum: 0, maximum: 1 })),
451
+ changeReason: Type.Optional(Type.String({ maxLength: 2000 }))
452
+ }, { additionalProperties: false }), { minItems: 1, maxItems: 40 })
453
+ }, { additionalProperties: false })
454
+ });
455
+ registerSubmit({
456
+ name: 'collab_submit_debate_arguments',
457
+ label: 'Submit Score Debate',
458
+ action: 'debate_arguments',
459
+ description: 'Submit one evidence-backed position for every required contested-criterion debate.',
460
+ parameters: Type.Object({
461
+ arguments: Type.Array(Type.Object({
462
+ debateRef: Type.String(),
463
+ stance: StringEnum(['raise', 'lower', 'hold']),
464
+ argument: Type.String({ minLength: 20, maxLength: 4000 }),
465
+ evidence: Type.Optional(Type.Array(evidence, { maxItems: 20 })),
466
+ respondingTo: Type.Optional(Type.String())
467
+ }, { additionalProperties: false }), { minItems: 1, maxItems: 40 })
468
+ }, { additionalProperties: false })
469
+ });
470
+ registerSubmit({
471
+ name: 'collab_escalate',
472
+ label: 'Escalate Collaboration',
473
+ action: 'escalate',
474
+ description: 'Escalate a genuine blocker or unresolved judgment to the human operator.',
475
+ terminate: false,
476
+ parameters: Type.Object({
477
+ kind: StringEnum(['issue_dispute', 'rubric_dispute', 'score_dispute', 'other']),
478
+ refRef: Type.Optional(Type.String()),
479
+ summary: Type.String({ minLength: 20, maxLength: 2000 }),
480
+ question: Type.String({ minLength: 10, maxLength: 1000 }),
481
+ options: Type.Optional(Type.Array(Type.String(), { maxItems: 10 })),
482
+ urgency: Type.Optional(StringEnum(['low', 'normal', 'high']))
483
+ }, { additionalProperties: false })
484
+ });
485
+ registerSubmit({
486
+ name: 'collab_withdraw_issue',
487
+ label: 'Withdraw Issue',
488
+ action: 'withdraw',
489
+ description: 'Withdraw one of your own findings when you no longer stand by it. Does not end your turn.',
490
+ terminate: false,
491
+ parameters: Type.Object({ issueRef: Type.String() }, { additionalProperties: false })
492
+ });
236
493
  };
237
494
  }