@sjawhar/pi-legion-envoy 6.0.1 → 6.0.3
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 +71 -39
- package/dist/legion.js +60 -31
- package/package.json +1 -1
package/dist/envoy.js
CHANGED
|
@@ -30360,13 +30360,13 @@ var dispatchToolSpecs = [
|
|
|
30360
30360
|
},
|
|
30361
30361
|
{
|
|
30362
30362
|
name: "dispatch_request_approval",
|
|
30363
|
-
example: { issue: "DSP-1", summary: "
|
|
30364
|
-
description: "Ask a human to approve a document at its current version. Opens an approval ask (Approve / " + "Request changes) in the human's Inbox whose question names the document and version, " + "followed by the summary; the answer pins a review to that version and arrives as " + "artifact.approved or artifact.changes_requested. A later version
|
|
30363
|
+
example: { issue: "DSP-1", summary: "A live sync replaces the nightly export." },
|
|
30364
|
+
description: "Ask a human to approve a document at its current version. Opens an approval ask (Approve / " + "Request changes) in the human's Inbox whose question names the document and version, " + "followed by the summary; the answer pins a review to that version and arrives as " + "artifact.approved or artifact.changes_requested. A later version carries the same open " + "request forward and leaves it waiting on you; once the revision is complete and the human " + "has agreed to every point in it, call this again to hand that request back. The request " + "carries nothing new. A call while it already waits on the human hands nothing back: the " + "same summary changes nothing, and a different one is refused, since it would rewrite the " + "card the human is reading. " + "Refused, with nothing sent, while the document holds an open decision block, even when a " + "human asked for approval: the refusal names each block; ask the human to answer or waive " + "it first. " + OWNER_REFERENCE,
|
|
30365
30365
|
arguments: (z2) => ({
|
|
30366
30366
|
issue: z2.string().describe(ISSUE_REFERENCE).optional(),
|
|
30367
30367
|
project: z2.string().describe("Project key owning the document.").optional(),
|
|
30368
30368
|
artifact: z2.string().describe("Project document artifact id, slug, or filename; primary document by default for an issue.").optional(),
|
|
30369
|
-
summary: z2.string({ min: 1 }).describe("
|
|
30369
|
+
summary: z2.string({ min: 1 }).describe("What the human is approving, in one to three sentences, and nothing else: no commentary on itself or the conversation, and no question. Request approval only once the human has agreed to every point in the document.")
|
|
30370
30370
|
}),
|
|
30371
30371
|
validation: documentOwnerValidation(true)
|
|
30372
30372
|
},
|
|
@@ -31819,6 +31819,7 @@ var DISPATCH_PAYLOAD_SCHEMAS = {
|
|
|
31819
31819
|
"ask.opened": AskEventPayloadSchema,
|
|
31820
31820
|
"ask.anchor_refreshed": AskEventPayloadSchema,
|
|
31821
31821
|
"ask.edited": AskEditedEventPayloadSchema,
|
|
31822
|
+
"ask.handed_back": AskEventPayloadSchema,
|
|
31822
31823
|
"ask.answered": AskEventPayloadSchema,
|
|
31823
31824
|
"ask.resolved": AskEventPayloadSchema,
|
|
31824
31825
|
"comment.created": CommentEventPayloadSchema,
|
|
@@ -32701,7 +32702,8 @@ class DispatchClient {
|
|
|
32701
32702
|
return this.#json("POST", ["api", "v1", "asks", id, "resolve"], input);
|
|
32702
32703
|
}
|
|
32703
32704
|
async requestApproval(artifactID, input) {
|
|
32704
|
-
|
|
32705
|
+
const answer = await this.#jsonAnswer("POST", ["api", "v1", "artifacts", artifactID, "approval-requests"], input);
|
|
32706
|
+
return { ...answer.payload, recorded: answer.status === 201 };
|
|
32705
32707
|
}
|
|
32706
32708
|
async editAsk(id, input) {
|
|
32707
32709
|
return this.#json("PATCH", ["api", "v1", "asks", id], input);
|
|
@@ -32846,6 +32848,9 @@ class DispatchClient {
|
|
|
32846
32848
|
}
|
|
32847
32849
|
}
|
|
32848
32850
|
async#json(method, path2, body, query) {
|
|
32851
|
+
return (await this.#jsonAnswer(method, path2, body, query)).payload;
|
|
32852
|
+
}
|
|
32853
|
+
async#jsonAnswer(method, path2, body, query) {
|
|
32849
32854
|
const headers = {
|
|
32850
32855
|
Accept: "application/json",
|
|
32851
32856
|
Authorization: `Bearer ${this.token}`
|
|
@@ -32859,7 +32864,7 @@ class DispatchClient {
|
|
|
32859
32864
|
signal: this.#signal,
|
|
32860
32865
|
...body === undefined ? {} : { body: JSON.stringify(body) }
|
|
32861
32866
|
});
|
|
32862
|
-
return this.#response(method, url2, response);
|
|
32867
|
+
return { status: response.status, payload: await this.#response(method, url2, response) };
|
|
32863
32868
|
}
|
|
32864
32869
|
async#form(method, path2, body) {
|
|
32865
32870
|
const url2 = this.#url(path2);
|
|
@@ -33704,8 +33709,10 @@ function approvalLine(artifact) {
|
|
|
33704
33709
|
if (approval === undefined || approval.state === "draft")
|
|
33705
33710
|
return;
|
|
33706
33711
|
switch (approval.state) {
|
|
33707
|
-
case "awaiting":
|
|
33708
|
-
|
|
33712
|
+
case "awaiting": {
|
|
33713
|
+
const turn = approval.waiting_on === undefined ? "" : `, waiting on ${approval.waiting_on}`;
|
|
33714
|
+
return `Approval: awaiting${turn} (requested by ${approval.requested_by?.id ?? "unknown"}, ask ${approval.ask_id ?? "?"})`;
|
|
33715
|
+
}
|
|
33709
33716
|
case "approved":
|
|
33710
33717
|
return `Approval: approved v${approval.version} by ${approval.by?.id ?? "unknown"}`;
|
|
33711
33718
|
case "stale":
|
|
@@ -33845,6 +33852,7 @@ function eventHead(event) {
|
|
|
33845
33852
|
case "ask.opened":
|
|
33846
33853
|
case "ask.anchor_refreshed":
|
|
33847
33854
|
case "ask.edited":
|
|
33855
|
+
case "ask.handed_back":
|
|
33848
33856
|
case "ask.resolved":
|
|
33849
33857
|
return textHead(event.payload.question);
|
|
33850
33858
|
case "ask.answered":
|
|
@@ -34592,7 +34600,7 @@ ${followsAsk(askOwner)}`,
|
|
|
34592
34600
|
replyToOwnAsk: replyToAsk !== undefined && (comment.advice?.your_open_asks?.some((ask) => ask.id === replyToAsk) ?? false)
|
|
34593
34601
|
});
|
|
34594
34602
|
if (replyToAsk !== undefined) {
|
|
34595
|
-
const askState = comment.
|
|
34603
|
+
const askState = comment.ask_waiting_on === undefined ? "" : `; ask now waiting on ${comment.ask_waiting_on}`;
|
|
34596
34604
|
return {
|
|
34597
34605
|
text: [
|
|
34598
34606
|
`Replied on ask ${replyToAsk} (comment ${comment.id}${askState}). ${followsAsk(commentOwner2)}`,
|
|
@@ -34603,7 +34611,7 @@ ${followsAsk(askOwner)}`,
|
|
|
34603
34611
|
...commentDetails,
|
|
34604
34612
|
ask: replyToAsk,
|
|
34605
34613
|
follows: { ask: replyToAsk },
|
|
34606
|
-
...comment.
|
|
34614
|
+
...comment.ask_waiting_on === undefined ? {} : { ask_waiting_on: comment.ask_waiting_on },
|
|
34607
34615
|
...comment.advice === undefined ? {} : { advice: comment.advice }
|
|
34608
34616
|
}
|
|
34609
34617
|
};
|
|
@@ -34776,8 +34784,9 @@ ${trailer.join(`
|
|
|
34776
34784
|
};
|
|
34777
34785
|
}
|
|
34778
34786
|
const details = await followedAskDetails(client, result.ask, resolved.artifact);
|
|
34787
|
+
const outcome = result.recorded ? `Approval requested for ${resolved.artifact.name} (document id ${resolved.artifact.id}) at version ${result.version} (ask ${result.ask.id}).` : `The approval request for ${resolved.artifact.name} (document id ${resolved.artifact.id}) at version ${result.version} (ask ${result.ask.id}) already waits on the human, so this call changed nothing: nothing since it last reached the human (a newer version, a human's reply in its thread, or your progress note) left it waiting on you.`;
|
|
34779
34788
|
return {
|
|
34780
|
-
text:
|
|
34789
|
+
text: `${outcome} The human's Inbox asks: ${JSON.stringify(result.ask.question)}. The answer arrives as artifact.approved or artifact.changes_requested; an edit after approval makes it stale, so request again for the new version.`,
|
|
34781
34790
|
details: { ...details, artifact: resolved.artifact.id, version: result.version }
|
|
34782
34791
|
};
|
|
34783
34792
|
}
|
|
@@ -35454,7 +35463,7 @@ function sourceSession(sessionID) {
|
|
|
35454
35463
|
}
|
|
35455
35464
|
|
|
35456
35465
|
// extensions/envoy.ts
|
|
35457
|
-
import { logger } from "@oh-my-pi/pi-utils";
|
|
35466
|
+
import { logger as logger2 } from "@oh-my-pi/pi-utils";
|
|
35458
35467
|
var import_nats2 = __toESM(require_mod4(), 1);
|
|
35459
35468
|
|
|
35460
35469
|
// src/agent-stream.ts
|
|
@@ -35932,33 +35941,56 @@ function sideTurn(pi, context) {
|
|
|
35932
35941
|
import fs from "fs";
|
|
35933
35942
|
import path3 from "path";
|
|
35934
35943
|
import * as host from "@oh-my-pi/pi-coding-agent";
|
|
35944
|
+
import { logger } from "@oh-my-pi/pi-utils";
|
|
35935
35945
|
var LEGION_BOOTSTRAPPED_SESSION = Symbol.for("legion.pi-envoy.bootstrapped-session");
|
|
35936
35946
|
var bootstrappedSessionStore = globalThis;
|
|
35937
35947
|
function recordBootstrappedSession(sessionFile) {
|
|
35938
35948
|
bootstrappedSessionStore[LEGION_BOOTSTRAPPED_SESSION] = sessionFile;
|
|
35939
35949
|
}
|
|
35940
35950
|
async function isSubagentSession(context) {
|
|
35941
|
-
|
|
35951
|
+
const registered = registeredSubagent(context);
|
|
35952
|
+
if (registered !== undefined)
|
|
35953
|
+
return { subagent: registered, settled: true };
|
|
35954
|
+
try {
|
|
35955
|
+
await context.sessionManager.ensureOnDisk();
|
|
35956
|
+
} catch (error48) {
|
|
35957
|
+
const subagent = transcriptSaysSubagent(context);
|
|
35958
|
+
logger.warn("subagent check: publishing the transcript failed; answered from the transcript on disk, and the next call asks again", { sessionFile: context.sessionManager.getSessionFile(), subagent, error: messageFor(error48) });
|
|
35959
|
+
return { subagent, settled: false };
|
|
35960
|
+
}
|
|
35961
|
+
return { subagent: transcriptSaysSubagent(context), settled: true };
|
|
35962
|
+
}
|
|
35963
|
+
function transcriptSaysSubagent(context) {
|
|
35942
35964
|
const sessionFile = context.sessionManager.getSessionFile();
|
|
35943
|
-
if (sessionFile !== undefined
|
|
35944
|
-
|
|
35965
|
+
if (sessionFile !== undefined) {
|
|
35966
|
+
if (fs.existsSync(`${path3.dirname(sessionFile)}.jsonl`))
|
|
35967
|
+
return true;
|
|
35968
|
+
if (fs.existsSync(sessionFile))
|
|
35969
|
+
return false;
|
|
35945
35970
|
}
|
|
35946
35971
|
const bootstrapped = bootstrappedSessionStore[LEGION_BOOTSTRAPPED_SESSION];
|
|
35947
35972
|
return bootstrapped !== undefined && bootstrapped !== sessionFile;
|
|
35948
35973
|
}
|
|
35949
|
-
function
|
|
35974
|
+
function registeredSubagent(context) {
|
|
35950
35975
|
const registry2 = host.AgentRegistry?.global();
|
|
35951
35976
|
if (registry2 === undefined)
|
|
35952
|
-
return
|
|
35977
|
+
return;
|
|
35953
35978
|
const sessionID = context.sessionManager.getSessionId();
|
|
35954
35979
|
const sessionFile = context.sessionManager.getSessionFile();
|
|
35955
|
-
|
|
35980
|
+
const own = registry2.list().filter((ref) => ref.session === null ? sessionFile !== undefined && ref.sessionFile === sessionFile : ref.session.sessionManager.getSessionId() === sessionID);
|
|
35981
|
+
if (own.length === 0)
|
|
35982
|
+
return;
|
|
35983
|
+
return own.some((ref) => ref.kind !== "main");
|
|
35956
35984
|
}
|
|
35957
35985
|
function subagentSessionCheck() {
|
|
35958
|
-
let
|
|
35959
|
-
return (context) => {
|
|
35960
|
-
|
|
35961
|
-
|
|
35986
|
+
let settled;
|
|
35987
|
+
return async (context) => {
|
|
35988
|
+
if (settled !== undefined)
|
|
35989
|
+
return settled;
|
|
35990
|
+
const answer = await isSubagentSession(context);
|
|
35991
|
+
if (answer.settled)
|
|
35992
|
+
settled = answer.subagent;
|
|
35993
|
+
return answer.subagent;
|
|
35962
35994
|
};
|
|
35963
35995
|
}
|
|
35964
35996
|
|
|
@@ -36118,7 +36150,7 @@ function resolveSkillsDirectory() {
|
|
|
36118
36150
|
var SKILLS_DIRECTORY = resolveSkillsDirectory();
|
|
36119
36151
|
var DISPATCH_FIRST_CONTEXT = readDispatchFirstContext(dispatchFirstSkillFile(SKILLS_DIRECTORY));
|
|
36120
36152
|
function envoyExtension(pi) {
|
|
36121
|
-
|
|
36153
|
+
logger2.debug("extension instance loaded", { extension: import.meta.url });
|
|
36122
36154
|
const defaults = envoyDefaultsFromEnvironment(process.env);
|
|
36123
36155
|
const dispatchConfig = resolveDispatchConfig(process.env, { cwd: process.cwd() });
|
|
36124
36156
|
const currentDispatchConfig = () => {
|
|
@@ -36181,7 +36213,7 @@ function envoyExtension(pi) {
|
|
|
36181
36213
|
if (selfCheckWarningSessionIDs.has(failedSessionID))
|
|
36182
36214
|
return;
|
|
36183
36215
|
selfCheckWarningSessionIDs.add(failedSessionID);
|
|
36184
|
-
|
|
36216
|
+
logger2.warn("envoy: run-end waiting self-check failed; no reminder was sent", {
|
|
36185
36217
|
sessionID: failedSessionID,
|
|
36186
36218
|
error: messageFor(error48)
|
|
36187
36219
|
});
|
|
@@ -36249,7 +36281,7 @@ function envoyExtension(pi) {
|
|
|
36249
36281
|
const { delivery } = rendered;
|
|
36250
36282
|
const key = handledAttemptKey(delivery.id, delivery.attempt);
|
|
36251
36283
|
if (handledDispatchAttempts.has(key)) {
|
|
36252
|
-
|
|
36284
|
+
logger2.warn("envoy: a frame names a Dispatch attempt this session already delivered; it arrives as a card", { attempt: delivery.attempt, messageID: delivery.id });
|
|
36253
36285
|
return;
|
|
36254
36286
|
}
|
|
36255
36287
|
handledDispatchAttempts.add(key);
|
|
@@ -36263,11 +36295,11 @@ function envoyExtension(pi) {
|
|
|
36263
36295
|
});
|
|
36264
36296
|
const turn = turnFromAccept(accepted);
|
|
36265
36297
|
if (turn === undefined) {
|
|
36266
|
-
|
|
36298
|
+
logger2.warn("envoy: Dispatch accepted a direct message without the stored body and mode to inject; it arrives as a card", { attempt: delivery.attempt, messageID: delivery.id });
|
|
36267
36299
|
}
|
|
36268
36300
|
return turn;
|
|
36269
36301
|
} catch (error48) {
|
|
36270
|
-
|
|
36302
|
+
logger2.warn("envoy: Dispatch did not accept a direct message as this session's turn; it arrives as a card", { attempt: delivery.attempt, messageID: delivery.id, error: messageFor(error48) });
|
|
36271
36303
|
return;
|
|
36272
36304
|
}
|
|
36273
36305
|
};
|
|
@@ -36426,7 +36458,7 @@ function envoyExtension(pi) {
|
|
|
36426
36458
|
continue;
|
|
36427
36459
|
connection?.publish(reply, codec2.encode(answer));
|
|
36428
36460
|
} catch (error48) {
|
|
36429
|
-
|
|
36461
|
+
logger2.warn("envoy: agent stream control message failed", {
|
|
36430
36462
|
error: messageFor(error48),
|
|
36431
36463
|
sessionID: session
|
|
36432
36464
|
});
|
|
@@ -36539,7 +36571,7 @@ function envoyExtension(pi) {
|
|
|
36539
36571
|
}
|
|
36540
36572
|
if (!result.claimed) {
|
|
36541
36573
|
endRole();
|
|
36542
|
-
|
|
36574
|
+
logger2.warn("envoy: role re-assertion refused; held by another live session", {
|
|
36543
36575
|
role,
|
|
36544
36576
|
sessionID: id,
|
|
36545
36577
|
holder: result.holder
|
|
@@ -36547,7 +36579,7 @@ function envoyExtension(pi) {
|
|
|
36547
36579
|
context.ui.notify(`envoy: role ${role} is now held by session ${result.holder}; this session no longer holds it`, "warning");
|
|
36548
36580
|
return;
|
|
36549
36581
|
}
|
|
36550
|
-
|
|
36582
|
+
logger2.warn("envoy: role re-asserted after the listener lost the claim", {
|
|
36551
36583
|
role,
|
|
36552
36584
|
sessionID: id,
|
|
36553
36585
|
previousHolder: holder ?? null
|
|
@@ -36558,7 +36590,7 @@ function envoyExtension(pi) {
|
|
|
36558
36590
|
if (regained === undefined)
|
|
36559
36591
|
return;
|
|
36560
36592
|
Promise.resolve().then(() => regained(role, reason)).catch((error48) => {
|
|
36561
|
-
|
|
36593
|
+
logger2.warn("envoy: role regain hook failed", { role, reason, error: messageFor(error48) });
|
|
36562
36594
|
});
|
|
36563
36595
|
};
|
|
36564
36596
|
const ensureHeartbeat = (context) => {
|
|
@@ -36615,7 +36647,7 @@ function envoyExtension(pi) {
|
|
|
36615
36647
|
previousSessionID: options.previousSessionID
|
|
36616
36648
|
});
|
|
36617
36649
|
if (!result.claimed) {
|
|
36618
|
-
|
|
36650
|
+
logger2.warn("envoy: role held by another live session; not reclaimed", {
|
|
36619
36651
|
role,
|
|
36620
36652
|
sessionID,
|
|
36621
36653
|
holder: result.holder
|
|
@@ -36670,7 +36702,7 @@ function envoyExtension(pi) {
|
|
|
36670
36702
|
return;
|
|
36671
36703
|
const continued = carryPreviousSessionRole && previousSessionID !== "" && previousSessionID !== sessionID ? previousSessionID : undefined;
|
|
36672
36704
|
await setEnvoyRole(role, { soft: true, previousSessionID: continued }).catch((error48) => {
|
|
36673
|
-
|
|
36705
|
+
logger2.warn("envoy: role reclaim failed", { role, sessionID, error: messageFor(error48) });
|
|
36674
36706
|
});
|
|
36675
36707
|
};
|
|
36676
36708
|
let sessionEstablishment = Promise.resolve();
|
|
@@ -36746,12 +36778,11 @@ function envoyExtension(pi) {
|
|
|
36746
36778
|
sessionID: () => activeSessionContext?.sessionManager.getSessionId() ?? sessionID
|
|
36747
36779
|
};
|
|
36748
36780
|
bridge.instances.push(claimInstance);
|
|
36749
|
-
const
|
|
36750
|
-
const
|
|
36751
|
-
const replyAddress = async (context, liveSessionID = "") => {
|
|
36781
|
+
const isSubagent = subagentSessionCheck();
|
|
36782
|
+
const replyAddress = async (context, liveSessionID = "", subagent) => {
|
|
36752
36783
|
if (sessionID !== "")
|
|
36753
36784
|
return liveSessionID || sessionID;
|
|
36754
|
-
if (!await isSubagent(context))
|
|
36785
|
+
if (!(subagent ?? await isSubagent(context)))
|
|
36755
36786
|
return liveSessionID;
|
|
36756
36787
|
return resolveEnvoySession(context.sessionManager.getSessionFile());
|
|
36757
36788
|
};
|
|
@@ -37116,14 +37147,15 @@ Warnings: ${warnings.join("; ")}`, {
|
|
|
37116
37147
|
}
|
|
37117
37148
|
case EnvoyToolOperation.publish: {
|
|
37118
37149
|
const topic = stringFor(parameters, "topic");
|
|
37119
|
-
const
|
|
37150
|
+
const inSubagent = await isSubagent(context);
|
|
37151
|
+
const source = await replyAddress(context, "", inSubagent);
|
|
37120
37152
|
const result = await client.publish({
|
|
37121
37153
|
sourceSessionID: source,
|
|
37122
37154
|
topic,
|
|
37123
37155
|
message: stringFor(parameters, "message"),
|
|
37124
37156
|
...toMessageMetadata(parameters)
|
|
37125
37157
|
});
|
|
37126
|
-
const undelivered = source !== "" && result.holder === source &&
|
|
37158
|
+
const undelivered = source !== "" && result.holder === source && inSubagent;
|
|
37127
37159
|
const published = result.holder === undefined ? `published ${result.envelope.event_id}` : `published ${result.envelope.event_id}; holder ${result.holder}`;
|
|
37128
37160
|
return toolSuccess(undelivered ? `${published}
|
|
37129
37161
|
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 over hub instead.` : published, {
|
|
@@ -37144,7 +37176,7 @@ Not delivered: that holder is the session this subagent sends as, and the listen
|
|
|
37144
37176
|
}
|
|
37145
37177
|
case EnvoyToolOperation.whoami: {
|
|
37146
37178
|
const inSubagent = await isSubagent(context);
|
|
37147
|
-
const address = await replyAddress(context);
|
|
37179
|
+
const address = await replyAddress(context, "", inSubagent);
|
|
37148
37180
|
const subagent = inSubagent ? {
|
|
37149
37181
|
session_id: context.sessionManager.getSessionId(),
|
|
37150
37182
|
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 over hub." : "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 over hub. Your own envoy_publish never reaches that agent \u2014 use hub for that hop."
|
package/dist/legion.js
CHANGED
|
@@ -14447,13 +14447,13 @@ var dispatchToolSpecs = [
|
|
|
14447
14447
|
},
|
|
14448
14448
|
{
|
|
14449
14449
|
name: "dispatch_request_approval",
|
|
14450
|
-
example: { issue: "DSP-1", summary: "
|
|
14451
|
-
description: "Ask a human to approve a document at its current version. Opens an approval ask (Approve / " + "Request changes) in the human's Inbox whose question names the document and version, " + "followed by the summary; the answer pins a review to that version and arrives as " + "artifact.approved or artifact.changes_requested. A later version
|
|
14450
|
+
example: { issue: "DSP-1", summary: "A live sync replaces the nightly export." },
|
|
14451
|
+
description: "Ask a human to approve a document at its current version. Opens an approval ask (Approve / " + "Request changes) in the human's Inbox whose question names the document and version, " + "followed by the summary; the answer pins a review to that version and arrives as " + "artifact.approved or artifact.changes_requested. A later version carries the same open " + "request forward and leaves it waiting on you; once the revision is complete and the human " + "has agreed to every point in it, call this again to hand that request back. The request " + "carries nothing new. A call while it already waits on the human hands nothing back: the " + "same summary changes nothing, and a different one is refused, since it would rewrite the " + "card the human is reading. " + "Refused, with nothing sent, while the document holds an open decision block, even when a " + "human asked for approval: the refusal names each block; ask the human to answer or waive " + "it first. " + OWNER_REFERENCE,
|
|
14452
14452
|
arguments: (z2) => ({
|
|
14453
14453
|
issue: z2.string().describe(ISSUE_REFERENCE).optional(),
|
|
14454
14454
|
project: z2.string().describe("Project key owning the document.").optional(),
|
|
14455
14455
|
artifact: z2.string().describe("Project document artifact id, slug, or filename; primary document by default for an issue.").optional(),
|
|
14456
|
-
summary: z2.string({ min: 1 }).describe("
|
|
14456
|
+
summary: z2.string({ min: 1 }).describe("What the human is approving, in one to three sentences, and nothing else: no commentary on itself or the conversation, and no question. Request approval only once the human has agreed to every point in the document.")
|
|
14457
14457
|
}),
|
|
14458
14458
|
validation: documentOwnerValidation(true)
|
|
14459
14459
|
},
|
|
@@ -15464,7 +15464,8 @@ class DispatchClient {
|
|
|
15464
15464
|
return this.#json("POST", ["api", "v1", "asks", id, "resolve"], input);
|
|
15465
15465
|
}
|
|
15466
15466
|
async requestApproval(artifactID, input) {
|
|
15467
|
-
|
|
15467
|
+
const answer = await this.#jsonAnswer("POST", ["api", "v1", "artifacts", artifactID, "approval-requests"], input);
|
|
15468
|
+
return { ...answer.payload, recorded: answer.status === 201 };
|
|
15468
15469
|
}
|
|
15469
15470
|
async editAsk(id, input) {
|
|
15470
15471
|
return this.#json("PATCH", ["api", "v1", "asks", id], input);
|
|
@@ -15609,6 +15610,9 @@ class DispatchClient {
|
|
|
15609
15610
|
}
|
|
15610
15611
|
}
|
|
15611
15612
|
async#json(method, path2, body, query) {
|
|
15613
|
+
return (await this.#jsonAnswer(method, path2, body, query)).payload;
|
|
15614
|
+
}
|
|
15615
|
+
async#jsonAnswer(method, path2, body, query) {
|
|
15612
15616
|
const headers = {
|
|
15613
15617
|
Accept: "application/json",
|
|
15614
15618
|
Authorization: `Bearer ${this.token}`
|
|
@@ -15622,7 +15626,7 @@ class DispatchClient {
|
|
|
15622
15626
|
signal: this.#signal,
|
|
15623
15627
|
...body === undefined ? {} : { body: JSON.stringify(body) }
|
|
15624
15628
|
});
|
|
15625
|
-
return this.#response(method, url2, response);
|
|
15629
|
+
return { status: response.status, payload: await this.#response(method, url2, response) };
|
|
15626
15630
|
}
|
|
15627
15631
|
async#form(method, path2, body) {
|
|
15628
15632
|
const url2 = this.#url(path2);
|
|
@@ -16490,8 +16494,10 @@ function approvalLine(artifact) {
|
|
|
16490
16494
|
if (approval === undefined || approval.state === "draft")
|
|
16491
16495
|
return;
|
|
16492
16496
|
switch (approval.state) {
|
|
16493
|
-
case "awaiting":
|
|
16494
|
-
|
|
16497
|
+
case "awaiting": {
|
|
16498
|
+
const turn = approval.waiting_on === undefined ? "" : `, waiting on ${approval.waiting_on}`;
|
|
16499
|
+
return `Approval: awaiting${turn} (requested by ${approval.requested_by?.id ?? "unknown"}, ask ${approval.ask_id ?? "?"})`;
|
|
16500
|
+
}
|
|
16495
16501
|
case "approved":
|
|
16496
16502
|
return `Approval: approved v${approval.version} by ${approval.by?.id ?? "unknown"}`;
|
|
16497
16503
|
case "stale":
|
|
@@ -16631,6 +16637,7 @@ function eventHead(event) {
|
|
|
16631
16637
|
case "ask.opened":
|
|
16632
16638
|
case "ask.anchor_refreshed":
|
|
16633
16639
|
case "ask.edited":
|
|
16640
|
+
case "ask.handed_back":
|
|
16634
16641
|
case "ask.resolved":
|
|
16635
16642
|
return textHead(event.payload.question);
|
|
16636
16643
|
case "ask.answered":
|
|
@@ -17378,7 +17385,7 @@ ${followsAsk(askOwner)}`,
|
|
|
17378
17385
|
replyToOwnAsk: replyToAsk !== undefined && (comment.advice?.your_open_asks?.some((ask) => ask.id === replyToAsk) ?? false)
|
|
17379
17386
|
});
|
|
17380
17387
|
if (replyToAsk !== undefined) {
|
|
17381
|
-
const askState = comment.
|
|
17388
|
+
const askState = comment.ask_waiting_on === undefined ? "" : `; ask now waiting on ${comment.ask_waiting_on}`;
|
|
17382
17389
|
return {
|
|
17383
17390
|
text: [
|
|
17384
17391
|
`Replied on ask ${replyToAsk} (comment ${comment.id}${askState}). ${followsAsk(commentOwner2)}`,
|
|
@@ -17389,7 +17396,7 @@ ${followsAsk(askOwner)}`,
|
|
|
17389
17396
|
...commentDetails,
|
|
17390
17397
|
ask: replyToAsk,
|
|
17391
17398
|
follows: { ask: replyToAsk },
|
|
17392
|
-
...comment.
|
|
17399
|
+
...comment.ask_waiting_on === undefined ? {} : { ask_waiting_on: comment.ask_waiting_on },
|
|
17393
17400
|
...comment.advice === undefined ? {} : { advice: comment.advice }
|
|
17394
17401
|
}
|
|
17395
17402
|
};
|
|
@@ -17562,8 +17569,9 @@ ${trailer.join(`
|
|
|
17562
17569
|
};
|
|
17563
17570
|
}
|
|
17564
17571
|
const details = await followedAskDetails(client, result.ask, resolved.artifact);
|
|
17572
|
+
const outcome = result.recorded ? `Approval requested for ${resolved.artifact.name} (document id ${resolved.artifact.id}) at version ${result.version} (ask ${result.ask.id}).` : `The approval request for ${resolved.artifact.name} (document id ${resolved.artifact.id}) at version ${result.version} (ask ${result.ask.id}) already waits on the human, so this call changed nothing: nothing since it last reached the human (a newer version, a human's reply in its thread, or your progress note) left it waiting on you.`;
|
|
17565
17573
|
return {
|
|
17566
|
-
text:
|
|
17574
|
+
text: `${outcome} The human's Inbox asks: ${JSON.stringify(result.ask.question)}. The answer arrives as artifact.approved or artifact.changes_requested; an edit after approval makes it stale, so request again for the new version.`,
|
|
17567
17575
|
details: { ...details, artifact: resolved.artifact.id, version: result.version }
|
|
17568
17576
|
};
|
|
17569
17577
|
}
|
|
@@ -17736,7 +17744,7 @@ function asObject(value) {
|
|
|
17736
17744
|
}
|
|
17737
17745
|
|
|
17738
17746
|
// extensions/legion.ts
|
|
17739
|
-
import { logger } from "@oh-my-pi/pi-utils";
|
|
17747
|
+
import { logger as logger2 } from "@oh-my-pi/pi-utils";
|
|
17740
17748
|
|
|
17741
17749
|
// src/dispatch-user-turn.ts
|
|
17742
17750
|
function isUserTurnCandidate(rendered) {
|
|
@@ -17835,7 +17843,7 @@ function endInjectedUserTurns(sessionID) {
|
|
|
17835
17843
|
// package.json
|
|
17836
17844
|
var package_default = {
|
|
17837
17845
|
name: "@sjawhar/pi-legion-envoy",
|
|
17838
|
-
version: "6.0.
|
|
17846
|
+
version: "6.0.3",
|
|
17839
17847
|
type: "module",
|
|
17840
17848
|
omp: {
|
|
17841
17849
|
extensions: [
|
|
@@ -17883,33 +17891,56 @@ var package_default = {
|
|
|
17883
17891
|
import fs from "fs";
|
|
17884
17892
|
import path2 from "path";
|
|
17885
17893
|
import * as host from "@oh-my-pi/pi-coding-agent";
|
|
17894
|
+
import { logger } from "@oh-my-pi/pi-utils";
|
|
17886
17895
|
var LEGION_BOOTSTRAPPED_SESSION = Symbol.for("legion.pi-envoy.bootstrapped-session");
|
|
17887
17896
|
var bootstrappedSessionStore = globalThis;
|
|
17888
17897
|
function recordBootstrappedSession(sessionFile) {
|
|
17889
17898
|
bootstrappedSessionStore[LEGION_BOOTSTRAPPED_SESSION] = sessionFile;
|
|
17890
17899
|
}
|
|
17891
17900
|
async function isSubagentSession(context) {
|
|
17892
|
-
|
|
17901
|
+
const registered = registeredSubagent(context);
|
|
17902
|
+
if (registered !== undefined)
|
|
17903
|
+
return { subagent: registered, settled: true };
|
|
17904
|
+
try {
|
|
17905
|
+
await context.sessionManager.ensureOnDisk();
|
|
17906
|
+
} catch (error48) {
|
|
17907
|
+
const subagent = transcriptSaysSubagent(context);
|
|
17908
|
+
logger.warn("subagent check: publishing the transcript failed; answered from the transcript on disk, and the next call asks again", { sessionFile: context.sessionManager.getSessionFile(), subagent, error: messageFor(error48) });
|
|
17909
|
+
return { subagent, settled: false };
|
|
17910
|
+
}
|
|
17911
|
+
return { subagent: transcriptSaysSubagent(context), settled: true };
|
|
17912
|
+
}
|
|
17913
|
+
function transcriptSaysSubagent(context) {
|
|
17893
17914
|
const sessionFile = context.sessionManager.getSessionFile();
|
|
17894
|
-
if (sessionFile !== undefined
|
|
17895
|
-
|
|
17915
|
+
if (sessionFile !== undefined) {
|
|
17916
|
+
if (fs.existsSync(`${path2.dirname(sessionFile)}.jsonl`))
|
|
17917
|
+
return true;
|
|
17918
|
+
if (fs.existsSync(sessionFile))
|
|
17919
|
+
return false;
|
|
17896
17920
|
}
|
|
17897
17921
|
const bootstrapped = bootstrappedSessionStore[LEGION_BOOTSTRAPPED_SESSION];
|
|
17898
17922
|
return bootstrapped !== undefined && bootstrapped !== sessionFile;
|
|
17899
17923
|
}
|
|
17900
|
-
function
|
|
17924
|
+
function registeredSubagent(context) {
|
|
17901
17925
|
const registry2 = host.AgentRegistry?.global();
|
|
17902
17926
|
if (registry2 === undefined)
|
|
17903
|
-
return
|
|
17927
|
+
return;
|
|
17904
17928
|
const sessionID = context.sessionManager.getSessionId();
|
|
17905
17929
|
const sessionFile = context.sessionManager.getSessionFile();
|
|
17906
|
-
|
|
17930
|
+
const own = registry2.list().filter((ref) => ref.session === null ? sessionFile !== undefined && ref.sessionFile === sessionFile : ref.session.sessionManager.getSessionId() === sessionID);
|
|
17931
|
+
if (own.length === 0)
|
|
17932
|
+
return;
|
|
17933
|
+
return own.some((ref) => ref.kind !== "main");
|
|
17907
17934
|
}
|
|
17908
17935
|
function subagentSessionCheck() {
|
|
17909
|
-
let
|
|
17910
|
-
return (context) => {
|
|
17911
|
-
|
|
17912
|
-
|
|
17936
|
+
let settled;
|
|
17937
|
+
return async (context) => {
|
|
17938
|
+
if (settled !== undefined)
|
|
17939
|
+
return settled;
|
|
17940
|
+
const answer = await isSubagentSession(context);
|
|
17941
|
+
if (answer.settled)
|
|
17942
|
+
settled = answer.subagent;
|
|
17943
|
+
return answer.subagent;
|
|
17913
17944
|
};
|
|
17914
17945
|
}
|
|
17915
17946
|
|
|
@@ -18496,7 +18527,7 @@ function createClaimSession(deps) {
|
|
|
18496
18527
|
|
|
18497
18528
|
// src/legion/controller-session.ts
|
|
18498
18529
|
function createControllerSession(deps) {
|
|
18499
|
-
const { daemon, persistedTranscript
|
|
18530
|
+
const { daemon, persistedTranscript } = deps;
|
|
18500
18531
|
let controllerSessionID;
|
|
18501
18532
|
let controllerCapability;
|
|
18502
18533
|
let mintControllerGrant;
|
|
@@ -18547,8 +18578,6 @@ function createControllerSession(deps) {
|
|
|
18547
18578
|
}
|
|
18548
18579
|
};
|
|
18549
18580
|
const reclaimAfterSessionChange = async (context) => {
|
|
18550
|
-
if (await checkSubagentSession(context))
|
|
18551
|
-
return;
|
|
18552
18581
|
if (classifySession(process.env).kind !== "controller")
|
|
18553
18582
|
return;
|
|
18554
18583
|
if (context.sessionManager.getSessionId() === controllerSessionID && context.sessionManager.getSessionFile() === controllerTranscript) {
|
|
@@ -19217,7 +19246,7 @@ function isToolDeviceInvocation(toolCall) {
|
|
|
19217
19246
|
}
|
|
19218
19247
|
function legionExtension(pi) {
|
|
19219
19248
|
const instance = randomUUID2().slice(0, 8);
|
|
19220
|
-
|
|
19249
|
+
logger2.debug("extension instance loaded", { extension: import.meta.url, instance });
|
|
19221
19250
|
globalThis[LEGION_LOADED_MARKER] = import.meta.url;
|
|
19222
19251
|
const checkSubagentSession = subagentSessionCheck();
|
|
19223
19252
|
let paneRules;
|
|
@@ -19246,8 +19275,7 @@ function legionExtension(pi) {
|
|
|
19246
19275
|
});
|
|
19247
19276
|
const controllerSession = createControllerSession({
|
|
19248
19277
|
daemon: roleDaemon,
|
|
19249
|
-
persistedTranscript
|
|
19250
|
-
checkSubagentSession
|
|
19278
|
+
persistedTranscript
|
|
19251
19279
|
});
|
|
19252
19280
|
const titleSession = async (context) => {
|
|
19253
19281
|
const title = legionSessionTitle(classifySession(process.env), process.env.LEGION_PROJECT);
|
|
@@ -19271,16 +19299,17 @@ function legionExtension(pi) {
|
|
|
19271
19299
|
await activateLegionTool();
|
|
19272
19300
|
});
|
|
19273
19301
|
const afterSessionChange = async (context) => {
|
|
19274
|
-
if (
|
|
19302
|
+
if (await checkSubagentSession(context))
|
|
19303
|
+
return;
|
|
19304
|
+
if (classifySession(process.env).kind === "controller")
|
|
19275
19305
|
await titleSession(context);
|
|
19276
|
-
}
|
|
19277
19306
|
await controllerSession.reclaimAfterSessionChange(context);
|
|
19278
19307
|
};
|
|
19279
19308
|
pi.on("session_switch", (_event, context) => afterSessionChange(context));
|
|
19280
19309
|
pi.on("session_branch", (_event, context) => afterSessionChange(context));
|
|
19281
19310
|
pi.on("session_tree", (_event, context) => afterSessionChange(context));
|
|
19282
19311
|
pi.on("tool_call", async (toolCall, context) => {
|
|
19283
|
-
|
|
19312
|
+
logger2.debug("legion tool_call hook", {
|
|
19284
19313
|
instance,
|
|
19285
19314
|
toolCallId: toolCall.toolCallId,
|
|
19286
19315
|
toolName: toolCall.toolName
|