@sjawhar/pi-legion-envoy 5.5.8 → 5.5.10
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 +5 -2
- package/dist/legion.js +29 -11
- package/dist/skills/dispatch/references/document-edits.md +13 -2
- package/package.json +1 -1
package/dist/envoy.js
CHANGED
|
@@ -30240,7 +30240,7 @@ var dispatchToolSpecs = [
|
|
|
30240
30240
|
ops: [{ op: "delete_column", block: "table-123", index: 1 }],
|
|
30241
30241
|
precondition: { blocks: [{ id: "table-123", token: "sha256:current-table-token" }] }
|
|
30242
30242
|
},
|
|
30243
|
-
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 and must be balanced; a leading '# ' matches a heading at any level. replace is inline: with is the new text of the matched span, so a marker of a different kind from the block's own stays literal text ('4. Design' written into a heading). A with that opens with a marker of the same kind as the matched block's own would write it twice and is INVALID_OP - including prose that merely looks like one ('1999. was a year' into an ordered item), which you write as text by escaping it ('1999\\. was a year'). The exception is a heading rename whose find carried a heading marker: replace(find="## Old", with="## New") gives '## New', and a different level applies only when find named the heading's actual level (find "## Old" with "### New" makes it an h3), since '# ' selects a heading without naming its level. Any non-empty with that renders to no text - a line indented four spaces or a tab, which markdown reads as a code block, or whitespace alone - is INVALID_OP rather than a silent deletion; pass an empty with to delete the matched text on purpose - a list item, quote, typed block or footnote definition left holding only the emptied paragraph keeps it. ` + "with cannot open a new block: after a hard line break inside with (two trailing spaces, or a backslash, before the newline) a heading, bullet, '1.'/'1)' ordered, or '>' blockquote marker is INVALID_OP too, since that line would stay escaped text inside the matched block - use insert, plus delete for what it replaces, to add the block. A hard break in with is itself INVALID_OP when the matched text is in a heading or a table cell, which are written on one line. " + "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 result carries the document token this edit produced, so a chain of guarded edits passes each result's token as the next edit's precondition with no dispatch_doc_read between them. " + "A batch that leaves the document exactly as it was mints no version, named or not, and the result says nothing changed and names each operation that did nothing. " + `The spec (or any document) holds requirements, design, and decisions - never progress, status, or timestamps. ${OWNER_REFERENCE} ${SPEC_WRITING_GUIDANCE}`,
|
|
30243
|
+
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 and must be balanced; a leading '# ' matches a heading at any level. replace is inline: with is the new text of the matched span, so a marker of a different kind from the block's own stays literal text ('4. Design' written into a heading). A with that opens with a marker of the same kind as the matched block's own would write it twice and is INVALID_OP - including prose that merely looks like one ('1999. was a year' into an ordered item), which you write as text by escaping it ('1999\\. was a year'). The exception is a heading rename whose find carried a heading marker: replace(find="## Old", with="## New") gives '## New', and a different level applies only when find named the heading's actual level (find "## Old" with "### New" makes it an h3), since '# ' selects a heading without naming its level. Any non-empty with that renders to no text - a line indented four spaces or a tab, which markdown reads as a code block, or whitespace alone - is INVALID_OP rather than a silent deletion; pass an empty with to delete the matched text on purpose - a list item, quote, typed block or footnote definition left holding only the emptied paragraph keeps it. ` + "with cannot open a new block: after a hard line break inside with (two trailing spaces, or a backslash, before the newline) a heading, bullet, '1.'/'1)' ordered, or '>' blockquote marker is INVALID_OP too, since that line would stay escaped text inside the matched block - use insert, plus delete for what it replaces, to add the block. A hard break in with is itself INVALID_OP when the matched text is in a heading or a table cell, which are written on one line. " + "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 result carries the document token this edit produced, so a chain of guarded edits passes each result's token as the next edit's precondition with no dispatch_doc_read between them. " + "A batch that leaves the document exactly as it was mints no version, named or not, and the result says nothing changed and names each operation that did nothing. " + "A change a browser removes while the edit is in flight is never reported as applied: EDIT_LOST_TO_CONCURRENT_CHANGE means the write was refused and nothing was written, so re-read the document and decide again, as with PRECONDITION_FAILED; lost_ops on a successful result names operations whose text the live document no longer has, because the deletion landed after the version was written. " + `The spec (or any document) holds requirements, design, and decisions - never progress, status, or timestamps. ${OWNER_REFERENCE} ${SPEC_WRITING_GUIDANCE}`,
|
|
30244
30244
|
arguments: (z2) => ({
|
|
30245
30245
|
issue: z2.string().describe(ISSUE_REFERENCE).optional(),
|
|
30246
30246
|
project: z2.string().describe("Project key owning the document.").optional(),
|
|
@@ -34095,7 +34095,9 @@ ${followsAsk(askOwner)}`,
|
|
|
34095
34095
|
const head = nothingChanged ? `Applied ${edited.applied} ops${retypedText}; nothing changed (${versionText})` : `Applied ${edited.applied} ops${retypedText} (${versionText})`;
|
|
34096
34096
|
const unchangedOps = edited.unchanged_ops ?? [];
|
|
34097
34097
|
const unchangedText = unchangedOps.length === 0 ? "" : `; ${unchangedOps.length === 1 ? "operation" : "operations"} ${unchangedOps.join(", ")} changed nothing`;
|
|
34098
|
-
const
|
|
34098
|
+
const lostOps = edited.lost_ops;
|
|
34099
|
+
const lostText = lostOps === undefined || lostOps !== null && lostOps.length === 0 ? "" : lostOps === null ? "; could not confirm this edit survived, because the live document is being reloaded \u2014 re-read it" : `; ${versionText} carries text the live document no longer has: a concurrent change removed what ${lostOps.length === 1 ? "operation" : "operations"} ${lostOps.join(", ")} wrote \u2014 re-read the document`;
|
|
34100
|
+
const applied = `${head}${unchangedText}${lostText}`;
|
|
34099
34101
|
const adviceLines = renderAdvice(input.tool, resolvedTopic(resolved).label, edited.advice, {});
|
|
34100
34102
|
const tokenTrailer = edited.token === undefined ? [] : [`Document token: ${edited.token}`];
|
|
34101
34103
|
return {
|
|
@@ -34109,6 +34111,7 @@ ${followsAsk(askOwner)}`,
|
|
|
34109
34111
|
applied: edited.applied,
|
|
34110
34112
|
...edited.version === null ? {} : { version: edited.version.number },
|
|
34111
34113
|
...nothingChanged ? { changed: false } : {},
|
|
34114
|
+
...lostOps === undefined ? {} : { lost_ops: lostOps },
|
|
34112
34115
|
...edited.token === undefined ? {} : { token: edited.token },
|
|
34113
34116
|
...edited.advice === undefined ? {} : { advice: edited.advice }
|
|
34114
34117
|
})
|
package/dist/legion.js
CHANGED
|
@@ -16142,7 +16142,7 @@ import { logger } from "@oh-my-pi/pi-utils";
|
|
|
16142
16142
|
// package.json
|
|
16143
16143
|
var package_default = {
|
|
16144
16144
|
name: "@sjawhar/pi-legion-envoy",
|
|
16145
|
-
version: "5.5.
|
|
16145
|
+
version: "5.5.10",
|
|
16146
16146
|
type: "module",
|
|
16147
16147
|
omp: {
|
|
16148
16148
|
extensions: [
|
|
@@ -30313,7 +30313,7 @@ var dispatchToolSpecs = [
|
|
|
30313
30313
|
ops: [{ op: "delete_column", block: "table-123", index: 1 }],
|
|
30314
30314
|
precondition: { blocks: [{ id: "table-123", token: "sha256:current-table-token" }] }
|
|
30315
30315
|
},
|
|
30316
|
-
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 and must be balanced; a leading '# ' matches a heading at any level. replace is inline: with is the new text of the matched span, so a marker of a different kind from the block's own stays literal text ('4. Design' written into a heading). A with that opens with a marker of the same kind as the matched block's own would write it twice and is INVALID_OP - including prose that merely looks like one ('1999. was a year' into an ordered item), which you write as text by escaping it ('1999\\. was a year'). The exception is a heading rename whose find carried a heading marker: replace(find="## Old", with="## New") gives '## New', and a different level applies only when find named the heading's actual level (find "## Old" with "### New" makes it an h3), since '# ' selects a heading without naming its level. Any non-empty with that renders to no text - a line indented four spaces or a tab, which markdown reads as a code block, or whitespace alone - is INVALID_OP rather than a silent deletion; pass an empty with to delete the matched text on purpose - a list item, quote, typed block or footnote definition left holding only the emptied paragraph keeps it. ` + "with cannot open a new block: after a hard line break inside with (two trailing spaces, or a backslash, before the newline) a heading, bullet, '1.'/'1)' ordered, or '>' blockquote marker is INVALID_OP too, since that line would stay escaped text inside the matched block - use insert, plus delete for what it replaces, to add the block. A hard break in with is itself INVALID_OP when the matched text is in a heading or a table cell, which are written on one line. " + "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 result carries the document token this edit produced, so a chain of guarded edits passes each result's token as the next edit's precondition with no dispatch_doc_read between them. " + "A batch that leaves the document exactly as it was mints no version, named or not, and the result says nothing changed and names each operation that did nothing. " + `The spec (or any document) holds requirements, design, and decisions - never progress, status, or timestamps. ${OWNER_REFERENCE} ${SPEC_WRITING_GUIDANCE}`,
|
|
30316
|
+
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 and must be balanced; a leading '# ' matches a heading at any level. replace is inline: with is the new text of the matched span, so a marker of a different kind from the block's own stays literal text ('4. Design' written into a heading). A with that opens with a marker of the same kind as the matched block's own would write it twice and is INVALID_OP - including prose that merely looks like one ('1999. was a year' into an ordered item), which you write as text by escaping it ('1999\\. was a year'). The exception is a heading rename whose find carried a heading marker: replace(find="## Old", with="## New") gives '## New', and a different level applies only when find named the heading's actual level (find "## Old" with "### New" makes it an h3), since '# ' selects a heading without naming its level. Any non-empty with that renders to no text - a line indented four spaces or a tab, which markdown reads as a code block, or whitespace alone - is INVALID_OP rather than a silent deletion; pass an empty with to delete the matched text on purpose - a list item, quote, typed block or footnote definition left holding only the emptied paragraph keeps it. ` + "with cannot open a new block: after a hard line break inside with (two trailing spaces, or a backslash, before the newline) a heading, bullet, '1.'/'1)' ordered, or '>' blockquote marker is INVALID_OP too, since that line would stay escaped text inside the matched block - use insert, plus delete for what it replaces, to add the block. A hard break in with is itself INVALID_OP when the matched text is in a heading or a table cell, which are written on one line. " + "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 result carries the document token this edit produced, so a chain of guarded edits passes each result's token as the next edit's precondition with no dispatch_doc_read between them. " + "A batch that leaves the document exactly as it was mints no version, named or not, and the result says nothing changed and names each operation that did nothing. " + "A change a browser removes while the edit is in flight is never reported as applied: EDIT_LOST_TO_CONCURRENT_CHANGE means the write was refused and nothing was written, so re-read the document and decide again, as with PRECONDITION_FAILED; lost_ops on a successful result names operations whose text the live document no longer has, because the deletion landed after the version was written. " + `The spec (or any document) holds requirements, design, and decisions - never progress, status, or timestamps. ${OWNER_REFERENCE} ${SPEC_WRITING_GUIDANCE}`,
|
|
30317
30317
|
arguments: (z2) => ({
|
|
30318
30318
|
issue: z2.string().describe(ISSUE_REFERENCE).optional(),
|
|
30319
30319
|
project: z2.string().describe("Project key owning the document.").optional(),
|
|
@@ -31587,6 +31587,10 @@ var LegionGoSignOffRequest = exports_external.strictObject({
|
|
|
31587
31587
|
grantId: nonEmptyString2,
|
|
31588
31588
|
issue: nonEmptyString2
|
|
31589
31589
|
});
|
|
31590
|
+
var LegionGoChildRequest = exports_external.strictObject({
|
|
31591
|
+
grantId: nonEmptyString2,
|
|
31592
|
+
issue: nonEmptyString2
|
|
31593
|
+
});
|
|
31590
31594
|
|
|
31591
31595
|
// src/legion/go-daemon-client.ts
|
|
31592
31596
|
class LegionGoDaemonApiError extends Error {
|
|
@@ -31682,7 +31686,9 @@ function createLegionGoDaemonClient(baseUrl, fetchFn = fetch) {
|
|
|
31682
31686
|
waveRelease: (input) => post("/legion/v1/waves/release", LegionGoWaveReleaseRequest, LegionGoWaveReleaseResponse, input),
|
|
31683
31687
|
phaseBackward: (input) => post("/legion/v1/phase/backward", LegionGoPhaseBackwardRequest, LegionGoEmptyResponse, input),
|
|
31684
31688
|
phaseRetry: (input) => post("/legion/v1/phase/retry", LegionGoPhaseRetryRequest, LegionGoEmptyResponse, input),
|
|
31685
|
-
signOff: (input) => post("/legion/v1/signoff", LegionGoSignOffRequest, LegionGoEmptyResponse, input)
|
|
31689
|
+
signOff: (input) => post("/legion/v1/signoff", LegionGoSignOffRequest, LegionGoEmptyResponse, input),
|
|
31690
|
+
childPark: (input) => post("/legion/v1/children/park", LegionGoChildRequest, LegionGoEmptyResponse, input),
|
|
31691
|
+
childRerun: (input) => post("/legion/v1/children/rerun", LegionGoChildRequest, LegionGoEmptyResponse, input)
|
|
31686
31692
|
};
|
|
31687
31693
|
}
|
|
31688
31694
|
|
|
@@ -31900,17 +31906,16 @@ function handoffSchemaFields(z2) {
|
|
|
31900
31906
|
ready: z2.boolean().optional()
|
|
31901
31907
|
};
|
|
31902
31908
|
}
|
|
31903
|
-
var HANDOFF_DESCRIPTION = "Handoff actions (phase workers and sub-architects): handoff_write writes this phase's handoff " + "(`phase`, and `data`: the phase-specific fields as a JSON object) to `.legion/<phase>.json` in " + "the issue workspace; handoff_read returns the handoffs (every phase, or `phase`); " + "handoff_complete reports this phase complete to the daemon (`summary`: two sentences for the " + "architect; `verdict` pass|fail when your role's instructions require one; `ready: true` for " + "the merger's READY; no `phase` is needed, and one other than your own is refused). Each " + "returns the command's output; a failed action changed nothing. What a later phase needs goes " + "in your handoff; a question for another live role goes to its role topic with envoy_publish.";
|
|
31909
|
+
var HANDOFF_DESCRIPTION = "Handoff actions (phase workers and sub-architects): handoff_write writes this phase's handoff " + "(`phase`, and `data`: the phase-specific fields as a JSON object, never the schemaVersion, " + "phase and completed the command writes itself) to `.legion/<phase>.json` in " + "the issue workspace; handoff_read returns the handoffs (every phase, or `phase`); " + "handoff_complete reports this phase complete to the daemon (`summary`: two sentences for the " + "architect; `verdict` pass|fail when your role's instructions require one; `ready: true` for " + "the merger's READY; no `phase` is needed, and one other than your own is refused). Each " + "returns the command's output; a failed action changed nothing. What a later phase needs goes " + "in your handoff; a question for another live role goes to its role topic with envoy_publish.";
|
|
31910
|
+
function rootArchitectHandoffRefusal(operation) {
|
|
31911
|
+
return `${operation} is not available to a root architect session; a root architect reads handoffs with \`legion handoff read [--phase <phase>]\` from bash`;
|
|
31912
|
+
}
|
|
31904
31913
|
var ROLE_HANDOFF_PHASE = {
|
|
31905
31914
|
architect: "architect",
|
|
31906
31915
|
planner: "plan",
|
|
31907
|
-
plan: "plan",
|
|
31908
31916
|
implementer: "implement",
|
|
31909
|
-
implement: "implement",
|
|
31910
31917
|
tester: "test",
|
|
31911
|
-
|
|
31912
|
-
reviewer: "review",
|
|
31913
|
-
review: "review"
|
|
31918
|
+
reviewer: "review"
|
|
31914
31919
|
};
|
|
31915
31920
|
var COMMAND_TIMEOUT_MS = 300000;
|
|
31916
31921
|
function required2(parameters, operation, name) {
|
|
@@ -32015,6 +32020,8 @@ var OPERATIONS = {
|
|
|
32015
32020
|
"request_backward_move",
|
|
32016
32021
|
"retry_or_escalate",
|
|
32017
32022
|
"sign_off",
|
|
32023
|
+
"park_child",
|
|
32024
|
+
"rerun_child",
|
|
32018
32025
|
"read_record"
|
|
32019
32026
|
],
|
|
32020
32027
|
"phase-worker": ["request_backward_move", "read_record"]
|
|
@@ -32025,6 +32032,8 @@ var OPERATION_FIELDS = {
|
|
|
32025
32032
|
request_backward_move: ["to", "reason"],
|
|
32026
32033
|
retry_or_escalate: ["issue", "decision"],
|
|
32027
32034
|
sign_off: ["issue"],
|
|
32035
|
+
park_child: ["issue"],
|
|
32036
|
+
rerun_child: ["issue"],
|
|
32028
32037
|
read_record: ["issue"]
|
|
32029
32038
|
};
|
|
32030
32039
|
var jsonSuccess = (details) => toolSuccess(JSON.stringify(details), details);
|
|
@@ -32037,6 +32046,8 @@ function toolSchema(pi) {
|
|
|
32037
32046
|
"request_backward_move",
|
|
32038
32047
|
"retry_or_escalate",
|
|
32039
32048
|
"sign_off",
|
|
32049
|
+
"park_child",
|
|
32050
|
+
"rerun_child",
|
|
32040
32051
|
"read_record",
|
|
32041
32052
|
...HANDOFF_OPERATIONS
|
|
32042
32053
|
]),
|
|
@@ -32097,7 +32108,7 @@ function createGoLegionTool(deps) {
|
|
|
32097
32108
|
const operation = requiredString(parameters, "legion", "op");
|
|
32098
32109
|
if (isHandoffOperation(operation)) {
|
|
32099
32110
|
if (active.kind === "architect" && active.issue === active.tree) {
|
|
32100
|
-
throw new Error(
|
|
32111
|
+
throw new Error(rootArchitectHandoffRefusal(operation));
|
|
32101
32112
|
}
|
|
32102
32113
|
return await runHandoffAction({
|
|
32103
32114
|
operation,
|
|
@@ -32169,6 +32180,13 @@ function createGoLegionTool(deps) {
|
|
|
32169
32180
|
});
|
|
32170
32181
|
return jsonSuccess({});
|
|
32171
32182
|
}
|
|
32183
|
+
case "park_child":
|
|
32184
|
+
case "rerun_child": {
|
|
32185
|
+
const grantId = await grantFor(client, active);
|
|
32186
|
+
const request = { grantId, issue: requiredString(parameters, operation, "issue") };
|
|
32187
|
+
await (operation === "park_child" ? client.childPark(request) : client.childRerun(request));
|
|
32188
|
+
return jsonSuccess({});
|
|
32189
|
+
}
|
|
32172
32190
|
default:
|
|
32173
32191
|
throw new Error(`Unsupported Go Legion operation: ${operation}`);
|
|
32174
32192
|
}
|
|
@@ -32282,7 +32300,7 @@ function createLegionTool(deps) {
|
|
|
32282
32300
|
const op = String(parameters.op);
|
|
32283
32301
|
if (isHandoffOperation(op)) {
|
|
32284
32302
|
if (active.kind !== "phase-worker") {
|
|
32285
|
-
throw new Error(
|
|
32303
|
+
throw new Error(rootArchitectHandoffRefusal(op));
|
|
32286
32304
|
}
|
|
32287
32305
|
return await runHandoffAction({
|
|
32288
32306
|
operation: op,
|
|
@@ -9,8 +9,8 @@ reject a stale edit.
|
|
|
9
9
|
dispatch_doc_edit({ issue?, project?, artifact, ops, precondition?, summary? })
|
|
10
10
|
```
|
|
11
11
|
It returns issue or project-document owner details plus `applied`, optional `version`, `changed`,
|
|
12
|
-
`unchanged_ops`, and the document token this edit produced, rendered as a
|
|
13
|
-
line. `ops` is an array of this exact `EditOp` shape:
|
|
12
|
+
`unchanged_ops`, `lost_ops`, and the document token this edit produced, rendered as a
|
|
13
|
+
`Document token: <token>` line. `ops` is an array of this exact `EditOp` shape:
|
|
14
14
|
|
|
15
15
|
```ts
|
|
16
16
|
type EditOp = {
|
|
@@ -160,6 +160,17 @@ token; Dispatch applies no part of that batch. It is the hashline `#TAG` propert
|
|
|
160
160
|
not line numbers: canonical Markdown lines shift under concurrent edits and rendering changes, while block ids
|
|
161
161
|
survive moves and retyping.
|
|
162
162
|
|
|
163
|
+
A browser editing the same document while your edit is in flight never makes your edit a silent
|
|
164
|
+
no-op. The two changes merge, and a human's deletion of the paragraph you are rewriting wins — but
|
|
165
|
+
you are told. `409 EDIT_LOST_TO_CONCURRENT_CHANGE` means the whole batch was refused and nothing
|
|
166
|
+
was written: re-read the document and decide again, as with `PRECONDITION_FAILED`. A success whose
|
|
167
|
+
`lost_ops` names operations means the version was written and the live document already lacks what
|
|
168
|
+
those operations wrote, because the deletion landed after the version: re-read before building on
|
|
169
|
+
it. `lost_ops: []` is the ordinary outcome, and a result that says it could not confirm the edit
|
|
170
|
+
survived means the room is reloading — re-read. An operation that only removes text (`delete`,
|
|
171
|
+
`delete_row`, `delete_column`, a `replace` that shortens) is never reported lost: a concurrent
|
|
172
|
+
deletion cannot undo a removal.
|
|
173
|
+
|
|
163
174
|
`retype` turns the paragraph or typed block with `block` into the named typed `type` in place. It keeps the
|
|
164
175
|
block id, keeps a typed block's body, and uses `attributes` for client-owned typed attributes. Use it when
|
|
165
176
|
an existing paragraph is the question that should become a decision.
|