@sjawhar/opencode-legion-envoy 3.19.3 → 3.19.5
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/src/server.js
CHANGED
|
@@ -14252,7 +14252,7 @@ var dispatchToolSpecs = [
|
|
|
14252
14252
|
{
|
|
14253
14253
|
name: "dispatch_read",
|
|
14254
14254
|
example: { issue: "DSP-1" },
|
|
14255
|
-
description: "Read an issue or project-document summary, targeted ask, or targeted comment reply chain, or the conversation " + "a message belongs to. Do not use it for document contents; use dispatch_doc_read instead. Supply ref, issue, " + "or project plus artifact; or message alone, which reads a human's direct message to this session and every " + "reply to it (they belong to no issue). " + "Every read ends with `Referenced by:` (what cites or hangs off this node, each with its dispatch:// address, " + "an excerpt, and when) and `Links:` (what it cites), so tracing provenance is one call. " + OWNER_REFERENCE,
|
|
14255
|
+
description: "Read an issue or project-document summary, targeted ask, or targeted comment reply chain, or the conversation " + "a message belongs to. Do not use it for document contents; use dispatch_doc_read instead. Supply ref, issue, " + "or project plus artifact; or message alone, which reads a human's direct message to this session and every " + "reply to it (they belong to no issue). " + "An anchored comment or ask also says where its quote sits, as `Position:`: the block's path from the top, " + "and in a table the row (0 is the header), the cells before the anchored one, and the column's header; " + "`Position: unavailable (<code>)` when Dispatch could not read the document: `DOC_SERVICE_UNAVAILABLE` " + "(try again shortly), `DOC_SCHEMA` (the document needs repair) or `INTERNAL`. " + "Every read ends with `Referenced by:` (what cites or hangs off this node, each with its dispatch:// address, " + "an excerpt, and when) and `Links:` (what it cites), so tracing provenance is one call. " + OWNER_REFERENCE,
|
|
14256
14256
|
arguments: (z2) => ({
|
|
14257
14257
|
issue: z2.string().describe(ISSUE_REFERENCE).optional(),
|
|
14258
14258
|
project: z2.string().describe("Project key owning the document.").optional(),
|
|
@@ -15144,6 +15144,7 @@ class DispatchServiceError extends Error {
|
|
|
15144
15144
|
current;
|
|
15145
15145
|
mismatches;
|
|
15146
15146
|
name = "DispatchServiceError";
|
|
15147
|
+
fromDispatch = true;
|
|
15147
15148
|
constructor(code, status, message, candidates, current, mismatches) {
|
|
15148
15149
|
super(message);
|
|
15149
15150
|
this.code = code;
|
|
@@ -15153,6 +15154,21 @@ class DispatchServiceError extends Error {
|
|
|
15153
15154
|
this.mismatches = mismatches;
|
|
15154
15155
|
}
|
|
15155
15156
|
}
|
|
15157
|
+
|
|
15158
|
+
class DispatchGatewayError extends DispatchServiceError {
|
|
15159
|
+
answer;
|
|
15160
|
+
advice;
|
|
15161
|
+
fromDispatch = false;
|
|
15162
|
+
transient;
|
|
15163
|
+
mayHaveReachedDispatch;
|
|
15164
|
+
constructor(status, answer, advice, message = `${answer}, so ${advice}.`) {
|
|
15165
|
+
super(`HTTP_${status}`, status, message);
|
|
15166
|
+
this.answer = answer;
|
|
15167
|
+
this.advice = advice;
|
|
15168
|
+
this.transient = transientStatus(status);
|
|
15169
|
+
this.mayHaveReachedDispatch = reachesDispatch(status);
|
|
15170
|
+
}
|
|
15171
|
+
}
|
|
15156
15172
|
function asErrorShape(value) {
|
|
15157
15173
|
return typeof value === "object" && value !== null ? value : {};
|
|
15158
15174
|
}
|
|
@@ -15166,7 +15182,7 @@ function requestSignal(signal) {
|
|
|
15166
15182
|
}
|
|
15167
15183
|
function whyNotAPage(answer) {
|
|
15168
15184
|
if (typeof answer === "string") {
|
|
15169
|
-
return
|
|
15185
|
+
return `text that is not a JSON page, ${GATEWAY_PAGE}, so a retry may succeed.`;
|
|
15170
15186
|
}
|
|
15171
15187
|
let what;
|
|
15172
15188
|
if (Array.isArray(answer)) {
|
|
@@ -15185,6 +15201,53 @@ function whyNotAPage(answer) {
|
|
|
15185
15201
|
}
|
|
15186
15202
|
return `${what}. Retrying will not help: the same request gets the same answer until that ` + "Dispatch is upgraded or fixed.";
|
|
15187
15203
|
}
|
|
15204
|
+
var GATEWAY_PAGE = "which looks like a proxy or gateway page rather than Dispatch's own answer";
|
|
15205
|
+
function gatewayAdvice(method, status) {
|
|
15206
|
+
if (!transientStatus(status)) {
|
|
15207
|
+
return "a retry gets the same answer until the Dispatch URL, or whatever answers in its place, is fixed";
|
|
15208
|
+
}
|
|
15209
|
+
if (method === "GET")
|
|
15210
|
+
return "a retry may succeed";
|
|
15211
|
+
return reachesDispatch(status) ? "the write may or may not have reached Dispatch: check whether it took effect before retrying it" : "the write did not reach Dispatch, and a retry may succeed";
|
|
15212
|
+
}
|
|
15213
|
+
function transientStatus(status) {
|
|
15214
|
+
return status >= 500 || status === 408 || status === 429;
|
|
15215
|
+
}
|
|
15216
|
+
function reachesDispatch(status) {
|
|
15217
|
+
return status >= 500;
|
|
15218
|
+
}
|
|
15219
|
+
var REDACTED = "[redacted]";
|
|
15220
|
+
var EXCERPT_SCAN_LIMIT = 64 * 1024;
|
|
15221
|
+
var BEARER_PIECE = 8;
|
|
15222
|
+
function redactBearer(text, bearer) {
|
|
15223
|
+
if (bearer === "")
|
|
15224
|
+
return text.slice(0, EXCERPT_SCAN_LIMIT);
|
|
15225
|
+
const length = Math.min(BEARER_PIECE, bearer.length);
|
|
15226
|
+
const scanned = text.slice(0, EXCERPT_SCAN_LIMIT + length - 1);
|
|
15227
|
+
const covered = new Uint8Array(scanned.length);
|
|
15228
|
+
const pieces = new Set;
|
|
15229
|
+
for (let at = 0;at + length <= bearer.length; at++)
|
|
15230
|
+
pieces.add(bearer.slice(at, at + length));
|
|
15231
|
+
for (const piece of pieces) {
|
|
15232
|
+
let found = scanned.indexOf(piece);
|
|
15233
|
+
while (found !== -1) {
|
|
15234
|
+
covered.fill(1, found, found + length);
|
|
15235
|
+
found = scanned.indexOf(piece, found + 1);
|
|
15236
|
+
}
|
|
15237
|
+
}
|
|
15238
|
+
let redacted = "";
|
|
15239
|
+
let kept = 0;
|
|
15240
|
+
for (let start = covered.indexOf(1);start !== -1; start = covered.indexOf(1, kept)) {
|
|
15241
|
+
redacted += `${scanned.slice(kept, start)}${REDACTED}`;
|
|
15242
|
+
const end = covered.indexOf(0, start);
|
|
15243
|
+
kept = end === -1 ? scanned.length : end;
|
|
15244
|
+
}
|
|
15245
|
+
return `${redacted}${scanned.slice(kept, EXCERPT_SCAN_LIMIT)}`.slice(0, EXCERPT_SCAN_LIMIT);
|
|
15246
|
+
}
|
|
15247
|
+
function excerpt(text, token) {
|
|
15248
|
+
const plain = redactBearer(text, token.trim()).replace(/<(script|style)\b[^<>]*>(?:[\s\S]*?<\/\1\s*>|[\s\S]*$)/gi, " ").replace(/<[^<>]*>/g, " ");
|
|
15249
|
+
return textHead(plain.replace(/(\bauthorization["']?\s*[:=]\s*["']?(?:(?:bearer|basic|digest|token)\s+)?)[^\s"'<>,;]+/gi, `$1${REDACTED}`).replace(/(\bbearer\s+)[^\s"'<>,;]+/gi, `$1${REDACTED}`));
|
|
15250
|
+
}
|
|
15188
15251
|
|
|
15189
15252
|
class DispatchClient {
|
|
15190
15253
|
token;
|
|
@@ -15442,22 +15505,24 @@ class DispatchClient {
|
|
|
15442
15505
|
};
|
|
15443
15506
|
if (body !== undefined)
|
|
15444
15507
|
headers["Content-Type"] = "application/json";
|
|
15445
|
-
const
|
|
15508
|
+
const url2 = this.#url(path2, query);
|
|
15509
|
+
const response = await this.fetchImpl(url2, {
|
|
15446
15510
|
method,
|
|
15447
15511
|
headers,
|
|
15448
15512
|
signal: this.#signal,
|
|
15449
15513
|
...body === undefined ? {} : { body: JSON.stringify(body) }
|
|
15450
15514
|
});
|
|
15451
|
-
return this.#response(response);
|
|
15515
|
+
return this.#response(method, url2, response);
|
|
15452
15516
|
}
|
|
15453
15517
|
async#form(method, path2, body) {
|
|
15454
|
-
const
|
|
15518
|
+
const url2 = this.#url(path2);
|
|
15519
|
+
const response = await this.fetchImpl(url2, {
|
|
15455
15520
|
method,
|
|
15456
15521
|
headers: { Accept: "application/json", Authorization: `Bearer ${this.token}` },
|
|
15457
15522
|
body,
|
|
15458
15523
|
signal: this.#signal
|
|
15459
15524
|
});
|
|
15460
|
-
return this.#response(response);
|
|
15525
|
+
return this.#response(method, url2, response);
|
|
15461
15526
|
}
|
|
15462
15527
|
#url(path2, query) {
|
|
15463
15528
|
const url2 = new URL(`${path2.map((segment) => encodeURIComponent(segment)).join("/")}`, `${this.#baseUrl}/`);
|
|
@@ -15473,7 +15538,7 @@ class DispatchClient {
|
|
|
15473
15538
|
}
|
|
15474
15539
|
return url2.toString();
|
|
15475
15540
|
}
|
|
15476
|
-
async#response(response) {
|
|
15541
|
+
async#response(method, url2, response) {
|
|
15477
15542
|
const text = await response.text();
|
|
15478
15543
|
let payload = text;
|
|
15479
15544
|
if (text && isJson(response)) {
|
|
@@ -15485,7 +15550,23 @@ class DispatchClient {
|
|
|
15485
15550
|
}
|
|
15486
15551
|
if (!response.ok) {
|
|
15487
15552
|
const error48 = asErrorShape(payload);
|
|
15488
|
-
|
|
15553
|
+
if (typeof error48.error === "string") {
|
|
15554
|
+
throw new DispatchServiceError(error48.code ?? `HTTP_${response.status}`, response.status, error48.error, error48.candidates, error48.current, error48.mismatches);
|
|
15555
|
+
}
|
|
15556
|
+
const quoted = excerpt(text, this.token);
|
|
15557
|
+
let body;
|
|
15558
|
+
if (quoted !== "") {
|
|
15559
|
+
body = `a body that is not Dispatch's error JSON (${JSON.stringify(quoted)})`;
|
|
15560
|
+
} else if (text === "") {
|
|
15561
|
+
body = "an empty body";
|
|
15562
|
+
} else {
|
|
15563
|
+
const within = text.length > EXCERPT_SCAN_LIMIT ? ` in its first ${EXCERPT_SCAN_LIMIT / 1024} KiB` : "";
|
|
15564
|
+
const size = Buffer.byteLength(text).toLocaleString("en-US");
|
|
15565
|
+
body = `a body of ${size} bytes and no readable text${within}`;
|
|
15566
|
+
}
|
|
15567
|
+
const reasonPhrase = excerpt(response.statusText, this.token);
|
|
15568
|
+
const reason = reasonPhrase === "" ? "" : ` ${reasonPhrase}`;
|
|
15569
|
+
throw new DispatchGatewayError(response.status, `${method} ${url2} answered ${response.status}${reason} with ${body}, ${GATEWAY_PAGE}`, gatewayAdvice(method, response.status));
|
|
15489
15570
|
}
|
|
15490
15571
|
return payload;
|
|
15491
15572
|
}
|
|
@@ -16228,7 +16309,7 @@ async function resolveArtifact(client, owner, artifactReference, { canonical = f
|
|
|
16228
16309
|
throw new Error("artifact is required for a project document");
|
|
16229
16310
|
}
|
|
16230
16311
|
const routed = await client.getProjectArtifact(owner.project, artifactReference).catch((error48) => {
|
|
16231
|
-
if (!(error48
|
|
16312
|
+
if (!dispatchAnswered(error48, 404))
|
|
16232
16313
|
throw error48;
|
|
16233
16314
|
return;
|
|
16234
16315
|
});
|
|
@@ -16396,12 +16477,12 @@ function referenceLines(edges) {
|
|
|
16396
16477
|
if (edges.length === 0)
|
|
16397
16478
|
return ["- none"];
|
|
16398
16479
|
return edges.map((edge) => {
|
|
16399
|
-
const
|
|
16400
|
-
return `- ${edge.kind} ${edge.node.kind} ${edge.node.ref ?? edge.node.id} (${
|
|
16480
|
+
const excerpt2 = edge.excerpt === undefined ? "" : `${textHead(edge.excerpt.text)} \xB7 `;
|
|
16481
|
+
return `- ${edge.kind} ${edge.node.kind} ${edge.node.ref ?? edge.node.id} (${excerpt2}${edge.created_at})`;
|
|
16401
16482
|
});
|
|
16402
16483
|
}
|
|
16403
16484
|
function unavailableReason(error48) {
|
|
16404
|
-
return error48
|
|
16485
|
+
return dispatchAnswered(error48, 404) ? "unavailable" : `unavailable: ${messageFor(error48)}`;
|
|
16405
16486
|
}
|
|
16406
16487
|
async function graphEdges(client, query) {
|
|
16407
16488
|
try {
|
|
@@ -16488,6 +16569,7 @@ function askSummary({ ask, replies }, graph) {
|
|
|
16488
16569
|
]);
|
|
16489
16570
|
return [
|
|
16490
16571
|
`Question: ${ask.question}`,
|
|
16572
|
+
...anchorLines(ask),
|
|
16491
16573
|
"Options:",
|
|
16492
16574
|
...ask.options.length === 0 ? ["- none"] : ask.options.map((option) => `- ${option.label}${option.description ? ` \u2014 ${option.description}` : ""}`),
|
|
16493
16575
|
`State: ${ask.state}`,
|
|
@@ -16551,12 +16633,12 @@ function formatOpenAsksSummary(response, baseUrl) {
|
|
|
16551
16633
|
function commentSummary({ comment, replies }, graph) {
|
|
16552
16634
|
const root = [
|
|
16553
16635
|
`${comment.id} \xB7 ${actorText(comment.author)}`,
|
|
16554
|
-
...comment
|
|
16636
|
+
...anchorLines(comment),
|
|
16555
16637
|
`Body: ${comment.body}`
|
|
16556
16638
|
];
|
|
16557
16639
|
const chain = replies.flatMap((reply) => [
|
|
16558
16640
|
`${reply.id} \xB7 ${actorText(reply.author)}`,
|
|
16559
|
-
...reply
|
|
16641
|
+
...anchorLines(reply),
|
|
16560
16642
|
`Body: ${reply.body}`
|
|
16561
16643
|
]);
|
|
16562
16644
|
return [
|
|
@@ -16568,6 +16650,28 @@ function commentSummary({ comment, replies }, graph) {
|
|
|
16568
16650
|
].join(`
|
|
16569
16651
|
`);
|
|
16570
16652
|
}
|
|
16653
|
+
function anchorLines(record2) {
|
|
16654
|
+
return [
|
|
16655
|
+
...record2.anchor?.quote === undefined ? [] : [`> ${record2.anchor.quote}`],
|
|
16656
|
+
...record2.anchor_block === undefined ? [] : [`Position: ${positionText(record2.anchor_block)}`],
|
|
16657
|
+
...record2.anchor_block_error === undefined ? [] : [`Position: unavailable (${record2.anchor_block_error})`]
|
|
16658
|
+
];
|
|
16659
|
+
}
|
|
16660
|
+
function positionText(block) {
|
|
16661
|
+
const { table, path: path2 } = block;
|
|
16662
|
+
const segments = path2.map((entry) => `${entry.type}[${entry.index}]`);
|
|
16663
|
+
if (table === undefined || table.row === null)
|
|
16664
|
+
return segments.join(" \u203A ");
|
|
16665
|
+
const tableAt = path2.findIndex((entry) => entry.type === "table");
|
|
16666
|
+
const cells = table.cells ?? [];
|
|
16667
|
+
const label = (table.column === null ? cells : cells.slice(0, table.column)).map((cell) => cell.trim()).filter((cell) => cell !== "" && !/^\d+$/.test(cell)).join(" \xB7 ");
|
|
16668
|
+
const row = label === "" ? `row ${table.row}` : `row ${table.row} (${label})`;
|
|
16669
|
+
const header = table.header === null || table.header === "" ? String(table.column) : table.header;
|
|
16670
|
+
return [
|
|
16671
|
+
...segments.slice(0, tableAt + 1),
|
|
16672
|
+
table.column === null ? row : `${row}, column ${header}`
|
|
16673
|
+
].join(" \u203A ");
|
|
16674
|
+
}
|
|
16571
16675
|
function messageSummary({ message, replies }, graph) {
|
|
16572
16676
|
const root = [`${message.id} \xB7 ${actorText(message.author)}`, `Body: ${message.body}`];
|
|
16573
16677
|
const chain = replies.flatMap((reply) => [
|
|
@@ -16591,9 +16695,8 @@ async function openArtifactMarks(client, resolved) {
|
|
|
16591
16695
|
const marks = asks.filter((ask) => ask.state === "open" && ask.anchor?.artifact_id === resolved.artifact.id).map((ask) => `ask ${ask.id}`);
|
|
16592
16696
|
const commentsResult = await commentsResultPromise;
|
|
16593
16697
|
if (commentsResult.status === "rejected") {
|
|
16594
|
-
if (commentsResult.reason
|
|
16698
|
+
if (dispatchAnswered(commentsResult.reason, 404))
|
|
16595
16699
|
return marks;
|
|
16596
|
-
}
|
|
16597
16700
|
throw commentsResult.reason;
|
|
16598
16701
|
}
|
|
16599
16702
|
return [
|
|
@@ -16680,10 +16783,30 @@ async function refuseRemovingOpenDecisionBlocks(client, tool, resolved, ops) {
|
|
|
16680
16783
|
`));
|
|
16681
16784
|
}
|
|
16682
16785
|
function refusalWithCode(error48, suffix = "") {
|
|
16786
|
+
if (error48 instanceof DispatchGatewayError) {
|
|
16787
|
+
let told = error48.message;
|
|
16788
|
+
if (suffix !== "") {
|
|
16789
|
+
told = error48.mayHaveReachedDispatch ? `${error48.answer}${suffix}` : `${error48.answer}, so ${error48.advice}${suffix}`;
|
|
16790
|
+
}
|
|
16791
|
+
return new DispatchGatewayError(error48.status, error48.answer, error48.advice, `${error48.code}: ${told}`);
|
|
16792
|
+
}
|
|
16683
16793
|
if (!(error48 instanceof DispatchServiceError))
|
|
16684
16794
|
return error48;
|
|
16685
16795
|
return new DispatchServiceError(error48.code, error48.status, `${error48.code}: ${error48.message}${suffix}`, error48.candidates, error48.current, error48.mismatches);
|
|
16686
16796
|
}
|
|
16797
|
+
function dispatchAnswered(error48, status) {
|
|
16798
|
+
return error48 instanceof DispatchServiceError && error48.fromDispatch && error48.status === status;
|
|
16799
|
+
}
|
|
16800
|
+
function writeMayHaveLanded(error48) {
|
|
16801
|
+
if (error48 instanceof DispatchGatewayError)
|
|
16802
|
+
return error48.mayHaveReachedDispatch;
|
|
16803
|
+
return !(error48 instanceof DispatchServiceError) || error48.status >= 500;
|
|
16804
|
+
}
|
|
16805
|
+
function withAccount(error48, account) {
|
|
16806
|
+
const message = messageFor(error48);
|
|
16807
|
+
const told = /[.!?]$/.test(message) ? `${message} ${account.charAt(0).toUpperCase()}${account.slice(1)}` : `${message}; ${account}`;
|
|
16808
|
+
return new Error(told, { cause: error48 });
|
|
16809
|
+
}
|
|
16687
16810
|
async function executeDispatchTool(input) {
|
|
16688
16811
|
const configUrl = input.config.url;
|
|
16689
16812
|
const configToken = input.config.token;
|
|
@@ -16853,7 +16976,10 @@ async function executeDispatchTool(input) {
|
|
|
16853
16976
|
ref: dispatchChildRef(dispatchIssueRef(issueKey), "message", message.id)
|
|
16854
16977
|
};
|
|
16855
16978
|
} catch (error48) {
|
|
16856
|
-
|
|
16979
|
+
const told = writeMayHaveLanded(error48) ? "the reason may or may not have been posted, and the close was not sent: read the issue's messages before retrying, since retrying this call posts its reason again" : "the reason was not posted, so the close was not sent";
|
|
16980
|
+
if (error48 instanceof DispatchServiceError)
|
|
16981
|
+
throw refusalWithCode(error48, `; ${told}`);
|
|
16982
|
+
throw withAccount(error48, told);
|
|
16857
16983
|
}
|
|
16858
16984
|
}
|
|
16859
16985
|
const linked = before.external_links.map((link) => link.url);
|
|
@@ -16872,17 +16998,15 @@ async function executeDispatchTool(input) {
|
|
|
16872
16998
|
actor
|
|
16873
16999
|
});
|
|
16874
17000
|
} catch (error48) {
|
|
16875
|
-
const taken = error48
|
|
17001
|
+
const taken = dispatchAnswered(error48, 500) && newLinks.length > 0 ? `; one of ${newLinks.join(", ")} may already be linked from another issue (a URL links exactly one issue)` : "";
|
|
16876
17002
|
if (closingNote === undefined)
|
|
16877
17003
|
throw refusalWithCode(error48, taken);
|
|
16878
|
-
const
|
|
16879
|
-
const
|
|
16880
|
-
const landed =
|
|
17004
|
+
const posted = `the reason already landed as message ${closingNote.id} (${closingNote.ref})`;
|
|
17005
|
+
const fix = error48 instanceof DispatchGatewayError && error48.transient ? "" : "fix what refused the close, then ";
|
|
17006
|
+
const landed = writeMayHaveLanded(error48) ? `${posted}, and the close may or may not have taken effect. Read the issue's status before retrying: done means it closed; otherwise retry with a reason that points at message ${closingNote.id}, since retrying this call posts its reason again` : `${posted} but the issue did not close. Retrying this call posts its reason again, so ${fix}retry with a reason that points at message ${closingNote.id}`;
|
|
16881
17007
|
if (error48 instanceof DispatchServiceError)
|
|
16882
|
-
throw refusalWithCode(error48, taken
|
|
16883
|
-
throw
|
|
16884
|
-
cause: error48
|
|
16885
|
-
});
|
|
17008
|
+
throw refusalWithCode(error48, `${taken}; ${landed}`);
|
|
17009
|
+
throw withAccount(error48, landed);
|
|
16886
17010
|
}
|
|
16887
17011
|
const linkCount = `(${after.external_links.length} ${after.external_links.length === 1 ? "link" : "links"})`;
|
|
16888
17012
|
const changes = [
|
|
@@ -17486,7 +17610,7 @@ async function resolveExistingIssue(client, issueReference) {
|
|
|
17486
17610
|
try {
|
|
17487
17611
|
return await client.resolveIssue(issueReference);
|
|
17488
17612
|
} catch (error48) {
|
|
17489
|
-
if (error48
|
|
17613
|
+
if (dispatchAnswered(error48, 404)) {
|
|
17490
17614
|
throw new Error(`no Dispatch issue is linked to ${issueReference}; create it first with ` + `dispatch_issue({ external: "${issueReference}", ... })`);
|
|
17491
17615
|
}
|
|
17492
17616
|
throw error48;
|
package/package.json
CHANGED
|
@@ -57,7 +57,10 @@ table's block id. A row index includes the header: row `0` is the header and its
|
|
|
57
57
|
row and any row's last column cannot be deleted. An index is required. A missing, non-integer, negative, or out-of-range index is
|
|
58
58
|
`INVALID_OP` on `index`, naming the supplied value and the table's actual dimensions before making any change. Markdown parsing
|
|
59
59
|
canonicalizes short ragged rows by padding missing cells, so column deletion preserves every non-selected cell in the canonical table.
|
|
60
|
-
`GET /api/v1/artifacts/<artifact UUID>/blocks` reports a table's own references plus its descendant cell anchors.
|
|
60
|
+
`GET /api/v1/artifacts/<artifact UUID>/blocks` reports a table's own references plus its descendant cell anchors.
|
|
61
|
+
`GET /api/v1/artifacts/<artifact UUID>/blocks/<block id>` places one block: its path from the top-level block down, and for a cell
|
|
62
|
+
the row index (0 is the header, what `delete_row` takes), the column index (what `delete_column` takes), the text of the header cell
|
|
63
|
+
drawn above it (with colspans or rowspans, the column the cell is drawn in) and the row's cells. A row or column
|
|
61
64
|
deletion that would remove an open ask or unresolved comment anchor is `INVALID_OP` on `index`, naming the axis and anchor ids;
|
|
62
65
|
answered asks and resolved comments are history and do not block it. A `find` or quote anchor tolerates inline Markdown
|
|
63
66
|
(`**bold**`, `` `code` ``) and a leading `# ` selects a heading by its text; a miss names the quote and the three nearest blocks so
|
|
@@ -16,6 +16,15 @@ document owner or ref, it returns a document summary with `details` `{ project,
|
|
|
16
16
|
question, options, state, answer, and its reply thread. With a comment ref, it returns that comment and its quoted reply chain. With a
|
|
17
17
|
message ref, it returns that message and its reply chain. Reads do not subscribe; use `dispatch_doc_read` for document contents.
|
|
18
18
|
|
|
19
|
+
An anchored comment or ask also prints `Position:`, where its quote's block stands — `table[3] › row 5 (Red-teamer loop), column Due`
|
|
20
|
+
is the fourth top-level block, a table, its row 5 (row 0 is the header; the index `delete_row` takes), labelled by the row's cells
|
|
21
|
+
before the anchored one, in the column headed Due; outside a table it is the path of types and child indexes down to the block. The
|
|
22
|
+
same facts are `anchor_block` on `GET /api/v1/comments/{id}` and `GET /api/v1/asks/{id}`, and
|
|
23
|
+
`GET /api/v1/artifacts/{id}/blocks/{block_id}` answers them for any block id a document holds. When Dispatch could not read the
|
|
24
|
+
document, the read still answers and prints `Position: unavailable (DOC_SERVICE_UNAVAILABLE)` (try again shortly),
|
|
25
|
+
`Position: unavailable (DOC_SCHEMA)` (the document needs repair) or `Position: unavailable (INTERNAL)`; the API carries that
|
|
26
|
+
code as `anchor_block_error`.
|
|
27
|
+
|
|
19
28
|
Every read ends with two sections from the reference graph. `Referenced by:` lists what points at the node — every document, ask,
|
|
20
29
|
comment, or message that cites it, plus its structure: child issues, attached documents, anchored and owned asks and comments, replies,
|
|
21
30
|
followers — and `Links:` lists what it cites. Each row is `- <edge kind> <node kind> dispatch://… (<excerpt> · <when>)`; for a
|