@nanobpm/nano-workforce 0.38.0 → 0.39.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 CHANGED
@@ -1,3 +1,10 @@
1
+ # [0.39.0](https://github.com/nanobpm/nano-workforce/compare/v0.38.0...v0.39.0) (2026-08-10)
2
+
3
+
4
+ ### Features
5
+
6
+ * adopt urban 0.38.0 typed delegate registry for the OpenAPI surface ([#110](https://github.com/nanobpm/nano-workforce/issues/110)) ([fe62908](https://github.com/nanobpm/nano-workforce/commit/fe62908968c0bd707f8e2da58fa550cca9fbab5b))
7
+
1
8
  # [0.38.0](https://github.com/nanobpm/nano-workforce/compare/v0.37.0...v0.38.0) (2026-08-10)
2
9
 
3
10
 
package/nano.app.json CHANGED
@@ -124,6 +124,16 @@
124
124
  "handler": "workers/retro-record/worker.ts"
125
125
  }
126
126
  ],
127
+ "externalTaskTypes": [
128
+ "senior:pr-review",
129
+ "senior:fix-ci",
130
+ "senior:rebase",
131
+ "senior:plan",
132
+ "senior:plan-review",
133
+ "senior:feature",
134
+ "senior:trial-merge",
135
+ "senior:retro"
136
+ ],
127
137
  "surfaces": {
128
138
  "pages": {
129
139
  "enabled": true,
package/openapi.yaml CHANGED
@@ -38,6 +38,7 @@ components:
38
38
  ActivePr:
39
39
  type: object
40
40
  description: A tracked PR that is not in a terminal (converged/abandoned) state.
41
+ additionalProperties: false
41
42
  required:
42
43
  - prKey
43
44
  - repo
@@ -107,6 +108,7 @@ components:
107
108
  VersionInfo:
108
109
  type: object
109
110
  description: The running app's identity (which code is actually live).
111
+ additionalProperties: false
110
112
  required:
111
113
  - name
112
114
  - version
@@ -232,6 +234,7 @@ components:
232
234
  description: The operator's answer that resumes the parked implementation agent.
233
235
  BlackboardEntry:
234
236
  type: object
237
+ additionalProperties: false
235
238
  required:
236
239
  - id
237
240
  - author_task
@@ -321,6 +324,7 @@ components:
321
324
  description: Prior sibling file-claims on the same file(s) (advisory first-writer-wins; never a lock).
322
325
  items:
323
326
  type: object
327
+ additionalProperties: false
324
328
  required:
325
329
  - file
326
330
  - author_task
@@ -10,25 +10,16 @@
10
10
  // Body accepts either the raw correlation key or a plan+task pair:
11
11
  // { "corrKey": "owner/repo#12:task-3", "answer": "…" }
12
12
  // { "plan": "owner/repo#12", "task": "task-3", "answer": "…" }
13
- import { defineOperation } from "@nanobpm/urban";
13
+
14
14
  import { answerTaskEscalation, featureCorrKey } from "../app/plan.ts";
15
15
  import { envVar } from "../app/version.ts";
16
+ import { defineOperation } from "../nano-generated/operations.ts";
16
17
 
17
18
  const WEBHOOK_SECRET = envVar("NANO_PR_WEBHOOK_SECRET") ?? "";
18
19
 
19
20
  const str = (v: unknown): string => (typeof v === "string" ? v.trim() : "");
20
21
 
21
- interface Body {
22
- corrKey?: unknown;
23
- plan?: unknown;
24
- task?: unknown;
25
- answer?: unknown;
26
- }
27
-
28
- export default defineOperation<
29
- { params: Record<string, string>; query: Record<string, string | string[] | undefined>; body: Body },
30
- { ok: boolean } & Record<string, unknown>
31
- >("answerFeatureEscalation", async ({ req, body }, app) => {
22
+ export default defineOperation("answerFeatureEscalation", async ({ req, body }, app) => {
32
23
  if (WEBHOOK_SECRET && req.headers.get("x-hook-secret") !== WEBHOOK_SECRET) {
33
24
  return { status: 401, body: { ok: false, error: "unauthorized" } };
34
25
  }
@@ -7,19 +7,16 @@
7
7
  // POST → append one entry: { author_task?, kind?, files?, body, wave?, dedupe_key? }. Idempotent
8
8
  // on (plan, dedupe_key). Returns { id, inserted, conflicts } — `conflicts` lists prior
9
9
  // sibling `file-claim`s on the same file(s) (advisory first-writer-wins; never a lock).
10
- import { defineOperation } from "@nanobpm/urban";
11
- import type { ClaimConflict } from "../app/blackboard.ts";
10
+
12
11
  import {
13
12
  appendEntry,
14
13
  detectFileClaimConflicts,
15
14
  normalizeKind,
16
15
  planKeyForToken,
17
16
  } from "../app/blackboard.ts";
17
+ import { defineOperation } from "../nano-generated/operations.ts";
18
18
 
19
- export default defineOperation<
20
- { params: Record<string, string>; query: { token?: string }; body: Record<string, unknown> },
21
- { id: number; inserted: boolean; conflicts: ClaimConflict[] } | { error: string }
22
- >("appendBlackboard", async ({ req, body }, app) => {
19
+ export default defineOperation("appendBlackboard", async ({ req, body }, app) => {
23
20
  const token = (req.query.get("token") ?? req.headers.get("x-blackboard-token") ?? "").trim();
24
21
  if (!token) return { status: 400, body: { error: "missing blackboard token" } };
25
22
  const planKey = await planKeyForToken(app.data, token);
@@ -9,13 +9,11 @@
9
9
  //
10
10
  // GET → { prKey, status, abandoned } — `abandoned` is derived from `pull_requests.status`,
11
11
  // which Urban's cancel primitive sets to 'abandoned' on cancel. `true` ⇒ the agent must stop.
12
- import { defineOperation } from "@nanobpm/urban";
12
+
13
13
  import { abandonStatusForToken } from "../app/abandon.ts";
14
+ import { defineOperation } from "../nano-generated/operations.ts";
14
15
 
15
- export default defineOperation<
16
- { params: Record<string, string>; query: { token?: string }; body: unknown },
17
- { prKey: string; status: string; abandoned: boolean } | { error: string }
18
- >("checkAbandon", async ({ req }, app) => {
16
+ export default defineOperation("checkAbandon", async ({ req }, app) => {
19
17
  const token = (req.query.get("token") ?? req.headers.get("x-abandon-token") ?? "").trim();
20
18
  if (!token) return { status: 400, body: { error: "missing abandon token" } };
21
19
  const state = await abandonStatusForToken(app.data, token);
@@ -6,17 +6,15 @@
6
6
  // Read-only and unauthenticated by design (no secrets in the payload). The optional shared-secret
7
7
  // guard stays HERE (the runtime does not enforce OpenAPI `security`); the OpenAPI document only
8
8
  // routes GET to this operation, so a wrong method is a 404 from the router (no explicit 405 needed).
9
- import { defineOperation } from "@nanobpm/urban";
10
- import { buildVersionInfo, envVar, type VersionInfo } from "../app/version.ts";
9
+
10
+ import { buildVersionInfo, envVar } from "../app/version.ts";
11
+ import { defineOperation } from "../nano-generated/operations.ts";
11
12
 
12
13
  // The optional shared-secret guard: when NANO_PR_WEBHOOK_SECRET is set, callers must present it via
13
14
  // the x-hook-secret header. Captured once, at module load.
14
15
  const SECRET = envVar("NANO_PR_WEBHOOK_SECRET") ?? "";
15
16
 
16
- export default defineOperation<
17
- { params: Record<string, string>; query: Record<string, string | string[] | undefined>; body: undefined },
18
- VersionInfo | { error: string }
19
- >("getVersion", ({ req }) => {
17
+ export default defineOperation("getVersion", ({ req }) => {
20
18
  if (SECRET && req.headers.get("x-hook-secret") !== SECRET) {
21
19
  return { status: 401, body: { error: "unauthorized" } };
22
20
  }
@@ -6,20 +6,16 @@
6
6
  // The runtime validates the (empty) request against openapi.yaml; the optional shared-secret guard
7
7
  // stays HERE (the runtime does not enforce OpenAPI `security`): when NANO_PR_WEBHOOK_SECRET is set,
8
8
  // callers must present it via the x-hook-secret header. Unset → open (unchanged default).
9
- import { defineOperation } from "@nanobpm/urban";
10
- import { type ActivePr, activePrs } from "../app/service.ts";
9
+
10
+ import { activePrs } from "../app/service.ts";
11
11
  import { envVar } from "../app/version.ts";
12
+ import { defineOperation } from "../nano-generated/operations.ts";
12
13
 
13
14
  // The optional shared-secret guard: when NANO_PR_WEBHOOK_SECRET is set, callers must present it via
14
15
  // the x-hook-secret header. Captured once, at module load.
15
16
  const SECRET = envVar("NANO_PR_WEBHOOK_SECRET") ?? "";
16
17
 
17
- type Res = { count: number; prs: ActivePr[] } | { error: string };
18
-
19
- export default defineOperation<
20
- { params: Record<string, string>; query: Record<string, string | string[] | undefined>; body: undefined },
21
- Res
22
- >("listActivePrs", async ({ req }, app) => {
18
+ export default defineOperation("listActivePrs", async ({ req }, app) => {
23
19
  if (SECRET && req.headers.get("x-hook-secret") !== SECRET) {
24
20
  return { status: 401, body: { error: "unauthorized" } };
25
21
  }
@@ -8,20 +8,12 @@
8
8
  // The runtime validates the body against openapi.yaml (`name` is required, so a missing name is a 400
9
9
  // for free); this delegate keeps the message-name dispatch — the discriminator + downstream behavior
10
10
  // is app logic, not something the JSON schema can express.
11
- import { defineOperation } from "@nanobpm/urban";
11
+
12
12
  import { answerTaskEscalation, FEATURE_ESCALATION_MESSAGE } from "../app/plan.ts";
13
13
  import { answerEscalation } from "../app/service.ts";
14
+ import { defineOperation } from "../nano-generated/operations.ts";
14
15
 
15
- interface Body {
16
- name?: unknown;
17
- correlationKey?: unknown;
18
- variables?: Record<string, unknown>;
19
- }
20
-
21
- export default defineOperation<
22
- { params: Record<string, string>; query: Record<string, string | string[] | undefined>; body: Body },
23
- Record<string, unknown>
24
- >("postMessage", async ({ body }, app) => {
16
+ export default defineOperation("postMessage", async ({ body }, app) => {
25
17
  const b = body ?? {};
26
18
  const name = String(b.name ?? "");
27
19
  if (!name) return { status: 400, body: { error: "name is required" } };
@@ -9,14 +9,11 @@
9
9
  // GET → { planKey, entries: [ { id, author_task, kind, files, body, wave, created_at } ], cursor }
10
10
  // optional ?since=<id> returns only entries with id > since (incremental poll). `cursor` is
11
11
  // the plan's current head id; pass it back as `since` on the next poll (Tier 2).
12
- import { defineOperation } from "@nanobpm/urban";
13
- import type { BlackboardPage } from "../app/blackboard.ts";
12
+
14
13
  import { planKeyForToken, readBlackboardPage } from "../app/blackboard.ts";
14
+ import { defineOperation } from "../nano-generated/operations.ts";
15
15
 
16
- export default defineOperation<
17
- { params: Record<string, string>; query: { token?: string; since?: string }; body: unknown },
18
- (BlackboardPage & { planKey: string }) | { error: string }
19
- >("readBlackboard", async ({ req }, app) => {
16
+ export default defineOperation("readBlackboard", async ({ req }, app) => {
20
17
  const token = (req.query.get("token") ?? req.headers.get("x-blackboard-token") ?? "").trim();
21
18
  if (!token) return { status: 400, body: { error: "missing blackboard token" } };
22
19
  const planKey = await planKeyForToken(app.data, token);
@@ -8,20 +8,11 @@
8
8
  // not leak the engine's variable-map concept to callers. The runtime validates the body against
9
9
  // openapi.yaml; this delegate keeps the PR-parse guard because the reference format (owner/repo#123
10
10
  // or a URL) is app logic, not something the JSON schema can express — an unparseable reference is a 400.
11
- import { defineOperation } from "@nanobpm/urban";
12
- import { clampRounds, MAX_ROUNDS, parsePr, submitPr } from "../app/service.ts";
13
11
 
14
- interface Body {
15
- pr?: string;
16
- url?: string;
17
- dependsOn?: unknown;
18
- maxRounds?: unknown;
19
- }
12
+ import { clampRounds, MAX_ROUNDS, parsePr, submitPr } from "../app/service.ts";
13
+ import { defineOperation } from "../nano-generated/operations.ts";
20
14
 
21
- export default defineOperation<
22
- { params: Record<string, string>; query: Record<string, string | string[] | undefined>; body: Body },
23
- { prKey: string; alreadyRunning?: boolean; processKey?: string | null } | { error: string }
24
- >("startConvergenceLoop", async ({ body }, app) => {
15
+ export default defineOperation("startConvergenceLoop", async ({ body }, app) => {
25
16
  const b = body ?? {};
26
17
  const raw = String(b.pr ?? b.url ?? "").trim();
27
18
  const parsed = parsePr(raw);
@@ -7,22 +7,11 @@
7
7
  //
8
8
  // The request body is FLAT (`{ issue | url }`), not wrapped in a `variables` envelope — this is a
9
9
  // purpose-built operation, not a generic engine "start process" call.
10
- import { defineOperation } from "@nanobpm/urban";
11
- import { parseIssue, startPlan } from "../app/plan.ts";
12
-
13
- interface Body {
14
- issue?: string;
15
- url?: string;
16
- }
17
10
 
18
- type Res =
19
- | { planKey: string; alreadyRunning?: boolean; processKey?: string | null }
20
- | { error: string };
11
+ import { parseIssue, startPlan } from "../app/plan.ts";
12
+ import { defineOperation } from "../nano-generated/operations.ts";
21
13
 
22
- export default defineOperation<
23
- { params: Record<string, string>; query: Record<string, string | string[] | undefined>; body: Body },
24
- Res
25
- >("startPlanFanout", async ({ body }, app) => {
14
+ export default defineOperation("startPlanFanout", async ({ body }, app) => {
26
15
  const b = body ?? {};
27
16
  const raw = String(b.issue ?? b.url ?? "").trim();
28
17
  const parsed = parseIssue(raw);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nanobpm/nano-workforce",
3
- "version": "0.38.0",
3
+ "version": "0.39.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",
@@ -33,6 +33,7 @@
33
33
  "upgrade": "node --experimental-strip-types scripts/upgrade-from-pack.ts",
34
34
  "check": "urban check",
35
35
  "typecheck": "tsc --noEmit",
36
+ "pretypecheck": "urban gen",
36
37
  "check:prompts": "node --experimental-strip-types scripts/check-agent-prompts.ts",
37
38
  "gen": "urban gen",
38
39
  "gen:check": "urban gen --check",
@@ -44,7 +45,7 @@
44
45
  "lint:fix": "biome check --write app operations workers pages components scripts main.ts"
45
46
  },
46
47
  "dependencies": {
47
- "@nanobpm/urban": "^0.35.0"
48
+ "@nanobpm/urban": "^0.38.0"
48
49
  },
49
50
  "devDependencies": {
50
51
  "@biomejs/biome": "^2.4.11",
package/tsconfig.json CHANGED
@@ -39,6 +39,7 @@
39
39
  "main.ts",
40
40
  "app/**/*.ts",
41
41
  "operations/**/*.ts",
42
+ "nano-generated/**/*.ts",
42
43
  "workers/**/*.ts",
43
44
  "lib/**/*.ts",
44
45
  "src/**/*.ts",