@nyxa/nyx-agent 0.3.1 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/runtime/buildPrompt.js +10 -0
- package/dist/runtime/runPhase.js +2 -0
- package/dist/runtime/runWorkflow.js +58 -22
- package/dist/runtime/workItemAnnotations.js +39 -0
- package/docs/nyxagent-v0-spec.md +26 -11
- package/package.json +1 -1
- package/templates/default/prompts/selection.md +19 -10
- package/templates/default/schemas/selection.schema.json +17 -0
|
@@ -41,11 +41,21 @@ async function buildRuntimeContract(input) {
|
|
|
41
41
|
JSON.stringify(input.context.available_work_items ?? [], null, 2),
|
|
42
42
|
"```",
|
|
43
43
|
"",
|
|
44
|
+
"Recommended work item queue:",
|
|
45
|
+
"```json",
|
|
46
|
+
JSON.stringify(input.context.recommended_work_item_queue ?? [], null, 2),
|
|
47
|
+
"```",
|
|
48
|
+
"",
|
|
44
49
|
"Selected work item queue:",
|
|
45
50
|
"```json",
|
|
46
51
|
JSON.stringify(input.context.selected_work_item_queue ?? [], null, 2),
|
|
47
52
|
"```",
|
|
48
53
|
"",
|
|
54
|
+
"Work item annotations:",
|
|
55
|
+
"```json",
|
|
56
|
+
JSON.stringify(input.context.work_item_annotations ?? [], null, 2),
|
|
57
|
+
"```",
|
|
58
|
+
"",
|
|
49
59
|
"Seen work item keys:",
|
|
50
60
|
"```json",
|
|
51
61
|
JSON.stringify(input.context.seen_work_item_keys ?? [], null, 2),
|
package/dist/runtime/runPhase.js
CHANGED
|
@@ -83,7 +83,9 @@ function buildContext(input) {
|
|
|
83
83
|
workflow: input.config.workflow,
|
|
84
84
|
work_items: input.config.work_items ?? {},
|
|
85
85
|
available_work_items: input.state.available_work_items ?? [],
|
|
86
|
+
recommended_work_item_queue: input.state.recommended_work_item_queue ?? [],
|
|
86
87
|
selected_work_item_queue: input.state.selected_work_item_queue ?? [],
|
|
88
|
+
work_item_annotations: input.state.work_item_annotations ?? [],
|
|
87
89
|
work_item: input.state.work_item ?? {},
|
|
88
90
|
seen_work_item_keys: input.state.seen_work_item_keys ?? [],
|
|
89
91
|
completed_work_item_keys: input.state.completed_work_item_keys ?? [],
|
|
@@ -9,6 +9,7 @@ import { getNyxDir, relativeToProject } from "./paths.js";
|
|
|
9
9
|
import { runPhase } from "./runPhase.js";
|
|
10
10
|
import { createRunId } from "./time.js";
|
|
11
11
|
import { validateWorkItemQueue } from "./validateWorkItem.js";
|
|
12
|
+
import { buildWorkItemKindMap, formatWorkItemChoiceName, normalizeWorkItemAnnotations } from "./workItemAnnotations.js";
|
|
12
13
|
import { filterAvailableWorkItems, listWorkItemCandidates } from "./workItems.js";
|
|
13
14
|
export async function runWorkflow(options) {
|
|
14
15
|
const projectRoot = path.resolve(options.projectRoot);
|
|
@@ -27,7 +28,9 @@ export async function runWorkflow(options) {
|
|
|
27
28
|
current_iteration: 0,
|
|
28
29
|
completed_iterations: 0,
|
|
29
30
|
available_work_items: [],
|
|
31
|
+
recommended_work_item_queue: [],
|
|
30
32
|
selected_work_item_queue: [],
|
|
33
|
+
work_item_annotations: [],
|
|
31
34
|
skipped_work_item_keys: [],
|
|
32
35
|
selection_groups: [],
|
|
33
36
|
seen_work_item_keys: [],
|
|
@@ -64,14 +67,19 @@ export async function runWorkflow(options) {
|
|
|
64
67
|
return;
|
|
65
68
|
}
|
|
66
69
|
runState.available_work_items = selection.availableWorkItems;
|
|
70
|
+
runState.recommended_work_item_queue = selection.workItems;
|
|
71
|
+
runState.work_item_annotations = selection.workItemAnnotations;
|
|
67
72
|
runState.selection_groups = selection.selectionGroups;
|
|
68
73
|
await writeJson(path.join(runDir, "state.json"), runState);
|
|
69
74
|
let confirmedWorkItems;
|
|
70
75
|
try {
|
|
71
76
|
confirmedWorkItems = normalizeConfirmedWorkItems({
|
|
72
|
-
|
|
77
|
+
availableWorkItems: selection.availableWorkItems,
|
|
73
78
|
confirmed: await (options.confirmWorkItems ?? confirmWorkItemsWithCheckbox)({
|
|
74
|
-
|
|
79
|
+
availableWorkItems: selection.availableWorkItems,
|
|
80
|
+
recommendedWorkItems: selection.workItems,
|
|
81
|
+
workItems: selection.availableWorkItems,
|
|
82
|
+
workItemAnnotations: selection.workItemAnnotations,
|
|
75
83
|
selectionGroups: selection.selectionGroups,
|
|
76
84
|
maxIterations: config.workflow.max_iterations
|
|
77
85
|
})
|
|
@@ -113,7 +121,9 @@ export async function runWorkflow(options) {
|
|
|
113
121
|
status: "running",
|
|
114
122
|
work_item: workItem,
|
|
115
123
|
available_work_items: selectedWorkItems,
|
|
124
|
+
recommended_work_item_queue: selection.workItems,
|
|
116
125
|
selected_work_item_queue: selectedWorkItems,
|
|
126
|
+
work_item_annotations: selection.workItemAnnotations,
|
|
117
127
|
seen_work_item_keys: [...runState.seen_work_item_keys],
|
|
118
128
|
completed_work_item_keys: [...ledger.completed_work_item_keys],
|
|
119
129
|
last_completed_work_item: ledger.last_completed_work_item,
|
|
@@ -202,7 +212,9 @@ async function runSelectionPhase(input) {
|
|
|
202
212
|
iteration: 0,
|
|
203
213
|
status: "running",
|
|
204
214
|
available_work_items: availableWorkItems,
|
|
215
|
+
recommended_work_item_queue: [],
|
|
205
216
|
selected_work_item_queue: [],
|
|
217
|
+
work_item_annotations: [],
|
|
206
218
|
seen_work_item_keys: [...input.runState.seen_work_item_keys],
|
|
207
219
|
completed_work_item_keys: [...input.ledger.completed_work_item_keys],
|
|
208
220
|
last_completed_work_item: input.ledger.last_completed_work_item,
|
|
@@ -250,13 +262,20 @@ async function runSelectionPhase(input) {
|
|
|
250
262
|
completedWorkItemKeys: selectionState.completed_work_item_keys
|
|
251
263
|
});
|
|
252
264
|
const selectionGroups = readSelectionGroups(phaseResult.result);
|
|
265
|
+
const workItemAnnotations = readWorkItemAnnotations({
|
|
266
|
+
result: phaseResult.result,
|
|
267
|
+
availableWorkItems
|
|
268
|
+
});
|
|
253
269
|
selectionState.status = "completed";
|
|
270
|
+
selectionState.recommended_work_item_queue = workItems;
|
|
254
271
|
selectionState.selected_work_item_queue = workItems;
|
|
272
|
+
selectionState.work_item_annotations = workItemAnnotations;
|
|
255
273
|
await writeJson(selectionStateFile, selectionState);
|
|
256
274
|
await writeJson(path.join(input.runDir, "state.json"), input.runState);
|
|
257
275
|
return {
|
|
258
276
|
status: "selected",
|
|
259
277
|
workItems,
|
|
278
|
+
workItemAnnotations,
|
|
260
279
|
selectionGroups,
|
|
261
280
|
availableWorkItems,
|
|
262
281
|
nextPhaseId: nextTarget
|
|
@@ -319,24 +338,30 @@ async function runWorkflowPhase(input) {
|
|
|
319
338
|
return phaseResult;
|
|
320
339
|
}
|
|
321
340
|
export async function confirmWorkItemsWithCheckbox(input) {
|
|
322
|
-
if (input.
|
|
341
|
+
if (input.availableWorkItems.length === 0) {
|
|
323
342
|
return [];
|
|
324
343
|
}
|
|
325
344
|
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
326
345
|
throw new Error("Work item selection confirmation requires an interactive TTY");
|
|
327
346
|
}
|
|
347
|
+
const confirmationView = buildConfirmationView(input);
|
|
328
348
|
const selectedKeys = await checkbox({
|
|
329
349
|
message: `Confirm work items to run (max ${input.maxIterations})`,
|
|
330
|
-
choices:
|
|
350
|
+
choices: confirmationView.choices,
|
|
331
351
|
required: false,
|
|
332
|
-
pageSize: Math.min(Math.max(input.
|
|
352
|
+
pageSize: Math.min(Math.max(input.availableWorkItems.length + 2, 5), 20),
|
|
353
|
+
validate: (selected) => selected.length <= input.maxIterations ||
|
|
354
|
+
`Select at most ${input.maxIterations} work items.`
|
|
333
355
|
});
|
|
334
356
|
const selected = new Set(selectedKeys);
|
|
335
|
-
return
|
|
357
|
+
return confirmationView.workItems.filter((item) => selected.has(item.key));
|
|
336
358
|
}
|
|
337
|
-
function
|
|
359
|
+
function buildConfirmationView(input) {
|
|
338
360
|
const choices = [];
|
|
339
|
-
const
|
|
361
|
+
const workItems = [];
|
|
362
|
+
const byKey = new Map(input.availableWorkItems.map((item) => [item.key, item]));
|
|
363
|
+
const recommendedKeys = new Set(input.recommendedWorkItems.map((item) => item.key));
|
|
364
|
+
const annotationsByKey = buildWorkItemKindMap(input.workItemAnnotations ?? []);
|
|
340
365
|
const displayed = new Set();
|
|
341
366
|
for (const group of input.selectionGroups) {
|
|
342
367
|
const groupItems = group.work_item_keys
|
|
@@ -352,25 +377,30 @@ function buildConfirmationChoices(input) {
|
|
|
352
377
|
}
|
|
353
378
|
choices.push(new Separator(`-- ${group.title} --`));
|
|
354
379
|
for (const item of groupItems) {
|
|
355
|
-
choices.push(buildConfirmationChoice(item));
|
|
380
|
+
choices.push(buildConfirmationChoice({ item, recommendedKeys, annotationsByKey }));
|
|
381
|
+
workItems.push(item);
|
|
356
382
|
displayed.add(item.key);
|
|
357
383
|
}
|
|
358
384
|
}
|
|
359
|
-
const ungroupedItems = input.
|
|
385
|
+
const ungroupedItems = input.availableWorkItems.filter((item) => !displayed.has(item.key));
|
|
360
386
|
if (input.selectionGroups.length > 0 && ungroupedItems.length > 0) {
|
|
361
387
|
choices.push(new Separator("-- Ungrouped --"));
|
|
362
388
|
}
|
|
363
389
|
for (const item of ungroupedItems) {
|
|
364
|
-
choices.push(buildConfirmationChoice(item));
|
|
390
|
+
choices.push(buildConfirmationChoice({ item, recommendedKeys, annotationsByKey }));
|
|
391
|
+
workItems.push(item);
|
|
365
392
|
}
|
|
366
|
-
return choices;
|
|
393
|
+
return { choices, workItems };
|
|
367
394
|
}
|
|
368
|
-
function buildConfirmationChoice(
|
|
395
|
+
function buildConfirmationChoice(input) {
|
|
369
396
|
return {
|
|
370
|
-
value: item.key,
|
|
371
|
-
name:
|
|
372
|
-
|
|
373
|
-
|
|
397
|
+
value: input.item.key,
|
|
398
|
+
name: formatWorkItemChoiceName({
|
|
399
|
+
item: input.item,
|
|
400
|
+
annotationsByKey: input.annotationsByKey
|
|
401
|
+
}),
|
|
402
|
+
description: input.item.excerpt,
|
|
403
|
+
checked: input.recommendedKeys.has(input.item.key)
|
|
374
404
|
};
|
|
375
405
|
}
|
|
376
406
|
function resolveNextTarget(phase, outcome) {
|
|
@@ -429,22 +459,28 @@ function readSelectedWorkItems(input) {
|
|
|
429
459
|
return validation.workItems;
|
|
430
460
|
}
|
|
431
461
|
function normalizeConfirmedWorkItems(input) {
|
|
432
|
-
const
|
|
462
|
+
const availableByKey = new Map(input.availableWorkItems.map((item) => [item.key, item]));
|
|
433
463
|
const selected = new Set();
|
|
434
464
|
const normalized = [];
|
|
435
465
|
for (const item of input.confirmed) {
|
|
436
|
-
const
|
|
437
|
-
if (!
|
|
438
|
-
throw new Error(`Confirmed work item key "${item.key}" is not in
|
|
466
|
+
const available = availableByKey.get(item.key);
|
|
467
|
+
if (!available) {
|
|
468
|
+
throw new Error(`Confirmed work item key "${item.key}" is not in available_work_items`);
|
|
439
469
|
}
|
|
440
470
|
if (selected.has(item.key)) {
|
|
441
471
|
throw new Error(`Confirmed work item key "${item.key}" was selected twice`);
|
|
442
472
|
}
|
|
443
473
|
selected.add(item.key);
|
|
444
|
-
normalized.push(
|
|
474
|
+
normalized.push(available);
|
|
445
475
|
}
|
|
446
476
|
return normalized;
|
|
447
477
|
}
|
|
478
|
+
function readWorkItemAnnotations(input) {
|
|
479
|
+
return normalizeWorkItemAnnotations({
|
|
480
|
+
annotations: readObjectProperty(input.result, "work_item_annotations"),
|
|
481
|
+
availableWorkItems: input.availableWorkItems
|
|
482
|
+
});
|
|
483
|
+
}
|
|
448
484
|
function readSelectionGroups(result) {
|
|
449
485
|
const groups = readObjectProperty(result, "selection_groups");
|
|
450
486
|
if (!Array.isArray(groups)) {
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export const workItemKinds = ["task", "plan", "prd", "work"];
|
|
2
|
+
export function normalizeWorkItemAnnotations(input) {
|
|
3
|
+
if (!Array.isArray(input.annotations)) {
|
|
4
|
+
return [];
|
|
5
|
+
}
|
|
6
|
+
const availableKeys = new Set(input.availableWorkItems.map((item) => item.key));
|
|
7
|
+
const seen = new Set();
|
|
8
|
+
const normalized = [];
|
|
9
|
+
for (const annotation of input.annotations) {
|
|
10
|
+
if (!isRecord(annotation) || typeof annotation.key !== "string") {
|
|
11
|
+
continue;
|
|
12
|
+
}
|
|
13
|
+
if (!availableKeys.has(annotation.key) || seen.has(annotation.key)) {
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
seen.add(annotation.key);
|
|
17
|
+
normalized.push({
|
|
18
|
+
key: annotation.key,
|
|
19
|
+
kind: normalizeWorkItemKind(annotation.kind)
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
return normalized;
|
|
23
|
+
}
|
|
24
|
+
export function buildWorkItemKindMap(annotations) {
|
|
25
|
+
return new Map(annotations.map((annotation) => [annotation.key, annotation.kind]));
|
|
26
|
+
}
|
|
27
|
+
export function formatWorkItemChoiceName(input) {
|
|
28
|
+
const kind = input.annotationsByKey.get(input.item.key) ?? "work";
|
|
29
|
+
return `[${kind}] ${input.item.title} (${input.item.key})`;
|
|
30
|
+
}
|
|
31
|
+
function normalizeWorkItemKind(value) {
|
|
32
|
+
return typeof value === "string" && isWorkItemKind(value) ? value : "work";
|
|
33
|
+
}
|
|
34
|
+
function isWorkItemKind(value) {
|
|
35
|
+
return workItemKinds.includes(value);
|
|
36
|
+
}
|
|
37
|
+
function isRecord(value) {
|
|
38
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
39
|
+
}
|
package/docs/nyxagent-v0-spec.md
CHANGED
|
@@ -185,12 +185,17 @@ max_visits_per_iteration = 1
|
|
|
185
185
|
- `work_items.excerpt_chars` defaults to `800` and bounds candidate excerpts.
|
|
186
186
|
- At run start, the engine scans the configured provider, normalizes candidates
|
|
187
187
|
into `available_work_items`, and sends that inventory to the selection phase.
|
|
188
|
-
- The selection phase returns an ordered `work_items` queue.
|
|
189
|
-
each
|
|
190
|
-
requires every selected key to exist in
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
188
|
+
- The selection phase returns an ordered recommended `work_items` queue.
|
|
189
|
+
NyxAgent validates each recommended identity against `[work_items]`, rejects
|
|
190
|
+
duplicates, and requires every selected key to exist in
|
|
191
|
+
`available_work_items`.
|
|
192
|
+
- Before executing, NyxAgent shows the full `available_work_items` inventory in
|
|
193
|
+
an interactive checkbox prompt. The recommended queue is pre-checked, and the
|
|
194
|
+
user can add or remove available items. Only confirmed items are executed.
|
|
195
|
+
- The selection phase may return optional `work_item_annotations` for display.
|
|
196
|
+
Supported kinds are `task`, `plan`, `prd`, and `work`. Unknown or missing
|
|
197
|
+
kinds are shown as `work`; annotations never affect work item identity or
|
|
198
|
+
ledger completion.
|
|
194
199
|
- Local markdown work items do not have a required frontmatter schema. The
|
|
195
200
|
provider infers titles from the first heading or filename and includes a
|
|
196
201
|
bounded content excerpt.
|
|
@@ -199,7 +204,8 @@ max_visits_per_iteration = 1
|
|
|
199
204
|
configured repository.
|
|
200
205
|
- NyxAgent does not decide whether an item is a plan, PRD, or task. The
|
|
201
206
|
selection agent makes that semantic choice from the deterministic inventory
|
|
202
|
-
and may include optional `selection_groups` for user review.
|
|
207
|
+
and may include optional `selection_groups` for user review. Groups may cover
|
|
208
|
+
the full available inventory, not only the recommended queue.
|
|
203
209
|
|
|
204
210
|
## Workflow Model
|
|
205
211
|
|
|
@@ -222,7 +228,7 @@ only knows phases, outcomes, transitions, and visit limits.
|
|
|
222
228
|
The default template expresses the standard run:
|
|
223
229
|
|
|
224
230
|
```text
|
|
225
|
-
selection -> user confirms queue
|
|
231
|
+
selection -> user confirms inventory with recommended queue pre-checked
|
|
226
232
|
for each confirmed work item:
|
|
227
233
|
execution -> review
|
|
228
234
|
review.approved -> closure -> next_iteration
|
|
@@ -299,7 +305,9 @@ The runtime contract includes:
|
|
|
299
305
|
- work item context, when selected
|
|
300
306
|
- work item config from `[work_items]`
|
|
301
307
|
- `available_work_items`
|
|
308
|
+
- `recommended_work_item_queue`
|
|
302
309
|
- `selected_work_item_queue`
|
|
310
|
+
- `work_item_annotations`
|
|
303
311
|
- `seen_work_item_keys`
|
|
304
312
|
- `completed_work_item_keys`
|
|
305
313
|
- `last_completed_work_item`
|
|
@@ -314,7 +322,9 @@ Prompts may use simple interpolation:
|
|
|
314
322
|
{{iteration_dir}}
|
|
315
323
|
{{phase_dir}}
|
|
316
324
|
{{state_file}}
|
|
325
|
+
{{recommended_work_item_queue}}
|
|
317
326
|
{{selected_work_item_queue}}
|
|
327
|
+
{{work_item_annotations}}
|
|
318
328
|
{{work_item.key}}
|
|
319
329
|
{{work_item.title}}
|
|
320
330
|
{{model.name}}
|
|
@@ -381,8 +391,10 @@ Each run creates a timestamped directory:
|
|
|
381
391
|
- current iteration
|
|
382
392
|
- completed iterations
|
|
383
393
|
- available work items seen by the initial selection phase
|
|
394
|
+
- recommended work item queue returned by the selection agent
|
|
384
395
|
- selected work item queue confirmed for the run
|
|
385
|
-
-
|
|
396
|
+
- work item annotations returned by the selection agent
|
|
397
|
+
- skipped recommended work item keys
|
|
386
398
|
- selection groups returned for user review
|
|
387
399
|
- seen work item keys
|
|
388
400
|
- completed work item keys
|
|
@@ -442,14 +454,17 @@ Default prompts should be concise but operational.
|
|
|
442
454
|
|
|
443
455
|
Selection:
|
|
444
456
|
|
|
445
|
-
-
|
|
457
|
+
- recommend an ordered queue from `available_work_items`
|
|
446
458
|
- treat candidates agnostically: they may be plans, PRDs, or tasks
|
|
447
459
|
- prefer the most actionable items based on candidate titles and excerpts
|
|
448
460
|
- if a plan references concrete candidate tasks, choose the concrete task when
|
|
449
461
|
that is the best next work
|
|
450
462
|
- avoid keys already present in `seen_work_item_keys` or
|
|
451
463
|
`completed_work_item_keys`
|
|
452
|
-
- optionally include `selection_groups` to present related work by
|
|
464
|
+
- optionally include `selection_groups` to present related work by the most
|
|
465
|
+
useful grouping the agent can infer
|
|
466
|
+
- optionally include `work_item_annotations` with display kinds `task`, `plan`,
|
|
467
|
+
`prd`, or `work`
|
|
453
468
|
- return `selected` or `no_work`
|
|
454
469
|
|
|
455
470
|
Execution:
|
package/package.json
CHANGED
|
@@ -1,13 +1,21 @@
|
|
|
1
|
-
|
|
1
|
+
Recommend the ordered work item queue for this run.
|
|
2
2
|
|
|
3
3
|
Use `available_work_items` from the runtime contract as the complete inventory
|
|
4
|
-
for selection. Some candidates may be plans, PRDs, or concrete tasks.
|
|
5
|
-
most actionable queue for this run, up to
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
4
|
+
for selection. Some candidates may be plans, PRDs, or concrete tasks.
|
|
5
|
+
Recommend the most actionable queue for this run, up to
|
|
6
|
+
`workflow.max_iterations` items. NyxAgent will show the complete inventory to
|
|
7
|
+
the user afterward with your recommendation pre-selected.
|
|
8
|
+
|
|
9
|
+
If candidates appear to belong together, use `selection_groups` to describe the
|
|
10
|
+
most useful review grouping. Infer grouping from titles, paths, excerpts, and
|
|
11
|
+
declared relationships when available, but do not assume every project uses
|
|
12
|
+
plan/PRD subdirectories. Groups may include any key from `available_work_items`,
|
|
13
|
+
including items that are not part of your recommended `work_items` queue.
|
|
14
|
+
|
|
15
|
+
When useful, include `work_item_annotations` to label available items for user
|
|
16
|
+
review. Each annotation should include `key` and `kind`. Use `kind` values:
|
|
17
|
+
`task`, `plan`, `prd`, or `work`. Use `work` when you are not sure. You may
|
|
18
|
+
annotate items that are not in the recommended `work_items` queue.
|
|
11
19
|
|
|
12
20
|
Prefer concrete tasks over their parent plan when both are present and the task
|
|
13
21
|
is ready to execute. Choose the plan itself only when it is the best next work.
|
|
@@ -27,8 +35,9 @@ The selected work item identities must match the inventory entries:
|
|
|
27
35
|
|
|
28
36
|
Return one of these outcomes:
|
|
29
37
|
|
|
30
|
-
- `selected`: include ordered `work_items`; optionally include
|
|
31
|
-
`selection_groups` with `title`, optional `kind`, and `work_item_keys
|
|
38
|
+
- `selected`: include recommended ordered `work_items`; optionally include
|
|
39
|
+
`selection_groups` with `title`, optional `kind`, and `work_item_keys`; and
|
|
40
|
+
optionally include `work_item_annotations` with `key` and `kind`
|
|
32
41
|
- `no_work`: include a short `reason`
|
|
33
42
|
|
|
34
43
|
For compatibility, `work_item` is still accepted for a single selected item, but
|
|
@@ -77,6 +77,23 @@
|
|
|
77
77
|
"additionalProperties": true
|
|
78
78
|
}
|
|
79
79
|
},
|
|
80
|
+
"work_item_annotations": {
|
|
81
|
+
"type": "array",
|
|
82
|
+
"items": {
|
|
83
|
+
"type": "object",
|
|
84
|
+
"required": ["key"],
|
|
85
|
+
"properties": {
|
|
86
|
+
"key": {
|
|
87
|
+
"type": "string",
|
|
88
|
+
"minLength": 1
|
|
89
|
+
},
|
|
90
|
+
"kind": {
|
|
91
|
+
"type": "string"
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
"additionalProperties": true
|
|
95
|
+
}
|
|
96
|
+
},
|
|
80
97
|
"reason": {
|
|
81
98
|
"type": "string"
|
|
82
99
|
}
|