@sjawhar/opencode-legion-envoy 12.1.0 → 12.3.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/dispatch.js
CHANGED
|
@@ -1243,6 +1243,667 @@ function consumeMarker(stores, argv, args, files, now) {
|
|
|
1243
1243
|
};
|
|
1244
1244
|
}
|
|
1245
1245
|
|
|
1246
|
+
// ../envoy-client/src/askgate-new-asks.ts
|
|
1247
|
+
var OPENING_CODE_POINTS = 60;
|
|
1248
|
+
function newAsks(toolName, input) {
|
|
1249
|
+
switch (toolName) {
|
|
1250
|
+
case "dispatch_ask":
|
|
1251
|
+
return [questionAsk(input)];
|
|
1252
|
+
case "dispatch_issue":
|
|
1253
|
+
return typeof input.spec === "string" ? askBlocks(input.spec) : [];
|
|
1254
|
+
case "dispatch_artifact":
|
|
1255
|
+
return typeof input.content === "string" ? askBlocks(input.content) : [];
|
|
1256
|
+
case "dispatch_doc_edit":
|
|
1257
|
+
return (Array.isArray(input.ops) ? input.ops : []).flatMap((op) => {
|
|
1258
|
+
if (typeof op !== "object" || op === null)
|
|
1259
|
+
return [];
|
|
1260
|
+
const { op: kind, markdown, type, block } = op;
|
|
1261
|
+
if (kind === "insert" && typeof markdown === "string") {
|
|
1262
|
+
const { before, after } = op;
|
|
1263
|
+
return scanAsks(markdown, before === "start" || after === "start");
|
|
1264
|
+
}
|
|
1265
|
+
if (kind === "retype" && type === "ask" && typeof block === "string") {
|
|
1266
|
+
return [{ kind: "retype", id: block, text: "", opening: "" }];
|
|
1267
|
+
}
|
|
1268
|
+
return [];
|
|
1269
|
+
});
|
|
1270
|
+
default:
|
|
1271
|
+
return [];
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
function questionAsk(input) {
|
|
1275
|
+
const question = typeof input.question === "string" ? input.question.trim() : "";
|
|
1276
|
+
const options = (Array.isArray(input.options) ? input.options : []).flatMap((option) => {
|
|
1277
|
+
if (typeof option !== "object" || option === null)
|
|
1278
|
+
return [];
|
|
1279
|
+
const { label, description } = option;
|
|
1280
|
+
return [
|
|
1281
|
+
typeof description === "string" && description !== "" ? `- ${String(label ?? "")}: ${description}` : `- ${String(label ?? "")}`
|
|
1282
|
+
];
|
|
1283
|
+
});
|
|
1284
|
+
return {
|
|
1285
|
+
kind: "ask",
|
|
1286
|
+
text: options.length === 0 ? question : `${question}
|
|
1287
|
+
|
|
1288
|
+
${options.join(`
|
|
1289
|
+
`)}`,
|
|
1290
|
+
opening: openingOf(question)
|
|
1291
|
+
};
|
|
1292
|
+
}
|
|
1293
|
+
function openingOf(text) {
|
|
1294
|
+
return Array.from(text.replace(/\s+/g, " ").trim()).slice(0, OPENING_CODE_POINTS).join("");
|
|
1295
|
+
}
|
|
1296
|
+
var FRONT_MATTER_FENCE = /^---[ \t]*$/;
|
|
1297
|
+
function askBlocks(markdown) {
|
|
1298
|
+
return scanAsks(markdown, true);
|
|
1299
|
+
}
|
|
1300
|
+
function scanAsks(markdown, frontMatter) {
|
|
1301
|
+
let lines = markdown.replace(/\r\n?/g, `
|
|
1302
|
+
`).split(`
|
|
1303
|
+
`);
|
|
1304
|
+
let unclosedFrontMatter = false;
|
|
1305
|
+
if (frontMatter && FRONT_MATTER_FENCE.test(lines[0] ?? "")) {
|
|
1306
|
+
const close = lines.findIndex((line, index) => index > 0 && FRONT_MATTER_FENCE.test(line));
|
|
1307
|
+
if (close === -1)
|
|
1308
|
+
unclosedFrontMatter = true;
|
|
1309
|
+
else
|
|
1310
|
+
lines = lines.slice(close + 1);
|
|
1311
|
+
}
|
|
1312
|
+
const scan = {
|
|
1313
|
+
lines,
|
|
1314
|
+
index: 0,
|
|
1315
|
+
opened: [],
|
|
1316
|
+
drafts: [],
|
|
1317
|
+
emptyItemWithBlankLines: false,
|
|
1318
|
+
skipList: false,
|
|
1319
|
+
unclosedFrontMatter
|
|
1320
|
+
};
|
|
1321
|
+
const document = {
|
|
1322
|
+
kind: "document",
|
|
1323
|
+
parent: undefined,
|
|
1324
|
+
previous: undefined,
|
|
1325
|
+
children: 0,
|
|
1326
|
+
last: undefined
|
|
1327
|
+
};
|
|
1328
|
+
for (const [index, text] of lines.entries()) {
|
|
1329
|
+
scan.index = index;
|
|
1330
|
+
readLine(scan, document, new Line(text));
|
|
1331
|
+
}
|
|
1332
|
+
return scan.drafts.map(({ id, lines: body }) => {
|
|
1333
|
+
const text = body.join(`
|
|
1334
|
+
`).trim();
|
|
1335
|
+
return { kind: "block", ...id === undefined ? {} : { id }, text, opening: openingOf(text) };
|
|
1336
|
+
});
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
class Line {
|
|
1340
|
+
text;
|
|
1341
|
+
#starts;
|
|
1342
|
+
#pos = 0;
|
|
1343
|
+
#column = 0;
|
|
1344
|
+
constructor(text) {
|
|
1345
|
+
this.text = text;
|
|
1346
|
+
const starts = [0];
|
|
1347
|
+
let column = 0;
|
|
1348
|
+
for (let index = 0;index < text.length; index++) {
|
|
1349
|
+
column += text[index] === "\t" ? 4 - column % 4 : 1;
|
|
1350
|
+
starts.push(column);
|
|
1351
|
+
}
|
|
1352
|
+
this.#starts = starts;
|
|
1353
|
+
}
|
|
1354
|
+
get column() {
|
|
1355
|
+
return this.#column;
|
|
1356
|
+
}
|
|
1357
|
+
columnAt(index) {
|
|
1358
|
+
return this.#starts[index];
|
|
1359
|
+
}
|
|
1360
|
+
#afterSpaces(index) {
|
|
1361
|
+
let end = index;
|
|
1362
|
+
while (end < this.text.length && (this.text[end] === " " || this.text[end] === "\t"))
|
|
1363
|
+
end++;
|
|
1364
|
+
return end;
|
|
1365
|
+
}
|
|
1366
|
+
spacesAt(index) {
|
|
1367
|
+
return this.columnAt(this.#afterSpaces(index)) - this.columnAt(index);
|
|
1368
|
+
}
|
|
1369
|
+
indent() {
|
|
1370
|
+
const at = this.#afterSpaces(this.#pos);
|
|
1371
|
+
return { width: this.columnAt(at) - this.#column, at };
|
|
1372
|
+
}
|
|
1373
|
+
blank() {
|
|
1374
|
+
return this.indent().at === this.text.length;
|
|
1375
|
+
}
|
|
1376
|
+
skip(columns) {
|
|
1377
|
+
this.#column += columns;
|
|
1378
|
+
while (this.#pos < this.text.length && this.columnAt(this.#pos + 1) <= this.#column)
|
|
1379
|
+
this.#pos++;
|
|
1380
|
+
}
|
|
1381
|
+
advanceTo(index) {
|
|
1382
|
+
this.#pos = index;
|
|
1383
|
+
this.#column = this.columnAt(index);
|
|
1384
|
+
}
|
|
1385
|
+
rest() {
|
|
1386
|
+
const start = this.columnAt(this.#pos);
|
|
1387
|
+
if (this.#pos < this.text.length && start < this.#column) {
|
|
1388
|
+
return " ".repeat(this.columnAt(this.#pos + 1) - this.#column) + this.text.slice(this.#pos + 1);
|
|
1389
|
+
}
|
|
1390
|
+
return this.text.slice(this.#pos);
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
function readLine(scan, document, line) {
|
|
1394
|
+
if (scan.opened.length === 0) {
|
|
1395
|
+
if (!line.blank())
|
|
1396
|
+
openBlocks(scan, document, line);
|
|
1397
|
+
return;
|
|
1398
|
+
}
|
|
1399
|
+
const opened = [...scan.opened];
|
|
1400
|
+
const lastIndex = opened.length - 1;
|
|
1401
|
+
for (const [index, block] of opened.entries()) {
|
|
1402
|
+
if (block.kind !== "paragraph") {
|
|
1403
|
+
const continuation = continueBlock(scan, block, line);
|
|
1404
|
+
if (continuation === "children" && index === lastIndex) {
|
|
1405
|
+
openBlocks(scan, block, line);
|
|
1406
|
+
return;
|
|
1407
|
+
}
|
|
1408
|
+
if (continuation !== "close")
|
|
1409
|
+
continue;
|
|
1410
|
+
}
|
|
1411
|
+
if (openBlocks(scan, opened[index - 1] ?? document, line) !== "paragraph") {
|
|
1412
|
+
scan.opened.splice(index, lastIndex - index + 1);
|
|
1413
|
+
}
|
|
1414
|
+
return;
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
function openBlocks(scan, parent, line) {
|
|
1418
|
+
const continuable = scan.opened.at(-1)?.kind === "paragraph";
|
|
1419
|
+
let result = "none";
|
|
1420
|
+
for (let into = parent;; ) {
|
|
1421
|
+
const { width, at } = line.indent();
|
|
1422
|
+
if (at === line.text.length)
|
|
1423
|
+
break;
|
|
1424
|
+
const char = line.text.charAt(at);
|
|
1425
|
+
const interrupting = continuable && result === "none";
|
|
1426
|
+
let opened;
|
|
1427
|
+
for (const parser of BLOCK_PARSERS) {
|
|
1428
|
+
if (parser.triggers !== undefined && !parser.triggers.includes(char))
|
|
1429
|
+
continue;
|
|
1430
|
+
if (interrupting && !parser.interrupts)
|
|
1431
|
+
continue;
|
|
1432
|
+
if (width > 3 && !parser.indented)
|
|
1433
|
+
continue;
|
|
1434
|
+
opened = parser.open(scan, into, line, width, at);
|
|
1435
|
+
if (opened !== undefined)
|
|
1436
|
+
break;
|
|
1437
|
+
}
|
|
1438
|
+
if (opened === undefined)
|
|
1439
|
+
break;
|
|
1440
|
+
into.children++;
|
|
1441
|
+
into.last = opened.block;
|
|
1442
|
+
scan.opened.push(opened.block);
|
|
1443
|
+
result = "opened";
|
|
1444
|
+
if (!opened.children)
|
|
1445
|
+
break;
|
|
1446
|
+
into = opened.block;
|
|
1447
|
+
}
|
|
1448
|
+
const last = scan.opened.at(-1);
|
|
1449
|
+
if (result === "none" && last?.kind === "paragraph" && !line.blank()) {
|
|
1450
|
+
last.rows.push(line.rest());
|
|
1451
|
+
last.lastLine = scan.index;
|
|
1452
|
+
line.advanceTo(line.text.length);
|
|
1453
|
+
return "paragraph";
|
|
1454
|
+
}
|
|
1455
|
+
return result;
|
|
1456
|
+
}
|
|
1457
|
+
function under(parent) {
|
|
1458
|
+
return { parent, previous: parent.last, children: 0, last: undefined };
|
|
1459
|
+
}
|
|
1460
|
+
function leafAt(parent, line) {
|
|
1461
|
+
line.advanceTo(line.text.length);
|
|
1462
|
+
return { block: { kind: "leaf", ...under(parent) }, children: false };
|
|
1463
|
+
}
|
|
1464
|
+
function frontMatterHolds(scan, parent) {
|
|
1465
|
+
return scan.unclosedFrontMatter && parent.kind === "document";
|
|
1466
|
+
}
|
|
1467
|
+
var BLOCK_PARSERS = [
|
|
1468
|
+
{
|
|
1469
|
+
triggers: "-=",
|
|
1470
|
+
interrupts: true,
|
|
1471
|
+
indented: false,
|
|
1472
|
+
open(scan, parent, line, _width, at) {
|
|
1473
|
+
const last = scan.opened.at(-1);
|
|
1474
|
+
if (last?.kind !== "paragraph" || last.parent !== parent)
|
|
1475
|
+
return;
|
|
1476
|
+
return setextUnderline(line.text.slice(at)) ? leafAt(parent, line) : undefined;
|
|
1477
|
+
}
|
|
1478
|
+
},
|
|
1479
|
+
{
|
|
1480
|
+
triggers: "-*_",
|
|
1481
|
+
interrupts: true,
|
|
1482
|
+
indented: false,
|
|
1483
|
+
open: (_scan, parent, line, _width, at) => thematicBreak(line.text.slice(at)) ? leafAt(parent, line) : undefined
|
|
1484
|
+
},
|
|
1485
|
+
{
|
|
1486
|
+
triggers: "-+*0123456789",
|
|
1487
|
+
interrupts: true,
|
|
1488
|
+
indented: false,
|
|
1489
|
+
open(scan, parent, line) {
|
|
1490
|
+
const last = scan.opened.at(-1);
|
|
1491
|
+
if (last?.kind === "list" || scan.skipList) {
|
|
1492
|
+
scan.skipList = false;
|
|
1493
|
+
return;
|
|
1494
|
+
}
|
|
1495
|
+
const marker = listMarker(line);
|
|
1496
|
+
if (marker === undefined || frontMatterHolds(scan, parent))
|
|
1497
|
+
return;
|
|
1498
|
+
if (marker.empty || marker.number !== undefined && marker.number !== "1") {
|
|
1499
|
+
if (last?.kind === "paragraph" && last.parent === parent)
|
|
1500
|
+
return;
|
|
1501
|
+
if (interruptsOpenBlock(scan, parent))
|
|
1502
|
+
return;
|
|
1503
|
+
}
|
|
1504
|
+
scan.emptyItemWithBlankLines = false;
|
|
1505
|
+
return { block: { kind: "list", marker: marker.char, ...under(parent) }, children: true };
|
|
1506
|
+
}
|
|
1507
|
+
},
|
|
1508
|
+
{
|
|
1509
|
+
triggers: "-+*0123456789",
|
|
1510
|
+
interrupts: true,
|
|
1511
|
+
indented: false,
|
|
1512
|
+
open(scan, parent, line) {
|
|
1513
|
+
if (parent.kind !== "list")
|
|
1514
|
+
return;
|
|
1515
|
+
const previous = parent.last?.kind === "item" ? parent.last.offset : 0;
|
|
1516
|
+
const marker = listMarker(line);
|
|
1517
|
+
if (marker === undefined || marker.indent - previous > 3)
|
|
1518
|
+
return;
|
|
1519
|
+
scan.emptyItemWithBlankLines = false;
|
|
1520
|
+
const after = line.spacesAt(marker.end);
|
|
1521
|
+
const spacing = marker.empty || after > 4 ? 1 : after;
|
|
1522
|
+
const offset = line.columnAt(marker.end) - line.column + spacing;
|
|
1523
|
+
const item = { kind: "item", offset, ...under(parent) };
|
|
1524
|
+
if (marker.empty)
|
|
1525
|
+
return { block: item, children: false };
|
|
1526
|
+
line.advanceTo(marker.end);
|
|
1527
|
+
line.skip(spacing);
|
|
1528
|
+
return { block: item, children: true };
|
|
1529
|
+
}
|
|
1530
|
+
},
|
|
1531
|
+
{
|
|
1532
|
+
interrupts: false,
|
|
1533
|
+
indented: true,
|
|
1534
|
+
open(scan, parent, line, width) {
|
|
1535
|
+
if (width < 4)
|
|
1536
|
+
return;
|
|
1537
|
+
line.skip(4);
|
|
1538
|
+
const previous = scan.lines[scan.index - 1];
|
|
1539
|
+
const block = {
|
|
1540
|
+
kind: "code",
|
|
1541
|
+
lastLine: scan.index,
|
|
1542
|
+
blankBefore: previous === undefined || /^[ \t]*$/.test(previous),
|
|
1543
|
+
...under(parent)
|
|
1544
|
+
};
|
|
1545
|
+
return { block, children: false };
|
|
1546
|
+
}
|
|
1547
|
+
},
|
|
1548
|
+
{
|
|
1549
|
+
triggers: "#",
|
|
1550
|
+
interrupts: true,
|
|
1551
|
+
indented: false,
|
|
1552
|
+
open: (_scan, parent, line, _width, at) => /^#{1,6}(?:[ \t]|$)/.test(line.text.slice(at)) ? leafAt(parent, line) : undefined
|
|
1553
|
+
},
|
|
1554
|
+
{
|
|
1555
|
+
triggers: "`~",
|
|
1556
|
+
interrupts: true,
|
|
1557
|
+
indented: false,
|
|
1558
|
+
open(_scan, parent, line, _width, at) {
|
|
1559
|
+
const char = line.text.charAt(at);
|
|
1560
|
+
const length = runOf(line.text, at, char);
|
|
1561
|
+
if (length < 3)
|
|
1562
|
+
return;
|
|
1563
|
+
if (char === "`" && line.text.slice(at + length).includes("`"))
|
|
1564
|
+
return;
|
|
1565
|
+
line.advanceTo(line.text.length);
|
|
1566
|
+
return { block: { kind: "fence", char, length, ...under(parent) }, children: false };
|
|
1567
|
+
}
|
|
1568
|
+
},
|
|
1569
|
+
{
|
|
1570
|
+
triggers: ">",
|
|
1571
|
+
interrupts: true,
|
|
1572
|
+
indented: false,
|
|
1573
|
+
open: (scan, parent, line) => !frontMatterHolds(scan, parent) && quoteMarker(line) ? { block: { kind: "quote", ...under(parent) }, children: true } : undefined
|
|
1574
|
+
},
|
|
1575
|
+
{
|
|
1576
|
+
triggers: ":",
|
|
1577
|
+
interrupts: true,
|
|
1578
|
+
indented: false,
|
|
1579
|
+
open: (_scan, parent, line, _width, at) => refusedDirective(line.text.slice(at)) ? leafAt(parent, line) : undefined
|
|
1580
|
+
},
|
|
1581
|
+
{
|
|
1582
|
+
triggers: ":",
|
|
1583
|
+
interrupts: true,
|
|
1584
|
+
indented: false,
|
|
1585
|
+
open(scan, parent, line, width, at) {
|
|
1586
|
+
const opener = typedOpener(line.text.slice(at));
|
|
1587
|
+
if (opener === undefined)
|
|
1588
|
+
return;
|
|
1589
|
+
let draft;
|
|
1590
|
+
if (opener.name === "ask") {
|
|
1591
|
+
draft = { id: opener.id, lines: [] };
|
|
1592
|
+
scan.drafts.push(draft);
|
|
1593
|
+
}
|
|
1594
|
+
line.advanceTo(line.text.length);
|
|
1595
|
+
const block = {
|
|
1596
|
+
kind: "typed",
|
|
1597
|
+
fence: opener.colons,
|
|
1598
|
+
indent: width,
|
|
1599
|
+
draft,
|
|
1600
|
+
...under(parent)
|
|
1601
|
+
};
|
|
1602
|
+
return { block, children: true };
|
|
1603
|
+
}
|
|
1604
|
+
},
|
|
1605
|
+
{
|
|
1606
|
+
triggers: "[",
|
|
1607
|
+
interrupts: true,
|
|
1608
|
+
indented: false,
|
|
1609
|
+
open(scan, parent, line, _width, at) {
|
|
1610
|
+
const definition = /^\[\^([^\]]*)\]:/.exec(line.text.slice(at));
|
|
1611
|
+
if (definition === null || /^\s*$/.test(definition[1] ?? ""))
|
|
1612
|
+
return;
|
|
1613
|
+
if (frontMatterHolds(scan, parent))
|
|
1614
|
+
return;
|
|
1615
|
+
const label = at + definition[0].length;
|
|
1616
|
+
const content = line.text.slice(label).search(/[^ \t]/);
|
|
1617
|
+
line.advanceTo(content < 0 ? label : label + content);
|
|
1618
|
+
return { block: { kind: "footnote", ...under(parent) }, children: true };
|
|
1619
|
+
}
|
|
1620
|
+
},
|
|
1621
|
+
{
|
|
1622
|
+
interrupts: false,
|
|
1623
|
+
indented: false,
|
|
1624
|
+
open: (scan, parent, line) => {
|
|
1625
|
+
const block = {
|
|
1626
|
+
kind: "paragraph",
|
|
1627
|
+
rows: [line.rest()],
|
|
1628
|
+
lastLine: scan.index,
|
|
1629
|
+
...under(parent)
|
|
1630
|
+
};
|
|
1631
|
+
line.advanceTo(line.text.length);
|
|
1632
|
+
return { block, children: false };
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
];
|
|
1636
|
+
function interruptsOpenBlock(scan, parent) {
|
|
1637
|
+
let previous = parent.last;
|
|
1638
|
+
const opensContainers = previous === undefined;
|
|
1639
|
+
let node = parent;
|
|
1640
|
+
while (previous === undefined && node !== undefined && node.kind !== "document") {
|
|
1641
|
+
previous = node.previous;
|
|
1642
|
+
node = node.parent;
|
|
1643
|
+
}
|
|
1644
|
+
if (previous?.kind === "paragraph") {
|
|
1645
|
+
return opensContainers && !previous.rows.slice(1).some((row) => TABLE_DELIMITER_ROW.test(row)) && scan.index - previous.lastLine <= 1;
|
|
1646
|
+
}
|
|
1647
|
+
if (previous?.kind !== "code")
|
|
1648
|
+
return false;
|
|
1649
|
+
if (previous.previous?.kind === "list")
|
|
1650
|
+
return false;
|
|
1651
|
+
if (previous.previous?.kind === "quote" && !previous.blankBefore)
|
|
1652
|
+
return false;
|
|
1653
|
+
return scan.lines.slice(previous.lastLine + 1, scan.index).every((row) => /^[ \t>]*$/.test(row));
|
|
1654
|
+
}
|
|
1655
|
+
var TABLE_DELIMITER_ROW = /^(?=.*\|)[ \t]*\|?[ \t]*:?-+:?[ \t]*(?:\|[ \t]*:?-+:?[ \t]*)*\|?[ \t]*$/;
|
|
1656
|
+
function continueBlock(scan, block, line) {
|
|
1657
|
+
switch (block.kind) {
|
|
1658
|
+
case "quote":
|
|
1659
|
+
return quoteMarker(line) ? "children" : "close";
|
|
1660
|
+
case "list":
|
|
1661
|
+
return continueList(scan, block, line);
|
|
1662
|
+
case "item":
|
|
1663
|
+
return continueItem(scan, block, line);
|
|
1664
|
+
case "footnote": {
|
|
1665
|
+
if (line.blank())
|
|
1666
|
+
return "children";
|
|
1667
|
+
const { width } = line.indent();
|
|
1668
|
+
if (width < 4)
|
|
1669
|
+
return "close";
|
|
1670
|
+
line.skip(4);
|
|
1671
|
+
return "children";
|
|
1672
|
+
}
|
|
1673
|
+
case "typed": {
|
|
1674
|
+
const { width, at } = line.indent();
|
|
1675
|
+
if (width < 4 && at < line.text.length && fenceColons(line.text.slice(at)) >= block.fence) {
|
|
1676
|
+
line.advanceTo(line.text.length);
|
|
1677
|
+
return "close";
|
|
1678
|
+
}
|
|
1679
|
+
if (at < line.text.length)
|
|
1680
|
+
line.skip(Math.min(width, block.indent));
|
|
1681
|
+
block.draft?.lines.push(line.rest());
|
|
1682
|
+
return "children";
|
|
1683
|
+
}
|
|
1684
|
+
case "fence": {
|
|
1685
|
+
const { width, at } = line.indent();
|
|
1686
|
+
if (width < 4) {
|
|
1687
|
+
const length = runOf(line.text, at, block.char);
|
|
1688
|
+
if (length >= block.length && /^[ \t]*$/.test(line.text.slice(at + length))) {
|
|
1689
|
+
line.advanceTo(line.text.length);
|
|
1690
|
+
return "close";
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
line.advanceTo(line.text.length);
|
|
1694
|
+
return "continue";
|
|
1695
|
+
}
|
|
1696
|
+
case "code": {
|
|
1697
|
+
if (line.blank())
|
|
1698
|
+
return "continue";
|
|
1699
|
+
if (line.indent().width < 4)
|
|
1700
|
+
return "close";
|
|
1701
|
+
block.lastLine = scan.index;
|
|
1702
|
+
line.advanceTo(line.text.length);
|
|
1703
|
+
return "continue";
|
|
1704
|
+
}
|
|
1705
|
+
default:
|
|
1706
|
+
return block.kind === "document" ? "children" : "close";
|
|
1707
|
+
}
|
|
1708
|
+
}
|
|
1709
|
+
function continueList(scan, list, line) {
|
|
1710
|
+
const last = list.last;
|
|
1711
|
+
const lastIsEmpty = last === undefined || last.children === 0;
|
|
1712
|
+
if (line.blank()) {
|
|
1713
|
+
if (lastIsEmpty)
|
|
1714
|
+
scan.emptyItemWithBlankLines = true;
|
|
1715
|
+
return "children";
|
|
1716
|
+
}
|
|
1717
|
+
const offset = last?.kind === "item" ? last.offset : 0;
|
|
1718
|
+
const { width, at } = line.indent();
|
|
1719
|
+
if (width < offset || lastIsEmpty) {
|
|
1720
|
+
if (width < 4) {
|
|
1721
|
+
const marker = listMarker(line);
|
|
1722
|
+
if (marker !== undefined && marker.indent - offset < 4) {
|
|
1723
|
+
if (marker.char !== list.marker)
|
|
1724
|
+
return "close";
|
|
1725
|
+
const fromMarker = line.text.slice(at);
|
|
1726
|
+
const heading = scan.opened.at(-1)?.kind === "paragraph" && setextUnderline(fromMarker);
|
|
1727
|
+
if (thematicBreak(fromMarker) && !heading)
|
|
1728
|
+
return "close";
|
|
1729
|
+
return "children";
|
|
1730
|
+
}
|
|
1731
|
+
}
|
|
1732
|
+
if (!lastIsEmpty)
|
|
1733
|
+
return "close";
|
|
1734
|
+
}
|
|
1735
|
+
if (lastIsEmpty && width < offset)
|
|
1736
|
+
return "close";
|
|
1737
|
+
if (scan.emptyItemWithBlankLines)
|
|
1738
|
+
return "close";
|
|
1739
|
+
return "children";
|
|
1740
|
+
}
|
|
1741
|
+
function continueItem(scan, item, line) {
|
|
1742
|
+
const { width } = line.indent();
|
|
1743
|
+
if (line.blank()) {
|
|
1744
|
+
line.skip(Math.min(width, item.offset));
|
|
1745
|
+
return "children";
|
|
1746
|
+
}
|
|
1747
|
+
const isEmpty = item.children === 0 && scan.emptyItemWithBlankLines;
|
|
1748
|
+
if ((isEmpty || width < item.offset) && width < 4) {
|
|
1749
|
+
if (listMarker(line) !== undefined) {
|
|
1750
|
+
scan.skipList = true;
|
|
1751
|
+
return "close";
|
|
1752
|
+
}
|
|
1753
|
+
if (!isEmpty)
|
|
1754
|
+
return "close";
|
|
1755
|
+
}
|
|
1756
|
+
line.skip(Math.min(width, item.offset));
|
|
1757
|
+
return "children";
|
|
1758
|
+
}
|
|
1759
|
+
function listMarker(line) {
|
|
1760
|
+
const { width, at } = line.indent();
|
|
1761
|
+
if (width > 3)
|
|
1762
|
+
return;
|
|
1763
|
+
const { text } = line;
|
|
1764
|
+
const first = text.charAt(at);
|
|
1765
|
+
let end = at;
|
|
1766
|
+
let number;
|
|
1767
|
+
if (first === "-" || first === "*" || first === "+") {
|
|
1768
|
+
end = at + 1;
|
|
1769
|
+
} else {
|
|
1770
|
+
while (end < text.length && end - at <= 9 && /[0-9]/.test(text.charAt(end)))
|
|
1771
|
+
end++;
|
|
1772
|
+
const delimiter = text.charAt(end);
|
|
1773
|
+
if (end === at || end - at > 9 || delimiter !== "." && delimiter !== ")")
|
|
1774
|
+
return;
|
|
1775
|
+
number = text.slice(at, end);
|
|
1776
|
+
end++;
|
|
1777
|
+
}
|
|
1778
|
+
const after = text.charAt(end);
|
|
1779
|
+
if (end < text.length && after !== " " && after !== "\t")
|
|
1780
|
+
return;
|
|
1781
|
+
return {
|
|
1782
|
+
indent: width,
|
|
1783
|
+
end,
|
|
1784
|
+
char: text.charAt(end - 1),
|
|
1785
|
+
number,
|
|
1786
|
+
empty: /^[ \t]*$/.test(text.slice(end))
|
|
1787
|
+
};
|
|
1788
|
+
}
|
|
1789
|
+
function quoteMarker(line) {
|
|
1790
|
+
const { width, at } = line.indent();
|
|
1791
|
+
if (width > 3 || line.text.charAt(at) !== ">")
|
|
1792
|
+
return false;
|
|
1793
|
+
line.advanceTo(at + 1);
|
|
1794
|
+
const next = line.text.charAt(at + 1);
|
|
1795
|
+
if (next === " " || next === "\t")
|
|
1796
|
+
line.skip(1);
|
|
1797
|
+
return true;
|
|
1798
|
+
}
|
|
1799
|
+
function thematicBreak(text) {
|
|
1800
|
+
const marks = text.replace(/[ \t]/g, "");
|
|
1801
|
+
return marks.length >= 3 && /^(?:\*+|-+|_+)$/.test(marks);
|
|
1802
|
+
}
|
|
1803
|
+
function setextUnderline(text) {
|
|
1804
|
+
return /^(?:=+|-+)[ \t]*$/.test(text);
|
|
1805
|
+
}
|
|
1806
|
+
function runOf(text, index, char) {
|
|
1807
|
+
let end = index;
|
|
1808
|
+
while (text.charAt(end) === char)
|
|
1809
|
+
end++;
|
|
1810
|
+
return end - index;
|
|
1811
|
+
}
|
|
1812
|
+
function fenceColons(text) {
|
|
1813
|
+
const trimmed = text.replace(/^[ \t]+|[ \t]+$/g, "");
|
|
1814
|
+
return /^:+$/.test(trimmed) ? trimmed.length : 0;
|
|
1815
|
+
}
|
|
1816
|
+
function nameChar(char) {
|
|
1817
|
+
return /^[A-Za-z0-9_-]$/.test(char);
|
|
1818
|
+
}
|
|
1819
|
+
function typedOpener(text) {
|
|
1820
|
+
const colons = runOf(text, 0, ":");
|
|
1821
|
+
if (colons < 3)
|
|
1822
|
+
return;
|
|
1823
|
+
const rest = text.slice(colons);
|
|
1824
|
+
let nameEnd = 0;
|
|
1825
|
+
while (nameEnd < rest.length && nameChar(rest.charAt(nameEnd)))
|
|
1826
|
+
nameEnd++;
|
|
1827
|
+
if (nameEnd === 0 || rest.charAt(nameEnd) !== "{" || !rest.endsWith("}"))
|
|
1828
|
+
return;
|
|
1829
|
+
const attributes = directiveAttributes(rest.slice(nameEnd + 1, -1));
|
|
1830
|
+
if (attributes === undefined)
|
|
1831
|
+
return;
|
|
1832
|
+
return { name: rest.slice(0, nameEnd), id: attributes.id, colons };
|
|
1833
|
+
}
|
|
1834
|
+
function refusedDirective(text) {
|
|
1835
|
+
if (text.startsWith(":::"))
|
|
1836
|
+
return text !== ":::" && typedOpener(text) === undefined;
|
|
1837
|
+
if (text.startsWith("::"))
|
|
1838
|
+
return text.length > 2 && nameChar(text.charAt(2));
|
|
1839
|
+
return text.length > 1 && nameChar(text.charAt(1)) && text.includes("{");
|
|
1840
|
+
}
|
|
1841
|
+
function attributeSpace(char) {
|
|
1842
|
+
return char === " " || char >= "\t" && char <= "\r";
|
|
1843
|
+
}
|
|
1844
|
+
var BLOCK_ID_ATTRIBUTE = "blockId";
|
|
1845
|
+
function directiveAttributes(value) {
|
|
1846
|
+
const named = new Set;
|
|
1847
|
+
let id;
|
|
1848
|
+
let offset = 0;
|
|
1849
|
+
const name = () => {
|
|
1850
|
+
const start = offset;
|
|
1851
|
+
while (offset < value.length && nameChar(value.charAt(offset)))
|
|
1852
|
+
offset++;
|
|
1853
|
+
return value.slice(start, offset);
|
|
1854
|
+
};
|
|
1855
|
+
while (offset < value.length) {
|
|
1856
|
+
while (offset < value.length && attributeSpace(value.charAt(offset)))
|
|
1857
|
+
offset++;
|
|
1858
|
+
if (offset === value.length)
|
|
1859
|
+
break;
|
|
1860
|
+
const sigil = value.charAt(offset);
|
|
1861
|
+
if (sigil === "#") {
|
|
1862
|
+
offset++;
|
|
1863
|
+
const read = name();
|
|
1864
|
+
if (read === "" || named.has(BLOCK_ID_ATTRIBUTE))
|
|
1865
|
+
return;
|
|
1866
|
+
named.add(BLOCK_ID_ATTRIBUTE);
|
|
1867
|
+
id = read;
|
|
1868
|
+
} else if (sigil === ".") {
|
|
1869
|
+
offset++;
|
|
1870
|
+
if (name() === "")
|
|
1871
|
+
return;
|
|
1872
|
+
named.add("class");
|
|
1873
|
+
} else {
|
|
1874
|
+
const key = name();
|
|
1875
|
+
if (key === "" || named.has(key))
|
|
1876
|
+
return;
|
|
1877
|
+
named.add(key);
|
|
1878
|
+
if (offset === value.length || value.charAt(offset) === " ")
|
|
1879
|
+
continue;
|
|
1880
|
+
if (value.charAt(offset) !== "=" || value.charAt(offset + 1) !== '"')
|
|
1881
|
+
return;
|
|
1882
|
+
offset += 2;
|
|
1883
|
+
const start = offset;
|
|
1884
|
+
let escaped = false;
|
|
1885
|
+
while (offset < value.length && (value.charAt(offset) !== '"' || escaped)) {
|
|
1886
|
+
escaped = value.charAt(offset) === "\\" && !escaped;
|
|
1887
|
+
offset++;
|
|
1888
|
+
}
|
|
1889
|
+
if (offset === value.length)
|
|
1890
|
+
return;
|
|
1891
|
+
const quoted = value.slice(start, offset);
|
|
1892
|
+
if (!goQuoted(quoted))
|
|
1893
|
+
return;
|
|
1894
|
+
if (key === BLOCK_ID_ATTRIBUTE)
|
|
1895
|
+
id = /[\\&]/.test(quoted) ? undefined : quoted;
|
|
1896
|
+
offset++;
|
|
1897
|
+
}
|
|
1898
|
+
if (offset < value.length && !attributeSpace(value.charAt(offset)))
|
|
1899
|
+
return;
|
|
1900
|
+
}
|
|
1901
|
+
return { id };
|
|
1902
|
+
}
|
|
1903
|
+
function goQuoted(body) {
|
|
1904
|
+
return /^(?:[^\\\n"]|\\(?:[abfnrtv\\"]|x[0-9A-Fa-f]{2}|[0-3][0-7]{2}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8}))*$/.test(body);
|
|
1905
|
+
}
|
|
1906
|
+
|
|
1246
1907
|
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/core/core.js
|
|
1247
1908
|
var NEVER = Object.freeze({
|
|
1248
1909
|
status: "aborted"
|
|
@@ -15892,8 +16553,8 @@ class DispatchClient {
|
|
|
15892
16553
|
async listProjectArtifacts(project, unlinked = false) {
|
|
15893
16554
|
return this.#json("GET", ["api", "v1", "projects", project, "artifacts"], undefined, unlinked ? { unlinked: "true" } : undefined);
|
|
15894
16555
|
}
|
|
15895
|
-
async projectArtifact(project, input) {
|
|
15896
|
-
return this.#upload(["api", "v1", "projects", project, "artifacts"], input);
|
|
16556
|
+
async projectArtifact(project, input, key) {
|
|
16557
|
+
return this.#upload(["api", "v1", "projects", project, "artifacts"], input, key);
|
|
15897
16558
|
}
|
|
15898
16559
|
async getProjectArtifact(project, slug) {
|
|
15899
16560
|
return this.#json("GET", ["api", "v1", "projects", project, "artifacts", slug]);
|
|
@@ -16145,8 +16806,8 @@ class DispatchClient {
|
|
|
16145
16806
|
async messageBlocks(id, session) {
|
|
16146
16807
|
return this.#json("GET", ["api", "v1", "messages", id, "blocks"], undefined, session === undefined ? undefined : { session });
|
|
16147
16808
|
}
|
|
16148
|
-
async docEdit(id, input) {
|
|
16149
|
-
return this.#json("POST", ["api", "v1", "artifacts", id, "edits"], input);
|
|
16809
|
+
async docEdit(id, input, key) {
|
|
16810
|
+
return this.#json("POST", ["api", "v1", "artifacts", id, "edits"], input, undefined, undefined, key);
|
|
16150
16811
|
}
|
|
16151
16812
|
async nameArtifactVersion(id, input) {
|
|
16152
16813
|
return this.#json("POST", ["api", "v1", "artifacts", id, "versions"], input);
|
|
@@ -16202,17 +16863,17 @@ class DispatchClient {
|
|
|
16202
16863
|
throw error;
|
|
16203
16864
|
}
|
|
16204
16865
|
}
|
|
16205
|
-
async#json(method, path, body, query, signal) {
|
|
16206
|
-
return (await this.#jsonAnswer(method, path, body, query, signal)).payload;
|
|
16866
|
+
async#json(method, path, body, query, signal, key) {
|
|
16867
|
+
return (await this.#jsonAnswer(method, path, body, query, signal, key)).payload;
|
|
16207
16868
|
}
|
|
16208
|
-
async#jsonAnswer(method, path, body, query, signal) {
|
|
16869
|
+
async#jsonAnswer(method, path, body, query, signal, key) {
|
|
16209
16870
|
const url = this.#url(path, query);
|
|
16210
|
-
const response = await this.#send(method, url, body === undefined ? undefined : { content: JSON.stringify(body), type: "application/json" }, undefined, signal);
|
|
16871
|
+
const response = await this.#send(method, url, body === undefined ? undefined : { content: JSON.stringify(body), type: "application/json" }, undefined, signal, key);
|
|
16211
16872
|
return { status: response.status, payload: await this.#response(method, url, response) };
|
|
16212
16873
|
}
|
|
16213
|
-
async#upload(path, input) {
|
|
16874
|
+
async#upload(path, input, key) {
|
|
16214
16875
|
if ("content" in input)
|
|
16215
|
-
return this.#json("POST", path, input);
|
|
16876
|
+
return this.#json("POST", path, input, undefined, undefined, key);
|
|
16216
16877
|
const form = new FormData;
|
|
16217
16878
|
form.set("name", input.name);
|
|
16218
16879
|
if (input.summary !== undefined)
|
|
@@ -16220,17 +16881,17 @@ class DispatchClient {
|
|
|
16220
16881
|
if (input.actor !== undefined)
|
|
16221
16882
|
form.set("actor", JSON.stringify(input.actor));
|
|
16222
16883
|
form.set("file", input.file, input.name);
|
|
16223
|
-
return this.#form("POST", path, form);
|
|
16884
|
+
return this.#form("POST", path, form, key);
|
|
16224
16885
|
}
|
|
16225
|
-
async#form(method, path, form) {
|
|
16886
|
+
async#form(method, path, form, key) {
|
|
16226
16887
|
const url = this.#url(path);
|
|
16227
16888
|
const encoded = new Response(form);
|
|
16228
16889
|
const type = encoded.headers.get("Content-Type") ?? "multipart/form-data";
|
|
16229
16890
|
const content = new Uint8Array(await encoded.arrayBuffer());
|
|
16230
|
-
const response = await this.#send(method, url, { content, type });
|
|
16891
|
+
const response = await this.#send(method, url, { content, type }, undefined, undefined, key);
|
|
16231
16892
|
return this.#response(method, url, response);
|
|
16232
16893
|
}
|
|
16233
|
-
async#send(method, url, body, accept = "application/json", signal) {
|
|
16894
|
+
async#send(method, url, body, accept = "application/json", signal, callerKey) {
|
|
16234
16895
|
const headers = {
|
|
16235
16896
|
Accept: accept,
|
|
16236
16897
|
Authorization: `Bearer ${this.token}`
|
|
@@ -16245,7 +16906,7 @@ class DispatchClient {
|
|
|
16245
16906
|
...body === undefined ? {} : { body: body.content }
|
|
16246
16907
|
});
|
|
16247
16908
|
}
|
|
16248
|
-
const key = crypto.randomUUID();
|
|
16909
|
+
const key = callerKey ?? crypto.randomUUID();
|
|
16249
16910
|
headers[IDEMPOTENCY_KEY_HEADER] = key;
|
|
16250
16911
|
const { pausesMs, withinMs, silenceMs, pause = pauseFor } = this.writeRetry;
|
|
16251
16912
|
const request = `${method} ${url}`;
|
|
@@ -17289,7 +17950,7 @@ var RELATION_PHRASES = {
|
|
|
17289
17950
|
replied_to_your_message: "replied to your message"
|
|
17290
17951
|
};
|
|
17291
17952
|
function isNoAnswersRoute(error) {
|
|
17292
|
-
return error
|
|
17953
|
+
return isNoRoute(error);
|
|
17293
17954
|
}
|
|
17294
17955
|
function renderSessionAnswerItem(item) {
|
|
17295
17956
|
const relation = `${RELATION_PHRASES[item.relation]}${item.replaced === true ? " (replaces an earlier answer)" : ""}`;
|
|
@@ -17328,7 +17989,7 @@ function renderSessionAnswersList(items, options) {
|
|
|
17328
17989
|
|
|
17329
17990
|
`);
|
|
17330
17991
|
}
|
|
17331
|
-
var STATE_KEY = Symbol.for("legion.pi-envoy.session-answers.
|
|
17992
|
+
var STATE_KEY = Symbol.for("legion.pi-envoy.session-answers.v3");
|
|
17332
17993
|
var processGlobal = globalThis;
|
|
17333
17994
|
var state = processGlobal[STATE_KEY] ?? {
|
|
17334
17995
|
sessions: new Map,
|
|
@@ -18142,6 +18803,9 @@ async function resolveOwnerArguments(tool, input, cwd, env, exec, dashboardUrls,
|
|
|
18142
18803
|
const issue = `${repo}#${legionIssue}`;
|
|
18143
18804
|
return { args: { ...args, issue }, ref, owner: { kind: "issue", issue } };
|
|
18144
18805
|
}
|
|
18806
|
+
|
|
18807
|
+
class DocumentNotFoundError extends Error {
|
|
18808
|
+
}
|
|
18145
18809
|
function artifactByReference(artifacts, artifactReference, owner, canonical) {
|
|
18146
18810
|
const bySlug = artifacts.find((candidate) => candidate.slug === artifactReference);
|
|
18147
18811
|
if (canonical && bySlug !== undefined)
|
|
@@ -18162,7 +18826,7 @@ function artifactByReference(artifacts, artifactReference, owner, canonical) {
|
|
|
18162
18826
|
}
|
|
18163
18827
|
const [artifact] = byName;
|
|
18164
18828
|
if (artifact === undefined) {
|
|
18165
|
-
throw new
|
|
18829
|
+
throw new DocumentNotFoundError(documentReferenceProblem(artifactReference, artifacts, owner));
|
|
18166
18830
|
}
|
|
18167
18831
|
return artifact;
|
|
18168
18832
|
}
|
|
@@ -18193,10 +18857,25 @@ async function resolveArtifact(client, owner, artifactReference, { canonical = f
|
|
|
18193
18857
|
artifact = artifactByReference(issue.artifacts, artifactReference, "issue", canonical);
|
|
18194
18858
|
}
|
|
18195
18859
|
if (!artifact) {
|
|
18196
|
-
throw new
|
|
18860
|
+
throw new DocumentNotFoundError(documentReferenceProblem(artifactReference ?? "spec", issue.artifacts, "issue"));
|
|
18197
18861
|
}
|
|
18198
18862
|
return { owner, issue, artifact };
|
|
18199
18863
|
}
|
|
18864
|
+
async function documentTargets(client, resolution) {
|
|
18865
|
+
const refDocument = resolution.ref?.kind === "artifact" ? resolution.ref.id : resolution.ref?.artifact;
|
|
18866
|
+
const owner = resolution.owner?.kind === "issue" ? { kind: "issue", issue: await resolveExistingIssue(client, resolution.owner.issue) } : resolution.owner;
|
|
18867
|
+
return {
|
|
18868
|
+
owner,
|
|
18869
|
+
documentOwner: () => {
|
|
18870
|
+
if (owner === null)
|
|
18871
|
+
throw new Error("issue or project is required");
|
|
18872
|
+
return owner;
|
|
18873
|
+
},
|
|
18874
|
+
resolveDocument: (documentOwner, reference) => resolveArtifact(client, documentOwner, reference, {
|
|
18875
|
+
canonical: reference !== undefined && reference === refDocument
|
|
18876
|
+
})
|
|
18877
|
+
};
|
|
18878
|
+
}
|
|
18200
18879
|
var documentHintLimit = 8;
|
|
18201
18880
|
function documentReferenceProblem(reference, documents, owner, use) {
|
|
18202
18881
|
const hints = documents.slice(0, documentHintLimit).map((document) => use === "id" ? `${document.id} (${document.slug}, ${document.name})` : `${document.slug} (${document.name})`);
|
|
@@ -18777,13 +19456,11 @@ ${text}`,
|
|
|
18777
19456
|
}
|
|
18778
19457
|
var BLOCK_OPENING_CHARS = 60;
|
|
18779
19458
|
async function listDocumentBlocks(client, artifact) {
|
|
18780
|
-
const
|
|
18781
|
-
|
|
18782
|
-
const after = await client.docRead(artifact.id);
|
|
18783
|
-
if (before.token !== after.token || before.markdown !== after.markdown) {
|
|
19459
|
+
const live = await liveBlocks(client, artifact);
|
|
19460
|
+
if (live === undefined) {
|
|
18784
19461
|
throw new Error(`${artifact.name} changed while dispatch doc-read --blocks read it, so the opening words could name the wrong text; nothing is listed. Run it again.`);
|
|
18785
19462
|
}
|
|
18786
|
-
const bytes =
|
|
19463
|
+
const { bytes, blocks } = live;
|
|
18787
19464
|
const decoder = new TextDecoder;
|
|
18788
19465
|
const lines = [
|
|
18789
19466
|
`Blocks of ${artifact.name}, in document order, each indented under the block holding it: id, type, (token; anchored comments and asks), opening words. A table lists its rows by the index delete_row takes and its cells by the row and the column delete_column takes.`
|
|
@@ -18814,7 +19491,15 @@ async function listDocumentBlocks(client, artifact) {
|
|
|
18814
19491
|
holders.push(block);
|
|
18815
19492
|
}
|
|
18816
19493
|
return { text: lines.join(`
|
|
18817
|
-
`), token:
|
|
19494
|
+
`), token: live.token };
|
|
19495
|
+
}
|
|
19496
|
+
async function liveBlocks(client, artifact) {
|
|
19497
|
+
const before = await client.docRead(artifact.id);
|
|
19498
|
+
const blocks = await client.artifactBlocks(artifact.id);
|
|
19499
|
+
const after = await client.docRead(artifact.id);
|
|
19500
|
+
if (before.token !== after.token || before.markdown !== after.markdown)
|
|
19501
|
+
return;
|
|
19502
|
+
return { bytes: new TextEncoder().encode(before.markdown), token: before.token, blocks };
|
|
18818
19503
|
}
|
|
18819
19504
|
async function refuseOpenDecisionBlocks(client, tool, resolved) {
|
|
18820
19505
|
const artifact = resolved.artifact;
|
|
@@ -19024,14 +19709,7 @@ async function executeDispatchTool(input) {
|
|
|
19024
19709
|
const args = checked.args;
|
|
19025
19710
|
const actor = toolActor(await resolveOrigin(env, exec, input.cwd), input);
|
|
19026
19711
|
const client = dispatchClient();
|
|
19027
|
-
const
|
|
19028
|
-
const resolveDocument = (documentOwner, reference) => resolveArtifact(client, documentOwner, reference, {
|
|
19029
|
-
canonical: reference !== undefined && reference === refDocument
|
|
19030
|
-
});
|
|
19031
|
-
const owner = ownerArguments.owner?.kind === "issue" ? {
|
|
19032
|
-
kind: "issue",
|
|
19033
|
-
issue: await resolveExistingIssue(client, ownerArguments.owner.issue)
|
|
19034
|
-
} : ownerArguments.owner;
|
|
19712
|
+
const { owner, documentOwner, resolveDocument } = await documentTargets(client, ownerArguments);
|
|
19035
19713
|
const issueArgument = optionalString(input.args, "issue");
|
|
19036
19714
|
const refText = optionalString(ownerArguments.args, "ref");
|
|
19037
19715
|
if (input.tool !== "dispatch_ask" && owner?.kind === "issue" && issueArgument !== undefined && refText !== undefined && ownerArguments.ref?.owner.kind === "issue" && owner.issue !== ownerArguments.ref.owner.issue) {
|
|
@@ -19044,11 +19722,6 @@ async function executeDispatchTool(input) {
|
|
|
19044
19722
|
throw new Error("issue is required");
|
|
19045
19723
|
return owner.issue;
|
|
19046
19724
|
};
|
|
19047
|
-
const documentOwner = () => {
|
|
19048
|
-
if (owner === null)
|
|
19049
|
-
throw new Error("issue or project is required");
|
|
19050
|
-
return owner;
|
|
19051
|
-
};
|
|
19052
19725
|
const postWithPictures = (text, limit, owner, upload) => textWithPictures(input.tool, text, pictures, limit, owner, (picture) => upload(picture).catch((error) => {
|
|
19053
19726
|
if (!(error instanceof DispatchWriteLandedError))
|
|
19054
19727
|
throw error;
|
|
@@ -21322,12 +21995,16 @@ async function runDispatchCli(argv, rawEnv, io) {
|
|
|
21322
21995
|
uploadBytes = bytes;
|
|
21323
21996
|
}
|
|
21324
21997
|
const spent = consumeMarker(gate.stores, argv, parsed.args, files, Date.now());
|
|
21998
|
+
const asksCarried = spent.ok ? 0 : newAsks(parsed.tool, uploadBytes === undefined ? parsed.args : { ...parsed.args, content: new TextDecoder().decode(uploadBytes) }).length;
|
|
21325
21999
|
if (!spent.ok && gate.mode === "block") {
|
|
21326
|
-
return refuse(spent.changed === undefined ? `${notCalled} AskGate is on for this session, and ${spent.why}. ${LITERAL_FORM} Send the write again in that form, as a bash call of its own.` : `${notCalled} AskGate is on for this session, and ${spent.why}. ${SAME_MESSAGE} Send the call again in a later message.`, REFUSED, parsed.tool, { mode: "block", why: spent.why });
|
|
22000
|
+
return refuse(spent.changed === undefined ? `${notCalled} AskGate is on for this session, and ${spent.why}. ${LITERAL_FORM} Send the write again in that form, as a bash call of its own.` : `${notCalled} AskGate is on for this session, and ${spent.why}. ${SAME_MESSAGE} Send the call again in a later message.`, REFUSED, parsed.tool, { mode: "block", why: spent.why, asksCarried });
|
|
21327
22001
|
}
|
|
21328
22002
|
if (!spent.ok) {
|
|
21329
22003
|
try {
|
|
21330
|
-
appendResult(dir, {
|
|
22004
|
+
appendResult(dir, {
|
|
22005
|
+
tool: parsed.tool,
|
|
22006
|
+
askgate: { mode: "warn", why: spent.why, asksCarried }
|
|
22007
|
+
});
|
|
21331
22008
|
} catch (error) {
|
|
21332
22009
|
return refuse(`${notCalled} AskGate is on for this session in warn mode, and ${spent.why}. A write that posts unreviewed is first recorded in this session's ledger, and the ledger could not be written: ${messageFor(error)}.`, REFUSED, parsed.tool);
|
|
21333
22010
|
}
|
package/dist/src/server.js
CHANGED
|
@@ -15643,7 +15643,7 @@ function sourceSession(sessionID) {
|
|
|
15643
15643
|
// src/server.ts
|
|
15644
15644
|
import { tool } from "@opencode-ai/plugin/tool";
|
|
15645
15645
|
// package.json
|
|
15646
|
-
var version2 = "12.
|
|
15646
|
+
var version2 = "12.3.0";
|
|
15647
15647
|
|
|
15648
15648
|
// src/log.ts
|
|
15649
15649
|
import { appendFile, mkdir } from "fs/promises";
|
package/package.json
CHANGED
|
@@ -29,6 +29,7 @@ and 7 (a conflict, the hand-over, the close) is in
|
|
|
29
29
|
| `pr-blocked` | Its `reason` names the pull request and the `max_fix_attempts` it reached, or reads `Issue reached review_round_cap=<N>.` (the end of this row). The count is of heads pushed onto a red verdict that changed something outside `.legion/<issue>/` — handoff-only pushes (`.legion/<issue>/` paths only) never count, a push by the review App (a planner's, tester's, reviewer's or architect's) never counts, and the head after a red the tester's red tests earned (a review-App push that changed a path outside `.legion/<issue>/`, however many handoff-only pushes follow it) does not count either — so after the tester's handoff-only push onto the implementer's red, the implementer's next push does count; a push the daemon cannot classify (a listener without `changed_paths`, a list the listener stopped at 100 paths or 32,768 runes of text, a push listing no commits) does. Published once per exhausted count, not on every later red verdict for that count. Read the failed CI evidence and recovery attempts. The notice moves nothing, and no later CI red within the attempt that ran the count out (the counted head, and every head pushed on top of it until a push lands on a head CI judged green or on a settled head whose base branch requires no check and no workflow, the tester's red tests and handoff onto the held red included) sends the work back from `testing`, `reviewing`, `accepting`, `retro` or `merging` either: the issue stays in its phase until you move it, or a worker's own decision does (a tester's `fail`, a reviewer's request for changes). A head pushed onto green CI, or onto a settled head whose base branch requires no check and no workflow, leaves that attempt, so its red still comes as a `checks-red`. In `implementing`, give the implementer the failing checks (`envoy_publish` to its role topic). In any later phase a worker holds, tell that worker (`envoy_publish` to its role topic) to move the issue back to `implementing` with `request_backward_move`, naming the failing checks, and the daemon starts the implementer; or file a corrective child. In `accepting`, where no worker holds a phase, the move is yours: `request_backward_move` with `issue`, naming the failing checks. In `awaiting_merge`, where no worker holds a phase and a backward move is refused, open a `dispatch ask` naming the failing checks for the human who merges. Do not treat the blocked PR as final. A `review_round_cap` reason is the implementer's round count instead: every move back to an earlier phase counts one, and the notice comes again with each round past the cap. It moves nothing either, since the round that reached the cap has already started its worker. Tell the rounds apart by what announced each: a tester's fail by its `phase-finished` with `verdict: "fail"`; a request for changes by the reviewer's `phase-finished` over a `CHANGES_REQUESTED` review; the daemon's own move by a `checks-red` notice, whose reason names a conflict (`the head conflicts with <base>`), the required checks that failed (`CI is red at <head>`), or the merge queue's failed merge and its failing run (`the merge queue took pull request #<n> out at READY's head …`); a worker's `request_backward_move` by a `phase-finished` from the role that asked, whose reason reaches only the task of the worker the move started, so ask that worker; your own send-back from `accepting` by the reason you gave. Judge the cap by the rounds that are not conflicts. Conflict returns need nothing from you beyond the next round. Tester fails, requests for changes and CI-red returns (a `checks-red` notice is raised for CI only when a required check fails on code the branch pushed, or when the merge queue fails the merge) are the rounds the cap is for. Each `review_round_cap` notice is a decision you make in the turn it wakes, never one left for the next round to make: read what every counted round found, choose one of the three below, act on it in the same turn, and post it as a `dispatch comment` on the issue naming the rounds you read and the choice. **Narrow**: the change itself is sound but each round finds more around it; tell the implementer and the reviewer (`envoy_publish` to each one's role topic) which acceptance lines the next round answers; a correctness finding is still fixed in this pull request, and so is a wording nit or any clear fix in a file it touches, so the `Fast-follow:` line carries only cleanup in files this pull request does not touch. **Split**: part of the work cannot settle in this pull request. On a child, file that part as a child of this issue's parent, never of this one (this issue closes at its own merge, and the split part usually builds on this pull request's unmerged code): `dispatch issue --project <project> --parent <parent> --title '<outcome>' --blocked-by <this issue> --spec-file -`. Release it now with `release_children`: Legion holds it on that `--blocked-by` link until this issue closes, and starts it then. On the root, narrow it to what settles here and open a `dispatch ask` for the rest (`skill://legion-worker/references/findings.md`): a person decides whether that becomes an issue. Tell the implementer and the reviewer that this pull request no longer carries it. **Accept the rounds**: each round found less than the one before it, and the last found no defect in the change itself (a stale line, a comment that states an old default); let the loop run and say why in the comment. A later notice is a new decision: accept again only when its round found less than the round you last accepted, and otherwise narrow or split. A finding that changes what the spec accepts is a decision block in the root spec ("After approval" in `skill://legion-architect/references/design-gate.md`), and `dispatch ask` is only for a decision that needs a human's authority; neither replaces the choice. Judge a worker's backward move by the reason that worker gives. A move you ordered for a conflict found before `awaiting_merge` ("A conflicting head" in `skill://legion-architect/references/acceptance.md`) is a conflict return. The merger's move back to `reviewing` for a READY refusal (`READY_UNREVIEWED_CHANGE`, or `READY_NOT_APPROVED` when its task names no approved head), and the reviewer's no-fix request for changes that follows it so the tester runs the head, are not the work going in circles either. A move that sends back code found wrong counts with the tester fails. |
|
|
30
30
|
| `pr-merged` | The pull request merged and the issue did not close; its `reason` says why. Merged before the issue reached `awaiting_merge`: the workflow runs on, and the merge closes the issue the moment it gets there, your acceptance and retro first. Merged while the rest of the tree still runs: the reason names the pull request and every issue the close waits for, and the root waits in `awaiting_merge`, slot and workers kept, until the last of them is done or parked ("The close" in `skill://legion-architect/references/acceptance.md`). Either way you start nothing and no further notice follows. |
|
|
31
31
|
| `pr-closed-unmerged` | Decide from current scope whether the work is reopened, started over (`park_child` then `rerun_child`, for a child), or ended with a reason. Delegate the repository action to the responsible phase worker and keep ownership. |
|
|
32
|
+
| `pull-request-missing` | Its `reason` names the issue, the repository, the branch and the handoff commit: the issue's implementer completed at that commit and GitHub lists no open pull request on the branch, so the issue stays in `implementing`. Tell the implementer (`envoy_publish` to its role topic) to open the pull request from that branch; the daemon records it and moves the issue to `testing` once it is open. When the work should not go on, decide as the `pr-closed-unmerged` row says. It comes once per handoff commit. |
|
|
32
33
|
| `merge-refused` | GitHub refused the daemon's submission of READY's merge in a way a retry would meet again, so it submits that merge no further: ask a person with `dispatch ask` to fix the repository setting or App permission and merge at READY's head (`skill://legion-architect/references/merge-notices.md#merge-refused`). |
|
|
33
34
|
| `merge-stalled` | GitHub shows the pull request closed, or at another head, while the record still awaits READY's head, and that change's webhook never reached the daemon: read the pull request on GitHub and act on what it shows (`skill://legion-architect/references/merge-notices.md#merge-stalled`). |
|
|
34
35
|
| `merge-removed` | A person, or GitHub on its own, took the pull request out of the merge queue or turned its auto-merge off at READY's head after the daemon submitted the merge: read the pull request on GitHub and act on the removal's reason (`skill://legion-architect/references/merge-notices.md#merge-removed`). |
|
|
@@ -19,7 +19,7 @@ You work one phase of one issue in a jj workspace every role of the issue shares
|
|
|
19
19
|
|
|
20
20
|
## Stop rules
|
|
21
21
|
|
|
22
|
-
- **A refused completion is information, not a retry loop.** Quote the answer verbatim to the architect, so "my work is lost" and "my work belongs to an ended run" read differently. `HANDOFF_STALE_GENERATION` (the issue is on a newer run: your turn began before the daemon's task for that run reached you, and the answer names that task when it is queued) or `HANDOFF_NO_RUN` (your claim holds no task): push nothing further, end the turn without completing, and tell the architect; if the task then arrives (none may: the new run's phase can be another claim's) and its work is already committed, call `handoff_complete` again at the same commit in that task's turn, and never redo the work or write a second handoff. `HANDOFF_NOT_CURRENT_PHASE`: the issue left your phase; report, never complete again. `HANDOFF_ALREADY_RECORDED`: the first answer stands, an acceptance or one of the refusals the daemon records (`HANDOFF_NOT_CURRENT_PHASE`, `READY_REQUIRED`, `HANDOFF_NOT_NEW`, and `HANDOFF_STALE_GENERATION` only when the issue moved during the call); if you did not see it, tell the architect. A refusal given before any fact is applied (`HANDOFF_STALE_GENERATION`, `HANDOFF_NO_RUN` and READY's checks of the head among them) records nothing, so the corrected call at the same commit is applied. A reminder is not a task: after an accepted completion, a second call is refused.
|
|
22
|
+
- **A refused completion is information, not a retry loop.** Quote the answer verbatim to the architect, so "my work is lost" and "my work belongs to an ended run" read differently. `HANDOFF_STALE_GENERATION` (the issue is on a newer run: your turn began before the daemon's task for that run reached you, and the answer names that task when it is queued) or `HANDOFF_NO_RUN` (your claim holds no task): push nothing further, end the turn without completing, and tell the architect; if the task then arrives (none may: the new run's phase can be another claim's) and its work is already committed, call `handoff_complete` again at the same commit in that task's turn, and never redo the work or write a second handoff. `HANDOFF_NOT_CURRENT_PHASE`: the issue left your phase; report, never complete again. `HANDOFF_NO_PULL_REQUEST` (the implementer's completion found no open pull request on `legion/<KEY>`, and none recorded merged): open it and complete again at the same commit; when the answer names a closed pull request, the architect decides the phase: tell it, and open or reopen nothing until it answers. `HANDOFF_ALREADY_RECORDED`: the first answer stands, an acceptance or one of the refusals the daemon records (`HANDOFF_NOT_CURRENT_PHASE`, `READY_REQUIRED`, `HANDOFF_NOT_NEW`, and `HANDOFF_STALE_GENERATION` only when the issue moved during the call); if you did not see it, tell the architect. A refusal given before any fact is applied (`HANDOFF_STALE_GENERATION`, `HANDOFF_NO_RUN`, `HANDOFF_NO_PULL_REQUEST` and READY's checks of the head among them) records nothing, so the corrected call at the same commit is applied. A reminder is not a task: after an accepted completion, a second call is refused.
|
|
23
23
|
- **A turn that ends with your phase open is reminded**, at the end of every turn for as long as the phase stays open. Answer by completing, or with a WAITING line naming what you wait on, which buys a quiet wait; a run of reminders that change nothing tells your architect.
|
|
24
24
|
- **After your phase completes, the workspace is read-only for you**: no commit, no `jj new`, no edit to a tracked file, in any later turn until a new task names your phase. The active phase owns it, and you stay live only to answer questions.
|
|
25
25
|
- **Never start another Legion role**: the daemon starts every phase worker.
|