@sjawhar/pi-legion-envoy 5.4.1 → 5.4.2
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 +100 -20
- package/dist/legion.js +1 -1
- package/dist/skills/envoy/SKILL.md +19 -0
- package/package.json +1 -1
package/dist/envoy.js
CHANGED
|
@@ -34509,7 +34509,7 @@ var envoyToolSpecs = [
|
|
|
34509
34509
|
},
|
|
34510
34510
|
{
|
|
34511
34511
|
name: "envoy_whoami",
|
|
34512
|
-
description: "Returns this session's Envoy identity: session ID, machine ID, port, and directory.",
|
|
34512
|
+
description: "Returns this session's Envoy identity: session ID, machine ID, port, and directory. session_id is the address a reply reaches. Where a host runs a task subagent inside its parent's process, such a subagent registers no Envoy session of its own, so its session_id is the parent session that spawned it and the result says so.",
|
|
34513
34513
|
arguments: () => ({}),
|
|
34514
34514
|
operation: EnvoyToolOperation.whoami,
|
|
34515
34515
|
requiresSubscriptionCapability: false
|
|
@@ -34706,7 +34706,7 @@ function createEnvoyClient(config2) {
|
|
|
34706
34706
|
const idempotencyKey = input.idempotencyKey ?? crypto.randomUUID();
|
|
34707
34707
|
const response = EnvelopeResponseSchema.parse(JSON.parse(await post("/v1/messages/send", {
|
|
34708
34708
|
source: input.source ?? "agent",
|
|
34709
|
-
...input.sourceSessionID
|
|
34709
|
+
...sourceSession(input.sourceSessionID),
|
|
34710
34710
|
target_session: input.targetSessionID,
|
|
34711
34711
|
message: input.message,
|
|
34712
34712
|
idempotency_key: idempotencyKey,
|
|
@@ -34722,7 +34722,7 @@ function createEnvoyClient(config2) {
|
|
|
34722
34722
|
const idempotencyKey = input.idempotencyKey ?? crypto.randomUUID();
|
|
34723
34723
|
const response = EnvelopeResponseSchema.parse(JSON.parse(await post("/v1/messages/publish", {
|
|
34724
34724
|
source: input.source ?? "agent",
|
|
34725
|
-
...input.sourceSessionID
|
|
34725
|
+
...sourceSession(input.sourceSessionID),
|
|
34726
34726
|
topic: input.topic,
|
|
34727
34727
|
message: input.message,
|
|
34728
34728
|
...input.payload === undefined ? {} : { payload: input.payload },
|
|
@@ -34785,11 +34785,56 @@ function messageMetadata(input) {
|
|
|
34785
34785
|
...input.expiresAt === undefined ? {} : { expires_at: input.expiresAt }
|
|
34786
34786
|
};
|
|
34787
34787
|
}
|
|
34788
|
+
function sourceSession(sessionID) {
|
|
34789
|
+
return sessionID === undefined || sessionID === "" ? {} : { source_session: sessionID };
|
|
34790
|
+
}
|
|
34788
34791
|
|
|
34789
34792
|
// extensions/envoy.ts
|
|
34790
34793
|
import { logger } from "@oh-my-pi/pi-utils";
|
|
34791
34794
|
var import_nats = __toESM(require_mod4(), 1);
|
|
34792
34795
|
|
|
34796
|
+
// src/envoy-session.ts
|
|
34797
|
+
import path2 from "path";
|
|
34798
|
+
var ENVOY_SESSIONS = Symbol.for("legion.pi-envoy.envoy-session");
|
|
34799
|
+
var store = globalThis;
|
|
34800
|
+
function recordedSessions() {
|
|
34801
|
+
const existing = store[ENVOY_SESSIONS];
|
|
34802
|
+
if (existing !== undefined)
|
|
34803
|
+
return existing;
|
|
34804
|
+
const created = new Map;
|
|
34805
|
+
store[ENVOY_SESSIONS] = created;
|
|
34806
|
+
return created;
|
|
34807
|
+
}
|
|
34808
|
+
function recordEnvoySession(entry) {
|
|
34809
|
+
const sessions = recordedSessions();
|
|
34810
|
+
const key = entry.sessionFile ?? (entry.sessionID === "" ? undefined : `session:${entry.sessionID}`);
|
|
34811
|
+
if (entry.previousKey !== undefined && entry.previousKey !== key) {
|
|
34812
|
+
sessions.delete(entry.previousKey);
|
|
34813
|
+
}
|
|
34814
|
+
if (key === undefined)
|
|
34815
|
+
return;
|
|
34816
|
+
sessions.set(key, entry.sessionID);
|
|
34817
|
+
return key;
|
|
34818
|
+
}
|
|
34819
|
+
function resolveEnvoySession(sessionFile) {
|
|
34820
|
+
const sessions = recordedSessions();
|
|
34821
|
+
for (let candidate = sessionFile;candidate !== undefined; ) {
|
|
34822
|
+
const directory = path2.dirname(candidate);
|
|
34823
|
+
const parent = `${directory}.jsonl`;
|
|
34824
|
+
if (parent === candidate)
|
|
34825
|
+
break;
|
|
34826
|
+
const recorded = sessions.get(parent);
|
|
34827
|
+
if (recorded !== undefined)
|
|
34828
|
+
return recorded;
|
|
34829
|
+
if (path2.dirname(directory) === directory)
|
|
34830
|
+
break;
|
|
34831
|
+
candidate = parent;
|
|
34832
|
+
}
|
|
34833
|
+
if (sessions.size !== 1)
|
|
34834
|
+
return "";
|
|
34835
|
+
return [...sessions.values()][0] ?? "";
|
|
34836
|
+
}
|
|
34837
|
+
|
|
34793
34838
|
// src/legion/phase-stall.ts
|
|
34794
34839
|
var PHASE_STALL_ENTRY = "legion-phase-stall";
|
|
34795
34840
|
var LOCAL_ENVOY_NOTICE = { localNotice: true };
|
|
@@ -34851,8 +34896,8 @@ function restorePhaseStall(branch) {
|
|
|
34851
34896
|
// src/legion/role-claim-bridge.ts
|
|
34852
34897
|
var LEGION_ROLE_CLAIM_BRIDGE = Symbol.for("legion.pi-envoy.role-claim-bridge");
|
|
34853
34898
|
function legionRoleClaimBridge() {
|
|
34854
|
-
const
|
|
34855
|
-
const bridge =
|
|
34899
|
+
const store2 = globalThis;
|
|
34900
|
+
const bridge = store2[LEGION_ROLE_CLAIM_BRIDGE];
|
|
34856
34901
|
if (bridge)
|
|
34857
34902
|
return bridge;
|
|
34858
34903
|
const createdBridge = {
|
|
@@ -34860,7 +34905,7 @@ function legionRoleClaimBridge() {
|
|
|
34860
34905
|
managedSessions: new Set,
|
|
34861
34906
|
regained: undefined
|
|
34862
34907
|
};
|
|
34863
|
-
|
|
34908
|
+
store2[LEGION_ROLE_CLAIM_BRIDGE] = createdBridge;
|
|
34864
34909
|
return createdBridge;
|
|
34865
34910
|
}
|
|
34866
34911
|
async function claimEnvoyRole(sessionID, role, context) {
|
|
@@ -34884,7 +34929,7 @@ function onEnvoyRoleRegained(listener) {
|
|
|
34884
34929
|
|
|
34885
34930
|
// src/subagent-session.ts
|
|
34886
34931
|
import fs from "fs";
|
|
34887
|
-
import
|
|
34932
|
+
import path3 from "path";
|
|
34888
34933
|
import * as host from "@oh-my-pi/pi-coding-agent";
|
|
34889
34934
|
var LEGION_BOOTSTRAPPED_SESSION = Symbol.for("legion.pi-envoy.bootstrapped-session");
|
|
34890
34935
|
var bootstrappedSessionStore = globalThis;
|
|
@@ -34895,7 +34940,7 @@ async function isSubagentSession(context) {
|
|
|
34895
34940
|
await context.sessionManager.ensureOnDisk();
|
|
34896
34941
|
const sessionFile = context.sessionManager.getSessionFile();
|
|
34897
34942
|
if (sessionFile !== undefined && fs.existsSync(sessionFile)) {
|
|
34898
|
-
return fs.existsSync(`${
|
|
34943
|
+
return fs.existsSync(`${path3.dirname(sessionFile)}.jsonl`);
|
|
34899
34944
|
}
|
|
34900
34945
|
const bootstrapped = bootstrappedSessionStore[LEGION_BOOTSTRAPPED_SESSION];
|
|
34901
34946
|
return bootstrapped !== undefined && bootstrapped !== sessionFile;
|
|
@@ -34954,11 +34999,11 @@ function registerEnvoyMessageRenderer(pi) {
|
|
|
34954
34999
|
|
|
34955
35000
|
// extensions/envoy-whoami-command.ts
|
|
34956
35001
|
import { copyToClipboard } from "@oh-my-pi/pi-coding-agent";
|
|
34957
|
-
function registerEnvoyWhoamiCommand(api2,
|
|
35002
|
+
function registerEnvoyWhoamiCommand(api2, replyAddress) {
|
|
34958
35003
|
api2.registerCommand("whoami", {
|
|
34959
35004
|
description: "Copy session ID",
|
|
34960
35005
|
handler: async (_args, context) => {
|
|
34961
|
-
const sessionID = context.sessionManager.getSessionId()
|
|
35006
|
+
const sessionID = await replyAddress(context, context.sessionManager.getSessionId());
|
|
34962
35007
|
if (sessionID === "") {
|
|
34963
35008
|
context.ui.notify("No active session", "warning");
|
|
34964
35009
|
return;
|
|
@@ -35056,6 +35101,7 @@ function envoyExtension(pi) {
|
|
|
35056
35101
|
let connection;
|
|
35057
35102
|
let sessionDirectory = "";
|
|
35058
35103
|
let sessionID = "";
|
|
35104
|
+
let recordedSessionKey;
|
|
35059
35105
|
let heartbeatRegistered = false;
|
|
35060
35106
|
let claimedRoleTopic;
|
|
35061
35107
|
let activeSessionContext;
|
|
@@ -35122,6 +35168,11 @@ function envoyExtension(pi) {
|
|
|
35122
35168
|
const restoreLocalSessionState = (context) => {
|
|
35123
35169
|
sessionDirectory = context.cwd;
|
|
35124
35170
|
sessionID = context.sessionManager.getSessionId();
|
|
35171
|
+
recordedSessionKey = recordEnvoySession({
|
|
35172
|
+
previousKey: recordedSessionKey,
|
|
35173
|
+
sessionFile: context.sessionManager.getSessionFile(),
|
|
35174
|
+
sessionID
|
|
35175
|
+
});
|
|
35125
35176
|
activeSessionContext = context;
|
|
35126
35177
|
const branch = context.sessionManager.getBranch?.() ?? [];
|
|
35127
35178
|
if (askAwareness.session_id !== sessionID) {
|
|
@@ -35509,6 +35560,13 @@ function envoyExtension(pi) {
|
|
|
35509
35560
|
bridge.instances.push(claimInstance);
|
|
35510
35561
|
const isSubagentTranscript = subagentSessionCheck();
|
|
35511
35562
|
const isSubagent = async (context) => await isSubagentTranscript(context) || isRegisteredSubagent(context);
|
|
35563
|
+
const replyAddress = async (context, liveSessionID = "") => {
|
|
35564
|
+
if (sessionID !== "")
|
|
35565
|
+
return liveSessionID || sessionID;
|
|
35566
|
+
if (!await isSubagent(context))
|
|
35567
|
+
return liveSessionID;
|
|
35568
|
+
return resolveEnvoySession(context.sessionManager.getSessionFile());
|
|
35569
|
+
};
|
|
35512
35570
|
pi.on("session_start", async (_event, context) => {
|
|
35513
35571
|
if (await isSubagent(context))
|
|
35514
35572
|
return;
|
|
@@ -35585,6 +35643,11 @@ function envoyExtension(pi) {
|
|
|
35585
35643
|
const bound = bridge.instances.indexOf(claimInstance);
|
|
35586
35644
|
if (bound !== -1)
|
|
35587
35645
|
bridge.instances.splice(bound, 1);
|
|
35646
|
+
recordedSessionKey = recordEnvoySession({
|
|
35647
|
+
previousKey: recordedSessionKey,
|
|
35648
|
+
sessionFile: undefined,
|
|
35649
|
+
sessionID: ""
|
|
35650
|
+
});
|
|
35588
35651
|
const deadline = Promise.withResolvers();
|
|
35589
35652
|
const timer = setTimeout(deadline.resolve, 1000);
|
|
35590
35653
|
try {
|
|
@@ -35616,7 +35679,7 @@ function envoyExtension(pi) {
|
|
|
35616
35679
|
description: spec.description,
|
|
35617
35680
|
parameters: schemaFor(pi, spec.operation),
|
|
35618
35681
|
lenientArgValidation: true,
|
|
35619
|
-
execute: async (_id, parameters) => execute(spec.operation, parameters)
|
|
35682
|
+
execute: async (_id, parameters, _signal, _onUpdate, context) => execute(spec.operation, parameters, context)
|
|
35620
35683
|
});
|
|
35621
35684
|
}
|
|
35622
35685
|
if (dispatchConfig.enabled) {
|
|
@@ -35648,7 +35711,7 @@ function envoyExtension(pi) {
|
|
|
35648
35711
|
});
|
|
35649
35712
|
}
|
|
35650
35713
|
}
|
|
35651
|
-
registerEnvoyWhoamiCommand(pi,
|
|
35714
|
+
registerEnvoyWhoamiCommand(pi, replyAddress);
|
|
35652
35715
|
pi.on("before_agent_start", async (event, context) => {
|
|
35653
35716
|
const id = context.sessionManager.getSessionId();
|
|
35654
35717
|
if (id === "" || event.prompt.trim() === "" || !context.hasUI || askEphemeral === undefined || legionManaged(id)) {
|
|
@@ -35796,7 +35859,7 @@ function envoyExtension(pi) {
|
|
|
35796
35859
|
}
|
|
35797
35860
|
announceFollow(event.details);
|
|
35798
35861
|
});
|
|
35799
|
-
async function execute(operation, rawParameters) {
|
|
35862
|
+
async function execute(operation, rawParameters, context) {
|
|
35800
35863
|
try {
|
|
35801
35864
|
const parameters = parseEnvoyToolArguments(operation, rawParameters);
|
|
35802
35865
|
switch (operation) {
|
|
@@ -35849,7 +35912,7 @@ Warnings: ${warnings.join("; ")}`, {
|
|
|
35849
35912
|
case EnvoyToolOperation.send: {
|
|
35850
35913
|
const targetSessionID = stringFor(parameters, "session_id");
|
|
35851
35914
|
const result = await client.send({
|
|
35852
|
-
sourceSessionID:
|
|
35915
|
+
sourceSessionID: await replyAddress(context),
|
|
35853
35916
|
targetSessionID,
|
|
35854
35917
|
message: stringFor(parameters, "message"),
|
|
35855
35918
|
...toMessageMetadata(parameters)
|
|
@@ -35862,16 +35925,21 @@ Warnings: ${warnings.join("; ")}`, {
|
|
|
35862
35925
|
}
|
|
35863
35926
|
case EnvoyToolOperation.publish: {
|
|
35864
35927
|
const topic = stringFor(parameters, "topic");
|
|
35928
|
+
const source = await replyAddress(context);
|
|
35865
35929
|
const result = await client.publish({
|
|
35866
|
-
sourceSessionID:
|
|
35930
|
+
sourceSessionID: source,
|
|
35867
35931
|
topic,
|
|
35868
35932
|
message: stringFor(parameters, "message"),
|
|
35869
35933
|
...toMessageMetadata(parameters)
|
|
35870
35934
|
});
|
|
35871
|
-
|
|
35935
|
+
const undelivered = source !== "" && result.holder === source && await isSubagent(context);
|
|
35936
|
+
const published = result.holder === undefined ? `published ${result.envelope.event_id}` : `published ${result.envelope.event_id}; holder ${result.holder}`;
|
|
35937
|
+
return toolSuccess(undelivered ? `${published}
|
|
35938
|
+
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, {
|
|
35872
35939
|
event_id: result.envelope.event_id,
|
|
35873
35940
|
topic,
|
|
35874
|
-
...result.holder === undefined ? {} : { holder: result.holder }
|
|
35941
|
+
...result.holder === undefined ? {} : { holder: result.holder },
|
|
35942
|
+
...undelivered ? { undelivered_echo: true } : {}
|
|
35875
35943
|
});
|
|
35876
35944
|
}
|
|
35877
35945
|
case EnvoyToolOperation.setRole: {
|
|
@@ -35884,9 +35952,21 @@ Warnings: ${warnings.join("; ")}`, {
|
|
|
35884
35952
|
return toolSuccess(JSON.stringify(role, null, 2), role);
|
|
35885
35953
|
}
|
|
35886
35954
|
case EnvoyToolOperation.whoami: {
|
|
35887
|
-
|
|
35888
|
-
|
|
35889
|
-
|
|
35955
|
+
const inSubagent = await isSubagent(context);
|
|
35956
|
+
const address = await replyAddress(context);
|
|
35957
|
+
const subagent = inSubagent ? {
|
|
35958
|
+
session_id: context.sessionManager.getSessionId(),
|
|
35959
|
+
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."
|
|
35960
|
+
} : undefined;
|
|
35961
|
+
return toolSuccess(JSON.stringify({
|
|
35962
|
+
session_id: address,
|
|
35963
|
+
machine_id: machineID(),
|
|
35964
|
+
dir: sessionDirectory || context.cwd,
|
|
35965
|
+
...subagent === undefined ? {} : { subagent }
|
|
35966
|
+
}, null, 2), {
|
|
35967
|
+
sessionID: address,
|
|
35968
|
+
topics: [...subscriptions.keys()],
|
|
35969
|
+
...subagent === undefined ? {} : { subagent }
|
|
35890
35970
|
});
|
|
35891
35971
|
}
|
|
35892
35972
|
case EnvoyToolOperation.listSessions: {
|
package/dist/legion.js
CHANGED
|
@@ -16142,7 +16142,7 @@ import { logger } from "@oh-my-pi/pi-utils";
|
|
|
16142
16142
|
// package.json
|
|
16143
16143
|
var package_default = {
|
|
16144
16144
|
name: "@sjawhar/pi-legion-envoy",
|
|
16145
|
-
version: "5.4.
|
|
16145
|
+
version: "5.4.2",
|
|
16146
16146
|
type: "module",
|
|
16147
16147
|
omp: {
|
|
16148
16148
|
extensions: [
|
|
@@ -106,6 +106,25 @@ envoy_send(
|
|
|
106
106
|
)
|
|
107
107
|
```
|
|
108
108
|
|
|
109
|
+
### Your own address, and a subagent's
|
|
110
|
+
|
|
111
|
+
`envoy_whoami`'s `session_id` is the address a reply to you reaches. Inside a `task` subagent it
|
|
112
|
+
is the session that spawned you: a subagent registers no Envoy session of its own, so a peer
|
|
113
|
+
answering it reaches that session, which relays to you over hub. The `subagent` field in that
|
|
114
|
+
output carries your own host session id — it is not an address, so never hand it to a peer.
|
|
115
|
+
|
|
116
|
+
`session_id` is empty when nothing can be reached: a process that took no Envoy identity, or a
|
|
117
|
+
subagent whose spawning session this process can no longer place (it forked away, and other
|
|
118
|
+
top-level sessions are running). Then your messages carry no sender either, so say who you are
|
|
119
|
+
in the message body. An empty address is deliberate — being handed an unrelated live session
|
|
120
|
+
would send your peers to an agent that never spawned you.
|
|
121
|
+
|
|
122
|
+
Your own `envoy_publish` never reaches the agent that spawned you. The listener delivers nothing
|
|
123
|
+
to the session a message names as its source, and inside a subagent that source is your parent,
|
|
124
|
+
so a publish to a role it holds — or to any topic it subscribes to — is accepted and delivered to
|
|
125
|
+
nobody. Use hub for that one hop. `envoy_send` to any other session, including a reply, is
|
|
126
|
+
unaffected.
|
|
127
|
+
|
|
109
128
|
### Delivery capabilities
|
|
110
129
|
|
|
111
130
|
Each session row from `envoy_sessions` carries `capabilities`, the targeted-delivery modes that
|