@nanobpm/nano-workforce 0.179.2 → 0.181.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/openapi.yaml CHANGED
@@ -1248,7 +1248,20 @@ components:
1248
1248
  - markdown
1249
1249
  instructions:
1250
1250
  type: string
1251
- description: The section's markdown, with example commands keyed to this instance.
1251
+ description: The section's markdown (or a bounded page of it when paginating), with example commands keyed to this instance.
1252
+ start:
1253
+ type: integer
1254
+ description: The CHARACTER offset this page starts at (present only when paginating, issue #740).
1255
+ length:
1256
+ type: integer
1257
+ description: The number of characters returned in this page (present only when paginating).
1258
+ totalLength:
1259
+ type: integer
1260
+ description: The total number of characters in the fully-rendered section (present only when paginating).
1261
+ nextStart:
1262
+ type: integer
1263
+ nullable: true
1264
+ description: The `start` to pass for the next page, or null when this is the last page (present only when paginating).
1252
1265
  SubmitResult:
1253
1266
  type: object
1254
1267
  required:
@@ -2832,6 +2845,95 @@ components:
2832
2845
  description: >-
2833
2846
  A NAVIGATIONAL cockpit deep-link to the staged proposal (helps the agent hand the human a
2834
2847
  link). It is a pointer only — NOT a dispatch handle; nothing in this response can start a run.
2848
+ superseded:
2849
+ type: array
2850
+ items:
2851
+ type: string
2852
+ description: >-
2853
+ Digests of prior staged proposals sharing this graph's LOGICAL key (derived from its `name`)
2854
+ that this stage replaced — flipped to `superseded` (issue #740). Empty on a first stage. Lets
2855
+ the agent tell the operator precisely which proposals it retired.
2856
+ siblingsStaged:
2857
+ type: integer
2858
+ description: >-
2859
+ How many OTHER live staged proposals remain after this stage (a DIFFERENT logical key from
2860
+ this one) — i.e. proposals this stage did NOT supersede (issue #740). A non-zero count flags
2861
+ potential ORPHANED SIBLINGS (e.g. an earlier stage of the "same" runbook under a different
2862
+ `name`, which supersede does not catch) cluttering the operator's Delivery Graphs list, so
2863
+ the agent can name them for cleanup.
2864
+ SequenceGate:
2865
+ description: >-
2866
+ An interleaved `wait` GATE (issue #740) that must go green before a given issue's agent starts
2867
+ — reusing the exact probe schema `wait` nodes accept (`npm`/`github-check`/`http`/`command`/
2868
+ `capability`/`pr`/`epic`). It lets an author splice e.g. "wait for `@nanobpm/agentic@0.13.0` to
2869
+ publish" between two sequence steps without hand-authoring raw node/edge JSON.
2870
+ type: object
2871
+ additionalProperties: false
2872
+ required:
2873
+ - kind
2874
+ - target
2875
+ properties:
2876
+ kind:
2877
+ type: string
2878
+ minLength: 1
2879
+ description: >-
2880
+ The wait-probe kind — one of the S3 vocabulary kinds (`npm`, `github-check`, `http`,
2881
+ `command`, `capability`, `pr`, `epic`). An unknown kind is a 400.
2882
+ target:
2883
+ type: string
2884
+ minLength: 1
2885
+ description: >-
2886
+ The probe target — kind-specific: `pkg@version` for `npm`, `owner/repo@ref` for
2887
+ `github-check`, a URL for `http`, an `owner/repo#N` PR for `pr`, etc.
2888
+ match:
2889
+ type: object
2890
+ description: >-
2891
+ OPTIONAL kind-specific readiness match fields (e.g. `{ version }` for `npm`,
2892
+ `{ conclusion, checkName }` for `github-check`).
2893
+ poll:
2894
+ type: object
2895
+ additionalProperties: false
2896
+ description: >-
2897
+ OPTIONAL poll budget. Defaults to the bounded merge-gate budget (re-probe every 5 minutes,
2898
+ budget 3 days) so a gate never falls into the 30-minute default trap.
2899
+ properties:
2900
+ everyMs:
2901
+ type: integer
2902
+ minimum: 1
2903
+ description: How often to re-probe (milliseconds).
2904
+ timeoutMs:
2905
+ type: integer
2906
+ minimum: 1
2907
+ description: The total budget before the gate escalates/continues (milliseconds).
2908
+ onTimeout:
2909
+ type: string
2910
+ enum:
2911
+ - escalate
2912
+ - continue
2913
+ description: >-
2914
+ What to do when the gate never goes green within its budget — `escalate` (default) parks on
2915
+ a human; `continue` proceeds anyway. `fail` is not supported on a `wait` node.
2916
+ credentialEnv:
2917
+ type: string
2918
+ minLength: 1
2919
+ description: OPTIONAL env-key name supplying a credential for the probe (`http`/`capability`).
2920
+ SequenceIssueEntry:
2921
+ description: >-
2922
+ One `issues[]` entry with an OPTIONAL leading gate (issue #740) — an object form of a sequence
2923
+ step. The `issue` is implemented + converged + merged like a bare-string entry; the optional
2924
+ `gate` must go green (in addition to the prior issue merging) before this issue's agent starts.
2925
+ type: object
2926
+ additionalProperties: false
2927
+ required:
2928
+ - issue
2929
+ properties:
2930
+ gate:
2931
+ $ref: "#/components/schemas/SequenceGate"
2932
+ issue:
2933
+ type: string
2934
+ minLength: 1
2935
+ maxLength: 255
2936
+ description: An `owner/repo#N` issue reference to implement + converge + merge, in sequence.
2835
2937
  SequenceIssuesIntent:
2836
2938
  description: >-
2837
2939
  The `sequenceIssues` INTENT (epic nano-workforce#605, S4) — a high-level shape that GENERATES
@@ -2843,15 +2945,21 @@ components:
2843
2945
  `compileDeliveryGraph` — it never dispatches (dispatch is an operator-only cockpit action, ADR
2844
2946
  0005 Decision 7). For each issue it emits `agent` (`senior:feature`, emits a `pr` fact) →
2845
2947
  `connector` (`converge-merge`, late-binding that `pr`) → `wait[pr, merged]` (a realistic
2846
- `poll.timeoutMs`), threading the `pr` fact per §9.4. Invalid input (empty `issues`, an
2847
- unparseable ref) is a 400 carrying `issues: [{ path, message }]`; nothing is staged.
2948
+ `poll.timeoutMs`), threading the `pr` fact per §9.4. An `issues[]` entry may be a bare
2949
+ `owner/repo#N` string OR a `{ gate?, issue }` object that interleaves a `wait` gate (the
2950
+ `npm`/`github-check`/… vocabulary) before that issue's agent (issue #740). Invalid input (empty
2951
+ `issues`, an unparseable ref, an unknown gate kind) is a 400 carrying `issues: [{ path, message
2952
+ }]`; nothing is staged.
2848
2953
  type: object
2849
2954
  additionalProperties: false
2850
2955
  example:
2851
- behind: nanobpm/nano-ide#488
2852
2956
  issues:
2853
- - nanobpm/nano-workforce#567
2854
- - nanobpm/nano-workforce#568
2957
+ - nanobpm/nano-ide#557
2958
+ - gate:
2959
+ kind: npm
2960
+ target: "@nanobpm/agentic@0.13.0"
2961
+ issue: jwulf/c8ctl-plugin-nano#186
2962
+ - nanobpm/nano-workforce#738
2855
2963
  required:
2856
2964
  - issues
2857
2965
  properties:
@@ -2868,15 +2976,22 @@ components:
2868
2976
  minItems: 1
2869
2977
  maxItems: 64
2870
2978
  items:
2871
- type: string
2872
- minLength: 1
2873
- maxLength: 255
2874
- description: An `owner/repo#N` issue reference to implement + converge + merge, in sequence.
2979
+ oneOf:
2980
+ - type: string
2981
+ minLength: 1
2982
+ maxLength: 255
2983
+ description: A bare `owner/repo#N` issue reference (no gate) — today's behaviour, byte-for-byte.
2984
+ - $ref: "#/components/schemas/SequenceIssueEntry"
2985
+ description: >-
2986
+ A sequence step — a bare `owner/repo#N` string, or a `{ gate?, issue }` object that
2987
+ interleaves a `wait` gate before that issue's agent (issue #740).
2875
2988
  description: >-
2876
2989
  The ordered issues to sequence — each is implemented by a `senior:feature` agent that opens
2877
2990
  a PR, driven to convergence + merge, and the NEXT issue's implementation starts only once
2878
- the prior issue has merged. At least one; at most 64 (keeps the generated graph within the
2879
- compiler's node ceiling).
2991
+ the prior issue has merged (and any interleaved gate has gone green). At least one; at most
2992
+ 64. The compiler's node ceiling is enforced SEPARATELY by a node-budget guard (interleaved
2993
+ gates each add nodes, so a fully-gated 64-issue sequence can exceed the ceiling and is
2994
+ rejected there) — `maxItems: 64` bounds the entry count, not the generated node count.
2880
2995
  SequenceIssuesRejected:
2881
2996
  description: >-
2882
2997
  A rejected `sequenceIssues` intent — the input failed validation (empty/oversized `issues`, an
@@ -3847,6 +3962,31 @@ paths:
3847
3962
  with no argument), e.g. `orient`, `submit-pr`, `submit-epic`, `escalations`, `lifecycle`,
3848
3963
  `debug`, `debug-models`, `unstick`, `raise-issue`, `delivery-graphs`, `tool-crosswalk`. Omit it to get the
3849
3964
  table of contents. An unknown id yields a 400 listing the valid ids.
3965
+ - name: start
3966
+ in: query
3967
+ required: false
3968
+ schema:
3969
+ type: integer
3970
+ minimum: 0
3971
+ example: 0
3972
+ description: >-
3973
+ OPTIONAL pagination cursor (issue #740) — the CHARACTER offset into a `section`'s markdown to
3974
+ start at. Passing `start` and/or `length` engages bounded-chunk retrieval so a large section
3975
+ (e.g. `delivery-graphs`) can be paged through without overflowing a tool-result limit; the
3976
+ response echoes `start`, `length`, `totalLength`, and a `nextStart` cursor (null on the last
3977
+ page). Ignored when no `section` is given. Omit both `start` and `length` to get the whole
3978
+ section unchanged.
3979
+ - name: length
3980
+ in: query
3981
+ required: false
3982
+ schema:
3983
+ type: integer
3984
+ minimum: 1
3985
+ example: 12000
3986
+ description: >-
3987
+ OPTIONAL pagination window (issue #740) — the maximum number of CHARACTERS to return for a
3988
+ `section`. Defaults to a bounded page size when `start` is given without `length`. Pair with
3989
+ `start`/`nextStart` to page through an oversized section.
3850
3990
  responses:
3851
3991
  "200":
3852
3992
  description: Either the table of contents (no `section`) or a single section's markdown.
@@ -4161,14 +4301,17 @@ paths:
4161
4301
  application/json:
4162
4302
  schema:
4163
4303
  # BEGIN generated:mcp-body source=#/components/schemas/SequenceIssuesIntent (scripts/inline-mcp-bodies.ts — do not hand-edit)
4164
- description: 'The `sequenceIssues` INTENT (epic nano-workforce#605, S4) — a high-level shape that GENERATES the canonical "implement issue → converge → merge" delivery graph (operator-guide §9.4) instead of making an agent hand-author its node/edge JSON. It names an ordered list of `issues` to sequence (each issue''s implementation starts once the PRIOR issue has merged) and an OPTIONAL leading `behind` gate (wait for that issue/epic/feature to be fully merged first, §9.5). The door GENERATES, then STAGES the graph through the same compile+stage flow as `compileDeliveryGraph` — it never dispatches (dispatch is an operator-only cockpit action, ADR 0005 Decision 7). For each issue it emits `agent` (`senior:feature`, emits a `pr` fact) → `connector` (`converge-merge`, late-binding that `pr`) → `wait[pr, merged]` (a realistic `poll.timeoutMs`), threading the `pr` fact per §9.4. Invalid input (empty `issues`, an unparseable ref) is a 400 carrying `issues: [{ path, message }]`; nothing is staged.'
4304
+ description: 'The `sequenceIssues` INTENT (epic nano-workforce#605, S4) — a high-level shape that GENERATES the canonical "implement issue → converge → merge" delivery graph (operator-guide §9.4) instead of making an agent hand-author its node/edge JSON. It names an ordered list of `issues` to sequence (each issue''s implementation starts once the PRIOR issue has merged) and an OPTIONAL leading `behind` gate (wait for that issue/epic/feature to be fully merged first, §9.5). The door GENERATES, then STAGES the graph through the same compile+stage flow as `compileDeliveryGraph` — it never dispatches (dispatch is an operator-only cockpit action, ADR 0005 Decision 7). For each issue it emits `agent` (`senior:feature`, emits a `pr` fact) → `connector` (`converge-merge`, late-binding that `pr`) → `wait[pr, merged]` (a realistic `poll.timeoutMs`), threading the `pr` fact per §9.4. An `issues[]` entry may be a bare `owner/repo#N` string OR a `{ gate?, issue }` object that interleaves a `wait` gate (the `npm`/`github-check`/… vocabulary) before that issue''s agent (issue #740). Invalid input (empty `issues`, an unparseable ref, an unknown gate kind) is a 400 carrying `issues: [{ path, message }]`; nothing is staged.'
4165
4305
  type: object
4166
4306
  additionalProperties: false
4167
4307
  example:
4168
- behind: nanobpm/nano-ide#488
4169
4308
  issues:
4170
- - nanobpm/nano-workforce#567
4171
- - nanobpm/nano-workforce#568
4309
+ - nanobpm/nano-ide#557
4310
+ - gate:
4311
+ kind: npm
4312
+ target: '@nanobpm/agentic@0.13.0'
4313
+ issue: jwulf/c8ctl-plugin-nano#186
4314
+ - nanobpm/nano-workforce#738
4172
4315
  required:
4173
4316
  - issues
4174
4317
  properties:
@@ -4182,11 +4325,66 @@ paths:
4182
4325
  minItems: 1
4183
4326
  maxItems: 64
4184
4327
  items:
4185
- type: string
4186
- minLength: 1
4187
- maxLength: 255
4188
- description: An `owner/repo#N` issue reference to implement + converge + merge, in sequence.
4189
- description: The ordered issues to sequence — each is implemented by a `senior:feature` agent that opens a PR, driven to convergence + merge, and the NEXT issue's implementation starts only once the prior issue has merged. At least one; at most 64 (keeps the generated graph within the compiler's node ceiling).
4328
+ oneOf:
4329
+ - type: string
4330
+ minLength: 1
4331
+ maxLength: 255
4332
+ description: A bare `owner/repo#N` issue reference (no gate) today's behaviour, byte-for-byte.
4333
+ - description: "One `issues[]` entry with an OPTIONAL leading gate (issue #740) — an object form of a sequence step. The `issue` is implemented + converged + merged like a bare-string entry; the optional `gate` must go green (in addition to the prior issue merging) before this issue's agent starts."
4334
+ type: object
4335
+ additionalProperties: false
4336
+ required:
4337
+ - issue
4338
+ properties:
4339
+ gate:
4340
+ description: 'An interleaved `wait` GATE (issue #740) that must go green before a given issue''s agent starts — reusing the exact probe schema `wait` nodes accept (`npm`/`github-check`/`http`/`command`/ `capability`/`pr`/`epic`). It lets an author splice e.g. "wait for `@nanobpm/agentic@0.13.0` to publish" between two sequence steps without hand-authoring raw node/edge JSON.'
4341
+ type: object
4342
+ additionalProperties: false
4343
+ required:
4344
+ - kind
4345
+ - target
4346
+ properties:
4347
+ kind:
4348
+ type: string
4349
+ minLength: 1
4350
+ description: The wait-probe kind — one of the S3 vocabulary kinds (`npm`, `github-check`, `http`, `command`, `capability`, `pr`, `epic`). An unknown kind is a 400.
4351
+ target:
4352
+ type: string
4353
+ minLength: 1
4354
+ description: 'The probe target — kind-specific: `pkg@version` for `npm`, `owner/repo@ref` for `github-check`, a URL for `http`, an `owner/repo#N` PR for `pr`, etc.'
4355
+ match:
4356
+ type: object
4357
+ description: OPTIONAL kind-specific readiness match fields (e.g. `{ version }` for `npm`, `{ conclusion, checkName }` for `github-check`).
4358
+ poll:
4359
+ type: object
4360
+ additionalProperties: false
4361
+ description: OPTIONAL poll budget. Defaults to the bounded merge-gate budget (re-probe every 5 minutes, budget 3 days) so a gate never falls into the 30-minute default trap.
4362
+ properties:
4363
+ everyMs:
4364
+ type: integer
4365
+ minimum: 1
4366
+ description: How often to re-probe (milliseconds).
4367
+ timeoutMs:
4368
+ type: integer
4369
+ minimum: 1
4370
+ description: The total budget before the gate escalates/continues (milliseconds).
4371
+ onTimeout:
4372
+ type: string
4373
+ enum:
4374
+ - escalate
4375
+ - continue
4376
+ description: What to do when the gate never goes green within its budget — `escalate` (default) parks on a human; `continue` proceeds anyway. `fail` is not supported on a `wait` node.
4377
+ credentialEnv:
4378
+ type: string
4379
+ minLength: 1
4380
+ description: OPTIONAL env-key name supplying a credential for the probe (`http`/`capability`).
4381
+ issue:
4382
+ type: string
4383
+ minLength: 1
4384
+ maxLength: 255
4385
+ description: An `owner/repo#N` issue reference to implement + converge + merge, in sequence.
4386
+ description: "A sequence step — a bare `owner/repo#N` string, or a `{ gate?, issue }` object that interleaves a `wait` gate before that issue's agent (issue #740)."
4387
+ description: "The ordered issues to sequence — each is implemented by a `senior:feature` agent that opens a PR, driven to convergence + merge, and the NEXT issue's implementation starts only once the prior issue has merged (and any interleaved gate has gone green). At least one; at most 64. The compiler's node ceiling is enforced SEPARATELY by a node-budget guard (interleaved gates each add nodes, so a fully-gated 64-issue sequence can exceed the ceiling and is rejected there) — `maxItems: 64` bounds the entry count, not the generated node count."
4190
4388
  # END generated:mcp-body
4191
4389
  responses:
4192
4390
  "200":
@@ -127,6 +127,10 @@ test("compile-delivery-graph: a changed graph with the same name SUPERSEDES the
127
127
  assert(a.body.digest !== b.body.digest, "the changed graph has a new digest");
128
128
  assertEquals((await deliveryGraphProposals(data).get(a.body.digest))?.status, "superseded");
129
129
  assertEquals((await deliveryGraphProposals(data).get(b.body.digest))?.status, "staged");
130
+ // The supersede is surfaced in the compile result body so the operator sees the retirement (#740).
131
+ assertEquals(b.body.superseded, [a.body.digest]);
132
+ assertEquals(b.body.siblingsStaged, 0);
133
+ assertEquals(a.body.superseded, []); // the first stage retired nothing
130
134
  });
131
135
  });
132
136
 
@@ -82,6 +82,19 @@ test("blank/whitespace section is treated as no section (TOC)", async () => {
82
82
  assertEquals(r.body.kind, "toc");
83
83
  });
84
84
 
85
+ test("a pagination cursor beyond MAX_SAFE_INTEGER → 400 (unsafe integers lose precision)", async () => {
86
+ // 9007199254740993 === 9007199254740992 in IEEE-754 double, so `Number.isInteger` accepts it
87
+ // while it no longer represents the caller's requested character offset. It must be rejected.
88
+ const unsafe = "9007199254740993";
89
+ const rStart = (await handler(input({ section: "delivery-graphs", start: unsafe }), app)) as any;
90
+ assertEquals(rStart.status, 400);
91
+ assert(Array.isArray(rStart.body.issues) && rStart.body.issues.some((i: any) => i.path === "start"));
92
+
93
+ const rLength = (await handler(input({ section: "delivery-graphs", length: unsafe }), app)) as any;
94
+ assertEquals(rLength.status, 400);
95
+ assert(Array.isArray(rLength.body.issues) && rLength.body.issues.some((i: any) => i.path === "length"));
96
+ });
97
+
85
98
  test("shared-secret guard: rejects when the secret is set and header is wrong", async () => {
86
99
  const prev = process.env.NANO_PR_WEBHOOK_SECRET;
87
100
  process.env.NANO_PR_WEBHOOK_SECRET = "s3cr3t";
@@ -15,13 +15,29 @@
15
15
  //
16
16
  // The optional shared-secret guard mirrors /agent and /version: enforced HERE only when
17
17
  // NANO_PR_WEBHOOK_SECRET is set (the runtime does not enforce OpenAPI `security`).
18
- import { guideToc, renderGuideSection, resolveEngineBase } from "../app/agentGuide.ts";
18
+ import { GUIDE_SECTION_PAGE_DEFAULT, guideToc, renderGuideSection, renderGuideSectionChunk, resolveEngineBase } from "../app/agentGuide.ts";
19
19
  import { resolveApiBase } from "../app/resolveApiBase.ts";
20
20
  import { buildVersionInfo, envVar } from "../app/version.ts";
21
21
  import { defineOperation } from "../nano-generated/operations.ts";
22
22
 
23
23
  const SECRET = envVar("NANO_PR_WEBHOOK_SECRET") ?? "";
24
24
 
25
+ /** Parse a `start`/`length` pagination query param: a present value must be a non-negative SAFE
26
+ * integer (`length` additionally >= 1). `Number.isSafeInteger` (not `Number.isInteger`) is required
27
+ * because these values are used as character cursors: an integer beyond `Number.MAX_SAFE_INTEGER`
28
+ * (e.g. `9007199254740993`) loses precision, so accepting it would silently misinterpret the
29
+ * caller's requested window. Returns the parsed number, `undefined` when absent, or a
30
+ * path-qualified validation issue. */
31
+ function parsePageArg(raw: unknown, path: string, min: number): { value?: number; issue?: { path: string; message: string } } {
32
+ if (raw === undefined || raw === null || (typeof raw === "string" && raw.trim() === "")) return {};
33
+ const s = typeof raw === "string" ? raw.trim() : String(raw);
34
+ const n = Number(s);
35
+ if (!Number.isSafeInteger(n) || n < min) {
36
+ return { issue: { path, message: `\`${path}\` must be an integer >= ${min} (character offset); got "${s}"` } };
37
+ }
38
+ return { value: n };
39
+ }
40
+
25
41
  export default defineOperation("getAgentGuide", ({ query, req }, app) => {
26
42
  if (SECRET && req.headers.get("x-hook-secret") !== SECRET) {
27
43
  app.log.warn("getAgentGuide rejected: missing/invalid shared secret");
@@ -46,9 +62,23 @@ export default defineOperation("getAgentGuide", ({ query, req }, app) => {
46
62
  };
47
63
  }
48
64
 
49
- // A section id just that section, or a 400 that names the valid ids.
50
- const instructions = renderGuideSection(section, baseUrl);
51
- if (instructions === undefined) {
65
+ // Pagination window (issue #740): a caller engages it by passing `start` and/or `length` (character
66
+ // offsets). Absent both, the whole section is returned unchanged (byte-for-byte identical to before).
67
+ const startArg = parsePageArg(query.start, "start", 0);
68
+ const lengthArg = parsePageArg(query.length, "length", 1);
69
+ const pageIssues = [startArg.issue, lengthArg.issue].filter((i): i is { path: string; message: string } => i !== undefined);
70
+ if (pageIssues.length > 0) {
71
+ app.log.warn("getAgentGuide rejected: invalid pagination args", { issues: pageIssues.length });
72
+ return { status: 400, body: { error: "invalid pagination arguments", issues: pageIssues } };
73
+ }
74
+ const paginate = startArg.value !== undefined || lengthArg.value !== undefined;
75
+
76
+ // A section id → just that section (or a bounded page of it), or a 400 that names the valid ids.
77
+ const chunk = paginate ? renderGuideSectionChunk(section, baseUrl, startArg.value ?? 0, lengthArg.value ?? GUIDE_SECTION_PAGE_DEFAULT) : undefined;
78
+ // The resolved body text: a page's slice when paginating, else the whole section. `undefined` from
79
+ // either path means the section id is unknown → the uniform 400 below.
80
+ const resolved = chunk ? chunk.instructions : paginate ? undefined : renderGuideSection(section, baseUrl);
81
+ if (resolved === undefined) {
52
82
  // Derive the valid ids from the PARSED table of contents — the sections this deployment can
53
83
  // actually serve — not the static registry. When the guide doc is unreadable (RAW_GUIDE
54
84
  // fallback, no `##` headings) the TOC is empty and NO id is retrievable, so say so explicitly
@@ -85,7 +115,12 @@ export default defineOperation("getAgentGuide", ({ query, req }, app) => {
85
115
  id: section,
86
116
  title,
87
117
  format: "markdown",
88
- instructions,
118
+ instructions: resolved,
119
+ // Pagination cursor state — present ONLY when the caller engaged a window, so an un-paginated
120
+ // `section=<id>` call's body stays byte-for-byte identical (issue #740).
121
+ ...(chunk
122
+ ? { start: chunk.start, length: chunk.length, totalLength: chunk.totalLength, nextStart: chunk.nextStart }
123
+ : {}),
89
124
  },
90
125
  },
91
126
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nanobpm/nano-workforce",
3
- "version": "0.179.2",
3
+ "version": "0.181.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",