@nanobpm/nano-workforce 0.209.1 → 0.210.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 +11 -1
- package/app/convergeAdversarial.test.ts +152 -0
- package/app/feature.test.ts +31 -0
- package/app/feature.ts +12 -0
- package/app/mergeLoopBehaviour.test.ts +48 -0
- package/app/pullRequestReadModel.test.ts +1 -1
- package/app/service.test.ts +125 -1
- package/app/service.ts +63 -1
- package/build-info.json +4 -4
- package/db/migrations/134_pull_requests_custom_instructions.sql +8 -0
- package/db/migrations/135_pull_requests_read_model_custom_instructions.sql +52 -0
- package/db/migrations/136_pull_requests_custom_instructions_text.sql +11 -0
- package/db/migrations/137_feature_runs_custom_instructions.sql +66 -0
- package/db/migrations/138_pull_requests_read_model_custom_instructions_text.sql +53 -0
- package/docs/agent-guide.md +1 -0
- package/openapi.yaml +24 -0
- package/operations/listActivePrs.test.ts +17 -0
- package/operations/startAndMessage.test.ts +80 -0
- package/operations/startConvergenceLoop.ts +13 -1
- package/package.json +1 -1
- package/pages/home.page.json +7 -2
- package/resources/processes/convergence-loop.bpmn +8 -2
- package/resources/processes/feature.bpmn +1 -0
- package/resources/processes/merge-loop.bpmn +6 -2
- package/workers/converge-feature/worker.ts +10 -1
- package/workers/reconverge/worker.test.ts +50 -0
- package/workers/reconverge/worker.ts +7 -0
|
@@ -108,3 +108,53 @@ test("a head read that resolves no SHA degrades to the checkpoint restore (no ov
|
|
|
108
108
|
await makeHandler(submit, head)({ processInstanceKey: "4242", variables: { prKey: "acme/w#7" } } as any, app);
|
|
109
109
|
assertEquals(args[11], null, "no live head SHA → no override (checkpoint restore, the pre-fix behaviour)");
|
|
110
110
|
});
|
|
111
|
+
|
|
112
|
+
// Issue #956 review (Copilot thread 4547353262): a merge-stage reconverge re-enters `submitPr`
|
|
113
|
+
// WITHOUT the run's operator custom instructions, so the replacement convergence loop runs
|
|
114
|
+
// unsteered (and `submitPr`'s run-scoped reopen resets `custom_instructions` to 0). The worker must
|
|
115
|
+
// read the durable text off the PR row (`custom_instructions_text`, stamped by `submitPr`) and pass
|
|
116
|
+
// it through, so convergence → merge → reconvergence keeps the steering.
|
|
117
|
+
test("preserves the run's custom instructions across the merge→reconvergence handoff (#956)", async () => {
|
|
118
|
+
const app = {
|
|
119
|
+
data: {
|
|
120
|
+
table: (name: string) => ({
|
|
121
|
+
get: async () =>
|
|
122
|
+
name === "pull_requests"
|
|
123
|
+
? { auto_resolve_escalations: 1, custom_instructions_text: " commit as josh.wulf@camunda.com " }
|
|
124
|
+
: undefined,
|
|
125
|
+
insert: async () => 1,
|
|
126
|
+
}),
|
|
127
|
+
},
|
|
128
|
+
engine: {},
|
|
129
|
+
log: noopLog(),
|
|
130
|
+
} as any;
|
|
131
|
+
let args: unknown[] = [];
|
|
132
|
+
const submit = (async (...a: unknown[]) => {
|
|
133
|
+
args = a;
|
|
134
|
+
return { prKey: "acme/w#7", processKey: "999" };
|
|
135
|
+
}) as unknown as Submit;
|
|
136
|
+
const head = (async () => null) as unknown as FetchPrHead;
|
|
137
|
+
await makeHandler(submit, head)({ processInstanceKey: "4242", variables: { prKey: "acme/w#7" } } as any, app);
|
|
138
|
+
assertEquals(args[12], " commit as josh.wulf@camunda.com ", "the durable instruction text rides the handoff into the fresh convergence run");
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test("a PR row with no stored instructions hands off with null (no steering)", async () => {
|
|
142
|
+
const app = {
|
|
143
|
+
data: {
|
|
144
|
+
table: (name: string) => ({
|
|
145
|
+
get: async () => (name === "pull_requests" ? { auto_resolve_escalations: 0, custom_instructions_text: null } : undefined),
|
|
146
|
+
insert: async () => 1,
|
|
147
|
+
}),
|
|
148
|
+
},
|
|
149
|
+
engine: {},
|
|
150
|
+
log: noopLog(),
|
|
151
|
+
} as any;
|
|
152
|
+
let args: unknown[] = [];
|
|
153
|
+
const submit = (async (...a: unknown[]) => {
|
|
154
|
+
args = a;
|
|
155
|
+
return { prKey: "acme/w#7", processKey: "999" };
|
|
156
|
+
}) as unknown as Submit;
|
|
157
|
+
const head = (async () => null) as unknown as FetchPrHead;
|
|
158
|
+
await makeHandler(submit, head)({ processInstanceKey: "4242", variables: { prKey: "acme/w#7" } } as any, app);
|
|
159
|
+
assertEquals(args[12], null, "no stored text → null (the fresh run's prompts stay byte-identical to no-instructions)");
|
|
160
|
+
});
|
|
@@ -27,6 +27,12 @@ export function makeHandler(submit: Submit, fetchHead: FetchPrHead = fetchPrHead
|
|
|
27
27
|
const row = await app.data.table("pull_requests", "pr_key").get(prKey);
|
|
28
28
|
// biome-ignore lint/plugin: runtime/framework contract boundary for the untyped row shape
|
|
29
29
|
const optIn = (row as { auto_resolve_escalations?: number } | undefined)?.auto_resolve_escalations === 1;
|
|
30
|
+
// Operator steering (issue #956 review): re-supply the run's durable instruction text so the
|
|
31
|
+
// merge→reconvergence handoff never starts an unsteered replacement loop (and never resets the
|
|
32
|
+
// `custom_instructions` projection). Read the SAME `pull_requests` row the opt-in came from.
|
|
33
|
+
// biome-ignore lint/plugin: runtime/framework contract boundary for the untyped row shape
|
|
34
|
+
const rowInstructions = (row as { custom_instructions_text?: string | null } | undefined)?.custom_instructions_text;
|
|
35
|
+
const customInstructions = typeof rowInstructions === "string" && rowInstructions.trim() !== "" ? rowInstructions : null;
|
|
30
36
|
await app.data.table("merges", "id").insert({
|
|
31
37
|
pr_key: prKey,
|
|
32
38
|
outcome: "reconverge",
|
|
@@ -63,6 +69,7 @@ export function makeHandler(submit: Submit, fetchHead: FetchPrHead = fetchPrHead
|
|
|
63
69
|
true,
|
|
64
70
|
job.processInstanceKey != null ? String(job.processInstanceKey) : null,
|
|
65
71
|
headSha,
|
|
72
|
+
customInstructions,
|
|
66
73
|
);
|
|
67
74
|
// `submitPr` can return `processKey: null` after rewriting the row to `converging` with no owner
|
|
68
75
|
// (the create-instance failure window). Completing HERE would end the merge loop at
|