@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 +7 -0
- package/nano.app.json +10 -0
- package/openapi.yaml +4 -0
- package/operations/answerFeatureEscalation.ts +3 -12
- package/operations/appendBlackboard.ts +3 -6
- package/operations/checkAbandon.ts +3 -5
- package/operations/getVersion.ts +4 -6
- package/operations/listActivePrs.ts +4 -8
- package/operations/postMessage.ts +3 -11
- package/operations/readBlackboard.ts +3 -6
- package/operations/startConvergenceLoop.ts +3 -12
- package/operations/startPlanFanout.ts +3 -14
- package/package.json +3 -2
- package/tsconfig.json +1 -0
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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);
|
package/operations/getVersion.ts
CHANGED
|
@@ -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
|
-
|
|
10
|
-
import { buildVersionInfo, envVar
|
|
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
|
-
|
|
10
|
-
import {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
15
|
-
|
|
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
|
-
|
|
19
|
-
|
|
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.
|
|
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.
|
|
48
|
+
"@nanobpm/urban": "^0.38.0"
|
|
48
49
|
},
|
|
49
50
|
"devDependencies": {
|
|
50
51
|
"@biomejs/biome": "^2.4.11",
|