@nanobpm/nano-workforce 0.202.0 → 0.203.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/SPEC.md +48 -0
- package/app/admittedStaging.test.ts +71 -0
- package/app/agentActivationLedger.ts +5 -0
- package/app/agentCompletion.test.ts +33 -0
- package/app/agentCompletion.ts +108 -2
- package/app/agentic/vocab/agent-sla-boundary.test.ts +6 -1
- package/app/contracts.ts +23 -0
- package/app/deliveryGraphDispatch.ts +6 -0
- package/app/deliveryGraphReadModel.test.ts +2 -2
- package/app/deliveryGraphRun.ts +18 -0
- package/app/deliveryRunner.ts +9 -1
- package/app/escalationSteward.test.ts +452 -0
- package/app/escalationSteward.ts +537 -0
- package/app/escalationStewardPoll.test.ts +123 -0
- package/app/feature.test.ts +16 -0
- package/app/feature.ts +11 -0
- package/app/featureDelivery.test.ts +23 -0
- package/app/mcpToolSurface.ts +17 -1
- package/app/plan.ts +49 -6
- package/app/planLowering.test.ts +26 -1
- package/app/planLowering.ts +5 -0
- package/app/promotionPoll.test.ts +19 -0
- package/app/pullRequestReadModel.test.ts +1 -1
- package/app/service.test.ts +36 -0
- package/app/service.ts +139 -5
- package/db/migrations/120_escalation_auto_resolutions.sql +229 -0
- package/db/migrations/121_delivery_graph_read_model_auto_resolve.sql +54 -0
- package/db/migrations/122_pull_requests_read_model_auto_resolve.sql +56 -0
- package/db/migrations/123_escalation_auto_resolutions_selected_option.sql +47 -0
- package/db/migrations/124_admitted_epics_auto_resolve.sql +21 -0
- package/docs/agent-guide.md +69 -30
- package/docs/mcp-runbook.md +2 -0
- package/e2e/escalation-steward.e2e.ts +211 -0
- package/nano.app.json +10 -1
- package/openapi.yaml +365 -0
- package/operations/dispatchDeliveryGraph.ts +6 -1
- package/operations/listActivePrs.test.ts +60 -1
- package/operations/listActivePrs.ts +3 -3
- package/operations/listAutoResolutions.ts +83 -0
- package/operations/reviewAutoResolution.test.ts +137 -0
- package/operations/reviewAutoResolution.ts +42 -0
- package/operations/startConvergenceLoop.ts +5 -1
- package/operations/startEpicSet.admission.integration.test.ts +2 -2
- package/operations/startEpicSet.ts +8 -2
- package/operations/startFeature.ts +5 -0
- package/operations/startPlanFanout.ts +4 -1
- package/package.json +1 -1
- package/pages/_nav.json +12 -0
- package/pages/auto-resolutions.page.json +191 -0
- package/pages/board.page.json +17 -0
- package/pages/cockpit.page.json +17 -0
- package/pages/delivery-graph-detail.page.json +17 -0
- package/pages/delivery-graphs/staged.mount.js +17 -5
- package/pages/delivery-graphs.page.json +17 -0
- package/pages/epic-detail.page.json +17 -0
- package/pages/epic.page.json +19 -1
- package/pages/feature.page.json +19 -1
- package/pages/home.page.json +22 -0
- package/pages/lineage.page.json +17 -0
- package/pages/mcp.page.json +17 -0
- package/pages/overview.page.json +17 -0
- package/pages/tasks.page.json +17 -0
- package/pages/velocity.page.json +17 -0
- package/resources/processes/escalation-steward.bpmn +162 -0
- package/resources/prompts/escalation-steward.md +87 -0
- package/test/delivery-graphs-staged-embed.test.ts +13 -0
- package/test/derivation-parity/derivation-parity.test.ts +1 -0
- package/test/derivation-parity/flows.ts +12 -0
- package/workers/converge-feature/worker.ts +6 -1
- package/workers/delivery-connector/worker.test.ts +72 -1
- package/workers/delivery-connector/worker.ts +53 -2
- package/workers/escalation-steward-collect/worker.test.ts +180 -0
- package/workers/escalation-steward-collect/worker.ts +99 -0
- package/workers/escalation-steward-record/worker.test.ts +740 -0
- package/workers/escalation-steward-record/worker.ts +428 -0
- package/workers/record-wave/worker.ts +5 -1
package/pages/cockpit.page.json
CHANGED
|
@@ -49,6 +49,23 @@
|
|
|
49
49
|
"hideWhenZero": true
|
|
50
50
|
}
|
|
51
51
|
},
|
|
52
|
+
{
|
|
53
|
+
"label": "Auto-resolutions",
|
|
54
|
+
"page": "auto-resolutions",
|
|
55
|
+
"badge": {
|
|
56
|
+
"source": "app",
|
|
57
|
+
"table": "escalation_auto_resolutions__read_model",
|
|
58
|
+
"filter": [
|
|
59
|
+
{
|
|
60
|
+
"field": "review_state",
|
|
61
|
+
"eq": "pending"
|
|
62
|
+
}
|
|
63
|
+
],
|
|
64
|
+
"tone": "warn",
|
|
65
|
+
"refreshMs": 5000,
|
|
66
|
+
"hideWhenZero": true
|
|
67
|
+
}
|
|
68
|
+
},
|
|
52
69
|
{
|
|
53
70
|
"label": "Cockpit",
|
|
54
71
|
"page": "cockpit"
|
|
@@ -49,6 +49,23 @@
|
|
|
49
49
|
"hideWhenZero": true
|
|
50
50
|
}
|
|
51
51
|
},
|
|
52
|
+
{
|
|
53
|
+
"label": "Auto-resolutions",
|
|
54
|
+
"page": "auto-resolutions",
|
|
55
|
+
"badge": {
|
|
56
|
+
"source": "app",
|
|
57
|
+
"table": "escalation_auto_resolutions__read_model",
|
|
58
|
+
"filter": [
|
|
59
|
+
{
|
|
60
|
+
"field": "review_state",
|
|
61
|
+
"eq": "pending"
|
|
62
|
+
}
|
|
63
|
+
],
|
|
64
|
+
"tone": "warn",
|
|
65
|
+
"refreshMs": 5000,
|
|
66
|
+
"hideWhenZero": true
|
|
67
|
+
}
|
|
68
|
+
},
|
|
52
69
|
{
|
|
53
70
|
"label": "Cockpit",
|
|
54
71
|
"page": "cockpit"
|
|
@@ -113,6 +113,7 @@ function dispatchControl(p, pending) {
|
|
|
113
113
|
if (isPending(pending, "dispatch", p.digest)) {
|
|
114
114
|
const repository = typeof pending.repository === "string" ? pending.repository : "";
|
|
115
115
|
const baseBranch = typeof pending.baseBranch === "string" ? pending.baseBranch : "";
|
|
116
|
+
const autoResolve = pending.autoResolveEscalations === true;
|
|
116
117
|
const mode = pending.mode;
|
|
117
118
|
const provDisabled = mode === "fallback" ? "" : " disabled";
|
|
118
119
|
return `<span class="confirm" data-confirm="dispatch">
|
|
@@ -123,6 +124,7 @@ function dispatchControl(p, pending) {
|
|
|
123
124
|
<span class="confirm-msg">Use node repositories sends no run-level repository settings; the server validates that every agent node is provisioned. A run-level repository is a fallback for nodes without their own repository, not an override of node declarations. Checkout-less explicitly opts out of repository provisioning.</span>
|
|
124
125
|
<label class="confirm-field">Repository <input class="confirm-input" type="text" data-dispatch-repository="${esc(p.digest)}" value="${esc(repository)}" placeholder="owner/repo" aria-label="Repository (owner/repo)"${provDisabled} /></label>
|
|
125
126
|
<label class="confirm-field">Base branch <input class="confirm-input" type="text" data-dispatch-base="${esc(p.digest)}" value="${esc(baseBranch)}" placeholder="main" aria-label="Base branch"${provDisabled} /></label>
|
|
127
|
+
<label class="confirm-check"><input type="checkbox" data-dispatch-autoresolve="${esc(p.digest)}"${autoResolve ? " checked" : ""} /> Automatically resolve escalations (the escalation steward answers this run's eligible escalations; every automatic decision stays reviewable)</label>
|
|
126
128
|
<button class="btn btn-primary" type="button" data-dispatch-confirm="${esc(p.digest)}">Confirm dispatch</button>
|
|
127
129
|
<button class="btn btn-ghost" type="button" data-dispatch-cancel="${esc(p.digest)}">Cancel</button>
|
|
128
130
|
</span>`;
|
|
@@ -458,7 +460,7 @@ export function mountStagedProposals(host, config = {}) {
|
|
|
458
460
|
function openConfirm(kind, digest, name) {
|
|
459
461
|
const staged = typeof digest === "string" ? digest.trim() : "";
|
|
460
462
|
if (staged === "") return;
|
|
461
|
-
pending = { kind, digest: staged, name: typeof name === "string" ? name : "", repository: "", baseBranch: "", mode: "nodes" };
|
|
463
|
+
pending = { kind, digest: staged, name: typeof name === "string" ? name : "", repository: "", baseBranch: "", mode: "nodes", autoResolveEscalations: false };
|
|
462
464
|
rerender();
|
|
463
465
|
// Move focus into the name input so the operator can type immediately (best-effort; not all hosts
|
|
464
466
|
// implement focus()).
|
|
@@ -514,8 +516,16 @@ export function mountStagedProposals(host, config = {}) {
|
|
|
514
516
|
}
|
|
515
517
|
});
|
|
516
518
|
|
|
517
|
-
// Preserve fallback text across mode changes, but only enable and submit it in fallback mode.
|
|
519
|
+
// Preserve fallback text across mode changes, but only enable and submit it in fallback mode. Also
|
|
520
|
+
// track the "Automatically resolve escalations" checkbox (a checkbox fires `change`, not `input`).
|
|
518
521
|
listEl.addEventListener("change", (ev) => {
|
|
522
|
+
if (pending != null && pending.kind === "dispatch") {
|
|
523
|
+
const autoResolve = ev.target && ev.target.closest ? ev.target.closest("[data-dispatch-autoresolve]") : null;
|
|
524
|
+
if (autoResolve && autoResolve.getAttribute("data-dispatch-autoresolve") === pending.digest) {
|
|
525
|
+
pending.autoResolveEscalations = autoResolve.checked === true;
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
519
529
|
const choice = ev.target && ev.target.closest ? ev.target.closest("[data-dispatch-mode]") : null;
|
|
520
530
|
if (choice && choice.checked && pending != null && pending.kind === "dispatch" &&
|
|
521
531
|
choice.getAttribute("data-dispatch-mode") === pending.digest &&
|
|
@@ -562,10 +572,12 @@ export function mountStagedProposals(host, config = {}) {
|
|
|
562
572
|
return;
|
|
563
573
|
}
|
|
564
574
|
// Construct only the selected mode's fields: disabled fallback text must never leak into node
|
|
565
|
-
// or checkout-less dispatches, and repoless must never be inferred from blank fields.
|
|
575
|
+
// or checkout-less dispatches, and repoless must never be inferred from blank fields. The
|
|
576
|
+
// escalation-steward opt-in is mode-independent — include it whenever the operator checked it.
|
|
577
|
+
const autoResolve = pending.autoResolveEscalations === true ? { autoResolveEscalations: true } : {};
|
|
566
578
|
const payload = mode === "fallback"
|
|
567
|
-
? { digest, repository, baseBranch }
|
|
568
|
-
: mode === "repoless" ? { digest, repoless: true } : { digest };
|
|
579
|
+
? { digest, repository, baseBranch, ...autoResolve }
|
|
580
|
+
: mode === "repoless" ? { digest, repoless: true, ...autoResolve } : { digest, ...autoResolve };
|
|
569
581
|
closeConfirm();
|
|
570
582
|
doDispatch(payload);
|
|
571
583
|
return;
|
|
@@ -49,6 +49,23 @@
|
|
|
49
49
|
"hideWhenZero": true
|
|
50
50
|
}
|
|
51
51
|
},
|
|
52
|
+
{
|
|
53
|
+
"label": "Auto-resolutions",
|
|
54
|
+
"page": "auto-resolutions",
|
|
55
|
+
"badge": {
|
|
56
|
+
"source": "app",
|
|
57
|
+
"table": "escalation_auto_resolutions__read_model",
|
|
58
|
+
"filter": [
|
|
59
|
+
{
|
|
60
|
+
"field": "review_state",
|
|
61
|
+
"eq": "pending"
|
|
62
|
+
}
|
|
63
|
+
],
|
|
64
|
+
"tone": "warn",
|
|
65
|
+
"refreshMs": 5000,
|
|
66
|
+
"hideWhenZero": true
|
|
67
|
+
}
|
|
68
|
+
},
|
|
52
69
|
{
|
|
53
70
|
"label": "Cockpit",
|
|
54
71
|
"page": "cockpit"
|
|
@@ -49,6 +49,23 @@
|
|
|
49
49
|
"hideWhenZero": true
|
|
50
50
|
}
|
|
51
51
|
},
|
|
52
|
+
{
|
|
53
|
+
"label": "Auto-resolutions",
|
|
54
|
+
"page": "auto-resolutions",
|
|
55
|
+
"badge": {
|
|
56
|
+
"source": "app",
|
|
57
|
+
"table": "escalation_auto_resolutions__read_model",
|
|
58
|
+
"filter": [
|
|
59
|
+
{
|
|
60
|
+
"field": "review_state",
|
|
61
|
+
"eq": "pending"
|
|
62
|
+
}
|
|
63
|
+
],
|
|
64
|
+
"tone": "warn",
|
|
65
|
+
"refreshMs": 5000,
|
|
66
|
+
"hideWhenZero": true
|
|
67
|
+
}
|
|
68
|
+
},
|
|
52
69
|
{
|
|
53
70
|
"label": "Cockpit",
|
|
54
71
|
"page": "cockpit"
|
package/pages/epic.page.json
CHANGED
|
@@ -49,6 +49,23 @@
|
|
|
49
49
|
"hideWhenZero": true
|
|
50
50
|
}
|
|
51
51
|
},
|
|
52
|
+
{
|
|
53
|
+
"label": "Auto-resolutions",
|
|
54
|
+
"page": "auto-resolutions",
|
|
55
|
+
"badge": {
|
|
56
|
+
"source": "app",
|
|
57
|
+
"table": "escalation_auto_resolutions__read_model",
|
|
58
|
+
"filter": [
|
|
59
|
+
{
|
|
60
|
+
"field": "review_state",
|
|
61
|
+
"eq": "pending"
|
|
62
|
+
}
|
|
63
|
+
],
|
|
64
|
+
"tone": "warn",
|
|
65
|
+
"refreshMs": 5000,
|
|
66
|
+
"hideWhenZero": true
|
|
67
|
+
}
|
|
68
|
+
},
|
|
52
69
|
{
|
|
53
70
|
"label": "Cockpit",
|
|
54
71
|
"page": "cockpit"
|
|
@@ -89,7 +106,8 @@
|
|
|
89
106
|
{ "key": "issue", "label": "owner/repo#123 or a GitHub issue URL", "type": "text", "required": true, "requiredMessage": "An issue reference is required (owner/repo#123 or an issue URL)" },
|
|
90
107
|
{ "key": "baseBranch", "label": "Base branch: e.g. epic/agent-protocol to land the whole epic on an integration branch. A missing epic/* branch is auto-created off default HEAD; a non-epic/* branch must already exist.", "type": "text", "required": true, "requiredMessage": "Name the branch the PR targets (e.g. main or epic/agent-protocol)" },
|
|
91
108
|
{ "key": "confirmDefaultBase", "label": "Confirm landing on the default branch \u2014 required only when the base above IS the repository default (every task lands directly on it, with any merge-to-default side effect firing per task)", "type": "checkbox" },
|
|
92
|
-
{ "key": "allowSharedBase", "label": "Allow sharing a custom integration branch with another active epic \u2014 required only when another in-flight epic already targets this same custom base", "type": "checkbox" }
|
|
109
|
+
{ "key": "allowSharedBase", "label": "Allow sharing a custom integration branch with another active epic \u2014 required only when another in-flight epic already targets this same custom base", "type": "checkbox" },
|
|
110
|
+
{ "key": "autoResolveEscalations", "label": "Automatically resolve escalations \u2014 the opt-in escalation steward reviews this epic's (and its child loops') open escalations every 30 min and submits a recommended choice, recorded reviewably in Auto-resolutions", "type": "checkbox" }
|
|
93
111
|
]
|
|
94
112
|
}
|
|
95
113
|
},
|
package/pages/feature.page.json
CHANGED
|
@@ -49,6 +49,23 @@
|
|
|
49
49
|
"hideWhenZero": true
|
|
50
50
|
}
|
|
51
51
|
},
|
|
52
|
+
{
|
|
53
|
+
"label": "Auto-resolutions",
|
|
54
|
+
"page": "auto-resolutions",
|
|
55
|
+
"badge": {
|
|
56
|
+
"source": "app",
|
|
57
|
+
"table": "escalation_auto_resolutions__read_model",
|
|
58
|
+
"filter": [
|
|
59
|
+
{
|
|
60
|
+
"field": "review_state",
|
|
61
|
+
"eq": "pending"
|
|
62
|
+
}
|
|
63
|
+
],
|
|
64
|
+
"tone": "warn",
|
|
65
|
+
"refreshMs": 5000,
|
|
66
|
+
"hideWhenZero": true
|
|
67
|
+
}
|
|
68
|
+
},
|
|
52
69
|
{
|
|
53
70
|
"label": "Cockpit",
|
|
54
71
|
"page": "cockpit"
|
|
@@ -93,7 +110,8 @@
|
|
|
93
110
|
{ "key": "humanApproval", "label": "Human approval before merge \u2014 a converged PR waits in Tasks for you to approve the merge or request changes (only applies when Auto-merge is on)", "type": "checkbox" },
|
|
94
111
|
{ "key": "autoMerge", "label": "Auto-merge \u2014 after convergence, drive the merge-loop (only applies when Converge is on)", "type": "checkbox" },
|
|
95
112
|
{ "key": "confirmDefaultBase", "label": "Confirm landing on the default branch \u2014 required only when the base above IS the repository default", "type": "checkbox" },
|
|
96
|
-
{ "key": "allowSharedBase", "label": "Allow sharing a custom integration branch with another active epic", "type": "checkbox" }
|
|
113
|
+
{ "key": "allowSharedBase", "label": "Allow sharing a custom integration branch with another active epic", "type": "checkbox" },
|
|
114
|
+
{ "key": "autoResolveEscalations", "label": "Automatically resolve escalations \u2014 the opt-in escalation steward reviews this run's open escalations every 30 min and submits a recommended choice, recorded reviewably in Auto-resolutions", "type": "checkbox" }
|
|
97
115
|
]
|
|
98
116
|
}
|
|
99
117
|
},
|
package/pages/home.page.json
CHANGED
|
@@ -49,6 +49,23 @@
|
|
|
49
49
|
"hideWhenZero": true
|
|
50
50
|
}
|
|
51
51
|
},
|
|
52
|
+
{
|
|
53
|
+
"label": "Auto-resolutions",
|
|
54
|
+
"page": "auto-resolutions",
|
|
55
|
+
"badge": {
|
|
56
|
+
"source": "app",
|
|
57
|
+
"table": "escalation_auto_resolutions__read_model",
|
|
58
|
+
"filter": [
|
|
59
|
+
{
|
|
60
|
+
"field": "review_state",
|
|
61
|
+
"eq": "pending"
|
|
62
|
+
}
|
|
63
|
+
],
|
|
64
|
+
"tone": "warn",
|
|
65
|
+
"refreshMs": 5000,
|
|
66
|
+
"hideWhenZero": true
|
|
67
|
+
}
|
|
68
|
+
},
|
|
52
69
|
{
|
|
53
70
|
"label": "Cockpit",
|
|
54
71
|
"page": "cockpit"
|
|
@@ -108,6 +125,11 @@
|
|
|
108
125
|
"key": "autoMerge",
|
|
109
126
|
"label": "Auto-merge \u2014 after convergence, drive the merge-loop",
|
|
110
127
|
"type": "checkbox"
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
"key": "autoResolveEscalations",
|
|
131
|
+
"label": "Automatically resolve escalations \u2014 the opt-in escalation steward reviews this loop's open escalations every 30 min and submits a recommended choice, recorded reviewably in Auto-resolutions",
|
|
132
|
+
"type": "checkbox"
|
|
111
133
|
}
|
|
112
134
|
]
|
|
113
135
|
}
|
package/pages/lineage.page.json
CHANGED
|
@@ -49,6 +49,23 @@
|
|
|
49
49
|
"hideWhenZero": true
|
|
50
50
|
}
|
|
51
51
|
},
|
|
52
|
+
{
|
|
53
|
+
"label": "Auto-resolutions",
|
|
54
|
+
"page": "auto-resolutions",
|
|
55
|
+
"badge": {
|
|
56
|
+
"source": "app",
|
|
57
|
+
"table": "escalation_auto_resolutions__read_model",
|
|
58
|
+
"filter": [
|
|
59
|
+
{
|
|
60
|
+
"field": "review_state",
|
|
61
|
+
"eq": "pending"
|
|
62
|
+
}
|
|
63
|
+
],
|
|
64
|
+
"tone": "warn",
|
|
65
|
+
"refreshMs": 5000,
|
|
66
|
+
"hideWhenZero": true
|
|
67
|
+
}
|
|
68
|
+
},
|
|
52
69
|
{
|
|
53
70
|
"label": "Cockpit",
|
|
54
71
|
"page": "cockpit"
|
package/pages/mcp.page.json
CHANGED
|
@@ -49,6 +49,23 @@
|
|
|
49
49
|
"hideWhenZero": true
|
|
50
50
|
}
|
|
51
51
|
},
|
|
52
|
+
{
|
|
53
|
+
"label": "Auto-resolutions",
|
|
54
|
+
"page": "auto-resolutions",
|
|
55
|
+
"badge": {
|
|
56
|
+
"source": "app",
|
|
57
|
+
"table": "escalation_auto_resolutions__read_model",
|
|
58
|
+
"filter": [
|
|
59
|
+
{
|
|
60
|
+
"field": "review_state",
|
|
61
|
+
"eq": "pending"
|
|
62
|
+
}
|
|
63
|
+
],
|
|
64
|
+
"tone": "warn",
|
|
65
|
+
"refreshMs": 5000,
|
|
66
|
+
"hideWhenZero": true
|
|
67
|
+
}
|
|
68
|
+
},
|
|
52
69
|
{
|
|
53
70
|
"label": "Cockpit",
|
|
54
71
|
"page": "cockpit"
|
package/pages/overview.page.json
CHANGED
|
@@ -49,6 +49,23 @@
|
|
|
49
49
|
"hideWhenZero": true
|
|
50
50
|
}
|
|
51
51
|
},
|
|
52
|
+
{
|
|
53
|
+
"label": "Auto-resolutions",
|
|
54
|
+
"page": "auto-resolutions",
|
|
55
|
+
"badge": {
|
|
56
|
+
"source": "app",
|
|
57
|
+
"table": "escalation_auto_resolutions__read_model",
|
|
58
|
+
"filter": [
|
|
59
|
+
{
|
|
60
|
+
"field": "review_state",
|
|
61
|
+
"eq": "pending"
|
|
62
|
+
}
|
|
63
|
+
],
|
|
64
|
+
"tone": "warn",
|
|
65
|
+
"refreshMs": 5000,
|
|
66
|
+
"hideWhenZero": true
|
|
67
|
+
}
|
|
68
|
+
},
|
|
52
69
|
{
|
|
53
70
|
"label": "Cockpit",
|
|
54
71
|
"page": "cockpit"
|
package/pages/tasks.page.json
CHANGED
|
@@ -49,6 +49,23 @@
|
|
|
49
49
|
"hideWhenZero": true
|
|
50
50
|
}
|
|
51
51
|
},
|
|
52
|
+
{
|
|
53
|
+
"label": "Auto-resolutions",
|
|
54
|
+
"page": "auto-resolutions",
|
|
55
|
+
"badge": {
|
|
56
|
+
"source": "app",
|
|
57
|
+
"table": "escalation_auto_resolutions__read_model",
|
|
58
|
+
"filter": [
|
|
59
|
+
{
|
|
60
|
+
"field": "review_state",
|
|
61
|
+
"eq": "pending"
|
|
62
|
+
}
|
|
63
|
+
],
|
|
64
|
+
"tone": "warn",
|
|
65
|
+
"refreshMs": 5000,
|
|
66
|
+
"hideWhenZero": true
|
|
67
|
+
}
|
|
68
|
+
},
|
|
52
69
|
{
|
|
53
70
|
"label": "Cockpit",
|
|
54
71
|
"page": "cockpit"
|
package/pages/velocity.page.json
CHANGED
|
@@ -49,6 +49,23 @@
|
|
|
49
49
|
"hideWhenZero": true
|
|
50
50
|
}
|
|
51
51
|
},
|
|
52
|
+
{
|
|
53
|
+
"label": "Auto-resolutions",
|
|
54
|
+
"page": "auto-resolutions",
|
|
55
|
+
"badge": {
|
|
56
|
+
"source": "app",
|
|
57
|
+
"table": "escalation_auto_resolutions__read_model",
|
|
58
|
+
"filter": [
|
|
59
|
+
{
|
|
60
|
+
"field": "review_state",
|
|
61
|
+
"eq": "pending"
|
|
62
|
+
}
|
|
63
|
+
],
|
|
64
|
+
"tone": "warn",
|
|
65
|
+
"refreshMs": 5000,
|
|
66
|
+
"hideWhenZero": true
|
|
67
|
+
}
|
|
68
|
+
},
|
|
52
69
|
{
|
|
53
70
|
"label": "Cockpit",
|
|
54
71
|
"page": "cockpit"
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:bpmndi_1="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:zeebe="http://camunda.org/schema/zeebe/1.0" xmlns:nano="https://nanobpm.io/schema/shapes/1.0" id="Definitions_nano_workforce_escalation_steward" targetNamespace="http://nanobpm.io/nano-workforce">
|
|
3
|
+
<bpmn:process id="escalation-steward" name="Escalation steward" isExecutable="true">
|
|
4
|
+
<bpmn:extensionElements>
|
|
5
|
+
<nano:shapes>
|
|
6
|
+
<nano:shape id="StewardCollectIn" name="Collect eligible escalations — input">
|
|
7
|
+
<nano:extend name="minAgeMs" type="integer" optional="true" />
|
|
8
|
+
</nano:shape>
|
|
9
|
+
<nano:shape id="StewardCollectOut" name="Collect eligible escalations — output">
|
|
10
|
+
<nano:extend name="eligibleCount" type="integer" />
|
|
11
|
+
<nano:extend name="claimed" type="string" list="true" optional="true" />
|
|
12
|
+
<nano:extend name="skippedOverlap" type="boolean" optional="true" />
|
|
13
|
+
</nano:shape>
|
|
14
|
+
<nano:shape id="StewardRecordIn" name="Record steward resolutions — input">
|
|
15
|
+
<nano:extend name="resolutions" type="string" optional="true" />
|
|
16
|
+
</nano:shape>
|
|
17
|
+
</nano:shapes>
|
|
18
|
+
</bpmn:extensionElements>
|
|
19
|
+
<bpmn:startEvent id="tick" name="Steward tick">
|
|
20
|
+
<bpmn:outgoing>es_toCollect</bpmn:outgoing>
|
|
21
|
+
</bpmn:startEvent>
|
|
22
|
+
<bpmn:serviceTask id="collect" name="Collect eligible escalations">
|
|
23
|
+
<bpmn:extensionElements>
|
|
24
|
+
<zeebe:taskDefinition type="pr.escalation-steward-collect" />
|
|
25
|
+
<zeebe:properties>
|
|
26
|
+
<zeebe:property name="io.nanobpm.dataEnvelope.in" value="StewardCollectIn" />
|
|
27
|
+
<zeebe:property name="io.nanobpm.dataEnvelope.out" value="StewardCollectOut" />
|
|
28
|
+
</zeebe:properties>
|
|
29
|
+
</bpmn:extensionElements>
|
|
30
|
+
<bpmn:incoming>es_toCollect</bpmn:incoming>
|
|
31
|
+
<bpmn:outgoing>es_collected</bpmn:outgoing>
|
|
32
|
+
</bpmn:serviceTask>
|
|
33
|
+
<bpmn:exclusiveGateway id="gw_any" name="any eligible?" default="es_done">
|
|
34
|
+
<bpmn:incoming>es_collected</bpmn:incoming>
|
|
35
|
+
<bpmn:outgoing>es_dispatch</bpmn:outgoing>
|
|
36
|
+
<bpmn:outgoing>es_done</bpmn:outgoing>
|
|
37
|
+
</bpmn:exclusiveGateway>
|
|
38
|
+
<bpmn:serviceTask id="steward-agent" name="Review escalations (agent)">
|
|
39
|
+
<bpmn:extensionElements>
|
|
40
|
+
<zeebe:taskDefinition type="senior:escalation-steward" />
|
|
41
|
+
<zeebe:agentDefinition agentType="external" />
|
|
42
|
+
<zeebe:linkedResources>
|
|
43
|
+
<zeebe:linkedResource resourceId="prompts/escalation-steward.md" bindingType="latest" resourceType="GenericScript" linkName="prompt" />
|
|
44
|
+
</zeebe:linkedResources>
|
|
45
|
+
<zeebe:ioMapping>
|
|
46
|
+
<zeebe:input source="=claimed" target="claimedTasks" />
|
|
47
|
+
<zeebe:output source="=if (is defined(resolutions)) then resolutions else null" target="resolutions" />
|
|
48
|
+
</zeebe:ioMapping>
|
|
49
|
+
</bpmn:extensionElements>
|
|
50
|
+
<bpmn:incoming>es_dispatch</bpmn:incoming>
|
|
51
|
+
<bpmn:outgoing>es_toRecord</bpmn:outgoing>
|
|
52
|
+
</bpmn:serviceTask>
|
|
53
|
+
<bpmn:boundaryEvent id="be_steward_sla" name="Agent SLA elapsed" attachedToRef="steward-agent">
|
|
54
|
+
<bpmn:outgoing>es_sla</bpmn:outgoing>
|
|
55
|
+
<bpmn:timerEventDefinition id="ted_steward_sla">
|
|
56
|
+
<bpmn:timeDuration xsi:type="bpmn:tFormalExpression">=agentSlaTimeout</bpmn:timeDuration>
|
|
57
|
+
</bpmn:timerEventDefinition>
|
|
58
|
+
</bpmn:boundaryEvent>
|
|
59
|
+
<bpmn:serviceTask id="record" name="Record steward resolutions">
|
|
60
|
+
<bpmn:extensionElements>
|
|
61
|
+
<zeebe:taskDefinition type="pr.escalation-steward-record" />
|
|
62
|
+
<zeebe:properties>
|
|
63
|
+
<zeebe:property name="io.nanobpm.dataEnvelope.in" value="StewardRecordIn" />
|
|
64
|
+
</zeebe:properties>
|
|
65
|
+
</bpmn:extensionElements>
|
|
66
|
+
<bpmn:incoming>es_toRecord</bpmn:incoming>
|
|
67
|
+
<bpmn:outgoing>es_recorded</bpmn:outgoing>
|
|
68
|
+
</bpmn:serviceTask>
|
|
69
|
+
<bpmn:endEvent id="done" name="Tick complete">
|
|
70
|
+
<bpmn:incoming>es_done</bpmn:incoming>
|
|
71
|
+
<bpmn:incoming>es_recorded</bpmn:incoming>
|
|
72
|
+
<bpmn:incoming>es_sla</bpmn:incoming>
|
|
73
|
+
</bpmn:endEvent>
|
|
74
|
+
<bpmn:sequenceFlow id="es_toCollect" sourceRef="tick" targetRef="collect" />
|
|
75
|
+
<bpmn:sequenceFlow id="es_collected" sourceRef="collect" targetRef="gw_any" />
|
|
76
|
+
<bpmn:sequenceFlow id="es_dispatch" name="eligible" sourceRef="gw_any" targetRef="steward-agent">
|
|
77
|
+
<bpmn:conditionExpression xsi:type="bpmn:tFormalExpression">=eligibleCount > 0</bpmn:conditionExpression>
|
|
78
|
+
</bpmn:sequenceFlow>
|
|
79
|
+
<bpmn:sequenceFlow id="es_done" name="none" sourceRef="gw_any" targetRef="done" />
|
|
80
|
+
<bpmn:sequenceFlow id="es_toRecord" sourceRef="steward-agent" targetRef="record" />
|
|
81
|
+
<bpmn:sequenceFlow id="es_recorded" sourceRef="record" targetRef="done" />
|
|
82
|
+
<bpmn:sequenceFlow id="es_sla" name="agent SLA elapsed" sourceRef="be_steward_sla" targetRef="done" />
|
|
83
|
+
</bpmn:process>
|
|
84
|
+
<bpmndi_1:BPMNDiagram id="BPMNDiagram_escalation-steward">
|
|
85
|
+
<bpmndi_1:BPMNPlane id="BPMNPlane_escalation-steward" bpmnElement="escalation-steward">
|
|
86
|
+
<bpmndi_1:BPMNShape id="BPMNShape_tick" bpmnElement="tick">
|
|
87
|
+
<dc:Bounds x="80" y="102" width="36" height="36" />
|
|
88
|
+
<bpmndi_1:BPMNLabel>
|
|
89
|
+
<dc:Bounds x="53" y="143" width="90" height="14" />
|
|
90
|
+
</bpmndi_1:BPMNLabel>
|
|
91
|
+
</bpmndi_1:BPMNShape>
|
|
92
|
+
<bpmndi_1:BPMNShape id="BPMNShape_collect" bpmnElement="collect">
|
|
93
|
+
<dc:Bounds x="216" y="80" width="100" height="80" />
|
|
94
|
+
</bpmndi_1:BPMNShape>
|
|
95
|
+
<bpmndi_1:BPMNShape id="BPMNShape_gw_any" bpmnElement="gw_any" isMarkerVisible="true">
|
|
96
|
+
<dc:Bounds x="416" y="95" width="50" height="50" />
|
|
97
|
+
<bpmndi_1:BPMNLabel>
|
|
98
|
+
<dc:Bounds x="408" y="62" width="67" height="28" />
|
|
99
|
+
</bpmndi_1:BPMNLabel>
|
|
100
|
+
</bpmndi_1:BPMNShape>
|
|
101
|
+
<bpmndi_1:BPMNShape id="BPMNShape_steward-agent" bpmnElement="steward-agent">
|
|
102
|
+
<dc:Bounds x="566" y="240" width="100" height="80" />
|
|
103
|
+
</bpmndi_1:BPMNShape>
|
|
104
|
+
<bpmndi_1:BPMNShape id="BPMNShape_record" bpmnElement="record">
|
|
105
|
+
<dc:Bounds x="766" y="240" width="100" height="80" />
|
|
106
|
+
</bpmndi_1:BPMNShape>
|
|
107
|
+
<bpmndi_1:BPMNShape id="BPMNShape_done" bpmnElement="done">
|
|
108
|
+
<dc:Bounds x="966" y="102" width="36" height="36" />
|
|
109
|
+
<bpmndi_1:BPMNLabel>
|
|
110
|
+
<dc:Bounds x="953" y="69" width="63" height="28" />
|
|
111
|
+
</bpmndi_1:BPMNLabel>
|
|
112
|
+
</bpmndi_1:BPMNShape>
|
|
113
|
+
<bpmndi_1:BPMNShape id="BPMNShape_be_steward_sla" bpmnElement="be_steward_sla">
|
|
114
|
+
<dc:Bounds x="598" y="302" width="36" height="36" />
|
|
115
|
+
<bpmndi_1:BPMNLabel>
|
|
116
|
+
<dc:Bounds x="580" y="363" width="72" height="28" />
|
|
117
|
+
</bpmndi_1:BPMNLabel>
|
|
118
|
+
</bpmndi_1:BPMNShape>
|
|
119
|
+
<bpmndi_1:BPMNEdge id="BPMNEdge_es_toCollect" bpmnElement="es_toCollect">
|
|
120
|
+
<di:waypoint x="116" y="120" />
|
|
121
|
+
<di:waypoint x="216" y="120" />
|
|
122
|
+
</bpmndi_1:BPMNEdge>
|
|
123
|
+
<bpmndi_1:BPMNEdge id="BPMNEdge_es_collected" bpmnElement="es_collected">
|
|
124
|
+
<di:waypoint x="316" y="120" />
|
|
125
|
+
<di:waypoint x="416" y="120" />
|
|
126
|
+
</bpmndi_1:BPMNEdge>
|
|
127
|
+
<bpmndi_1:BPMNEdge id="BPMNEdge_es_done" bpmnElement="es_done">
|
|
128
|
+
<di:waypoint x="466" y="120" />
|
|
129
|
+
<di:waypoint x="966" y="120" />
|
|
130
|
+
<bpmndi_1:BPMNLabel>
|
|
131
|
+
<dc:Bounds x="700" y="98" width="32" height="14" />
|
|
132
|
+
</bpmndi_1:BPMNLabel>
|
|
133
|
+
</bpmndi_1:BPMNEdge>
|
|
134
|
+
<bpmndi_1:BPMNEdge id="BPMNEdge_es_dispatch" bpmnElement="es_dispatch">
|
|
135
|
+
<di:waypoint x="441" y="145" />
|
|
136
|
+
<di:waypoint x="441" y="280" />
|
|
137
|
+
<di:waypoint x="566" y="280" />
|
|
138
|
+
<bpmndi_1:BPMNLabel>
|
|
139
|
+
<dc:Bounds x="446" y="206" width="60" height="14" />
|
|
140
|
+
</bpmndi_1:BPMNLabel>
|
|
141
|
+
</bpmndi_1:BPMNEdge>
|
|
142
|
+
<bpmndi_1:BPMNEdge id="BPMNEdge_es_toRecord" bpmnElement="es_toRecord">
|
|
143
|
+
<di:waypoint x="666" y="280" />
|
|
144
|
+
<di:waypoint x="766" y="280" />
|
|
145
|
+
</bpmndi_1:BPMNEdge>
|
|
146
|
+
<bpmndi_1:BPMNEdge id="BPMNEdge_es_recorded" bpmnElement="es_recorded">
|
|
147
|
+
<di:waypoint x="866" y="280" />
|
|
148
|
+
<di:waypoint x="984" y="280" />
|
|
149
|
+
<di:waypoint x="984" y="138" />
|
|
150
|
+
</bpmndi_1:BPMNEdge>
|
|
151
|
+
<bpmndi_1:BPMNEdge id="BPMNEdge_es_sla" bpmnElement="es_sla">
|
|
152
|
+
<di:waypoint x="616" y="338" />
|
|
153
|
+
<di:waypoint x="616" y="358" />
|
|
154
|
+
<di:waypoint x="984" y="358" />
|
|
155
|
+
<di:waypoint x="984" y="138" />
|
|
156
|
+
<bpmndi_1:BPMNLabel>
|
|
157
|
+
<dc:Bounds x="765" y="325" width="70" height="28" />
|
|
158
|
+
</bpmndi_1:BPMNLabel>
|
|
159
|
+
</bpmndi_1:BPMNEdge>
|
|
160
|
+
</bpmndi_1:BPMNPlane>
|
|
161
|
+
</bpmndi_1:BPMNDiagram>
|
|
162
|
+
</bpmn:definitions>
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Escalation steward — review open escalations and recommend a choice for each
|
|
2
|
+
|
|
3
|
+
You are the **escalation steward** for an opt-in fleet. Once per tick you are handed a
|
|
4
|
+
set of **claimed** open escalations — each a human-in-the-loop wait that is parking a
|
|
5
|
+
piece of work — and your job is to investigate each one and either **resolve** it
|
|
6
|
+
(submit the recommended choice) or **abstain** (deliberately leave it for a human).
|
|
7
|
+
|
|
8
|
+
Every decision you make is written to a durable, reviewable record and shown to an
|
|
9
|
+
operator in the **Auto-resolutions** view, so a human can examine or step in after the
|
|
10
|
+
fact. You are an agent acting with **agent** attribution — your answers are never
|
|
11
|
+
recorded as a human decision.
|
|
12
|
+
|
|
13
|
+
## Job input (`job.variables`)
|
|
14
|
+
|
|
15
|
+
| var | meaning |
|
|
16
|
+
|----------------|-------------------------------------------------------------------------|
|
|
17
|
+
| `claimedTasks` | an array of JSON strings, one per claimed escalation. Parse each. |
|
|
18
|
+
|
|
19
|
+
Each parsed entry carries:
|
|
20
|
+
|
|
21
|
+
| field | meaning |
|
|
22
|
+
|-----------------------|----------------------------------------------------------------|
|
|
23
|
+
| `user_task_key` | the completable user-task key you act on |
|
|
24
|
+
| `element_id` | the BPMN element id (the escalation kind) |
|
|
25
|
+
| `escalation_kind` | the human-readable kind label |
|
|
26
|
+
| `subject_type` | `feature` \| `plan` \| `pr` \| `delivery` |
|
|
27
|
+
| `subject_key` | the subject's key (e.g. `owner/repo#123`, a run key) |
|
|
28
|
+
| `subject_url` | an external link (the PR/issue URL), when known |
|
|
29
|
+
| `process_instance_key`| the owning engine process instance |
|
|
30
|
+
| `question` | the escalation question / findings, when recorded |
|
|
31
|
+
|
|
32
|
+
## Evidence before choosing — verify, don't guess
|
|
33
|
+
|
|
34
|
+
For **every** escalation, before you choose, run the same checks an operator would:
|
|
35
|
+
|
|
36
|
+
- Read the escalation's **question, options and form schema**. Use the form's real keys
|
|
37
|
+
(for example `decision` / `value` / `escalationNote` for delivery tasks, `answer` for
|
|
38
|
+
a `wait-answer`, `resolution` / `answer` for a feature escalation).
|
|
39
|
+
- **Verify any claim that matters**, using the repo and the engine:
|
|
40
|
+
- Does the PR, branch or WIP ref actually exist? (`gh pr view`, `git ls-remote`)
|
|
41
|
+
- Were commits pushed? What do the review threads say?
|
|
42
|
+
- Was this question ruled on before? Check `pr_adjudications` (via the
|
|
43
|
+
`getPrHistory` / prior-resolution context) and earlier auto-resolutions.
|
|
44
|
+
- Choose an option and, where the option allows, write a **corrective note** (for
|
|
45
|
+
example a `Retry` with guidance on what to fix).
|
|
46
|
+
|
|
47
|
+
## When you MUST abstain
|
|
48
|
+
|
|
49
|
+
Abstain (record `abstained`, leave the escalation for a human) when **any** of these
|
|
50
|
+
hold:
|
|
51
|
+
|
|
52
|
+
- The right choice is **unclear** after investigation.
|
|
53
|
+
- The option is **irreversible or destructive** — for example *abandon*, deleting work,
|
|
54
|
+
or merging past a failing required check.
|
|
55
|
+
- A **credential or permission** change is needed.
|
|
56
|
+
- The **loop guard** trips: the same subject has already been auto-resolved twice and
|
|
57
|
+
has escalated again. Repeated escalation means the automatic answer isn't working —
|
|
58
|
+
say so in your rationale.
|
|
59
|
+
|
|
60
|
+
When in doubt, abstain. A human can always answer; a wrong automatic answer is worse.
|
|
61
|
+
|
|
62
|
+
## Output contract
|
|
63
|
+
|
|
64
|
+
Write a JSON object to the file named by the `AGENT_RESULT_FILE` environment variable
|
|
65
|
+
(or, as a fallback, print a single last stdout line `::nano:result:: {json}`). The
|
|
66
|
+
object MUST carry a `resolutions` field: a JSON **string** encoding an array with one
|
|
67
|
+
entry per claimed escalation:
|
|
68
|
+
|
|
69
|
+
```json
|
|
70
|
+
{
|
|
71
|
+
"resolutions": "[{\"userTaskKey\":\"...\",\"outcome\":\"resolved\",\"choice\":\"retry\",\"variables\":{\"answer\":\"...\"},\"rationale\":\"...\",\"evidence\":\"...\",\"options\":[\"retry\",\"abandon\"]}]"
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Each entry:
|
|
76
|
+
|
|
77
|
+
- `userTaskKey` (required) — the escalation this decision is for.
|
|
78
|
+
- `outcome` (required) — `"resolved"` or `"abstained"`.
|
|
79
|
+
- `variables` (required when `resolved`) — the exact typed form variables to submit.
|
|
80
|
+
- `choice` — the option value you picked (used by the host's destructive-option guard).
|
|
81
|
+
- `rationale` — one line: why this choice (or why you abstained).
|
|
82
|
+
- `evidence` — what you checked, with links (a short markdown/JSON note).
|
|
83
|
+
- `options` — the option values the form offered (a snapshot for the record).
|
|
84
|
+
|
|
85
|
+
**Emitting a machine-readable result is your mandatory final step — never exit
|
|
86
|
+
silently.** Exit `0` on every path. If you cannot reach a confident answer for an
|
|
87
|
+
escalation, record it as `abstained` with the reason — do not guess.
|