@sjawhar/pi-legion-envoy 1.54.0 → 1.55.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/dist/envoy.js CHANGED
@@ -29913,6 +29913,20 @@ function documentOwnerValidation(requireArtifact, alwaysRequireArtifact = false)
29913
29913
  message: alwaysRequireArtifact ? "Exactly one of issue and project is required; artifact or ref must name the document." : "Exactly one of issue and project is required; with project, artifact names the document."
29914
29914
  };
29915
29915
  }
29916
+ var documentEditValidation = {
29917
+ check: (value) => {
29918
+ if (!documentOwnerValidation(true, true).check(value))
29919
+ return false;
29920
+ const input = value;
29921
+ if (input.precondition === undefined)
29922
+ return true;
29923
+ if (typeof input.precondition !== "object" || input.precondition === null)
29924
+ return false;
29925
+ const precondition = input.precondition;
29926
+ return typeof precondition.document === "string" !== Array.isArray(precondition.blocks);
29927
+ },
29928
+ message: "Exactly one of issue and project is required; artifact or ref must name the document. A precondition selects exactly one of document or blocks."
29929
+ };
29916
29930
  var commentOwner = documentOwnerValidation(true);
29917
29931
  var commentValidation = {
29918
29932
  check: (value) => {
@@ -30143,9 +30157,10 @@ var dispatchToolSpecs = [
30143
30157
  example: {
30144
30158
  issue: "DSP-1",
30145
30159
  artifact: "spec",
30146
- ops: [{ op: "delete_column", block: "table-123", index: 1 }]
30160
+ ops: [{ op: "delete_column", block: "table-123", index: 1 }],
30161
+ precondition: { blocks: [{ id: "table-123", token: "sha256:current-table-token" }] }
30147
30162
  },
30148
- description: "Apply deterministic document edits: replace or delete quoted text, insert markdown at an anchor, retype an identified paragraph or typed block into a schema-declared typed block, delete or move a whole block by its id, or delete a table row or column in place. " + "Do not use it for review feedback or for reading; use dispatch_comment, dispatch_suggest, or dispatch_doc_read instead. " + "For replace, delete, and quote anchors, find text as rendered: inline Markdown (**bold**, `code`) is tolerated; a leading '# ' matches a heading. replace is inline: with is the new text of the matched span, so a leading list or heading marker stays literal text. " + "A delete whose find is a block's entire text removes the block (a list emptied of its items goes too); delete with block removes any block by id, and move with block relocates one. delete_row and delete_column take a table block and a zero-based index, preserving the table block id and refusing to remove cells with open asks or unresolved comments. " + 'Insert and move anchors also accept "start", "end", "heading:<exact heading text>", and "block:<id>"; block ids are the #id of a typed block or come from GET /api/v1/artifacts/{artifact UUID}/blocks (the route takes the artifact UUID, not its slug). ' + `The spec (or any document) holds requirements, design, and decisions - never progress, status, or timestamps. ${OWNER_REFERENCE} ${SPEC_WRITING_GUIDANCE}`,
30163
+ description: "Apply deterministic document edits: replace or delete quoted text, insert markdown at an anchor, retype an identified paragraph or typed block into a schema-declared typed block, delete or move a whole block by its id, or delete a table row or column in place. " + "Do not use it for review feedback or for reading; use dispatch_comment, dispatch_suggest, or dispatch_doc_read instead. " + "For replace, delete, and quote anchors, find text as rendered: inline Markdown (**bold**, `code`) is tolerated; a leading '# ' matches a heading. replace is inline: with is the new text of the matched span, so a leading list or heading marker stays literal text. " + "A delete whose find is a block's entire text removes the block (a list emptied of its items goes too); delete with block removes any block by id, and move with block relocates one. delete_row and delete_column take a table block and a zero-based index, preserving the table block id and refusing to remove cells with open asks or unresolved comments. " + 'Insert and move anchors also accept "start", "end", "heading:<exact heading text>", and "block:<id>"; block ids and their tokens come from GET /api/v1/artifacts/{artifact UUID}/blocks (the route takes the artifact UUID, not its slug). ' + "Optionally require the state just read: precondition selects exactly one of a document token from dispatch_doc_read, or block {id, token} values from /blocks. A block guard must include every block the batch changes; Dispatch resolves quote targets and rejects an uncovered batch rather than applying it. Use a document token for insert or move, which depend on document order. Prefer block tokens when the covered content blocks are independent sections. Tokens include inline marks, so a fresh human comment also makes a stale edit fail. PRECONDITION_FAILED means re-read; EDIT_QUEUE_FULL means back off before retrying. " + `The spec (or any document) holds requirements, design, and decisions - never progress, status, or timestamps. ${OWNER_REFERENCE} ${SPEC_WRITING_GUIDANCE}`,
30149
30164
  arguments: (z) => ({
30150
30165
  issue: z.string().describe(ISSUE_REFERENCE).optional(),
30151
30166
  project: z.string().describe("Project key owning the document.").optional(),
@@ -30164,14 +30179,21 @@ var dispatchToolSpecs = [
30164
30179
  type: z.string().describe("Typed block name for retype.").optional(),
30165
30180
  attributes: z.unknown().describe("Typed block attributes for retype.").optional()
30166
30181
  })).describe("Flat tagged edits; the server validates fields required for each operation."),
30182
+ precondition: z.object({
30183
+ document: z.string({ min: 1 }).describe("Token for the exact canonical document returned by dispatch_doc_read.").optional(),
30184
+ blocks: z.array(z.object({
30185
+ id: z.string({ min: 1 }).describe("Stable block id from GET /api/v1/artifacts/{id}/blocks."),
30186
+ token: z.string({ min: 1 }).describe("That block's full-state token, including inline marks.")
30187
+ }), { min: 1 }).describe("Every content block this batch changes, each with the token returned by /blocks.").optional()
30188
+ }).describe("Optional optimistic-concurrency guard; select exactly one of document or blocks.").optional(),
30167
30189
  summary: z.string().describe("Optional named-version summary.").optional()
30168
30190
  }),
30169
- validation: documentOwnerValidation(true, true)
30191
+ validation: documentEditValidation
30170
30192
  },
