@sjawhar/pi-legion-envoy 7.5.2 → 7.6.1
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 +90 -47
- package/dist/legion.js +101 -33
- package/dist/skills/dispatch/SKILL.md +7 -8
- package/dist/skills/dispatch/references/issues.md +10 -0
- package/dist/skills/dispatch/references/messages.md +5 -5
- package/dist/skills/envoy/SKILL.md +5 -4
- package/dist/skills/legion-worker/SKILL.md +9 -8
- package/package.json +1 -1
package/dist/envoy.js
CHANGED
|
@@ -30073,6 +30073,7 @@ var ASK_URGENCIES = ["low", "med", "high", "blocking"];
|
|
|
30073
30073
|
var ASK_QUESTION_MAX = 800;
|
|
30074
30074
|
var SEARCH_QUERY_MAX = 1000;
|
|
30075
30075
|
var SEARCH_QUERY_HINT = "search with a short phrase of a few words, not a passage";
|
|
30076
|
+
var SEARCH_KIND_DEPTH = 100;
|
|
30076
30077
|
var PROJECT_KEY_PATTERN = /^[A-Z][A-Z0-9]{1,9}$/;
|
|
30077
30078
|
var ISSUE_STATUSES = [
|
|
30078
30079
|
"triage",
|
|
@@ -30408,7 +30409,7 @@ var dispatchToolSpecs = [
|
|
|
30408
30409
|
{
|
|
30409
30410
|
name: "dispatch_search",
|
|
30410
30411
|
example: { query: "astrolabe" },
|
|
30411
|
-
description: "Search every issue, document, comment, ask, and message for a keyword or phrase and get deep links. " + "Use it before creating an issue or a design document, and to find where a word was written. " + 'Websearch syntax: "quoted phrase", -excluded, OR.',
|
|
30412
|
+
description: "Search every issue, document, comment, ask, and message for a keyword or phrase and get deep links. " + "Use it before creating an issue or a design document, and to find where a word was written. " + 'Websearch syntax: "quoted phrase", -excluded, OR. ' + "Each kind of content is ranked on its own and the lists are merged, so the top holds the best " + "issue, document, ask, comment and message; an issue key searched alone lists that issue first. " + `The answer names how many results match; offset pages through them, and each kind lists at most its best ${SEARCH_KIND_DEPTH}, ` + "so narrow the query or name a project to reach the rest. Paging is exact only while the " + "corpus holds still: content added, changed, or removed between two offsets can shift rows " + "across a page boundary, so one hit can come back twice and another never.",
|
|
30412
30413
|
arguments: (z2) => ({
|
|
30413
30414
|
query: z2.string({
|
|
30414
30415
|
min: 2,
|
|
@@ -30416,7 +30417,8 @@ var dispatchToolSpecs = [
|
|
|
30416
30417
|
maxHint: SEARCH_QUERY_HINT
|
|
30417
30418
|
}).describe(`Keyword, phrase, or websearch expression; 2 to ${SEARCH_QUERY_MAX} characters.`),
|
|
30418
30419
|
project: z2.string().describe("Optional project key to search within.").optional(),
|
|
30419
|
-
limit: z2.number({ int: true, min: 1, max: 50 }).describe("Maximum results, 1-50; default 20.").optional()
|
|
30420
|
+
limit: z2.number({ int: true, min: 1, max: 50 }).describe("Maximum results, 1-50; default 20.").optional(),
|
|
30421
|
+
offset: z2.number({ int: true, min: 0 }).describe("Results to skip before the page; nonnegative integer; default 0.").optional()
|
|
30420
30422
|
}),
|
|
30421
30423
|
validation: {
|
|
30422
30424
|
check: (value) => {
|
|
@@ -32513,6 +32515,71 @@ class DispatchClient {
|
|
|
32513
32515
|
}
|
|
32514
32516
|
}
|
|
32515
32517
|
|
|
32518
|
+
// ../envoy-client/src/search-answer.ts
|
|
32519
|
+
function pageSummaryText(offset, count, total) {
|
|
32520
|
+
return `showing ${offset + 1}-${offset + count} of ${total}`;
|
|
32521
|
+
}
|
|
32522
|
+
function searchResultLine(result, baseUrl) {
|
|
32523
|
+
const href = new URL(result.href, baseUrl).toString();
|
|
32524
|
+
const { owner } = result;
|
|
32525
|
+
if (owner.kind === "document") {
|
|
32526
|
+
const reference = dispatchDocumentRef(owner.project, owner.slug);
|
|
32527
|
+
return `${reference} [document] ${owner.name} - ${result.kind}: ${snippetText(result.snippet)} -> ${href}`;
|
|
32528
|
+
}
|
|
32529
|
+
const artifactName = result.artifact ? ` ${result.artifact.name}` : "";
|
|
32530
|
+
const label = `${owner.key} [${owner.status}] ${owner.title} - ${result.kind}${artifactName}`;
|
|
32531
|
+
return `${label}: ${snippetText(result.snippet)} -> ${href}`;
|
|
32532
|
+
}
|
|
32533
|
+
function searchAnswer(search, query, offset, configUrl) {
|
|
32534
|
+
const results = search.results;
|
|
32535
|
+
const count = results.length;
|
|
32536
|
+
const lines = results.map((result) => searchResultLine(result, configUrl));
|
|
32537
|
+
const noun = count === 1 ? "result" : "results";
|
|
32538
|
+
const noResults = `No results for "${query}".`;
|
|
32539
|
+
if (typeof search.total !== "number") {
|
|
32540
|
+
if (offset !== undefined && offset > 0) {
|
|
32541
|
+
throw new Error(`Dispatch answered without a total: it predates search paging and ignored offset ${offset}, so this would be its first page again.`);
|
|
32542
|
+
}
|
|
32543
|
+
return {
|
|
32544
|
+
text: count === 0 ? noResults : [`${count} ${noun} for "${query}" (${search.took_ms} ms)`, ...lines].join(`
|
|
32545
|
+
`),
|
|
32546
|
+
details: { query, results }
|
|
32547
|
+
};
|
|
32548
|
+
}
|
|
32549
|
+
const { total, reachable, offset: pageOffset } = search;
|
|
32550
|
+
const end = pageOffset + count;
|
|
32551
|
+
const cut = reachable < total ? `Each kind lists only its best ${SEARCH_KIND_DEPTH} matches, so ${reachable} of the ${total} can be paged to; narrow the query or name a project to reach the rest.` : undefined;
|
|
32552
|
+
const details = {
|
|
32553
|
+
query,
|
|
32554
|
+
results,
|
|
32555
|
+
total,
|
|
32556
|
+
reachable,
|
|
32557
|
+
offset: pageOffset,
|
|
32558
|
+
limit: search.limit
|
|
32559
|
+
};
|
|
32560
|
+
if (count === 0) {
|
|
32561
|
+
return {
|
|
32562
|
+
text: total === 0 ? noResults : [
|
|
32563
|
+
`No results for "${query}" at offset ${pageOffset}: it matches ${total}, and the pages reach the first ${reachable}.`,
|
|
32564
|
+
...cut === undefined ? [] : [cut]
|
|
32565
|
+
].join(`
|
|
32566
|
+
`),
|
|
32567
|
+
details
|
|
32568
|
+
};
|
|
32569
|
+
}
|
|
32570
|
+
const showing = pageOffset === 0 && count === total ? "" : `${pageSummaryText(pageOffset, count, total)}, `;
|
|
32571
|
+
return {
|
|
32572
|
+
text: [
|
|
32573
|
+
`${count} ${noun} for "${query}" (${showing}${search.took_ms} ms)`,
|
|
32574
|
+
...cut === undefined ? [] : [cut],
|
|
32575
|
+
...lines,
|
|
32576
|
+
...end < reachable ? [`Next page: offset ${end}.`] : []
|
|
32577
|
+
].join(`
|
|
32578
|
+
`),
|
|
32579
|
+
details
|
|
32580
|
+
};
|
|
32581
|
+
}
|
|
32582
|
+
|
|
32516
32583
|
// ../envoy-client/src/tool-input-errors.ts
|
|
32517
32584
|
class ToolInputError extends Error {
|
|
32518
32585
|
tool;
|
|
@@ -32822,17 +32889,6 @@ function duplicateCandidates(error48) {
|
|
|
32822
32889
|
throw error48;
|
|
32823
32890
|
return error48.candidates;
|
|
32824
32891
|
}
|
|
32825
|
-
function searchResultLine(result, baseUrl) {
|
|
32826
|
-
const href = new URL(result.href, baseUrl).toString();
|
|
32827
|
-
const { owner } = result;
|
|
32828
|
-
if (owner.kind === "document") {
|
|
32829
|
-
const reference = dispatchDocumentRef(owner.project, owner.slug);
|
|
32830
|
-
return `${reference} [document] ${owner.name} - ${result.kind}: ${snippetText(result.snippet)} -> ${href}`;
|
|
32831
|
-
}
|
|
32832
|
-
const artifactName = result.artifact ? ` ${result.artifact.name}` : "";
|
|
32833
|
-
const label = `${owner.key} [${owner.status}] ${owner.title} - ${result.kind}${artifactName}`;
|
|
32834
|
-
return `${label}: ${snippetText(result.snippet)} -> ${href}`;
|
|
32835
|
-
}
|
|
32836
32892
|
function askUrgency(args) {
|
|
32837
32893
|
const value = args.urgency;
|
|
32838
32894
|
return ASK_URGENCIES.find((urgency) => urgency === value);
|
|
@@ -34031,20 +34087,13 @@ async function executeDispatchTool(input) {
|
|
|
34031
34087
|
const query = stringArg(args, "query");
|
|
34032
34088
|
const project = optionalString(args, "project");
|
|
34033
34089
|
const limit = optionalNumber(args, "limit");
|
|
34090
|
+
const offset = optionalNumber(args, "offset");
|
|
34034
34091
|
const search = await client.search(query, {
|
|
34035
34092
|
...project === undefined ? {} : { project },
|
|
34036
|
-
...limit === undefined ? {} : { limit }
|
|
34093
|
+
...limit === undefined ? {} : { limit },
|
|
34094
|
+
...offset === undefined ? {} : { offset }
|
|
34037
34095
|
});
|
|
34038
|
-
|
|
34039
|
-
const count = results.length;
|
|
34040
|
-
return {
|
|
34041
|
-
text: count === 0 ? `No results for "${query}".` : [
|
|
34042
|
-
`${count} ${count === 1 ? "result" : "results"} for "${query}" (${search.took_ms} ms)`,
|
|
34043
|
-
...results.map((result) => searchResultLine(result, configUrl))
|
|
34044
|
-
].join(`
|
|
34045
|
-
`),
|
|
34046
|
-
details: { query, results }
|
|
34047
|
-
};
|
|
34096
|
+
return searchAnswer(search, query, offset, configUrl);
|
|
34048
34097
|
}
|
|
34049
34098
|
case "dispatch_issues": {
|
|
34050
34099
|
const project = stringArg(args, "project");
|
|
@@ -34084,7 +34133,7 @@ async function executeDispatchTool(input) {
|
|
|
34084
34133
|
}));
|
|
34085
34134
|
const titles = await liveSessionTitles(client, rows.some((row) => holdsSession(row.claim)));
|
|
34086
34135
|
const isPartial = offset !== 0 || rows.length !== total;
|
|
34087
|
-
const showing = !isPartial ? "" : rows.length === 0 ? `showing 0-0 of ${total}` :
|
|
34136
|
+
const showing = !isPartial ? "" : rows.length === 0 ? `showing 0-0 of ${total}` : pageSummaryText(offset, rows.length, total);
|
|
34088
34137
|
return {
|
|
34089
34138
|
text: rows.length === 0 ? `No issues in ${project}.${isPartial ? ` (${showing})` : ""}` : [
|
|
34090
34139
|
`${rows.length} ${rows.length === 1 ? "issue" : "issues"} in ${project}` + (isPartial ? ` (${showing})` : ""),
|
|
@@ -35588,10 +35637,10 @@ function btwPrompt(question) {
|
|
|
35588
35637
|
].join(`
|
|
35589
35638
|
`);
|
|
35590
35639
|
}
|
|
35591
|
-
function sideTurn(
|
|
35640
|
+
function sideTurn(context) {
|
|
35592
35641
|
const runEphemeralTurn = context?.runEphemeralTurn;
|
|
35593
35642
|
if (runEphemeralTurn === undefined)
|
|
35594
|
-
return
|
|
35643
|
+
return;
|
|
35595
35644
|
return ({ prompt, signal }) => runEphemeralTurn({ promptText: btwPrompt(prompt), signal });
|
|
35596
35645
|
}
|
|
35597
35646
|
|
|
@@ -36002,7 +36051,7 @@ function envoyExtension(pi) {
|
|
|
36002
36051
|
} else if (rendered.malformedDelivery === true) {
|
|
36003
36052
|
console.warn("[envoy] dropping malformed Dispatch targeted delivery without a reply address");
|
|
36004
36053
|
} else if (rendered.delivery?.mode === "btw") {
|
|
36005
|
-
const answer = sideTurn(
|
|
36054
|
+
const answer = sideTurn(activeSessionContext);
|
|
36006
36055
|
if (shuttingDown) {
|
|
36007
36056
|
await refuse(rendered.delivery, "This OMP session is shutting down");
|
|
36008
36057
|
} else if (answer === undefined) {
|
|
@@ -36199,7 +36248,7 @@ function envoyExtension(pi) {
|
|
|
36199
36248
|
],
|
|
36200
36249
|
port: 0,
|
|
36201
36250
|
title: activeSessionContext?.sessionManager.getSessionName?.() ?? "",
|
|
36202
|
-
capabilities: sideTurn(
|
|
36251
|
+
capabilities: sideTurn(activeSessionContext) === undefined ? CAPABILITIES_WITHOUT_BTW : DELIVERY_CAPABILITIES,
|
|
36203
36252
|
driving: false,
|
|
36204
36253
|
selfSubscribed: true
|
|
36205
36254
|
});
|
|
@@ -36596,7 +36645,7 @@ function envoyExtension(pi) {
|
|
|
36596
36645
|
registerEnvoyWhoamiCommand(pi, replyAddress);
|
|
36597
36646
|
pi.on("before_agent_start", async (event, context) => {
|
|
36598
36647
|
const id = context.sessionManager.getSessionId();
|
|
36599
|
-
if (id === "" || event.prompt.trim() === "" || !context.hasUI || sideTurn(
|
|
36648
|
+
if (id === "" || event.prompt.trim() === "" || !context.hasUI || sideTurn(context) === undefined || legionManaged(id)) {
|
|
36600
36649
|
return;
|
|
36601
36650
|
}
|
|
36602
36651
|
if (await isSubagent(context))
|
|
@@ -36628,7 +36677,7 @@ function envoyExtension(pi) {
|
|
|
36628
36677
|
const id = context.sessionManager.getSessionId();
|
|
36629
36678
|
endInjectedUserTurns(sessionID);
|
|
36630
36679
|
const lastReply = event.messages?.findLast((message) => message.role === "assistant");
|
|
36631
|
-
const ask = sideTurn(
|
|
36680
|
+
const ask = sideTurn(context);
|
|
36632
36681
|
if (event.willContinue === true || lastReply?.stopReason !== "stop" || !context.hasUI || id === "" || ask === undefined || !checkOwed(id)) {
|
|
36633
36682
|
return;
|
|
36634
36683
|
}
|
|
@@ -36676,20 +36725,14 @@ function envoyExtension(pi) {
|
|
|
36676
36725
|
return;
|
|
36677
36726
|
const abort = new AbortController;
|
|
36678
36727
|
askCheckAbort = abort;
|
|
36679
|
-
|
|
36680
|
-
|
|
36681
|
-
|
|
36682
|
-
|
|
36683
|
-
|
|
36684
|
-
|
|
36685
|
-
|
|
36686
|
-
|
|
36687
|
-
return;
|
|
36688
|
-
});
|
|
36689
|
-
} catch (error48) {
|
|
36690
|
-
logSelfCheckFailure(id, error48);
|
|
36691
|
-
answered = Promise.resolve(undefined);
|
|
36692
|
-
}
|
|
36728
|
+
const answered = ask({
|
|
36729
|
+
prompt: ASK_SELF_CHECK_PROMPT(open.snapshot.asks),
|
|
36730
|
+
signal: abort.signal
|
|
36731
|
+
}).then((reply) => reply.replyText, (error48) => {
|
|
36732
|
+
if (!abort.signal.aborted)
|
|
36733
|
+
logSelfCheckFailure(id, error48);
|
|
36734
|
+
return;
|
|
36735
|
+
});
|
|
36693
36736
|
const expiry = Promise.withResolvers();
|
|
36694
36737
|
const expire = setTimeout(() => {
|
|
36695
36738
|
const timedOut = new Error(`self-check timed out after ${selfCheckTimeoutMs} ms`);
|
|
@@ -36816,7 +36859,7 @@ Warnings: ${warnings.join("; ")}`, {
|
|
|
36816
36859
|
const undelivered = source !== "" && result.holder === source && inSubagent;
|
|
36817
36860
|
const published = result.holder === undefined ? `published ${result.envelope.event_id}` : `published ${result.envelope.event_id}; holder ${result.holder}`;
|
|
36818
36861
|
return toolSuccess(undelivered ? `${published}
|
|
36819
|
-
Not delivered: that holder is the session this subagent sends as, and the listener drops a message whose source session is its recipient, so the agent that spawned you did not receive it. Reach it
|
|
36862
|
+
Not delivered: that holder is the session this subagent sends as, and the listener drops a message whose source session is its recipient, so the agent that spawned you did not receive it. Reach it with a write to its agent:// address instead.` : published, {
|
|
36820
36863
|
event_id: result.envelope.event_id,
|
|
36821
36864
|
topic,
|
|
36822
36865
|
...result.holder === undefined ? {} : { holder: result.holder },
|
|
@@ -36837,7 +36880,7 @@ Not delivered: that holder is the session this subagent sends as, and the listen
|
|
|
36837
36880
|
const address = await replyAddress(context, "", inSubagent);
|
|
36838
36881
|
const subagent = inSubagent ? {
|
|
36839
36882
|
session_id: context.sessionManager.getSessionId(),
|
|
36840
|
-
note: address === "" ? "This session is a task subagent and registers no Envoy session of its own, and this process records no top-level session its transcript traces back to, so it has no reply address at all: say who you are in the message body, and reach the agent that spawned you
|
|
36883
|
+
note: address === "" ? "This session is a task subagent and registers no Envoy session of its own, and this process records no top-level session its transcript traces back to, so it has no reply address at all: say who you are in the message body, and reach the agent that spawned you with a write to its agent:// address." : "This session is a task subagent and registers no Envoy session of its own; a reply to session_id reaches the agent that spawned it, which relays it with a write to your agent:// address. Your own envoy_publish never reaches that agent \u2014 use a write to its agent:// address for that hop."
|
|
36841
36884
|
} : undefined;
|
|
36842
36885
|
return toolSuccess(JSON.stringify({
|
|
36843
36886
|
session_id: address,
|
package/dist/legion.js
CHANGED
|
@@ -14160,6 +14160,7 @@ var ASK_URGENCIES = ["low", "med", "high", "blocking"];
|
|
|
14160
14160
|
var ASK_QUESTION_MAX = 800;
|
|
14161
14161
|
var SEARCH_QUERY_MAX = 1000;
|
|
14162
14162
|
var SEARCH_QUERY_HINT = "search with a short phrase of a few words, not a passage";
|
|
14163
|
+
var SEARCH_KIND_DEPTH = 100;
|
|
14163
14164
|
var PROJECT_KEY_PATTERN = /^[A-Z][A-Z0-9]{1,9}$/;
|
|
14164
14165
|
var ISSUE_STATUSES = [
|
|
14165
14166
|
"triage",
|
|
@@ -14495,7 +14496,7 @@ var dispatchToolSpecs = [
|
|
|
14495
14496
|
{
|
|
14496
14497
|
name: "dispatch_search",
|
|
14497
14498
|
example: { query: "astrolabe" },
|
|
14498
|
-
description: "Search every issue, document, comment, ask, and message for a keyword or phrase and get deep links. " + "Use it before creating an issue or a design document, and to find where a word was written. " + 'Websearch syntax: "quoted phrase", -excluded, OR.',
|
|
14499
|
+
description: "Search every issue, document, comment, ask, and message for a keyword or phrase and get deep links. " + "Use it before creating an issue or a design document, and to find where a word was written. " + 'Websearch syntax: "quoted phrase", -excluded, OR. ' + "Each kind of content is ranked on its own and the lists are merged, so the top holds the best " + "issue, document, ask, comment and message; an issue key searched alone lists that issue first. " + `The answer names how many results match; offset pages through them, and each kind lists at most its best ${SEARCH_KIND_DEPTH}, ` + "so narrow the query or name a project to reach the rest. Paging is exact only while the " + "corpus holds still: content added, changed, or removed between two offsets can shift rows " + "across a page boundary, so one hit can come back twice and another never.",
|
|
14499
14500
|
arguments: (z2) => ({
|
|
14500
14501
|
query: z2.string({
|
|
14501
14502
|
min: 2,
|
|
@@ -14503,7 +14504,8 @@ var dispatchToolSpecs = [
|
|
|
14503
14504
|
maxHint: SEARCH_QUERY_HINT
|
|
14504
14505
|
}).describe(`Keyword, phrase, or websearch expression; 2 to ${SEARCH_QUERY_MAX} characters.`),
|
|
14505
14506
|
project: z2.string().describe("Optional project key to search within.").optional(),
|
|
14506
|
-
limit: z2.number({ int: true, min: 1, max: 50 }).describe("Maximum results, 1-50; default 20.").optional()
|
|
14507
|
+
limit: z2.number({ int: true, min: 1, max: 50 }).describe("Maximum results, 1-50; default 20.").optional(),
|
|
14508
|
+
offset: z2.number({ int: true, min: 0 }).describe("Results to skip before the page; nonnegative integer; default 0.").optional()
|
|
14507
14509
|
}),
|
|
14508
14510
|
validation: {
|
|
14509
14511
|
check: (value) => {
|
|
@@ -15298,6 +15300,71 @@ function dispatchChildRef(ownerRef, kind, id) {
|
|
|
15298
15300
|
return `${ownerRef}/${kind}/${id}`;
|
|
15299
15301
|
}
|
|
15300
15302
|
|
|
15303
|
+
// ../envoy-client/src/search-answer.ts
|
|
15304
|
+
function pageSummaryText(offset, count, total) {
|
|
15305
|
+
return `showing ${offset + 1}-${offset + count} of ${total}`;
|
|
15306
|
+
}
|
|
15307
|
+
function searchResultLine(result, baseUrl) {
|
|
15308
|
+
const href = new URL(result.href, baseUrl).toString();
|
|
15309
|
+
const { owner } = result;
|
|
15310
|
+
if (owner.kind === "document") {
|
|
15311
|
+
const reference = dispatchDocumentRef(owner.project, owner.slug);
|
|
15312
|
+
return `${reference} [document] ${owner.name} - ${result.kind}: ${snippetText(result.snippet)} -> ${href}`;
|
|
15313
|
+
}
|
|
15314
|
+
const artifactName = result.artifact ? ` ${result.artifact.name}` : "";
|
|
15315
|
+
const label = `${owner.key} [${owner.status}] ${owner.title} - ${result.kind}${artifactName}`;
|
|
15316
|
+
return `${label}: ${snippetText(result.snippet)} -> ${href}`;
|
|
15317
|
+
}
|
|
15318
|
+
function searchAnswer(search, query, offset, configUrl) {
|
|
15319
|
+
const results = search.results;
|
|
15320
|
+
const count = results.length;
|
|
15321
|
+
const lines = results.map((result) => searchResultLine(result, configUrl));
|
|
15322
|
+
const noun = count === 1 ? "result" : "results";
|
|
15323
|
+
const noResults = `No results for "${query}".`;
|
|
15324
|
+
if (typeof search.total !== "number") {
|
|
15325
|
+
if (offset !== undefined && offset > 0) {
|
|
15326
|
+
throw new Error(`Dispatch answered without a total: it predates search paging and ignored offset ${offset}, so this would be its first page again.`);
|
|
15327
|
+
}
|
|
15328
|
+
return {
|
|
15329
|
+
text: count === 0 ? noResults : [`${count} ${noun} for "${query}" (${search.took_ms} ms)`, ...lines].join(`
|
|
15330
|
+
`),
|
|
15331
|
+
details: { query, results }
|
|
15332
|
+
};
|
|
15333
|
+
}
|
|
15334
|
+
const { total, reachable, offset: pageOffset } = search;
|
|
15335
|
+
const end = pageOffset + count;
|
|
15336
|
+
const cut = reachable < total ? `Each kind lists only its best ${SEARCH_KIND_DEPTH} matches, so ${reachable} of the ${total} can be paged to; narrow the query or name a project to reach the rest.` : undefined;
|
|
15337
|
+
const details = {
|
|
15338
|
+
query,
|
|
15339
|
+
results,
|
|
15340
|
+
total,
|
|
15341
|
+
reachable,
|
|
15342
|
+
offset: pageOffset,
|
|
15343
|
+
limit: search.limit
|
|
15344
|
+
};
|
|
15345
|
+
if (count === 0) {
|
|
15346
|
+
return {
|
|
15347
|
+
text: total === 0 ? noResults : [
|
|
15348
|
+
`No results for "${query}" at offset ${pageOffset}: it matches ${total}, and the pages reach the first ${reachable}.`,
|
|
15349
|
+
...cut === undefined ? [] : [cut]
|
|
15350
|
+
].join(`
|
|
15351
|
+
`),
|
|
15352
|
+
details
|
|
15353
|
+
};
|
|
15354
|
+
}
|
|
15355
|
+
const showing = pageOffset === 0 && count === total ? "" : `${pageSummaryText(pageOffset, count, total)}, `;
|
|
15356
|
+
return {
|
|
15357
|
+
text: [
|
|
15358
|
+
`${count} ${noun} for "${query}" (${showing}${search.took_ms} ms)`,
|
|
15359
|
+
...cut === undefined ? [] : [cut],
|
|
15360
|
+
...lines,
|
|
15361
|
+
...end < reachable ? [`Next page: offset ${end}.`] : []
|
|
15362
|
+
].join(`
|
|
15363
|
+
`),
|
|
15364
|
+
details
|
|
15365
|
+
};
|
|
15366
|
+
}
|
|
15367
|
+
|
|
15301
15368
|
// ../envoy-client/src/tool-input-errors.ts
|
|
15302
15369
|
class ToolInputError extends Error {
|
|
15303
15370
|
tool;
|
|
@@ -15607,17 +15674,6 @@ function duplicateCandidates(error48) {
|
|
|
15607
15674
|
throw error48;
|
|
15608
15675
|
return error48.candidates;
|
|
15609
15676
|
}
|
|
15610
|
-
function searchResultLine(result, baseUrl) {
|
|
15611
|
-
const href = new URL(result.href, baseUrl).toString();
|
|
15612
|
-
const { owner } = result;
|
|
15613
|
-
if (owner.kind === "document") {
|
|
15614
|
-
const reference = dispatchDocumentRef(owner.project, owner.slug);
|
|
15615
|
-
return `${reference} [document] ${owner.name} - ${result.kind}: ${snippetText(result.snippet)} -> ${href}`;
|
|
15616
|
-
}
|
|
15617
|
-
const artifactName = result.artifact ? ` ${result.artifact.name}` : "";
|
|
15618
|
-
const label = `${owner.key} [${owner.status}] ${owner.title} - ${result.kind}${artifactName}`;
|
|
15619
|
-
return `${label}: ${snippetText(result.snippet)} -> ${href}`;
|
|
15620
|
-
}
|
|
15621
15677
|
function askUrgency(args) {
|
|
15622
15678
|
const value = args.urgency;
|
|
15623
15679
|
return ASK_URGENCIES.find((urgency) => urgency === value);
|
|
@@ -16816,20 +16872,13 @@ async function executeDispatchTool(input) {
|
|
|
16816
16872
|
const query = stringArg(args, "query");
|
|
16817
16873
|
const project = optionalString(args, "project");
|
|
16818
16874
|
const limit = optionalNumber(args, "limit");
|
|
16875
|
+
const offset = optionalNumber(args, "offset");
|
|
16819
16876
|
const search = await client.search(query, {
|
|
16820
16877
|
...project === undefined ? {} : { project },
|
|
16821
|
-
...limit === undefined ? {} : { limit }
|
|
16878
|
+
...limit === undefined ? {} : { limit },
|
|
16879
|
+
...offset === undefined ? {} : { offset }
|
|
16822
16880
|
});
|
|
16823
|
-
|
|
16824
|
-
const count = results.length;
|
|
16825
|
-
return {
|
|
16826
|
-
text: count === 0 ? `No results for "${query}".` : [
|
|
16827
|
-
`${count} ${count === 1 ? "result" : "results"} for "${query}" (${search.took_ms} ms)`,
|
|
16828
|
-
...results.map((result) => searchResultLine(result, configUrl))
|
|
16829
|
-
].join(`
|
|
16830
|
-
`),
|
|
16831
|
-
details: { query, results }
|
|
16832
|
-
};
|
|
16881
|
+
return searchAnswer(search, query, offset, configUrl);
|
|
16833
16882
|
}
|
|
16834
16883
|
case "dispatch_issues": {
|
|
16835
16884
|
const project = stringArg(args, "project");
|
|
@@ -16869,7 +16918,7 @@ async function executeDispatchTool(input) {
|
|
|
16869
16918
|
}));
|
|
16870
16919
|
const titles = await liveSessionTitles(client, rows.some((row) => holdsSession(row.claim)));
|
|
16871
16920
|
const isPartial = offset !== 0 || rows.length !== total;
|
|
16872
|
-
const showing = !isPartial ? "" : rows.length === 0 ? `showing 0-0 of ${total}` :
|
|
16921
|
+
const showing = !isPartial ? "" : rows.length === 0 ? `showing 0-0 of ${total}` : pageSummaryText(offset, rows.length, total);
|
|
16873
16922
|
return {
|
|
16874
16923
|
text: rows.length === 0 ? `No issues in ${project}.${isPartial ? ` (${showing})` : ""}` : [
|
|
16875
16924
|
`${rows.length} ${rows.length === 1 ? "issue" : "issues"} in ${project}` + (isPartial ? ` (${showing})` : ""),
|
|
@@ -17475,7 +17524,7 @@ function endInjectedUserTurns(sessionID) {
|
|
|
17475
17524
|
// package.json
|
|
17476
17525
|
var package_default = {
|
|
17477
17526
|
name: "@sjawhar/pi-legion-envoy",
|
|
17478
|
-
version: "7.
|
|
17527
|
+
version: "7.6.1",
|
|
17479
17528
|
license: "Apache-2.0",
|
|
17480
17529
|
type: "module",
|
|
17481
17530
|
omp: {
|
|
@@ -18887,6 +18936,29 @@ function refusedCommand(command, commands, rule) {
|
|
|
18887
18936
|
}
|
|
18888
18937
|
return;
|
|
18889
18938
|
}
|
|
18939
|
+
var NON_FILE_WRITE_URL = /^(xd|agent|proc):\/\//iu;
|
|
18940
|
+
var READ_HEADER_TAG = /#[0-9A-Fa-f]{4}$/u;
|
|
18941
|
+
var PREFIXED_CONFLICT_URL = /^(.+):(conflict:\/\/.+)$/u;
|
|
18942
|
+
function unwrapReadHeader(path6) {
|
|
18943
|
+
const trimmed = path6.trimEnd();
|
|
18944
|
+
if (trimmed.length < 2 || !trimmed.startsWith("[") || !trimmed.endsWith("]"))
|
|
18945
|
+
return path6;
|
|
18946
|
+
const inner = trimmed.slice(1, -1);
|
|
18947
|
+
const tag = READ_HEADER_TAG.exec(inner);
|
|
18948
|
+
const target = tag === null ? inner : inner.slice(0, tag.index);
|
|
18949
|
+
if (target.length === 0 || tag === null && target.includes("#"))
|
|
18950
|
+
return path6;
|
|
18951
|
+
return target;
|
|
18952
|
+
}
|
|
18953
|
+
function writeTarget(path6) {
|
|
18954
|
+
const unwrapped = unwrapReadHeader(path6);
|
|
18955
|
+
return PREFIXED_CONFLICT_URL.exec(unwrapped)?.[2] ?? unwrapped;
|
|
18956
|
+
}
|
|
18957
|
+
function nonFileWriteScheme(toolCall) {
|
|
18958
|
+
if (toolCall.toolName !== "write" || typeof toolCall.input.path !== "string")
|
|
18959
|
+
return;
|
|
18960
|
+
return NON_FILE_WRITE_URL.exec(writeTarget(toolCall.input.path))?.[1]?.toLowerCase();
|
|
18961
|
+
}
|
|
18890
18962
|
function paneRuleRefusal(toolCall, rules) {
|
|
18891
18963
|
if (rules.length === 0)
|
|
18892
18964
|
return;
|
|
@@ -18905,8 +18977,8 @@ function paneRuleRefusal(toolCall, rules) {
|
|
|
18905
18977
|
let text;
|
|
18906
18978
|
if (toolName === "eval" && typeof input.code === "string")
|
|
18907
18979
|
text = input.code;
|
|
18908
|
-
else if (
|
|
18909
|
-
text =
|
|
18980
|
+
else if (nonFileWriteScheme(toolCall) === "proc" && typeof input.content === "string") {
|
|
18981
|
+
text = input.content;
|
|
18910
18982
|
} else
|
|
18911
18983
|
return;
|
|
18912
18984
|
for (const rule of rules) {
|
|
@@ -18923,10 +18995,6 @@ var CODE_TOOL_REFUSAL = {
|
|
|
18923
18995
|
reviewer: "the reviewer edits nothing except the final .legion/ cleanup commit via bash",
|
|
18924
18996
|
merger: "the merger only verifies and reports"
|
|
18925
18997
|
};
|
|
18926
|
-
var TOOL_DEVICE_URL = /^xd:\/\//iu;
|
|
18927
|
-
function isToolDeviceInvocation(toolCall) {
|
|
18928
|
-
return toolCall.toolName === "write" && typeof toolCall.input.path === "string" && TOOL_DEVICE_URL.test(toolCall.input.path);
|
|
18929
|
-
}
|
|
18930
18998
|
function legionExtension(pi) {
|
|
18931
18999
|
const instance = randomUUID2().slice(0, 8);
|
|
18932
19000
|
logger2.debug("extension instance loaded", { extension: import.meta.url, instance });
|
|
@@ -19007,7 +19075,7 @@ function legionExtension(pi) {
|
|
|
19007
19075
|
const active = claimSession.capability(sessionID);
|
|
19008
19076
|
const codeToolRefusal = active === undefined ? undefined : CODE_TOOL_REFUSAL[active.role];
|
|
19009
19077
|
const mutatesCode = CODE_MUTATION_TOOLS.includes(toolCall.toolName) || active?.role === "architect" && toolCall.toolName === "bash" && !isSingleLegionCommand(toolCall.input.command);
|
|
19010
|
-
if (codeToolRefusal !== undefined && mutatesCode &&
|
|
19078
|
+
if (codeToolRefusal !== undefined && mutatesCode && nonFileWriteScheme(toolCall) === undefined) {
|
|
19011
19079
|
return { block: true, reason: codeToolRefusal };
|
|
19012
19080
|
}
|
|
19013
19081
|
if (!needsGrant(toolCall))
|
|
@@ -160,15 +160,14 @@ follow [Writing a spec](#writing-a-spec).
|
|
|
160
160
|
you plan, start a design document, file an issue, ask, post a finding or start work, and what to
|
|
161
161
|
do with each hit. What it leaves out:
|
|
162
162
|
```ts
|
|
163
|
-
dispatch_search({ query, project?, limit? })
|
|
163
|
+
dispatch_search({ query, project?, limit?, offset? })
|
|
164
164
|
```
|
|
165
|
-
Websearch syntax applies: `"merge queue"`, `-daemon`, `OR`.
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
"no prior issue" in the spec.
|
|
165
|
+
Websearch syntax applies: `"merge queue"`, `-daemon`, `OR`. Issues, documents, asks, comments, and messages rank separately; the
|
|
166
|
+
merged page orders each kind's best in turn — issue, document, ask, comment, message — and a bare issue key ranks first. A page
|
|
167
|
+
holds `limit` hits (20 default, 50 max); the first line gives the total (`showing 1-20 of 312`); see
|
|
168
|
+
[Search paging](skill://dispatch/references/issues.md#search-paging) for more. Queries over 1,000 characters are refused: use the
|
|
169
|
+
few words `skill://dispatch-first` names, never a pasted passage. Issue hits start with the issue key; document hits start with
|
|
170
|
+
`dispatch://PROJECT/artifact/<slug>`, then the link. Cite the hit (`dispatch://KEY` or the doc ref) or say "no prior issue".
|
|
172
171
|
|
|
173
172
|
`dispatch_issue` refuses a title that near-duplicates an issue in the same project and returns the candidates (`POSSIBLE_DUPLICATE`).
|
|
174
173
|
Read them; reference the existing issue, or repeat the call with `force: true` when it is genuinely new work.
|
|
@@ -193,6 +193,16 @@ The audit finds four shapes:
|
|
|
193
193
|
Run the audit as a step of a coordinator's loop, at each checkpoint, not as a habit: these shapes
|
|
194
194
|
are found by running the check, not by noticing them.
|
|
195
195
|
|
|
196
|
+
## Search paging
|
|
197
|
+
|
|
198
|
+
`skill://dispatch` sends you here when a `dispatch_search` page is not enough: more hits remain,
|
|
199
|
+
or a kind runs out before the page does.
|
|
200
|
+
|
|
201
|
+
A page holds `limit` hits (20 by default, 50 at most); the first line names how many match
|
|
202
|
+
(`showing 1-20 of 312`), and, while more can be reached, the last line names the next `offset`.
|
|
203
|
+
Each kind lists at most its best 100, so when the result says the rest cannot be paged to, narrow
|
|
204
|
+
the query or name a `project`.
|
|
205
|
+
|
|
196
206
|
## Syncing a project's architecture model
|
|
197
207
|
|
|
198
208
|
Import a project's architecture model from its configured source repository now (a human configures the source in Settings):
|
|
@@ -25,11 +25,11 @@ reply. A human may reply to your message in turn — the follow-up arrives as a
|
|
|
25
25
|
`in_reply_to` names your message and whose `reply_body` quotes it; answer it the same way,
|
|
26
26
|
`dispatch_message({ issue, in_reply_to: "<their reply id>", body })`, so the exchange reads as one
|
|
27
27
|
thread. `dispatch_message` itself never carries `target` or `delivery`: agent-to-agent traffic goes
|
|
28
|
-
through Envoy or
|
|
29
|
-
(`{kind: "session", id}`), and the card shows that session as the author.
|
|
30
|
-
(any authenticated caller) lists live sessions with their capabilities
|
|
31
|
-
target only a session that advertises the mode you want. Sending to a
|
|
32
|
-
(`POST /api/v1/agents/{session_id}/messages`) stays human-only.
|
|
28
|
+
through Envoy or Oh My Pi's `agent://` messages. A bearer that targets over HTTP names its own
|
|
29
|
+
session in `actor` (`{kind: "session", id}`), and the card shows that session as the author.
|
|
30
|
+
`GET /api/v1/agents` (any authenticated caller) lists live sessions with their capabilities
|
|
31
|
+
(`aside`, `btw`, `steer`); target only a session that advertises the mode you want. Sending to a
|
|
32
|
+
session with no issue (`POST /api/v1/agents/{session_id}/messages`) stays human-only.
|
|
33
33
|
|
|
34
34
|
## Answering a direct message
|
|
35
35
|
|
|
@@ -122,8 +122,9 @@ envoy_send(
|
|
|
122
122
|
|
|
123
123
|
`envoy_whoami`'s `session_id` is the address a reply to you reaches. Inside a `task` subagent it
|
|
124
124
|
is the session that spawned you: a subagent registers no Envoy session of its own, so a peer
|
|
125
|
-
answering it reaches that session, which relays to you
|
|
126
|
-
output carries your own host session id — it is not an address, so
|
|
125
|
+
answering it reaches that session, which relays to you with a `write` to your `agent://` address.
|
|
126
|
+
The `subagent` field in that output carries your own host session id — it is not an address, so
|
|
127
|
+
never hand it to a peer.
|
|
127
128
|
|
|
128
129
|
`session_id` is empty when nothing can be reached: a process that took no Envoy identity, or a
|
|
129
130
|
subagent whose spawning session this process can no longer place (it forked away, and other
|
|
@@ -134,8 +135,8 @@ would send your peers to an agent that never spawned you.
|
|
|
134
135
|
Your own `envoy_publish` never reaches the agent that spawned you. The listener delivers nothing
|
|
135
136
|
to the session a message names as its source, and inside a subagent that source is your parent,
|
|
136
137
|
so a publish to a role it holds — or to any topic it subscribes to — is accepted and delivered to
|
|
137
|
-
nobody. Use
|
|
138
|
-
unaffected.
|
|
138
|
+
nobody. Use a `write` to its `agent://` address for that one hop. `envoy_send` to any other
|
|
139
|
+
session, including a reply, is unaffected.
|
|
139
140
|
|
|
140
141
|
### Delivery capabilities
|
|
141
142
|
|
|
@@ -62,11 +62,11 @@ workflow table. You may still use ordinary `task` subagents for your own phase w
|
|
|
62
62
|
is a Legion role.
|
|
63
63
|
Escalate a product, scope, design, cross-phase, or lifecycle decision to the owning architect with
|
|
64
64
|
`envoy_publish` to its role topic (`notifications.role.` followed by its encoded token, see
|
|
65
|
-
above), carrying the verified facts and the decision needed. `
|
|
66
|
-
inside your own process, not the architect's separate one. Never write a decision block
|
|
67
|
-
spec yourself: the architect decides whether the human must answer it and writes the block,
|
|
68
|
-
a new version of an approved root spec closes the tree's design gate. A standalone to-do
|
|
69
|
-
human can do is a `dispatch_ask`, and its replies return to your own session.
|
|
65
|
+
above), carrying the verified facts and the decision needed. A `write` to `agent://` only reaches
|
|
66
|
+
agents inside your own process, not the architect's separate one. Never write a decision block
|
|
67
|
+
into a spec yourself: the architect decides whether the human must answer it and writes the block,
|
|
68
|
+
since a new version of an approved root spec closes the tree's design gate. A standalone to-do
|
|
69
|
+
only a human can do is a `dispatch_ask`, and its replies return to your own session.
|
|
70
70
|
|
|
71
71
|
Because the same agent is always resumed for its phase, you may receive more than one
|
|
72
72
|
assignment across your lifetime: once the daemon ends your phase it suspends you, and when a later
|
|
@@ -152,9 +152,10 @@ new work.
|
|
|
152
152
|
clone, so they all share one operation log: `jj undo`, `jj abandon`, and
|
|
153
153
|
`jj op restore|revert|abandon|undo` rewrite it for every tree at once. The extension refuses them in every
|
|
154
154
|
phase-worker pane before they run — a `bash` command in any position of a pipeline or `&&`
|
|
155
|
-
chain, with or without `-R`, judged on the whole argument list
|
|
156
|
-
|
|
157
|
-
|
|
155
|
+
chain, with or without `-R`, judged on the whole argument list (a supervised service's start
|
|
156
|
+
included); `eval` code; and stdin written to a service (a `write` to `proc://<id>`) — from your
|
|
157
|
+
own tool calls and from any `task` subagent you spawn (it runs in your pane, against the same
|
|
158
|
+
log), and a `bash` command whose quoted text merely mentions `jj`
|
|
158
159
|
with one of those words (a heredoc, an echo, a commit message) is refused too: write such text
|
|
159
160
|
with the `write` tool or say "operation-log rollback" instead. `jj restore <paths>`,
|
|
160
161
|
`jj op log`, and `jj op show` stay allowed. Recover forward only: a new commit
|