@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
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
// POST /app/api/actions/answer-escalation → operationId `answerFeatureEscalation` (issue #210).
|
|
2
|
-
// The nwf UI's answer affordance for a native feature-run escalation: an operator answers the parked
|
|
3
|
-
// `feature-escalation` user task (resolution=answer/abandon + optional guidance) directly from the
|
|
4
|
-
// Feature / Overview pages, instead of only from the generic task inbox.
|
|
5
|
-
//
|
|
6
|
-
// It routes through the ONE canonical attributed completer (`completeEscalationAsHuman` →
|
|
7
|
-
// `completeUserTaskAttributed`), so the completion uses the exact same typed `.form` variables and
|
|
8
|
-
// engine resume path a human drives from the task inbox — no parallel completion — while recording
|
|
9
|
-
// WHO answered in the `task_completions` ledger. The poller then reconciles the run off `escalated`
|
|
10
|
-
// (pollFeatureEscalations) once the task is gone.
|
|
11
|
-
//
|
|
12
|
-
// The runtime validates the body against openapi.yaml (`userTaskKey` + `resolution` required); this
|
|
13
|
-
// delegate narrows the validated shape, enforces the answer/abandon contract, and builds the typed
|
|
14
|
-
// completion variables the `feature-escalation` form + the `w_gw_answer` gateway expect.
|
|
15
|
-
|
|
16
|
-
import { completeEscalationAsHuman } from "../app/agentCompletion.ts";
|
|
17
|
-
import { featureRuns } from "../app/feature.ts";
|
|
18
|
-
import { defineOperation } from "../nano-generated/operations.ts";
|
|
19
|
-
|
|
20
|
-
const str = (v: unknown): string => (typeof v === "string" ? v.trim() : "");
|
|
21
|
-
|
|
22
|
-
export default defineOperation("answerFeatureEscalation", async ({ body }, app) => {
|
|
23
|
-
if (!body || typeof body !== "object") {
|
|
24
|
-
app.log.warn("answer-escalation rejected: missing request body");
|
|
25
|
-
return { status: 400, body: { ok: false, error: "userTaskKey and resolution are required" } };
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const userTaskKey = str(body.userTaskKey);
|
|
29
|
-
if (!userTaskKey) return { status: 400, body: { ok: false, error: "userTaskKey is required" } };
|
|
30
|
-
|
|
31
|
-
const resolution = str(body.resolution);
|
|
32
|
-
if (resolution !== "answer" && resolution !== "abandon") {
|
|
33
|
-
return { status: 400, body: { ok: false, error: "resolution must be 'answer' or 'abandon'" } };
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// The `feature-escalation` form completes with `{ resolution, answer }`; the `w_gw_answer` gateway
|
|
37
|
-
// re-dispatches implement-task with `answer` when resolution=answer, else routes to record-feature.
|
|
38
|
-
// Guidance is required for an answer (an empty answer re-dispatches the agent with nothing new).
|
|
39
|
-
const answer = str(body.answer);
|
|
40
|
-
if (resolution === "answer" && !answer) {
|
|
41
|
-
return { status: 400, body: { ok: false, error: "answer (guidance) is required when resolution is 'answer'" } };
|
|
42
|
-
}
|
|
43
|
-
const variables: Record<string, unknown> = resolution === "answer" ? { resolution, answer } : { resolution };
|
|
44
|
-
|
|
45
|
-
// The completing operator, for the attribution ledger. Optional — the UI has no per-operator auth,
|
|
46
|
-
// so default to a generic handle rather than blocking the answer.
|
|
47
|
-
const operatorId = str(body.operator) || "operator";
|
|
48
|
-
|
|
49
|
-
const r = await completeEscalationAsHuman(app.data, app.engine, { userTaskKey, operatorId, variables });
|
|
50
|
-
if (r.ok) {
|
|
51
|
-
// Reconcile this operation's OWN action immediately: clear the denormalised escalation pointer +
|
|
52
|
-
// question so the pages stop offering an answer affordance for a task that is now completed. Leave
|
|
53
|
-
// `status` to the poller (escalated → running on the answer loop) / record-feature (terminal on
|
|
54
|
-
// abandon), so we never overwrite a status the resumed run has already advanced to.
|
|
55
|
-
for (const run of await featureRuns(app.data).find({ escalation_user_task_key: userTaskKey })) {
|
|
56
|
-
await featureRuns(app.data).update(run.feature_key, {
|
|
57
|
-
escalation_question: null,
|
|
58
|
-
escalation_user_task_key: null,
|
|
59
|
-
updated_at: new Date().toISOString(),
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
app.log.info("operator answered feature escalation", { userTaskKey, resolution, elementId: r.elementId });
|
|
63
|
-
return { status: 200, body: { ok: true, completionId: r.completionId, elementId: r.elementId } };
|
|
64
|
-
}
|
|
65
|
-
const status = r.reason === "no open escalation task" ? 404 : 400;
|
|
66
|
-
app.log.warn("answer-escalation: not completed", { userTaskKey, reason: r.reason });
|
|
67
|
-
return { status, body: { ok: false, error: r.reason } };
|
|
68
|
-
});
|