30171
30193
  {
30172
30194
  name: "dispatch_doc_read",
30173
30195
  example: { issue: "DSP-1" },
30174
- description: "Read a live document or a named document version. Do not use it for issue status, asks, or events; " + "use dispatch_read instead. Supply ref, issue, or project plus artifact; issue plus an omitted artifact reads the primary document. " + OWNER_REFERENCE,
30196
+ description: "Read a live document or a named document version. Do not use it for issue status, asks, or events; " + "use dispatch_read instead. Supply ref, issue, or project plus artifact; issue plus an omitted artifact reads the primary document. " + "A live read returns its document token for an optional dispatch_doc_edit precondition; use /blocks for per-block tokens. " + OWNER_REFERENCE,
30175
30197
  arguments: (z) => ({
30176
30198
  issue: z.string().describe(ISSUE_REFERENCE).optional(),
30177
30199
  project: z.string().describe("Project key owning the document.").optional(),
@@ -30396,6 +30418,14 @@ var reviewSchema = baseHandoffSchema.extend({
30396
30418
  verdict: _enum2(["approved", "changes_requested"]).optional(),
30397
30419
  keyFindings: array(object({ severity: string2(), file: string2(), description: string2() }).passthrough()).optional()
30398
30420
  });
30421
+ var nonEmptySkillList = array(string2().trim().min(1)).min(1);
30422
+ var planWriteSchema = planSchema.extend({
30423
+ requiredSkills: object({
30424
+ implement: nonEmptySkillList,
30425
+ test: nonEmptySkillList,
30426
+ review: nonEmptySkillList
30427
+ }).passthrough()
30428
+ });
30399
30429
  var phaseHandoffSchema = discriminatedUnion("phase", [
30400
30430
  architectSchema,
30401
30431
  planSchema,
@@ -32073,12 +32103,16 @@ class DispatchServiceError extends Error {
32073
32103
  code;
32074
32104
  status;
32075
32105
  candidates;
32106
+ current;
32107
+ mismatches;
32076
32108
  name = "DispatchServiceError";
32077
- constructor(code, status, message, candidates) {
32109
+ constructor(code, status, message, candidates, current, mismatches) {
32078
32110
  super(message);
32079
32111
  this.code = code;
32080
32112
  this.status = status;
32081
32113
  this.candidates = candidates;
32114
+ this.current = current;
32115
+ this.mismatches = mismatches;
32082
32116
  }
32083
32117
  }
32084
32118
  function asErrorShape(value) {
@@ -32390,7 +32424,7 @@ class DispatchClient {
32390
32424
  }
32391
32425
  if (!response.ok) {
32392
32426
  const error = asErrorShape(payload);
32393
- throw new DispatchServiceError(error.code ?? `HTTP_${response.status}`, response.status, error.error ?? (typeof payload === "string" && payload ? payload : response.statusText), error.candidates);
32427
+ throw new DispatchServiceError(error.code ?? `HTTP_${response.status}`, response.status, error.error ?? (typeof payload === "string" && payload ? payload : response.statusText), error.candidates, error.current, error.mismatches);
32394
32428
  }
32395
32429
  return payload;
32396
32430
  }
@@ -33717,9 +33751,12 @@ ${followsAsk(askOwner)}`,
33717
33751
  const resolved = await resolveArtifact(client, documentOwner(), stringArg(args, "artifact"));
33718
33752
  const ops = args.ops;
33719
33753
  const summary = optionalString(args, "summary");
33754
+ const { precondition: rawPrecondition } = args;
33755
+ const precondition = rawPrecondition;
33720
33756
  const edited = await client.docEdit(resolved.artifact.id, {
33721
33757
  ops,
33722
33758
  ...summary === undefined ? {} : { summary },
33759
+ ...precondition === undefined ? {} : { precondition },
33723
33760
  actor
33724
33761
  });
33725
33762
  const retyped = ops.filter((operation) => operation.op === "retype").length;
@@ -33747,6 +33784,7 @@ ${followsAsk(askOwner)}`,
33747
33784
  const marks = marksResult.value;
33748
33785
  const approval = approvalLine(resolved.artifact);
33749
33786
  const trailer = [
33787
+ ..."token" in document && document.token !== undefined ? [`Document token: ${document.token}`] : [],
33750
33788
  ...marks.length === 0 ? [] : [`Open anchored asks/comments: ${marks.join(", ")}`],
33751
33789
  ...approval === undefined ? [] : [approval]
33752
33790
  ];
package/dist/legion.js CHANGED
@@ -16145,7 +16145,7 @@ import { logger } from "@oh-my-pi/pi-utils";
16145
16145
  // package.json
16146
16146
  var package_default = {
16147
16147
  name: "@sjawhar/pi-legion-envoy",
16148
- version: "1.54.0",
16148
+ version: "1.55.0",
16149
16149
  type: "module",
16150
16150
  omp: {
16151
16151
  extensions: [
@@ -29232,6 +29232,20 @@ function documentOwnerValidation(requireArtifact, alwaysRequireArtifact = false)
29232
29232
  message: alwaysRequireArtifact ? "Exactly one of issue and project is required; artifact or ref must name the document." : "Exactly one of issue and project is required; with project, artifact names the document."
29233
29233
  };
29234
29234
  }
29235
+ var documentEditValidation = {
29236
+ check: (value) => {
29237
+ if (!documentOwnerValidation(true, true).check(value))
29238
+ return false;
29239
+ const input = value;
29240
+ if (input.precondition === undefined)
29241
+ return true;
29242
+ if (typeof input.precondition !== "object" || input.precondition === null)
29243
+ return false;
29244
+ const precondition = input.precondition;
29245
+ return typeof precondition.document === "string" !== Array.isArray(precondition.blocks);
29246
+ },
29247
+ message: "Exactly one of issue and project is required; artifact or ref must name the document. A precondition selects exactly one of document or blocks."
29248
+ };
29235
29249
  var commentOwner = documentOwnerValidation(true);
29236
29250
  var commentValidation = {
29237
29251
  check: (value) => {
@@ -29462,9 +29476,10 @@ var dispatchToolSpecs = [
29462
29476
  example: {
29463
29477
  issue: "DSP-1",
29464
29478
  artifact: "spec",
29465
- ops: [{ op: "delete_column", block: "table-123", index: 1 }]
29479
+ ops: [{ op: "delete_column", block: "table-123", index: 1 }],
29480
+ precondition: { blocks: [{ id: "table-123", token: "sha256:current-table-token" }] }
29466
29481
  },
29467
- description: "Apply deterministic document edits: replace or delete quoted text, insert markdown at an anchor, retype an identified paragraph or typed block into a schema-declared typed block, delete or move a whole block by its id, or delete a table row or column in place. " + "Do not use it for review feedback or for reading; use dispatch_comment, dispatch_suggest, or dispatch_doc_read instead. " + "For replace, delete, and quote anchors, find text as rendered: inline Markdown (**bold**, `code`) is tolerated; a leading '# ' matches a heading. replace is inline: with is the new text of the matched span, so a leading list or heading marker stays literal text. " + "A delete whose find is a block's entire text removes the block (a list emptied of its items goes too); delete with block removes any block by id, and move with block relocates one. delete_row and delete_column take a table block and a zero-based index, preserving the table block id and refusing to remove cells with open asks or unresolved comments. " + 'Insert and move anchors also accept "start", "end", "heading:<exact heading text>", and "block:<id>"; block ids are the #id of a typed block or come from GET /api/v1/artifacts/{artifact UUID}/blocks (the route takes the artifact UUID, not its slug). ' + `The spec (or any document) holds requirements, design, and decisions - never progress, status, or timestamps. ${OWNER_REFERENCE} ${SPEC_WRITING_GUIDANCE}`,
29482
+ description: "Apply deterministic document edits: replace or delete quoted text, insert markdown at an anchor, retype an identified paragraph or typed block into a schema-declared typed block, delete or move a whole block by its id, or delete a table row or column in place. " + "Do not use it for review feedback or for reading; use dispatch_comment, dispatch_suggest, or dispatch_doc_read instead. " + "For replace, delete, and quote anchors, find text as rendered: inline Markdown (**bold**, `code`) is tolerated; a leading '# ' matches a heading. replace is inline: with is the new text of the matched span, so a leading list or heading marker stays literal text. " + "A delete whose find is a block's entire text removes the block (a list emptied of its items goes too); delete with block removes any block by id, and move with block relocates one. delete_row and delete_column take a table block and a zero-based index, preserving the table block id and refusing to remove cells with open asks or unresolved comments. " + 'Insert and move anchors also accept "start", "end", "heading:<exact heading text>", and "block:<id>"; block ids and their tokens come from GET /api/v1/artifacts/{artifact UUID}/blocks (the route takes the artifact UUID, not its slug). ' + "Optionally require the state just read: precondition selects exactly one of a document token from dispatch_doc_read, or block {id, token} values from /blocks. A block guard must include every block the batch changes; Dispatch resolves quote targets and rejects an uncovered batch rather than applying it. Use a document token for insert or move, which depend on document order. Prefer block tokens when the covered content blocks are independent sections. Tokens include inline marks, so a fresh human comment also makes a stale edit fail. PRECONDITION_FAILED means re-read; EDIT_QUEUE_FULL means back off before retrying. " + `The spec (or any document) holds requirements, design, and decisions - never progress, status, or timestamps. ${OWNER_REFERENCE} ${SPEC_WRITING_GUIDANCE}`,
29468
29483
  arguments: (z) => ({
29469
29484
  issue: z.string().describe(ISSUE_REFERENCE).optional(),
29470
29485
  project: z.string().describe("Project key owning the document.").optional(),
@@ -29483,14 +29498,21 @@ var dispatchToolSpecs = [
29483
29498
  type: z.string().describe("Typed block name for retype.").optional(),
29484
29499
  attributes: z.unknown().describe("Typed block attributes for retype.").optional()
29485
29500
  })).describe("Flat tagged edits; the server validates fields required for each operation."),
29501
+ precondition: z.object({
29502
+ document: z.string({ min: 1 }).describe("Token for the exact canonical document returned by dispatch_doc_read.").optional(),
29503
+ blocks: z.array(z.object({
29504
+ id: z.string({ min: 1 }).describe("Stable block id from GET /api/v1/artifacts/{id}/blocks."),
29505
+ token: z.string({ min: 1 }).describe("That block's full-state token, including inline marks.")
29506
+ }), { min: 1 }).describe("Every content block this batch changes, each with the token returned by /blocks.").optional()
29507
+ }).describe("Optional optimistic-concurrency guard; select exactly one of document or blocks.").optional(),
29486
29508
  summary: z.string().describe("Optional named-version summary.").optional()
29487
29509
  }),
29488
- validation: documentOwnerValidation(true, true)
29510
+ validation: documentEditValidation
29489
29511
  },
29490
29512
  {
29491
29513
  name: "dispatch_doc_read",
29492
29514
  example: { issue: "DSP-1" },
29493
- description: "Read a live document or a named document version. Do not use it for issue status, asks, or events; " + "use dispatch_read instead. Supply ref, issue, or project plus artifact; issue plus an omitted artifact reads the primary document. " + OWNER_REFERENCE,
29515
+ description: "Read a live document or a named document version. Do not use it for issue status, asks, or events; " + "use dispatch_read instead. Supply ref, issue, or project plus artifact; issue plus an omitted artifact reads the primary document. " + "A live read returns its document token for an optional dispatch_doc_edit precondition; use /blocks for per-block tokens. " + OWNER_REFERENCE,
29494
29516
  arguments: (z) => ({
29495
29517
  issue: z.string().describe(ISSUE_REFERENCE).optional(),
29496
29518
  project: z.string().describe("Project key owning the document.").optional(),
@@ -29715,6 +29737,14 @@ var reviewSchema = baseHandoffSchema.extend({
29715
29737
  verdict: _enum2(["approved", "changes_requested"]).optional(),
29716
29738
  keyFindings: array(object({ severity: string2(), file: string2(), description: string2() }).passthrough()).optional()
29717
29739
  });
29740
+ var nonEmptySkillList = array(string2().trim().min(1)).min(1);
29741
+ var planWriteSchema = planSchema.extend({
29742
+ requiredSkills: object({
29743
+ implement: nonEmptySkillList,
29744
+ test: nonEmptySkillList,
29745
+ review: nonEmptySkillList
29746
+ }).passthrough()
29747
+ });
29718
29748
  var phaseHandoffSchema = discriminatedUnion("phase", [
29719
29749
  architectSchema,
29720
29750
  planSchema,
@@ -4,44 +4,17 @@ Legion skills guide the architect and its sequential phase workers in a shared i
4
4
  They are Markdown instructions loaded by Oh My Pi sessions; the daemon and OMP extension own
5
5
  event intake, process lifecycle, credentials, and role delivery.
6
6
 
7
- ## Structure
8
-
9
- ```
10
- skills/
11
- ├── dispatch/ # Writing specs, asks, comments, and artifacts on native Dispatch
12
- ├── envoy/ # Envoy subscriptions, agent-to-agent messages, and topic formats
13
- ├── legion-architect/ # Tree ownership, decomposition, gates, and scheduling
14
- ├── legion-controller/ # Derived-verdict control-plane operation
15
- ├── legion-oracle/ # Repository-grounded research
16
- ├── legion-retro/ # Post-review retrospective
17
- └── legion-worker/ # Sequential architect, plan, implement, test, and review phases
18
- ```
19
-
20
- ## Phase workers
21
-
22
- The extension supplies a phase worker with its issue, workspace, role token, and structured
23
- output schema. The worker claims its supplied role, works only on its phase artifact, and
24
- returns that schema to the architect. It writes the same phase-specific payload to
25
- `.legion/<phase>.json`, verifies it exists, and commits the handoff before reporting completion.
26
- The committed predecessor handoff wins after revival or re-creation.
27
-
28
- Workers do not run a controller loop or mutate lifecycle labels. Workers coordinate
29
- lifecycle, scope, and cross-phase decisions with the owning architect by `envoy_publish` to its
30
- role topic, sending the verified observation and decision needed (`hub` reaches only subagents
31
- inside the worker's own process). A worker may call the native `dispatch_*` tools directly for a
32
- durable human question; replies come back to the worker's own session.
33
-
34
- ## Durable artifacts
35
-
36
- Phase handoffs are committed in lifecycle order: architect, plan, implement, test, and review.
37
- Only the implementer pushes them: it and the merger act as the code-writing GitHub App, while
38
- the planner, tester, reviewer, and architects act as the review App (`appRoleForLegionRole`,
39
- `packages/daemon/src/daemon/github-apps.ts`), which holds no `contents` permission — their
40
- handoff commits stay on the shared workspace's issue branch and ride the implementer's next push.
41
- A clean review ends with the `.legion/` deletion pushed by the implementer at the reviewer's
42
- direction, which the reviewer then approves; retro records its learning in
43
- `docs/solutions/` and writes no handoff. GitHub comments and reviews carry the required Legion
44
- footer so the daemon can attribute artifacts to their worker session.
45
- The implement handoff carries the implementer's own production-like proof and the test handoff the
46
- tester's verdict on it plus the tester's own; `legion handoff write` refuses a payload the phase's
47
- schema rejects and names the field. Retro's message goes to the Dispatch issue (`dispatch_message`), never a GitHub issue.
7
+ | Skill | Who reads it | What it owns |
8
+ | --- | --- | --- |
9
+ | `dispatch/` | every role, and any session writing to Dispatch | specs, asks, comments, artifacts, and messages on native Dispatch |
10
+ | `envoy/` | every role | subscriptions, agent-to-agent messages, and topic formats |
11
+ | `legion-architect/` | root and sub-architects | tree ownership, decomposition, waves, gates, integration, sign-off |
12
+ | `legion-controller/` | the controller root process | wake routing, backlog admission, escalation |
13
+ | `legion-oracle/` | any role doing research | repository-grounded research |
14
+ | `legion-retro/` | the implementer, at retro | the pre-merge retrospective and its Dispatch message |
15
+ | `legion-worker/` | planner, implementer, tester, reviewer, merger | the phase contracts: handoffs, GitHub identity, PR body and READY discipline, the merge-gate order |
16
+
17
+ The owning skill above is where each contract is defined; a role prompt that needs a contract from its own seat points there or restates only its own step. This file lists and does not restate.
18
+ The text a worker boots with (its role prompt) lives in `packages/pi-envoy/roles/` and is
19
+ composed per role in `packages/daemon/src/daemon/processes.ts`; `packages/pi-envoy/roles/roles.test.ts`
20
+ holds the structural rules for those parts.
@@ -381,11 +381,12 @@ Read the current document before changing it:
381
381
  ```ts
382
382
  dispatch_doc_read({ issue?, project?, artifact?, version?, ref? })
383
383
  ```
384
- It returns live or versioned markdown with open marks. `issue` with an omitted `artifact` reads the issue specification; a project needs
385
- `artifact`; and a `dispatch://PROJECT/artifact/<document-ref>` ref supplies both, where `document-ref` is the id, slug, or filename.
384
+ It returns live or versioned markdown with open marks. A live read ends with a document token; `issue` with an
385
+ omitted `artifact` reads the issue specification; a project needs `artifact`; and a
386
+ `dispatch://PROJECT/artifact/<document-ref>` ref supplies both, where `document-ref` is the id, slug, or filename.
386
387
 
387
388
  ```ts
388
- dispatch_doc_edit({ issue?, project?, artifact, ops, summary? })
389
+ dispatch_doc_edit({ issue?, project?, artifact, ops, precondition?, summary? })
389
390
  ```
390
391
  It returns issue or project-document owner details plus `applied` and optional `version`. `ops` is an array of this
391
392
  exact `EditOp` shape:
@@ -445,6 +446,18 @@ an earlier `delete {block}` fails as `INVALID_OP` naming the earlier operation a
445
446
  whose anchor lies inside the moved block, or a delete that would leave a typed block without the body its content rule requires, is
446
447
  `INVALID_OP` naming the field and the rule.
447
448
 
449
+ `GET /api/v1/artifacts/<artifact UUID>/blocks` includes a full-state `token` on every block, including
450
+ inline marks. To reject a stale edit, pass `precondition` with exactly one of
451
+ `{ document: "<token from dispatch_doc_read>" }` or
452
+ `{ blocks: [{ id: "<block id>", token: "<block token>" }] }`. The server resolves the whole batch before
453
+ mutation: a block guard must cover every content block it changes, or Dispatch returns
454
+ `400 INVALID_PRECONDITION` without applying anything. Use a document token for insert and move because they
455
+ depend on document order. A block token lets other sections change concurrently; a new anchored ask or comment
456
+ changes the relevant token. A stale guard returns `409 PRECONDITION_FAILED` with each mismatch and current
457
+ token; Dispatch applies no part of that batch. It is the hashline `#TAG` property applied to stable block ids,
458
+ not line numbers: canonical Markdown lines shift under concurrent edits and rendering changes, while block ids
459
+ survive moves and retyping.
460
+
448
461
  `retype` turns the paragraph or typed block with `block` into the named typed `type` in place. It keeps the
449
462
  block id, keeps a typed block's body, and uses `attributes` for client-owned typed attributes. Use it when
450
463
  an existing paragraph is the question that should become a decision.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sjawhar/pi-legion-envoy",
3
- "version": "1.54.0",
3
+ "version": "1.55.0",
4
4
  "type": "module",
5
5
  "omp": {
6
6
  "extensions": [