@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 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 === undefined ? {} : { source_session: 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 === undefined ? {} : { source_session: 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 store = globalThis;
34855
- const bridge = store[LEGION_ROLE_CLAIM_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
- store[LEGION_ROLE_CLAIM_BRIDGE] = createdBridge;
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 path2 from "path";
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(`${path2.dirname(sessionFile)}.jsonl`);
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, cachedSessionID) {
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() || cachedSessionID();
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, () => sessionID);
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: sessionID,
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: sessionID,
35930
+ sourceSessionID: source,
35867
35931
  topic,
35868
35932
  message: stringFor(parameters, "message"),
35869
35933
  ...toMessageMetadata(parameters)
35870
35934
  });
35871
- return toolSuccess(result.holder === undefined ? `published ${result.envelope.event_id}` : `published ${result.envelope.event_id}; holder ${result.holder}`, {
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
- return toolSuccess(JSON.stringify({ session_id: sessionID, machine_id: machineID(), dir: sessionDirectory }, null, 2), {
35888
- sessionID,
35889
- topics: [...subscriptions.keys()]
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.1",
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sjawhar/pi-legion-envoy",
3
- "version": "5.4.1",
3
+ "version": "5.4.2",
4
4
  "type": "module",
5
5
  "omp": {
6
6
  "extensions": [