@nanobpm/nano-workforce 0.179.1 → 0.179.2

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 CHANGED
@@ -1,3 +1,9 @@
1
+ ## [0.179.2](https://github.com/nanobpm/nano-workforce/compare/v0.179.1...v0.179.2) (2026-09-04)
2
+
3
+ ### Code Refactoring
4
+
5
+ * drop redundant testkit engine-client shim in e2e ([#734](https://github.com/nanobpm/nano-workforce/issues/734)) ([8ea7ccd](https://github.com/nanobpm/nano-workforce/commit/8ea7ccd5c38a2b0e363cac74b126dd86bee0f0b1)), closes [#336](https://github.com/nanobpm/nano-workforce/issues/336)
6
+
1
7
  ## [0.179.1](https://github.com/nanobpm/nano-workforce/compare/v0.179.0...v0.179.1) (2026-09-04)
2
8
 
3
9
  ### Bug Fixes
@@ -26,7 +26,6 @@ import { fileURLToPath } from "node:url";
26
26
  import type { EngineJob } from "@nanobpm/urban/runtime";
27
27
  import { bootTestApp, type TestApp } from "@nanobpm/urban-testkit";
28
28
  import { admitGithubState, installAdmitGithub } from "./support/github-admit.ts";
29
- import { asEngineClient } from "./support/engine-client.ts";
30
29
  import {
31
30
  completeEscalationAsAgent,
32
31
  latestCompletion,
@@ -142,7 +141,7 @@ describe("agent-answerable escalations (U6 — same form, agent completer, attri
142
141
 
143
142
  // Complete AS AN AGENT through the host-side completer — the same typed `{resolution, answer}`
144
143
  // a human submits through the inbox, only the caller differs.
145
- const r = await completeEscalationAsAgent(app.db, asEngineClient(app.engine), {
144
+ const r = await completeEscalationAsAgent(app.db, app.engine, {
146
145
  userTaskKey: task.userTaskKey,
147
146
  agentId: "senior:answer-bot",
148
147
  variables: { resolution: "answer", answer: "use v2" },
@@ -29,7 +29,6 @@ import { after, before, describe, test } from "node:test";
29
29
  import { fileURLToPath } from "node:url";
30
30
  import { bootTestApp, type TestApp } from "@nanobpm/urban-testkit";
31
31
  import { pollUserTasks } from "../app/service.ts";
32
- import { asEngineClient } from "./support/engine-client.ts";
33
32
 
34
33
  const APP_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
35
34
  const DB_DIR = mkdtempSync(join(tmpdir(), "nwf-u4-"));
@@ -159,7 +158,7 @@ describe("nano-workforce PR review-loop escalation (U4 userTask)", () => {
159
158
  // denormalised `open_escalation_*` pointer is written or read. That read model is projected by
160
159
  // `pollUserTasks` (part of the imperative main.ts poll loop the testkit does not run), so drive it
161
160
  // explicitly (mirrors feature-run.e2e.ts) to project the parked `wait-answer` task first.
162
- await pollUserTasks(app.db, asEngineClient(app.engine));
161
+ await pollUserTasks(app.db, app.engine);
163
162
  const status = await app.callRoute<StatusBody>({ method: "GET", path: "/app/api/status" });
164
163
  assert.equal(status.status, 200, "the status endpoint responds");
165
164
  const statusRow = status.body.prs.find((p) => p.prKey === prKey);
@@ -198,7 +197,7 @@ describe("nano-workforce PR review-loop escalation (U4 userTask)", () => {
198
197
  // reconciles away, so no open escalation lingers on the status endpoint once answered + resumed.
199
198
  // Re-run the projection (as the durable poller would) so the closed task's read-model row is
200
199
  // deleted before re-reading /status.
201
- await pollUserTasks(app.db, asEngineClient(app.engine));
200
+ await pollUserTasks(app.db, app.engine);
202
201
  const afterStatus = await app.callRoute<StatusBody>({ method: "GET", path: "/app/api/status" });
203
202
  const afterRow = afterStatus.body.prs.find((p) => p.prKey === prKey);
204
203
  if (afterRow) {
@@ -23,7 +23,6 @@ import { fileURLToPath } from "node:url";
23
23
  import type { EngineJob } from "@nanobpm/urban/runtime";
24
24
  import { bootTestApp, type TestApp } from "@nanobpm/urban-testkit";
25
25
  import { admitGithubState, installAdmitGithub } from "./support/github-admit.ts";
26
- import { asEngineClient } from "./support/engine-client.ts";
27
26
  import { pollUserTasks } from "../app/service.ts";
28
27
 
29
28
  const APP_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
@@ -208,7 +207,7 @@ describe("single-issue feature run (#172 — feature.bpmn)", () => {
208
207
  // read-model (issue #332 dropped the denormalised `feature_runs.blocked_user_task_key` pointer),
209
208
  // so `pollUserTasks` projects the parked `feature-blocked` task onto `user_tasks` by reading the
210
209
  // engine directly — no per-run column write.
211
- await pollUserTasks(app.db, asEngineClient(app.engine));
210
+ await pollUserTasks(app.db, app.engine);
212
211
  const inboxRow = await app.db
213
212
  .table<{ user_task_key: string; element_id: string }>("user_tasks", "user_task_key")
214
213
  .findOne({ user_task_key: task!.userTaskKey });
@@ -233,7 +232,7 @@ describe("single-issue feature run (#172 — feature.bpmn)", () => {
233
232
 
234
233
  // A further poll pass is an idempotent no-op — the task is completed, so the read-model row is
235
234
  // reconciled away and a terminal run is not a candidate.
236
- await pollUserTasks(app.db, asEngineClient(app.engine));
235
+ await pollUserTasks(app.db, app.engine);
237
236
  assert.equal((await featureRow(app, featureKey)).status, "blocked");
238
237
  },
239
238
  );
@@ -385,7 +384,7 @@ describe("single-issue feature run (#172 — feature.bpmn)", () => {
385
384
 
386
385
  // The poller projects the parked task onto the Tasks inbox `user_tasks` read-model by reading
387
386
  // the engine directly, sourcing the question from the `feature_escalations` audit log.
388
- await pollUserTasks(app.db, asEngineClient(app.engine));
387
+ await pollUserTasks(app.db, app.engine);
389
388
  const inboxRow = await app.db
390
389
  .table<{ user_task_key: string; element_id: string; question: string | null }>("user_tasks", "user_task_key")
391
390
  .findOne({ user_task_key: task!.userTaskKey });
@@ -414,7 +413,7 @@ describe("single-issue feature run (#172 — feature.bpmn)", () => {
414
413
  assert.equal(settled.status, "opened", "the resumed run opened its PR");
415
414
 
416
415
  // A further poll pass reconciles the completed task's read-model row away.
417
- await pollUserTasks(app.db, asEngineClient(app.engine));
416
+ await pollUserTasks(app.db, app.engine);
418
417
  const gone = await app.db
419
418
  .table<{ user_task_key: string }>("user_tasks", "user_task_key")
420
419
  .findOne({ user_task_key: task!.userTaskKey });
@@ -26,7 +26,6 @@ import { after, before, describe, test } from "node:test";
26
26
  import { fileURLToPath } from "node:url";
27
27
  import { bootTestApp, type TestApp } from "@nanobpm/urban-testkit";
28
28
  import { pollUserTasks } from "../app/service.ts";
29
- import { asEngineClient } from "./support/engine-client.ts";
30
29
 
31
30
  const APP_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
32
31
  const DB_DIR = mkdtempSync(join(tmpdir(), "nwf-u7-"));
@@ -237,7 +236,7 @@ describe("retire escalation subsystem (U7 — destructive contract phase)", () =
237
236
  // written. That read model is projected by `pollUserTasks` (part of the imperative main.ts poll
238
237
  // loop the testkit does not run), so drive it explicitly (mirrors feature-run.e2e.ts) to project
239
238
  // the parked `wait-answer` task before reading /status.
240
- await pollUserTasks(app.db, asEngineClient(app.engine));
239
+ await pollUserTasks(app.db, app.engine);
241
240
  const status = await app.callRoute<StatusBody>({ method: "GET", path: "/app/api/status" });
242
241
  const statusRow = status.body.prs.find((p) => p.prKey === prKey);
243
242
  assert.equal(statusRow?.status, "escalated", "the PR reads as escalated");
@@ -265,7 +264,7 @@ describe("retire escalation subsystem (U7 — destructive contract phase)", () =
265
264
  // No addressed escalation lingers — the completed task's `user_tasks` row reconciles away. Re-run
266
265
  // the projection (as the durable poller would) so the closed task's read-model row is deleted
267
266
  // before re-reading /status.
268
- await pollUserTasks(app.db, asEngineClient(app.engine));
267
+ await pollUserTasks(app.db, app.engine);
269
268
  const afterStatus = await app.callRoute<StatusBody>({ method: "GET", path: "/app/api/status" });
270
269
  const afterRow = afterStatus.body.prs.find((p) => p.prKey === prKey);
271
270
  if (afterRow) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nanobpm/nano-workforce",
3
- "version": "0.179.1",
3
+ "version": "0.179.2",
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",
@@ -1,33 +0,0 @@
1
- import type { EngineClient } from "@nanobpm/urban";
2
- import type { TestApp } from "@nanobpm/urban-testkit";
3
-
4
- // `@nanobpm/urban-testkit`'s `WasmEngineClient` has historically lagged urban's `EngineClient`
5
- // interface: a method lands on the real seam a release or two before the testkit fake grows it. Both
6
- // guards below defensively COMPLETE the contract for any method the *installed* testkit hasn't
7
- // implemented yet — each is an idempotent `if (typeof … !== "function")`, so it no-ops the moment a
8
- // testkit release ships the real method (no version pins to drift). Scoped to the test harness;
9
- // production adapters (`SdkEngineClient`) implement both for real.
10
- //
11
- // - `getForm` (urban 0.49.0, ADR 0062): the original instance of this lag. Now implemented by the
12
- // currently pinned testkit, so this guard is a no-op there; kept as defence against version skew.
13
- // These hermetic flows drive completion directly and never resolve a form schema, so the fallback
14
- // returns `null` — the documented "no matching form" path.
15
- // - `openUserTasks` (issue #294 moved the pollers onto it): the CURRENT gap — the pinned testkit has
16
- // `searchUserTasks` but not the open-task-scoped `openUserTasks`, so a poller call throws
17
- // `not a function` (swallowed by the poller's try/catch) and the read-model denormalisation
18
- // silently no-ops. Polyfill it as `searchUserTasks({ state: "CREATED" })` — byte-for-byte what
19
- // urban's real `SdkEngineClient.openUserTasks` does, so the two cannot drift. Fix: nano-workforce
20
- // #309; categorical (testkit) fix upstream in nanobpm/nano-ide#341.
21
- export function asEngineClient(engine: TestApp["engine"]): EngineClient {
22
- const e = engine as unknown as EngineClient & {
23
- getForm?: EngineClient["getForm"];
24
- openUserTasks?: EngineClient["openUserTasks"];
25
- };
26
- if (typeof e.getForm !== "function") {
27
- e.getForm = async () => null;
28
- }
29
- if (typeof e.openUserTasks !== "function") {
30
- e.openUserTasks = (filter) => e.searchUserTasks({ ...filter, state: "CREATED" });
31
- }
32
- return e;
33
- }