@nanobpm/nano-workforce 0.200.7 → 0.200.9
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 +12 -0
- package/SPEC.md +41 -0
- package/app/agentSla.test.ts +48 -0
- package/app/agentSla.ts +36 -11
- package/app/agentic/vocab/agent-sla-boundary.test.ts +170 -0
- package/app/agentic/vocab/job-types.ts +60 -0
- package/app/conformance.test.ts +114 -0
- package/app/conformance.ts +72 -0
- package/app/contracts.ts +2 -2
- package/app/deliveryRunner.ts +5 -0
- package/app/feature.ts +5 -0
- package/app/implementEscalationReason.test.ts +216 -0
- package/app/implementEscalationReason.ts +213 -0
- package/app/implementReconcile.test.ts +168 -11
- package/app/implementReconcile.ts +112 -18
- package/app/plan.ts +12 -0
- package/app/retro.test.ts +5 -0
- package/app/retro.ts +4 -0
- package/app/service.ts +27 -8
- package/e2e/agent-sla-boundary.e2e.ts +598 -0
- package/e2e/feature-run.e2e.ts +8 -1
- package/e2e/plan-fanout.e2e.ts +75 -2
- package/e2e/support/pin-agent-sla.ts +47 -0
- package/package.json +1 -1
- package/resources/processes/feature.bpmn +1 -0
- package/resources/processes/implement-cell.bpmn +122 -47
- package/resources/processes/merge-cell.bpmn +60 -12
- package/resources/processes/plan-fanout.bpmn +237 -211
- package/resources/processes/retro.bpmn +100 -3
- package/resources/prompts/feature.md +16 -1
- package/test/derivation-parity/derivation-parity.test.ts +35 -11
- package/test/derivation-parity/flows.ts +14 -1
- package/workers/conformance-record/worker.test.ts +87 -1
- package/workers/conformance-record/worker.ts +34 -7
- package/workers/reconcile-implement/worker.test.ts +31 -3
- package/workers/reconcile-implement/worker.ts +5 -0
- package/workers/record-feature-escalation/worker.test.ts +58 -3
- package/workers/record-feature-escalation/worker.ts +24 -10
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
import type { AppJobHandler } from "@nanobpm/urban";
|
|
26
26
|
import { classifyEscalation } from "../../app/escalationTaxonomy.ts";
|
|
27
27
|
import { featureRuns, recordFeatureEscalation } from "../../app/feature.ts";
|
|
28
|
+
import { implementEscalationQuestion, NO_RESULT_QUESTION } from "../../app/implementEscalationReason.ts";
|
|
28
29
|
import type { WorkerInputs } from "../../nano-generated/worker-io.d.ts";
|
|
29
30
|
|
|
30
31
|
// Input typed off the model data envelope (`RecordEscalationIn` in implement-cell.bpmn) — ADR 0040.
|
|
@@ -36,12 +37,6 @@ interface Out extends Record<string, unknown> {
|
|
|
36
37
|
const str = (v: unknown): string | undefined =>
|
|
37
38
|
typeof v === "string" && v.trim().length > 0 ? v.trim() : undefined;
|
|
38
39
|
|
|
39
|
-
// The answerable prompt synthesised when the agent left no usable question — the implement-stage
|
|
40
|
-
// analogue of record-trial-merge's synthesised trial-merge question. It names the recoverable work (a
|
|
41
|
-
// PR may exist on the slice's branch) and the two answers the cell's `ic_gw_answer` gateway routes on.
|
|
42
|
-
const NO_RESULT_QUESTION =
|
|
43
|
-
'The implementation agent finished without a machine-readable result (no status was reported), so we cannot tell whether the slice succeeded. It may still have opened a PR (check for a branch targeting the epic base). Choose "Answer" and give guidance to re-run the slice — or choose "Abandon" to skip it and continue.';
|
|
44
|
-
|
|
45
40
|
const handler: AppJobHandler<In, Out> = async (job, app) => {
|
|
46
41
|
const subjectKey = job.variables.subjectKey;
|
|
47
42
|
// Fail fast rather than keying the `feature_escalations` audit log (and the guarded `feature_runs`
|
|
@@ -61,11 +56,30 @@ const handler: AppJobHandler<In, Out> = async (job, app) => {
|
|
|
61
56
|
// so the parked task is never a dead end. Route through the single canonical taxonomy so this net can
|
|
62
57
|
// never drift from the tier logic every other raise site uses. A "task"-kind escalation is
|
|
63
58
|
// `decision-required` only when the agent left an answerable question, so this already covers the
|
|
64
|
-
// blank-question case
|
|
65
|
-
// through without an assertion.
|
|
59
|
+
// blank-question case.
|
|
66
60
|
const agentEscalated = classifyEscalation({ kind: "task", status: job.variables.status, question: rawQuestion }) ===
|
|
67
61
|
"decision-required";
|
|
68
|
-
|
|
62
|
+
// Build the escalation question through the single canonical builder (issue #865), ALWAYS — including
|
|
63
|
+
// when the agent raised its own answerable question. Passing the agent's `question` lets the builder
|
|
64
|
+
// lead with it AND fold in the agent's `summary` + transcript as supporting context (#865 review —
|
|
65
|
+
// otherwise an `escalated` result's summary/transcript were dropped and only the bare question
|
|
66
|
+
// survived). With no answerable question it synthesises an accurate reason — distinguishing a true
|
|
67
|
+
// no-result (blank status) from a claimed-completion-without-delivery (an affirmative-completion alias
|
|
68
|
+
// such as `completed`) and from a reported non-completion status — never the false "no status was
|
|
69
|
+
// reported" diagnosis for a reported `escalated`/`failed` (#865 review). The reason can never drift
|
|
70
|
+
// from the reconcile step because both read the same canonical builder.
|
|
71
|
+
const question = implementEscalationQuestion({
|
|
72
|
+
status: job.variables.status,
|
|
73
|
+
question: rawQuestion,
|
|
74
|
+
summary: job.variables.summary,
|
|
75
|
+
transcriptUrl: job.variables.transcriptUrl,
|
|
76
|
+
// Delivery EVIDENCE (#865 review): the preserved PR (an `escalated`/`failed` with a blank question
|
|
77
|
+
// retains it — never claim "opened no pull request") and whether the reconcile step's GitHub lookup
|
|
78
|
+
// actually CONFIRMED the delivery state (only then may the reason say "none was found"). Both are
|
|
79
|
+
// process variables the upstream `reconcile-implement` step emits into scope.
|
|
80
|
+
pr: job.variables.pr,
|
|
81
|
+
deliveryVerified: job.variables.deliveryVerified,
|
|
82
|
+
});
|
|
69
83
|
|
|
70
84
|
// Append to the canonical `feature_escalations` audit log (the surviving table `pollUserTasks` reads),
|
|
71
85
|
// keyed by `subjectKey` — the feature run's `feature_key`, or the epic's `plan_key` for a wave slice.
|
|
@@ -79,7 +93,7 @@ const handler: AppJobHandler<In, Out> = async (job, app) => {
|
|
|
79
93
|
await runs.update(subjectKey, { status: "escalated", updated_at: new Date().toISOString() });
|
|
80
94
|
}
|
|
81
95
|
|
|
82
|
-
app.log.info("record-escalation", { subjectKey, synthesised:
|
|
96
|
+
app.log.info("record-escalation", { subjectKey, synthesised: !(agentEscalated && rawQuestion) });
|
|
83
97
|
|
|
84
98
|
// Re-emit the resolved question so the `feature-escalation` form (and the answer loop) see it.
|
|
85
99
|
return { question };
|