engineering-memory 1.11.27 → 1.11.29

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.
@@ -7,6 +7,7 @@ const option = z.strictObject({
7
7
  label: z.string().trim().min(1).max(100),
8
8
  description: z.string().trim().min(1).max(240),
9
9
  });
10
+ const reviewLine = z.string().trim().min(1).max(300);
10
11
  const copySchema = z.strictObject({
11
12
  message: z.string().trim().min(1).max(240),
12
13
  context: z.string().trim().min(1).max(500),
@@ -17,12 +18,26 @@ const copySchema = z.strictObject({
17
18
  commit_push_draft_pr: option,
18
19
  commit_push_pr: option,
19
20
  defer: option,
21
+ commit_push_review_pr: option.optional(),
22
+ commit_push_pr_unreviewed: option.optional(),
23
+ reviewRequired: reviewLine.optional(),
24
+ reviewOptional: reviewLine.optional(),
25
+ reviewFix: reviewLine.optional(),
20
26
  baseMessage: z.string().trim().min(1).max(240),
21
27
  baseContext: z.string().trim().min(1).max(500),
22
28
  baseTitle: z.string().trim().min(1).max(100),
23
29
  baseWrite: option,
24
30
  recorded: z.string().trim().min(1).max(100).optional(),
25
31
  });
32
+ const deliveryChoices = [
33
+ 'commit',
34
+ 'commit_push',
35
+ 'commit_push_review_pr',
36
+ 'commit_push_draft_pr',
37
+ 'commit_push_pr',
38
+ 'defer',
39
+ ];
40
+ const reviewPolicy = z.enum(['none', 'optional', 'required']);
26
41
  const closedSchema = z.object({
27
42
  closed: z.literal(true),
28
43
  taskVersion: z.number().int().positive(),
@@ -47,8 +62,11 @@ const closedSchema = z.object({
47
62
  taskId: z.string(),
48
63
  lockVersion: z.number().int(),
49
64
  choice: z.enum(['commit', 'commit_push', 'commit_push_draft_pr', 'commit_push_pr']).nullish(),
65
+ reviewState: z.string().nullish(),
50
66
  })
51
67
  .nullish(),
68
+ reviewPolicy: reviewPolicy.optional(),
69
+ reviewFixOf: z.object({ taskId: z.string(), externalTaskId: z.string() }).nullish(),
52
70
  });
53
71
  const recordWording = z.strictObject({
54
72
  message: z.string().trim().min(1).max(240),
@@ -63,10 +81,53 @@ const recordSchema = z.object({
63
81
  branch: z.string().nullable(),
64
82
  state: z.enum(['unanswered', 'answered', 'delivered', 'cancelled']),
65
83
  lockVersion: z.number().int(),
84
+ reviewPolicy: reviewPolicy.optional(),
85
+ reviewFixOfDeliveryId: z.string().nullish(),
66
86
  });
87
+ function offered(policy, fix) {
88
+ if (fix)
89
+ return ['commit', 'commit_push'];
90
+ if (policy === 'required')
91
+ return ['commit', 'commit_push', 'commit_push_review_pr'];
92
+ if (policy === 'optional')
93
+ return ['commit', 'commit_push', 'commit_push_review_pr', 'commit_push_pr'];
94
+ return ['commit', 'commit_push', 'commit_push_draft_pr', 'commit_push_pr'];
95
+ }
96
+ function wording(copy, policy, choice) {
97
+ return choice === 'commit_push_pr' && policy === 'optional'
98
+ ? copy.commit_push_pr_unreviewed
99
+ : copy[choice];
100
+ }
101
+ function pullRequest(choice) {
102
+ return (choice === 'commit_push_review_pr' ||
103
+ choice === 'commit_push_draft_pr' ||
104
+ choice === 'commit_push_pr');
105
+ }
106
+ const incompleteCopy = {
107
+ ok: false,
108
+ error: {
109
+ kind: 'delivery',
110
+ message: "Supply complete delivery copy in the conversation language before closing, including the code review wording this project's question needs (commit_push_review_pr, commit_push_pr_unreviewed, reviewRequired, reviewOptional, reviewFix).",
111
+ recovery: 'task.close',
112
+ retryable: false,
113
+ },
114
+ };
115
+ function settingChanged(tool, now) {
116
+ return output({
117
+ ok: false,
118
+ error: {
119
+ kind: 'delivery',
120
+ message: `The project's code review setting changed to ${now} while the delivery question was open, so the answer was not recorded and no Git action was performed. Tell the user, then call ${tool} again: its question follows the new setting.`,
121
+ recovery: tool,
122
+ retryable: true,
123
+ },
124
+ });
125
+ }
67
126
  export function registerDeliveryTools(server, service) {
68
127
  registerTaskDelivery(server, service);
69
128
  registerTaskReviewRequest(server, service);
129
+ registerTaskOpenReviewRequest(server, service);
130
+ registerGitLabToken(server, service);
70
131
  server.registerTool('task.close', {
71
132
  description: 'Close the verified task, then open its durable mode-governed Git delivery selector in the same call. Memory publication is separate. Cancellation/feedback leave delivery pending; retry identical input to resume. PR/MR is one review-request concept, not an instruction to merge. No Git action is performed by this tool. Read-only tasks need no delivery. Supply complete copy when the product text catalogue lacks the conversation language. If the user already explicitly chose Git delivery in their own message, deliveryInstruction preserves that choice with its exact relevant excerpt; it is an agent-reported instruction, never a native answer or a substitute for missing user consent. Do not use it for agent decisions or memory-publication approval.',
72
133
  inputSchema: z.object({
@@ -77,13 +138,7 @@ export function registerDeliveryTools(server, service) {
77
138
  decisionAttempt: z.number().int().min(1).max(10000).optional(),
78
139
  deliveryInstruction: z
79
140
  .strictObject({
80
- choice: z.enum([
81
- 'commit',
82
- 'commit_push',
83
- 'commit_push_draft_pr',
84
- 'commit_push_pr',
85
- 'defer',
86
- ]),
141
+ choice: z.enum(deliveryChoices),
87
142
  userRequestExcerpt: z.string().trim().min(1).max(1200),
88
143
  baseBranch: z.string().trim().min(1).max(240).optional(),
89
144
  })
@@ -121,6 +176,8 @@ export function registerDeliveryTools(server, service) {
121
176
  const data = parsed.data;
122
177
  if (data.deliveryContext.mode === 'read_only')
123
178
  return output(closed);
179
+ const policy = data.reviewPolicy ?? 'none';
180
+ const fix = data.reviewFixOf ?? null;
124
181
  const delivered = data.delivery.alreadyDelivered;
125
182
  if (delivered)
126
183
  return output({
@@ -128,7 +185,12 @@ export function registerDeliveryTools(server, service) {
128
185
  data: {
129
186
  ...closed.data,
130
187
  deliveryStatus: 'delivered',
131
- nextAction: `Everything this task changed was already committed and pushed by hand: ${delivered.ref.replace(/^refs\/remotes\//, '')} contains ${delivered.commit.slice(0, 12)}. There is nothing left to deliver, so no delivery question is asked, and the delivery is recorded for the project. Call worktree.release in this folder with deliveryOutcome delivered to settle the folder.`,
188
+ nextAction: `Everything this task changed was already committed and pushed by hand: ${delivered.ref.replace(/^refs\/remotes\//, '')} contains ${delivered.commit.slice(0, 12)}. There is nothing left to deliver, so no delivery question is asked, and the delivery is recorded for the project. Call worktree.release in this folder with deliveryOutcome delivered to settle the folder.` +
189
+ (fix
190
+ ? ` This task fixes the review findings of ${fix.externalTaskId}: once the push is recorded, its open findings are marked fixed at this commit. Read review.get for ${fix.externalTaskId} and ask the user whether to review it again (review.start) or end its review as passed (review.conclude).`
191
+ : policy === 'required'
192
+ ? ' Code review is required in this project: if a PR/MR is opened for this branch, open it as a draft and record it (task.open_review_request on GitLab, task.review_request elsewhere). Recording it starts the review, which review.start runs.'
193
+ : ''),
132
194
  },
133
195
  });
134
196
  const destination = data.delivery.destination;
@@ -150,6 +212,8 @@ export function registerDeliveryTools(server, service) {
150
212
  language,
151
213
  copy,
152
214
  decisionAttempt: input.decisionAttempt ?? 0,
215
+ reviewPolicy: policy,
216
+ reviewFixOf: fix?.taskId ?? null,
153
217
  };
154
218
  const binding = { deliveryDigest: sha256(stableStringify(identity)) };
155
219
  const owner = {
@@ -161,20 +225,47 @@ export function registerDeliveryTools(server, service) {
161
225
  const questionnaireId = 'task-delivery-' + binding.deliveryDigest;
162
226
  const canPush = Boolean(destination.remote && destination.pushUrl);
163
227
  const ids = canPush
164
- ? ['commit', 'commit_push', 'commit_push_draft_pr', 'commit_push_pr', 'defer']
228
+ ? [...offered(policy, Boolean(fix)), 'defer']
165
229
  : ['commit', 'defer'];
166
230
  const instruction = input.deliveryInstruction;
167
- const recorded = data.deliveryRecord?.choice;
168
- if (instruction && !ids.some((id) => id === instruction.choice))
231
+ const instructed = !instruction
232
+ ? null
233
+ : fix && pullRequest(instruction.choice)
234
+ ? 'commit_push'
235
+ : policy === 'required' && pullRequest(instruction.choice)
236
+ ? 'commit_push_review_pr'
237
+ : instruction.choice;
238
+ if (instructed && !ids.includes(instructed))
169
239
  return output({
170
240
  ok: false,
171
241
  error: {
172
242
  kind: 'delivery',
173
- message: 'This delivery needs an available remote and push URL. No Git action was performed.',
243
+ message: !canPush
244
+ ? 'This delivery needs an available remote and push URL. No Git action was performed.'
245
+ : policy === 'none'
246
+ ? 'Code review is off in this project, so a reviewed PR/MR is not offered. No Git action was performed. Call task.close without deliveryInstruction to ask the user how to deliver.'
247
+ : 'Code review is optional in this project, so a draft PR/MR is either reviewed first or opened without review, and the instruction does not say which. No Git action was performed. Call task.close without deliveryInstruction to ask the user.',
174
248
  recovery: 'task.close',
175
249
  retryable: false,
176
250
  },
177
251
  });
252
+ const line = !canPush
253
+ ? ''
254
+ : fix
255
+ ? copy.reviewFix && format(copy.reviewFix, language ?? 'en', { task: fix.externalTaskId })
256
+ : policy === 'required'
257
+ ? copy.reviewRequired
258
+ : policy === 'optional'
259
+ ? copy.reviewOptional
260
+ : '';
261
+ if (!instruction && (line === undefined || ids.some((id) => !wording(copy, policy, id))))
262
+ return output(incompleteCopy);
263
+ const recorded = data.deliveryRecord?.choice;
264
+ const recordedLabel = !recorded
265
+ ? undefined
266
+ : data.deliveryRecord?.reviewState === 'pending'
267
+ ? (copy.commit_push_review_pr ?? copy[recorded]).label
268
+ : (wording(copy, policy, recorded) ?? copy[recorded]).label;
178
269
  const form = instruction
179
270
  ? null
180
271
  : await askQuestionnaire(server, service, {
@@ -190,18 +281,15 @@ export function registerDeliveryTools(server, service) {
190
281
  ': ' +
191
282
  destination.branch +
192
283
  (canPush ? ' → ' + destination.remote + ' (' + destination.pushUrl + ')' : '') +
193
- (recorded && copy.recorded
194
- ? '\n' + copy.recorded + ': ' + copy[recorded].label
195
- : ''),
284
+ (recordedLabel && copy.recorded
285
+ ? '\n' + copy.recorded + ': ' + recordedLabel
286
+ : '') +
287
+ (line ? '\n' + line : ''),
196
288
  example: copy.example,
197
- options: ids.map((id) => ({
198
- id,
199
- label: copy[id].label,
200
- description: copy[id].description,
201
- })),
289
+ options: ids.map((id) => ({ id, ...wording(copy, policy, id) })),
202
290
  binding,
203
291
  }, context, [], owner);
204
- const choice = instruction?.choice ?? (form ? answerChoice(form) : null);
292
+ const choice = instructed ?? (form ? answerChoice(form) : null);
205
293
  if (!choice)
206
294
  return form;
207
295
  const { deliveryInstruction: _instruction, ...formInput } = input;
@@ -225,11 +313,9 @@ export function registerDeliveryTools(server, service) {
225
313
  return deferred();
226
314
  service.live.deliveryStarted();
227
315
  try {
228
- let baseBranch = choice === 'commit_push_draft_pr' || choice === 'commit_push_pr'
229
- ? instruction?.baseBranch
230
- : undefined;
316
+ let baseBranch = pullRequest(choice) ? instruction?.baseBranch : undefined;
231
317
  let baseQuestionnaireId;
232
- if ((choice === 'commit_push_draft_pr' || choice === 'commit_push_pr') && !baseBranch) {
318
+ if (pullRequest(choice) && !baseBranch) {
233
319
  baseQuestionnaireId = 'task-delivery-base-' + sha256(questionnaireId + choice);
234
320
  const base = await askPullRequestBase(server, service, context, { ...input, questionnaireId: baseQuestionnaireId, binding: { ...binding, choice } }, language, copy, owner);
235
321
  if (!answerChoice(base))
@@ -240,7 +326,7 @@ export function registerDeliveryTools(server, service) {
240
326
  if (!baseBranch)
241
327
  return base;
242
328
  }
243
- if (choice === 'commit_push_draft_pr' || choice === 'commit_push_pr') {
329
+ if (pullRequest(choice)) {
244
330
  const valid = await service.taskDeliveryBase({
245
331
  repoRoot: input.repoRoot,
246
332
  branch: baseBranch,
@@ -297,6 +383,9 @@ export function registerDeliveryTools(server, service) {
297
383
  retryable: false,
298
384
  },
299
385
  });
386
+ const now = current.data.reviewPolicy ?? 'none';
387
+ if (pullRequest(choice) && now !== policy)
388
+ return settingChanged('task.close', now);
300
389
  const question = instruction
301
390
  ? null
302
391
  : await service.questionnaireResume({ repoRoot: input.repoRoot, questionnaireId });
@@ -311,14 +400,21 @@ export function registerDeliveryTools(server, service) {
311
400
  projectId: record.projectId,
312
401
  taskId: record.taskId,
313
402
  expectedVersion: record.lockVersion,
314
- choice,
403
+ choice: choice === 'commit_push_review_pr' ? 'commit_push_draft_pr' : choice,
315
404
  ...(baseBranch ? { baseBranch } : {}),
405
+ ...(pullRequest(choice) && data.reviewPolicy
406
+ ? { expectedReviewPolicy: data.reviewPolicy }
407
+ : {}),
316
408
  answerSource: question ? answerSourceName(question.answerSource) : 'user_instruction',
317
409
  })
318
410
  : null;
319
411
  const answered = answer?.ok ? answer.data : null;
320
412
  const refused = answer && !answer.ok ? answer.error : null;
321
- const other = refused?.details?.delivery;
413
+ const details = refused?.details;
414
+ if (details?.reviewPolicy)
415
+ return settingChanged('task.close', details.reviewPolicy);
416
+ const other = details?.delivery;
417
+ const told = instruction && instructed !== instruction.choice ? instruction.choice : null;
322
418
  return output({
323
419
  ok: true,
324
420
  data: {
@@ -350,7 +446,24 @@ export function registerDeliveryTools(server, service) {
350
446
  ...(baseQuestionnaireId ? { baseQuestionnaireId } : {}),
351
447
  destination,
352
448
  },
353
- nextAction: 'Perform only the selected Git delivery under host permissions. No commit, push, PR/MR or merge was executed by task.close. Preserve this exact branch and destination.',
449
+ nextAction: [
450
+ 'Perform only the selected Git delivery under host permissions. No commit, push, PR/MR or merge was executed by task.close. Preserve this exact branch and destination.',
451
+ told && fix
452
+ ? `The user asked for a PR/MR, but this task fixes the review findings of ${fix.externalTaskId}, whose PR/MR is already open: its changes are committed and pushed onto that branch and no new PR/MR is opened. Tell the user.`
453
+ : told
454
+ ? 'The user asked for a PR/MR without saying it is reviewed, and code review is required in this project, so it opens as a draft and becomes ready once the review ends. Tell the user.'
455
+ : '',
456
+ choice === 'commit_push_review_pr'
457
+ ? `After the push and worktree.release, open the PR/MR into ${baseBranch} as a draft: on GitLab task.open_review_request opens it as a draft; elsewhere open it as a draft with your own tools and record it with task.review_request. Then start the code review with review.start for this task. The PR/MR becomes ready when the review ends.`
458
+ : '',
459
+ fix && choice === 'commit_push'
460
+ ? `The push goes onto ${destination.branch}, the open PR/MR branch of ${fix.externalTaskId}; open no new PR/MR. Once the push is recorded by worktree.release, the open findings of ${fix.externalTaskId} are marked fixed at that commit: read review.get for ${fix.externalTaskId} and ask the user whether to review it again (review.start) or end its review as passed (review.conclude).`
461
+ : fix
462
+ ? `The findings of ${fix.externalTaskId} stay open until this commit is pushed onto its PR/MR branch.`
463
+ : '',
464
+ ]
465
+ .filter(Boolean)
466
+ .join(' '),
354
467
  },
355
468
  });
356
469
  }
@@ -373,10 +486,10 @@ function registerTaskDelivery(server, service) {
373
486
  }, async (input, context) => {
374
487
  const language = await service.language(input.language);
375
488
  const copy = language ? copySchema.safeParse(texts('delivery', language)).data : undefined;
376
- const wording = language
489
+ const recordCopy = language
377
490
  ? recordWording.safeParse(texts('taskDelivery', language)).data
378
491
  : undefined;
379
- if (!language || !copy || !wording)
492
+ if (!language || !copy || !recordCopy)
380
493
  return output({
381
494
  ok: false,
382
495
  error: {
@@ -408,7 +521,14 @@ function registerTaskDelivery(server, service) {
408
521
  nextAction: 'This delivery is already concluded. Tell the user what was recorded.',
409
522
  },
410
523
  });
411
- const binding = { taskId: record.taskId, lockVersion: record.lockVersion };
524
+ const policy = record.reviewPolicy ?? 'none';
525
+ const fix = Boolean(record.reviewFixOfDeliveryId);
526
+ const binding = {
527
+ taskId: record.taskId,
528
+ lockVersion: record.lockVersion,
529
+ reviewPolicy: policy,
530
+ reviewFix: fix,
531
+ };
412
532
  const questionnaireId = 'task-delivery-record-' +
413
533
  sha256(stableStringify({ ...binding, decisionAttempt: input.decisionAttempt ?? 0 }));
414
534
  const owner = {
@@ -421,28 +541,38 @@ function registerTaskDelivery(server, service) {
421
541
  language: input.language,
422
542
  },
423
543
  };
424
- const ids = [
425
- 'commit',
426
- 'commit_push',
427
- 'commit_push_draft_pr',
428
- 'commit_push_pr',
429
- 'abandon',
430
- 'defer',
431
- ];
544
+ const ids = offered(policy, fix);
545
+ const line = fix || policy === 'none'
546
+ ? ''
547
+ : policy === 'required'
548
+ ? copy.reviewRequired
549
+ : copy.reviewOptional;
550
+ if (line === undefined || ids.some((id) => !wording(copy, policy, id)))
551
+ return output({
552
+ ok: false,
553
+ error: {
554
+ kind: 'delivery',
555
+ message: "The delivery question has no code review text in the conversation language, and this project's question needs it. Ask again in a language Engineering Memory offers.",
556
+ recovery: 'task.delivery',
557
+ retryable: false,
558
+ },
559
+ });
432
560
  const form = await askQuestionnaire(server, service, {
433
561
  repoRoot: input.repoRoot,
434
562
  presentation: input.presentation,
435
563
  questionnaireId,
436
564
  language,
437
565
  impact: 'critical',
438
- message: format(wording.message, language, { task: record.externalTaskId }),
439
- context: wording.context + (record.branch ? '\n' + copy.destination + ': ' + record.branch : ''),
440
- example: wording.example,
441
- options: ids.map((id) => ({
442
- id,
443
- label: id === 'abandon' ? wording.abandon.label : copy[id].label,
444
- description: id === 'abandon' ? wording.abandon.description : copy[id].description,
445
- })),
566
+ message: format(recordCopy.message, language, { task: record.externalTaskId }),
567
+ context: recordCopy.context +
568
+ (record.branch ? '\n' + copy.destination + ': ' + record.branch : '') +
569
+ (line ? '\n' + line : ''),
570
+ example: recordCopy.example,
571
+ options: [
572
+ ...ids.map((id) => ({ id, ...wording(copy, policy, id) })),
573
+ { id: 'abandon', ...recordCopy.abandon },
574
+ { id: 'defer', ...copy.defer },
575
+ ],
446
576
  binding,
447
577
  }, context, [], owner);
448
578
  const choice = answerChoice(form);
@@ -464,7 +594,7 @@ function registerTaskDelivery(server, service) {
464
594
  reason: 'abandoned',
465
595
  }));
466
596
  let baseBranch;
467
- if (choice === 'commit_push_draft_pr' || choice === 'commit_push_pr') {
597
+ if (pullRequest(choice)) {
468
598
  const base = await askPullRequestBase(server, service, context, {
469
599
  ...input,
470
600
  questionnaireId: 'task-delivery-base-' + sha256(questionnaireId + choice),
@@ -493,10 +623,17 @@ function registerTaskDelivery(server, service) {
493
623
  projectId: input.projectId,
494
624
  taskId: input.taskId,
495
625
  expectedVersion: record.lockVersion,
496
- choice,
626
+ choice: choice === 'commit_push_review_pr' ? 'commit_push_draft_pr' : choice,
497
627
  ...(baseBranch ? { baseBranch } : {}),
628
+ ...(pullRequest(choice) && record.reviewPolicy
629
+ ? { expectedReviewPolicy: record.reviewPolicy }
630
+ : {}),
498
631
  answerSource: answerSourceName(question.answerSource),
499
632
  });
633
+ const changed = answer.error?.details
634
+ ?.reviewPolicy;
635
+ if (changed)
636
+ return settingChanged('task.delivery', changed);
500
637
  if (!answer.ok)
501
638
  return output(answer);
502
639
  return output({
@@ -536,6 +673,60 @@ function registerTaskReviewRequest(server, service) {
536
673
  });
537
674
  });
538
675
  }
676
+ function registerTaskOpenReviewRequest(server, service) {
677
+ server.registerTool('task.open_review_request', {
678
+ description: "Open the merge request of a delivered task on GitLab from this computer and record it, in one call. It works for a GitLab only this computer's network reaches, with the user's own token saved on this computer through gitlab.token. The target branch and draft come from the delivery answer unless given here. The branch must already be on GitLab with the delivered commit. An open merge request of the branch is reused, never opened twice. When it refuses, it says why and what works instead; opening the merge request in GitLab by hand and recording it with task.review_request always works.",
679
+ inputSchema: z.strictObject({
680
+ projectId: z.string().uuid(),
681
+ taskId: z.string().uuid(),
682
+ repoRoot: z.string().min(1).optional(),
683
+ targetBranch: z.string().trim().min(1).max(240).optional(),
684
+ draft: z.boolean().optional(),
685
+ title: z
686
+ .string()
687
+ .trim()
688
+ .min(1)
689
+ .max(200)
690
+ .regex(/^[^\r\n]+$/)
691
+ .optional(),
692
+ description: z.string().trim().min(1).max(4000).optional(),
693
+ }),
694
+ }, async (input) => {
695
+ const result = await service.taskOpenReviewRequest(input);
696
+ if (!result.ok)
697
+ return output(result);
698
+ const data = result.data;
699
+ const opened = data.mergeRequest;
700
+ const said = opened
701
+ ? `${opened.reused ? 'The branch already had an open merge request, and it is used' : 'The merge request is opened'}: ${opened.url} (${opened.state}, into ${opened.targetBranch}).`
702
+ : '';
703
+ return output({
704
+ ok: true,
705
+ data: {
706
+ ...data,
707
+ nextAction: data.alreadyRecorded
708
+ ? `This delivery already has its merge request recorded: ${data.alreadyRecorded.url} (${data.alreadyRecorded.state}). Nothing was opened. When the user says its state changed, record that with task.review_request.`
709
+ : data.reportRefused
710
+ ? `${said} Recording it in Engineering Memory was refused: ${data.reportRefused} Tell the user; once the cause is fixed, record it with task.review_request.`
711
+ : data.report?.queued
712
+ ? `${said} Engineering Memory is unreachable, so recording it is queued; it is recorded, and the work item moves, when Engineering Memory is reachable again.`
713
+ : `${said} ${data.report?.data?.reviewEvent?.nextAction ??
714
+ (data.report?.data?.reviewEvent
715
+ ? 'It is recorded; the work item stays where it is.'
716
+ : 'It is recorded. The work item does not move yet: other work on it is still open or undelivered, or the task has no active work item.')}`,
717
+ },
718
+ });
719
+ });
720
+ }
721
+ function registerGitLabToken(server, service) {
722
+ server.registerTool('gitlab.token', {
723
+ description: "Give the user a page on this computer where they save their own GitLab personal access token, so task.open_review_request can open merge requests and Engineering Memory can follow them on a GitLab only this computer's network reaches. The token is typed only on that page: it never passes through the chat, this agent or Engineering Memory's server, and it is kept in this computer's credential store for the signed-in account. Never open or fill in the page yourself. The same page removes a saved token.",
724
+ inputSchema: z.strictObject({
725
+ repoRoot: z.string().min(1).optional(),
726
+ language: languageTag.optional(),
727
+ }),
728
+ }, async (input) => output(await service.gitlabToken(input)));
729
+ }
539
730
  function askPullRequestBase(server, service, context, input, language, copy, owner) {
540
731
  return askQuestionnaire(server, service, {
541
732
  repoRoot: input.repoRoot,