@nanobpm/nano-workforce 0.99.0 → 0.100.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 +14 -0
- package/app/agentCompletion.test.ts +92 -6
- package/app/agentCompletion.ts +86 -58
- package/app/convergeGate.test.ts +8 -4
- package/app/convergenceEscalationGuard.test.ts +106 -0
- package/app/feature.ts +15 -166
- package/app/featureGateway.test.ts +6 -57
- package/app/persist-escalation.test.ts +32 -0
- package/app/pollUserTasks.test.ts +39 -13
- package/app/roundProgress.test.ts +7 -4
- package/app/service.ts +60 -132
- package/app/stage.test.ts +7 -53
- package/app/stage.ts +5 -37
- package/app/userTasks.test.ts +1 -1
- package/app/userTasks.ts +3 -3
- package/db/migrations/049_drop_feature_escalation_surface.sql +25 -0
- package/e2e/feature-run.e2e.ts +52 -41
- package/e2e/retire-escalation-subsystem.e2e.ts +26 -0
- package/openapi.yaml +7 -108
- package/operations/agentCompleteEscalation.ts +2 -2
- package/operations/completeUserTask.test.ts +25 -6
- package/operations/completeUserTask.ts +12 -10
- package/package.json +2 -2
- package/pages/feature.page.json +1 -37
- package/pages/overview.page.json +1 -39
- package/pages/tasks.page.json +78 -93
- package/resources/forms/feature-escalation.form +3 -0
- package/resources/processes/convergence-loop.bpmn +114 -118
- package/workers/persist-escalation/worker.ts +7 -4
- package/workers/record-blocked-ack/worker.test.ts +1 -4
- package/workers/record-blocked-ack/worker.ts +0 -5
- package/workers/record-feature/worker.ts +0 -6
- package/workers/record-feature-escalation/worker.test.ts +14 -27
- package/workers/record-feature-escalation/worker.ts +16 -22
- package/app/featureBlocked.test.ts +0 -182
- package/app/featureEscalation.test.ts +0 -235
- package/operations/acknowledgeBlocked.test.ts +0 -111
- package/operations/acknowledgeBlocked.ts +0 -62
- package/operations/answerFeatureEscalation.ts +0 -68
package/openapi.yaml
CHANGED
|
@@ -1908,69 +1908,17 @@ paths:
|
|
|
1908
1908
|
application/json:
|
|
1909
1909
|
schema:
|
|
1910
1910
|
$ref: "#/components/schemas/MessageResult"
|
|
1911
|
-
/actions/answer-escalation:
|
|
1912
|
-
post:
|
|
1913
|
-
operationId: answerFeatureEscalation
|
|
1914
|
-
summary: "Answer a native feature-run escalation (issue #210). Completes the parked
|
|
1915
|
-
`feature-escalation` user task with the operator's typed form variables (resolution=answer/abandon
|
|
1916
|
-
+ guidance), driving the canonical attributed completer (completeUserTaskAttributed) — the same
|
|
1917
|
-
resume path the task inbox uses, recording who answered. The poller then reconciles the run off
|
|
1918
|
-
`escalated`. This is the nwf UI's answer affordance for a feature run parked on a human decision."
|
|
1919
|
-
requestBody:
|
|
1920
|
-
required: true
|
|
1921
|
-
content:
|
|
1922
|
-
application/json:
|
|
1923
|
-
schema:
|
|
1924
|
-
type: object
|
|
1925
|
-
additionalProperties: false
|
|
1926
|
-
required:
|
|
1927
|
-
- userTaskKey
|
|
1928
|
-
- resolution
|
|
1929
|
-
properties:
|
|
1930
|
-
userTaskKey:
|
|
1931
|
-
type: string
|
|
1932
|
-
minLength: 1
|
|
1933
|
-
description: The parked `feature-escalation` user-task key (feature_runs.escalation_user_task_key).
|
|
1934
|
-
resolution:
|
|
1935
|
-
type: string
|
|
1936
|
-
enum: [answer, abandon]
|
|
1937
|
-
description: answer ⇒ re-dispatch the implement task with the guidance; abandon ⇒ give up on the task.
|
|
1938
|
-
answer:
|
|
1939
|
-
type: string
|
|
1940
|
-
description: Guidance for the implementation agent. Required (non-blank) when resolution is answer.
|
|
1941
|
-
operator:
|
|
1942
|
-
type: string
|
|
1943
|
-
description: Optional operator handle recorded in the attribution ledger; defaults to "operator".
|
|
1944
|
-
responses:
|
|
1945
|
-
"200":
|
|
1946
|
-
description: The escalation user task was completed and the process resumed.
|
|
1947
|
-
content:
|
|
1948
|
-
application/json:
|
|
1949
|
-
schema:
|
|
1950
|
-
$ref: "#/components/schemas/MessageResult"
|
|
1951
|
-
"400":
|
|
1952
|
-
description: A required field was missing/invalid, or the target is not an escalation task.
|
|
1953
|
-
content:
|
|
1954
|
-
application/json:
|
|
1955
|
-
schema:
|
|
1956
|
-
$ref: "#/components/schemas/MessageResult"
|
|
1957
|
-
"404":
|
|
1958
|
-
description: No open escalation user task matches the userTaskKey.
|
|
1959
|
-
content:
|
|
1960
|
-
application/json:
|
|
1961
|
-
schema:
|
|
1962
|
-
$ref: "#/components/schemas/MessageResult"
|
|
1963
1911
|
/actions/complete-user-task:
|
|
1964
1912
|
post:
|
|
1965
1913
|
operationId: completeUserTask
|
|
1966
1914
|
summary: "Complete an open native user-task escalation from the nwf Tasks inbox (issue #236).
|
|
1967
1915
|
Submits the parked task's typed `.form` variables (e.g. a plan-review `{ directive, notes }`, a
|
|
1968
|
-
trial-merge `{ action, notes }`,
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
(answer-escalation / acknowledge-blocked)
|
|
1973
|
-
`
|
|
1916
|
+
trial-merge `{ action, notes }`, a PR `{ answer }`, a feature escalation `{ resolution, answer }`,
|
|
1917
|
+
or a blocked-run `{ note }`) to the canonical human completer (completeEscalationAsHuman →
|
|
1918
|
+
completeUserTaskAttributed) — the same resume path the task inbox uses, recording who answered.
|
|
1919
|
+
The completer refuses any user task that is not one of the human-completable elements. Issue #332
|
|
1920
|
+
retired the bespoke feature-run doors (answer-escalation / acknowledge-blocked) and folded
|
|
1921
|
+
`feature-escalation` / `feature-blocked` onto this one canonical door. On success the answered
|
|
1974
1922
|
task's read-model row is dropped so the Tasks grid stops offering a decision for it."
|
|
1975
1923
|
requestBody:
|
|
1976
1924
|
required: true
|
|
@@ -2013,61 +1961,12 @@ paths:
|
|
|
2013
1961
|
application/json:
|
|
2014
1962
|
schema:
|
|
2015
1963
|
$ref: "#/components/schemas/MessageResult"
|
|
2016
|
-
/actions/acknowledge-blocked:
|
|
2017
|
-
post:
|
|
2018
|
-
operationId: acknowledgeBlocked
|
|
2019
|
-
summary: "Acknowledge a blocked native feature run (issue #220). Completes the parked
|
|
2020
|
-
`feature-blocked` operator user task with the operator's optional disposition note, driving the
|
|
2021
|
-
canonical attributed completer (completeUserTaskAttributed) — the same resume path the task inbox
|
|
2022
|
-
uses, recording who acknowledged. Completing it fires pr.record-blocked-ack, settling the run to
|
|
2023
|
-
the terminal `blocked` status with the note, and the poller reconciles the completable-task
|
|
2024
|
-
pointer off the row. This is the nwf UI's completion affordance for a feature run parked as
|
|
2025
|
-
blocked (the blocked twin of answer-escalation)."
|
|
2026
|
-
requestBody:
|
|
2027
|
-
required: true
|
|
2028
|
-
content:
|
|
2029
|
-
application/json:
|
|
2030
|
-
schema:
|
|
2031
|
-
type: object
|
|
2032
|
-
additionalProperties: false
|
|
2033
|
-
required:
|
|
2034
|
-
- userTaskKey
|
|
2035
|
-
properties:
|
|
2036
|
-
userTaskKey:
|
|
2037
|
-
type: string
|
|
2038
|
-
minLength: 1
|
|
2039
|
-
description: The parked `feature-blocked` user-task key (feature_runs.blocked_user_task_key).
|
|
2040
|
-
note:
|
|
2041
|
-
type: string
|
|
2042
|
-
description: Optional operator disposition note, recorded on the run (delivery_label). Blank ⇒ "acknowledged".
|
|
2043
|
-
operator:
|
|
2044
|
-
type: string
|
|
2045
|
-
description: Optional operator handle recorded in the attribution ledger; defaults to "operator".
|
|
2046
|
-
responses:
|
|
2047
|
-
"200":
|
|
2048
|
-
description: The blocked user task was completed and the process resumed.
|
|
2049
|
-
content:
|
|
2050
|
-
application/json:
|
|
2051
|
-
schema:
|
|
2052
|
-
$ref: "#/components/schemas/MessageResult"
|
|
2053
|
-
"400":
|
|
2054
|
-
description: A required field was missing/invalid, or the target is not a blocked task.
|
|
2055
|
-
content:
|
|
2056
|
-
application/json:
|
|
2057
|
-
schema:
|
|
2058
|
-
$ref: "#/components/schemas/MessageResult"
|
|
2059
|
-
"404":
|
|
2060
|
-
description: No open blocked user task matches the userTaskKey.
|
|
2061
|
-
content:
|
|
2062
|
-
application/json:
|
|
2063
|
-
schema:
|
|
2064
|
-
$ref: "#/components/schemas/MessageResult"
|
|
2065
1964
|
/actions/acknowledge-done:
|
|
2066
1965
|
post:
|
|
2067
1966
|
operationId: acknowledgeDone
|
|
2068
1967
|
summary: "Tick off a TERMINAL feature run (issue #254 §5). Stamps `acknowledged_at` on the run so
|
|
2069
1968
|
the gateway recomputes its `list_bucket` to 'history', dropping the finished run out of the
|
|
2070
|
-
primary Active list into History. The Done twin of
|
|
1969
|
+
primary Active list into History. The Done twin of the blocked acknowledgement, but a terminal run is
|
|
2071
1970
|
not parked at a user task, so this completes no user task and only writes the row. Keyed on the
|
|
2072
1971
|
run's `feature_key`; idempotent-safe (re-acknowledging keeps it in History)."
|
|
2073
1972
|
requestBody:
|
|
@@ -46,8 +46,8 @@ export default defineOperation("agentCompleteEscalation", async ({ req, body },
|
|
|
46
46
|
app.log.info("agent completed escalation", { userTaskKey, agentId, elementId: r.elementId });
|
|
47
47
|
return { status: 200, body: { ok: true, completionId: r.completionId, elementId: r.elementId } };
|
|
48
48
|
}
|
|
49
|
-
// A non-
|
|
50
|
-
const status = r.reason === "no open
|
|
49
|
+
// A non-completable / missing-target is a client error; an unknown key is a 404.
|
|
50
|
+
const status = r.reason === "no open completable task" ? 404 : 400;
|
|
51
51
|
app.log.warn("agent-complete: not completed", { userTaskKey, reason: r.reason });
|
|
52
52
|
return { status, body: { ok: false, error: r.reason } };
|
|
53
53
|
});
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
// Tests for the POST /app/api/actions/complete-user-task operation `completeUserTask` (issue #236).
|
|
2
2
|
// The nwf Tasks page's decision affordance for the plan-review / trial-merge / PR `wait-answer`
|
|
3
|
-
// escalations
|
|
4
|
-
//
|
|
5
|
-
//
|
|
3
|
+
// escalations plus the feature kinds `feature-escalation` / `feature-blocked` (issue #332 folded the
|
|
4
|
+
// bespoke feature doors onto this one canonical door): it routes the typed form variables through the
|
|
5
|
+
// canonical human completer (completeEscalationAsHuman → completeUserTaskAttributed), resuming the
|
|
6
|
+
// process exactly as the task inbox would, and drops the answered task's read-model row so the grid
|
|
7
|
+
// stops offering a decision.
|
|
6
8
|
import { test } from "node:test";
|
|
7
9
|
import { assertEquals } from "#test-assert";
|
|
8
10
|
import type { AppApi } from "@nanobpm/urban";
|
|
9
11
|
import { noopLog } from "../test/log.ts";
|
|
10
12
|
import handler from "./completeUserTask.ts";
|
|
11
13
|
|
|
12
|
-
// biome-ignore lint/suspicious/noExplicitAny: in-memory doubles, mirrors
|
|
14
|
+
// biome-ignore lint/suspicious/noExplicitAny: in-memory doubles, mirrors sibling op tests
|
|
13
15
|
function memApp(openTasks: { userTaskKey: string; elementId?: string }[]): {
|
|
14
16
|
app: AppApi;
|
|
15
17
|
// biome-ignore lint/suspicious/noExplicitAny: see above
|
|
@@ -49,6 +51,7 @@ function memApp(openTasks: { userTaskKey: string; elementId?: string }[]): {
|
|
|
49
51
|
};
|
|
50
52
|
}
|
|
51
53
|
const engine = {
|
|
54
|
+
openUserTasks: async () => openTasks,
|
|
52
55
|
searchUserTasks: async () => openTasks,
|
|
53
56
|
completeUserTask: async (userTaskKey: string, variables: Record<string, unknown>) => {
|
|
54
57
|
completed.push({ userTaskKey, variables });
|
|
@@ -106,14 +109,30 @@ test("complete-user-task: non-object variables are rejected 400", async () => {
|
|
|
106
109
|
assertEquals(res.status, 400);
|
|
107
110
|
});
|
|
108
111
|
|
|
109
|
-
test("complete-user-task: an unknown key is a 404 (no open
|
|
112
|
+
test("complete-user-task: an unknown key is a 404 (no open completable task)", async () => {
|
|
110
113
|
const { app } = memApp([]);
|
|
111
114
|
const res = await call(app, { userTaskKey: "ghost", variables: { answer: "x" } });
|
|
112
115
|
assertEquals(res.status, 404);
|
|
113
116
|
});
|
|
114
117
|
|
|
115
|
-
test("complete-user-task:
|
|
118
|
+
test("complete-user-task: completes a feature-blocked acknowledgement with the typed note (issue #332)", async () => {
|
|
116
119
|
const { app, completed } = memApp([{ userTaskKey: "ut-4", elementId: "feature-blocked" }]);
|
|
120
|
+
const res = await call(app, { userTaskKey: "ut-4", variables: { note: "reassigned" } });
|
|
121
|
+
assertEquals(res.status, 200);
|
|
122
|
+
assertEquals(res.body.elementId, "feature-blocked");
|
|
123
|
+
assertEquals(completed, [{ userTaskKey: "ut-4", variables: { note: "reassigned" } }]);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test("complete-user-task: completes a feature-escalation answer (issue #332)", async () => {
|
|
127
|
+
const { app, completed } = memApp([{ userTaskKey: "ut-6", elementId: "feature-escalation" }]);
|
|
128
|
+
const res = await call(app, { userTaskKey: "ut-6", variables: { resolution: "answer", answer: "use v2" } });
|
|
129
|
+
assertEquals(res.status, 200);
|
|
130
|
+
assertEquals(res.body.elementId, "feature-escalation");
|
|
131
|
+
assertEquals(completed, [{ userTaskKey: "ut-6", variables: { resolution: "answer", answer: "use v2" } }]);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test("complete-user-task: refuses a non-completable internal user task (400)", async () => {
|
|
135
|
+
const { app, completed } = memApp([{ userTaskKey: "ut-4", elementId: "some-internal-task" }]);
|
|
117
136
|
const res = await call(app, { userTaskKey: "ut-4", variables: { note: "x" } });
|
|
118
137
|
assertEquals(res.status, 400);
|
|
119
138
|
assertEquals(completed, []);
|
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
// POST /app/api/actions/complete-user-task → operationId `completeUserTask` (issue #236).
|
|
2
2
|
//
|
|
3
|
-
// The nwf **Tasks** page's decision affordance for the
|
|
4
|
-
// app-side completion path — `plan-review-decision`, `trial-merge-decision`,
|
|
5
|
-
// `wait-answer
|
|
6
|
-
//
|
|
7
|
-
//
|
|
3
|
+
// The nwf **Tasks** page's decision affordance for the native user-task escalations that had no
|
|
4
|
+
// app-side completion path — `plan-review-decision`, `trial-merge-decision`, the PR review-loop
|
|
5
|
+
// `wait-answer`, the feature escalation `feature-escalation`, and the human-only `feature-blocked`
|
|
6
|
+
// acknowledgement. An operator submits the parked task's typed `.form` variables (e.g. a plan-review
|
|
7
|
+
// `{ directive, notes }`, a trial-merge `{ action, notes }`, a PR `{ answer }`, a feature escalation
|
|
8
|
+
// `{ resolution, answer }`, or a blocked-run `{ note }`) directly from the Tasks inbox instead of only
|
|
9
|
+
// from Urban's read-only task-inbox stub.
|
|
8
10
|
//
|
|
9
11
|
// It routes through the ONE canonical human completer (`completeEscalationAsHuman` →
|
|
10
12
|
// `completeUserTaskAttributed`), so the completion uses the exact same typed variables and engine
|
|
11
13
|
// resume path a human drives from the task inbox — no parallel completion — while recording WHO
|
|
12
14
|
// answered in the `task_completions` ledger. That completer refuses any user task that is not one of
|
|
13
|
-
// the
|
|
14
|
-
// complete an arbitrary internal user task.
|
|
15
|
-
//
|
|
16
|
-
//
|
|
15
|
+
// the human-completable elements (`HUMAN_COMPLETABLE_ELEMENTS`), so this generic door can never
|
|
16
|
+
// complete an arbitrary internal user task. Issue #332 retired the bespoke feature-run reconcile doors
|
|
17
|
+
// (`answer-escalation` / `acknowledge-blocked`) and folded `feature-escalation` / `feature-blocked`
|
|
18
|
+
// onto this one canonical door.
|
|
17
19
|
//
|
|
18
20
|
// On success it removes the answered task's `user_tasks` read-model row so the Tasks grid stops
|
|
19
21
|
// offering a decision for a task that is now completed, without waiting a poll cycle; the poller
|
|
@@ -66,7 +68,7 @@ export default defineOperation("completeUserTask", async ({ body }, app) => {
|
|
|
66
68
|
app.log.info("operator completed user task", { userTaskKey, elementId: r.elementId });
|
|
67
69
|
return { status: 200, body: { ok: true, completionId: r.completionId, elementId: r.elementId } };
|
|
68
70
|
}
|
|
69
|
-
const status = r.reason === "no open
|
|
71
|
+
const status = r.reason === "no open completable task" ? 404 : 400;
|
|
70
72
|
app.log.warn("complete-user-task: not completed", { userTaskKey, reason: r.reason });
|
|
71
73
|
return { status, body: { ok: false, error: r.reason } };
|
|
72
74
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nanobpm/nano-workforce",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.100.0",
|
|
4
4
|
"description": "Nano Workforce — an Agent Graph Orchestration application for Agentic SDLC: durable BPMN processes that coordinate a graph of AI agents across the software delivery lifecycle.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "main.ts",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
57
|
"@nanobpm/agentic": "^0.1.0",
|
|
58
|
-
"@nanobpm/urban": "^0.
|
|
58
|
+
"@nanobpm/urban": "^0.68.1"
|
|
59
59
|
},
|
|
60
60
|
"devDependencies": {
|
|
61
61
|
"@biomejs/biome": "^2.4.11",
|
package/pages/feature.page.json
CHANGED
|
@@ -126,24 +126,6 @@
|
|
|
126
126
|
{ "field": "updated_at", "header": "Updated", "width": "9rem", "format": "datetime" }
|
|
127
127
|
],
|
|
128
128
|
"rowActions": [
|
|
129
|
-
{
|
|
130
|
-
"label": "Abandon",
|
|
131
|
-
"confirm": "Abandon this escalated task? The run gives up on it (no PR).",
|
|
132
|
-
"showWhenField": "escalation_open",
|
|
133
|
-
"action": {
|
|
134
|
-
"path": "/app/api/actions/answer-escalation",
|
|
135
|
-
"body": { "userTaskKey": "{{row.escalation_user_task_key}}", "resolution": "abandon" }
|
|
136
|
-
}
|
|
137
|
-
},
|
|
138
|
-
{
|
|
139
|
-
"label": "Acknowledge blocked",
|
|
140
|
-
"confirm": "Acknowledge this blocked run? It settles to terminal blocked (the agent could not open a PR).",
|
|
141
|
-
"showWhenField": "blocked_user_task_key",
|
|
142
|
-
"action": {
|
|
143
|
-
"path": "/app/api/actions/acknowledge-blocked",
|
|
144
|
-
"body": { "userTaskKey": "{{row.blocked_user_task_key}}" }
|
|
145
|
-
}
|
|
146
|
-
},
|
|
147
129
|
{
|
|
148
130
|
"label": "Dismiss",
|
|
149
131
|
"confirm": "Tick off this finished run? It acknowledges the run as done and files it under History.",
|
|
@@ -156,28 +138,10 @@
|
|
|
156
138
|
],
|
|
157
139
|
"detail": {
|
|
158
140
|
"fields": [
|
|
159
|
-
{ "field": "escalation_question", "label": "Escalation question" },
|
|
160
141
|
{ "field": "base_branch", "label": "Base branch" },
|
|
161
142
|
{ "field": "outcome", "label": "Outcome" },
|
|
162
143
|
{ "field": "delivery_label", "label": "Delivery" }
|
|
163
|
-
]
|
|
164
|
-
"form": {
|
|
165
|
-
"showWhenField": "escalation_open",
|
|
166
|
-
"title": "Answer escalation",
|
|
167
|
-
"promptField": "escalation_question",
|
|
168
|
-
"inputKey": "answer",
|
|
169
|
-
"inputLabel": "Guidance for the implementation agent",
|
|
170
|
-
"submitLabel": "Answer & re-dispatch",
|
|
171
|
-
"action": {
|
|
172
|
-
"path": "/app/api/actions/answer-escalation",
|
|
173
|
-
"successLabel": "Answered — the agent will resume",
|
|
174
|
-
"body": {
|
|
175
|
-
"userTaskKey": "{{row.escalation_user_task_key}}",
|
|
176
|
-
"resolution": "answer",
|
|
177
|
-
"answer": "{{form.answer}}"
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
}
|
|
144
|
+
]
|
|
181
145
|
}
|
|
182
146
|
}
|
|
183
147
|
}
|
package/pages/overview.page.json
CHANGED
|
@@ -165,48 +165,10 @@
|
|
|
165
165
|
{ "field": "delivery_label", "header": "Delivery" },
|
|
166
166
|
{ "field": "updated_at", "header": "Updated", "width": "9rem", "format": "datetime" }
|
|
167
167
|
],
|
|
168
|
-
"rowActions": [
|
|
169
|
-
{
|
|
170
|
-
"label": "Abandon",
|
|
171
|
-
"confirm": "Abandon this escalated task? The run gives up on it (no PR).",
|
|
172
|
-
"showWhenField": "escalation_open",
|
|
173
|
-
"action": {
|
|
174
|
-
"path": "/app/api/actions/answer-escalation",
|
|
175
|
-
"body": { "userTaskKey": "{{row.escalation_user_task_key}}", "resolution": "abandon" }
|
|
176
|
-
}
|
|
177
|
-
},
|
|
178
|
-
{
|
|
179
|
-
"label": "Acknowledge blocked",
|
|
180
|
-
"confirm": "Acknowledge this blocked run? It settles to terminal blocked (the agent could not open a PR).",
|
|
181
|
-
"showWhenField": "blocked_user_task_key",
|
|
182
|
-
"action": {
|
|
183
|
-
"path": "/app/api/actions/acknowledge-blocked",
|
|
184
|
-
"body": { "userTaskKey": "{{row.blocked_user_task_key}}" }
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
],
|
|
188
168
|
"detail": {
|
|
189
169
|
"fields": [
|
|
190
|
-
{ "field": "escalation_question", "label": "Escalation question" },
|
|
191
170
|
{ "field": "outcome", "label": "Outcome" }
|
|
192
|
-
]
|
|
193
|
-
"form": {
|
|
194
|
-
"showWhenField": "escalation_open",
|
|
195
|
-
"title": "Answer escalation",
|
|
196
|
-
"promptField": "escalation_question",
|
|
197
|
-
"inputKey": "answer",
|
|
198
|
-
"inputLabel": "Guidance for the implementation agent",
|
|
199
|
-
"submitLabel": "Answer & re-dispatch",
|
|
200
|
-
"action": {
|
|
201
|
-
"path": "/app/api/actions/answer-escalation",
|
|
202
|
-
"successLabel": "Answered — the agent will resume",
|
|
203
|
-
"body": {
|
|
204
|
-
"userTaskKey": "{{row.escalation_user_task_key}}",
|
|
205
|
-
"resolution": "answer",
|
|
206
|
-
"answer": "{{form.answer}}"
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
}
|
|
171
|
+
]
|
|
210
172
|
}
|
|
211
173
|
}
|
|
212
174
|
}
|
package/pages/tasks.page.json
CHANGED
|
@@ -119,22 +119,6 @@
|
|
|
119
119
|
}
|
|
120
120
|
],
|
|
121
121
|
"rowKey": "user_task_key",
|
|
122
|
-
"rowActions": [
|
|
123
|
-
{
|
|
124
|
-
"label": "Abandon",
|
|
125
|
-
"confirm": "Abandon this escalated task? The run gives up on it (no PR).",
|
|
126
|
-
"showWhenField": "user_task_key",
|
|
127
|
-
"action": {
|
|
128
|
-
"path": "/app/api/actions/complete-user-task",
|
|
129
|
-
"body": {
|
|
130
|
-
"userTaskKey": "{{row.user_task_key}}",
|
|
131
|
-
"variables": {
|
|
132
|
-
"resolution": "abandon"
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
],
|
|
138
122
|
"detail": {
|
|
139
123
|
"linkField": "subject_url",
|
|
140
124
|
"fields": [
|
|
@@ -147,18 +131,32 @@
|
|
|
147
131
|
"showWhenField": "user_task_key",
|
|
148
132
|
"title": "Answer escalation",
|
|
149
133
|
"promptField": "question",
|
|
150
|
-
"
|
|
151
|
-
"
|
|
152
|
-
|
|
134
|
+
"submitLabel": "Submit decision",
|
|
135
|
+
"fields": [
|
|
136
|
+
{
|
|
137
|
+
"inputKey": "resolution",
|
|
138
|
+
"type": "select",
|
|
139
|
+
"inputLabel": "Resolution",
|
|
140
|
+
"options": [
|
|
141
|
+
{ "value": "answer", "label": "Answer — re-dispatch the task with this guidance" },
|
|
142
|
+
{ "value": "abandon", "label": "Abandon — give up on this task" }
|
|
143
|
+
]
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
"inputKey": "answer",
|
|
147
|
+
"type": "textarea",
|
|
148
|
+
"inputLabel": "Answer / guidance for the implementation agent",
|
|
149
|
+
"conditional": {
|
|
150
|
+
"hide": "=resolution != \"answer\""
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
],
|
|
153
154
|
"action": {
|
|
154
155
|
"path": "/app/api/actions/complete-user-task",
|
|
155
|
-
"successLabel": "
|
|
156
|
+
"successLabel": "Decision submitted — the process will continue",
|
|
156
157
|
"body": {
|
|
157
158
|
"userTaskKey": "{{row.user_task_key}}",
|
|
158
|
-
"variables": {
|
|
159
|
-
"resolution": "answer",
|
|
160
|
-
"answer": "{{form.answer}}"
|
|
161
|
-
}
|
|
159
|
+
"variables": "{{form}}"
|
|
162
160
|
}
|
|
163
161
|
}
|
|
164
162
|
}
|
|
@@ -220,22 +218,6 @@
|
|
|
220
218
|
}
|
|
221
219
|
],
|
|
222
220
|
"rowKey": "user_task_key",
|
|
223
|
-
"rowActions": [
|
|
224
|
-
{
|
|
225
|
-
"label": "Proceed",
|
|
226
|
-
"confirm": "Proceed with the current plan as-is? It is dispatched unchanged.",
|
|
227
|
-
"showWhenField": "user_task_key",
|
|
228
|
-
"action": {
|
|
229
|
-
"path": "/app/api/actions/complete-user-task",
|
|
230
|
-
"body": {
|
|
231
|
-
"userTaskKey": "{{row.user_task_key}}",
|
|
232
|
-
"variables": {
|
|
233
|
-
"directive": "proceed"
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
],
|
|
239
221
|
"detail": {
|
|
240
222
|
"linkField": "subject_url",
|
|
241
223
|
"fields": [
|
|
@@ -246,20 +228,34 @@
|
|
|
246
228
|
],
|
|
247
229
|
"form": {
|
|
248
230
|
"showWhenField": "user_task_key",
|
|
249
|
-
"title": "
|
|
231
|
+
"title": "Review the plan",
|
|
250
232
|
"promptField": "question",
|
|
251
|
-
"
|
|
252
|
-
"
|
|
253
|
-
|
|
233
|
+
"submitLabel": "Submit decision",
|
|
234
|
+
"fields": [
|
|
235
|
+
{
|
|
236
|
+
"inputKey": "directive",
|
|
237
|
+
"type": "select",
|
|
238
|
+
"inputLabel": "Directive",
|
|
239
|
+
"options": [
|
|
240
|
+
{ "value": "proceed", "label": "Proceed — dispatch the current plan as-is" },
|
|
241
|
+
{ "value": "revise", "label": "Revise — send the plan back to the planner" }
|
|
242
|
+
]
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
"inputKey": "notes",
|
|
246
|
+
"type": "textarea",
|
|
247
|
+
"inputLabel": "Revision guidance for the planner",
|
|
248
|
+
"conditional": {
|
|
249
|
+
"hide": "=directive != \"revise\""
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
],
|
|
254
253
|
"action": {
|
|
255
254
|
"path": "/app/api/actions/complete-user-task",
|
|
256
|
-
"successLabel": "
|
|
255
|
+
"successLabel": "Decision recorded",
|
|
257
256
|
"body": {
|
|
258
257
|
"userTaskKey": "{{row.user_task_key}}",
|
|
259
|
-
"variables": {
|
|
260
|
-
"directive": "revise",
|
|
261
|
-
"notes": "{{form.notes}}"
|
|
262
|
-
}
|
|
258
|
+
"variables": "{{form}}"
|
|
263
259
|
}
|
|
264
260
|
}
|
|
265
261
|
}
|
|
@@ -321,58 +317,45 @@
|
|
|
321
317
|
}
|
|
322
318
|
],
|
|
323
319
|
"rowKey": "user_task_key",
|
|
324
|
-
"
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
"path": "/app/api/actions/complete-user-task",
|
|
331
|
-
"body": {
|
|
332
|
-
"userTaskKey": "{{row.user_task_key}}",
|
|
333
|
-
"variables": {
|
|
334
|
-
"action": "proceed"
|
|
335
|
-
}
|
|
336
|
-
}
|
|
320
|
+
"detail": {
|
|
321
|
+
"linkField": "subject_url",
|
|
322
|
+
"fields": [
|
|
323
|
+
{
|
|
324
|
+
"field": "question",
|
|
325
|
+
"label": "Trial merge"
|
|
337
326
|
}
|
|
338
|
-
|
|
339
|
-
{
|
|
340
|
-
"label": "Rebase",
|
|
341
|
-
"confirm": "Re-run the trial merge?",
|
|
327
|
+
],
|
|
328
|
+
"form": {
|
|
342
329
|
"showWhenField": "user_task_key",
|
|
343
|
-
"
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
330
|
+
"title": "Trial-merge decision",
|
|
331
|
+
"promptField": "question",
|
|
332
|
+
"submitLabel": "Submit decision",
|
|
333
|
+
"fields": [
|
|
334
|
+
{
|
|
335
|
+
"inputKey": "action",
|
|
336
|
+
"type": "select",
|
|
337
|
+
"inputLabel": "Action",
|
|
338
|
+
"options": [
|
|
339
|
+
{ "value": "proceed", "label": "Proceed — accept the red trial merge and continue" },
|
|
340
|
+
{ "value": "rebase", "label": "Rebase — re-run the trial merge" },
|
|
341
|
+
{ "value": "abandon", "label": "Abandon — stop and finalize the plan" }
|
|
342
|
+
]
|
|
343
|
+
},
|
|
344
|
+
{
|
|
345
|
+
"inputKey": "notes",
|
|
346
|
+
"type": "textarea",
|
|
347
|
+
"inputLabel": "Notes"
|
|
350
348
|
}
|
|
351
|
-
|
|
352
|
-
},
|
|
353
|
-
{
|
|
354
|
-
"label": "Abandon",
|
|
355
|
-
"confirm": "Stop and finalize the plan?",
|
|
356
|
-
"showWhenField": "user_task_key",
|
|
349
|
+
],
|
|
357
350
|
"action": {
|
|
358
351
|
"path": "/app/api/actions/complete-user-task",
|
|
352
|
+
"successLabel": "Decision recorded",
|
|
359
353
|
"body": {
|
|
360
354
|
"userTaskKey": "{{row.user_task_key}}",
|
|
361
|
-
"variables": {
|
|
362
|
-
"action": "abandon"
|
|
363
|
-
}
|
|
355
|
+
"variables": "{{form}}"
|
|
364
356
|
}
|
|
365
357
|
}
|
|
366
358
|
}
|
|
367
|
-
],
|
|
368
|
-
"detail": {
|
|
369
|
-
"linkField": "subject_url",
|
|
370
|
-
"fields": [
|
|
371
|
-
{
|
|
372
|
-
"field": "question",
|
|
373
|
-
"label": "Trial merge"
|
|
374
|
-
}
|
|
375
|
-
]
|
|
376
359
|
},
|
|
377
360
|
"refreshMs": 5000
|
|
378
361
|
}
|
|
@@ -532,11 +515,13 @@
|
|
|
532
515
|
"inputLabel": "Disposition note (what you did / why)",
|
|
533
516
|
"submitLabel": "Acknowledge & close",
|
|
534
517
|
"action": {
|
|
535
|
-
"path": "/app/api/actions/
|
|
518
|
+
"path": "/app/api/actions/complete-user-task",
|
|
536
519
|
"successLabel": "Acknowledged — the run will close",
|
|
537
520
|
"body": {
|
|
538
521
|
"userTaskKey": "{{row.user_task_key}}",
|
|
539
|
-
"
|
|
522
|
+
"variables": {
|
|
523
|
+
"note": "{{form.note}}"
|
|
524
|
+
}
|
|
540
525
|
}
|
|
541
526
|
}
|
|
542
527
|
}
|