@sjawhar/pi-legion-envoy 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/envoy.js +92 -48
  2. package/package.json +1 -1
package/dist/envoy.js CHANGED
@@ -20829,11 +20829,25 @@ function superRefine(fn) {
20829
20829
  }
20830
20830
  // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/classic/external.js
20831
20831
  config(en_default());
20832
+ // ../contracts/src/dispatch-question.ts
20833
+ var DispatchQuestionOptionSchema = strictObject({
20834
+ label: string2().min(1),
20835
+ description: string2().optional()
20836
+ });
20837
+ var DispatchQuestionSchema = strictObject({
20838
+ askId: string2().optional(),
20839
+ question: string2().min(1),
20840
+ header: string2().optional(),
20841
+ options: array(DispatchQuestionOptionSchema).optional(),
20842
+ multiple: boolean2().optional(),
20843
+ custom: boolean2().optional()
20844
+ });
20845
+ var DispatchQuestionInputSchema = DispatchQuestionSchema.omit({ askId: true });
20832
20846
  // ../contracts/src/envelope.ts
20833
20847
  var isSubject = (value) => typeof value === "string" && value.length > 0;
20834
20848
  var EnvelopeSchema = object({
20835
20849
  event_id: string2().min(1),
20836
- source: _enum(["agent", "envoy", "github", "slack", "whatsapp", "ghostwispr"]),
20850
+ source: _enum(["agent", "human", "envoy", "github", "slack", "whatsapp", "ghostwispr"]),
20837
20851
  source_event_id: string2().min(1),
20838
20852
  source_session: string2().optional(),
20839
20853
  topic: custom(isSubject, { message: "topic must be a non-empty subject" }),
@@ -21143,25 +21157,13 @@ var DISPATCH_ARGUMENTS = {
21143
21157
  repo: "owner/name. Opening a thread only; defaults to the working directory's GitHub repo.",
21144
21158
  parent: "<n> | owner/name#<n>[#<commentId>]. Opening a thread only: link the thread as a sub-issue of an existing issue and append a breadcrumb to the comment."
21145
21159
  };
21146
-
21147
21160
  class DispatchArgumentError extends Error {
21148
21161
  name = "DispatchArgumentError";
21149
21162
  }
21150
- var QuestionOptionSchema = strictObject({
21151
- label: string2().min(1),
21152
- description: string2().optional()
21153
- });
21154
- var DispatchQuestionSchema = strictObject({
21155
- question: string2().min(1),
21156
- header: string2().optional(),
21157
- options: array(QuestionOptionSchema).optional(),
21158
- multiple: boolean2().optional(),
21159
- custom: boolean2().optional()
21160
- });
21161
21163
  var prose = {
21162
21164
  context: string2(),
21163
21165
  question: string2(),
21164
- ask: array(DispatchQuestionSchema).optional()
21166
+ ask: array(DispatchQuestionInputSchema).optional()
21165
21167
  };
21166
21168
  var OpenThreadCallSchema = strictObject({
21167
21169
  subject: string2(),
@@ -21176,7 +21178,7 @@ var dispatchToolShape = {
21176
21178
  thread: string2().describe(DISPATCH_ARGUMENTS.thread).optional(),
21177
21179
  context: string2().describe(DISPATCH_ARGUMENTS.context),
21178
21180
  question: string2().describe(DISPATCH_ARGUMENTS.question),
21179
- ask: array(DispatchQuestionSchema).describe(DISPATCH_ARGUMENTS.ask).optional(),
21181
+ ask: array(DispatchQuestionInputSchema).describe(DISPATCH_ARGUMENTS.ask).optional(),
21180
21182
  urgency: _enum(DISPATCH_URGENCIES).describe(DISPATCH_ARGUMENTS.urgency).optional(),
21181
21183
  repo: string2().describe(DISPATCH_ARGUMENTS.repo).optional(),
21182
21184
  parent: string2().describe(DISPATCH_ARGUMENTS.parent).optional()
@@ -21284,7 +21286,7 @@ async function resolveOrigin(env, exec, cwd) {
21284
21286
  cwd,
21285
21287
  machine: machineID()
21286
21288
  };
21287
- const pane = env["TMUX_PANE"];
21289
+ const pane = env.TMUX_PANE;
21288
21290
  if (pane) {
21289
21291
  const output = await tryExec(exec, "tmux", ["display-message", "-p", "-t", pane, "#S:#I.#P #{pane_id}"], cwd);
21290
21292
  const [target, paneId] = output?.trim().split(" ") ?? [];
@@ -21478,10 +21480,10 @@ function readEnvoyFile(filePath) {
21478
21480
  return { kind: "valid", settings: parsed.data.dispatch ?? null };
21479
21481
  }
21480
21482
  function resolveDispatchConfig(env, options = {}) {
21481
- const explicit = env["DISPATCH_MCP_URL"];
21483
+ const explicit = env.DISPATCH_MCP_URL;
21482
21484
  if (explicit)
21483
21485
  return { url: explicit, error: null };
21484
- const home = options.home ?? env["HOME"] ?? homedir();
21486
+ const home = options.home ?? env.HOME ?? homedir();
21485
21487
  const cwd = options.cwd ?? process.cwd();
21486
21488
  const userFile = readEnvoyFile(path.join(home, ".config", "opencode", "envoy.json"));
21487
21489
  const repoFile = readEnvoyFile(path.join(cwd, ".opencode", "envoy.json"));
@@ -21521,6 +21523,26 @@ function dispatchSubscriptionTopic(tool, output) {
21521
21523
  return dispatchThreadTopic(owner, repo, thread);
21522
21524
  }
21523
21525
 
21526
+ // ../envoy-client/src/delivery.ts
21527
+ function senderLabel(envelope) {
21528
+ return envelope.source_session ?? envelope.source;
21529
+ }
21530
+ function replyWith(envelope) {
21531
+ if (envelope.source !== "agent" || envelope.source_session === undefined)
21532
+ return;
21533
+ return `envoy_send(session_id="${envelope.source_session}", message="...")`;
21534
+ }
21535
+ function isOwnDispatchEcho(envelope, sessionID) {
21536
+ if (envelope.source !== "github" || envelope.payload === undefined)
21537
+ return false;
21538
+ try {
21539
+ const payload = JSON.parse(envelope.payload);
21540
+ return typeof payload === "object" && payload !== null && "dispatch_session" in payload && payload.dispatch_session === sessionID;
21541
+ } catch {
21542
+ return false;
21543
+ }
21544
+ }
21545
+
21524
21546
  // ../envoy-client/src/tool-contract.ts
21525
21547
  var EnvoyToolOperation = {
21526
21548
  subscribe: "subscribe",
@@ -21663,18 +21685,23 @@ function createEnvoyClient(config) {
21663
21685
  },
21664
21686
  getInterest: async (sessionID) => InterestWireSchema.parse(JSON.parse(await request(`/v1/interests/${sessionID}`, {}))),
21665
21687
  send: async (input) => toEnvelope(await post("/v1/messages/send", {
21666
- source_session: input.sourceSessionID,
21688
+ source: input.source ?? "agent",
21689
+ ...input.sourceSessionID === undefined ? {} : { source_session: input.sourceSessionID },
21667
21690
  target_session: input.targetSessionID,
21668
21691
  message: input.message,
21669
21692
  ...input.idempotencyKey === undefined ? {} : { idempotency_key: input.idempotencyKey }
21670
21693
  })),
21671
21694
  publish: async (input) => toEnvelope(await post("/v1/messages/publish", {
21672
- source_session: input.sourceSessionID,
21695
+ source: input.source ?? "agent",
21696
+ ...input.sourceSessionID === undefined ? {} : { source_session: input.sourceSessionID },
21673
21697
  topic: input.topic,
21674
21698
  message: input.message,
21675
21699
  ...input.payload === undefined ? {} : { payload: input.payload },
21676
21700
  ...input.idempotencyKey === undefined ? {} : { idempotency_key: input.idempotencyKey }
21677
21701
  })),
21702
+ unregisterSession: async (sessionID) => {
21703
+ await request(`/v1/sessions/${encodeURIComponent(sessionID)}`, { method: "DELETE" });
21704
+ },
21678
21705
  setRole: async (input) => InterestWireSchema.parse(JSON.parse(await post("/v1/roles/set", { session_id: input.sessionID, role: input.role }))),
21679
21706
  listSessions: async () => SessionWireSchema.array().parse(JSON.parse(await request("/v1/sessions", {})))
21680
21707
  };
@@ -22312,6 +22339,7 @@ function envoyExtension(pi) {
22312
22339
  const deliver = async (subject, raw, reply) => {
22313
22340
  let content = "";
22314
22341
  let duplicate = false;
22342
+ let dispatchEcho = false;
22315
22343
  try {
22316
22344
  const envelope = EnvelopeSchema.parse(JSON.parse(raw));
22317
22345
  duplicate = dedupeKeys.has(envelope.dedupe_key);
@@ -22322,6 +22350,7 @@ function envoyExtension(pi) {
22322
22350
  if (!oldest.done)
22323
22351
  dedupeKeys.delete(oldest.value);
22324
22352
  }
22353
+ dispatchEcho = isOwnDispatchEcho(envelope, sessionID);
22325
22354
  let message;
22326
22355
  if (envelope.payload !== undefined) {
22327
22356
  try {
@@ -22330,26 +22359,26 @@ function envoyExtension(pi) {
22330
22359
  message = envelope.payload;
22331
22360
  }
22332
22361
  }
22333
- const echo = envelope.source_session !== undefined && envelope.source_session === sessionID;
22334
- content = encode2({
22335
- envoy: {
22336
- topic: envelope.topic,
22337
- from: envelope.source,
22338
- ...echo ? {
22339
- echo: `your own message, sent by this session (${sessionID}) \u2014 not an incoming reply`
22340
- } : envelope.source_session === undefined ? {} : {
22341
- reply_to: envelope.source_session,
22342
- reply_with: `envoy_send(session_id="${envelope.source_session}", message="...")`
22343
- },
22344
- summary: envelope.payload_summary,
22345
- ...message === undefined ? {} : { message }
22346
- }
22347
- });
22362
+ if (!dispatchEcho) {
22363
+ const reply = replyWith(envelope);
22364
+ const echo = envelope.source_session !== undefined && envelope.source_session === sessionID;
22365
+ content = encode2({
22366
+ envoy: {
22367
+ topic: envelope.topic,
22368
+ from: senderLabel(envelope),
22369
+ ...echo ? {
22370
+ echo: `your own message, sent by this session (${sessionID}) \u2014 not an incoming reply`
22371
+ } : reply === undefined ? {} : { reply_with: reply },
22372
+ summary: envelope.payload_summary,
22373
+ ...message === undefined ? {} : { message }
22374
+ }
22375
+ });
22376
+ }
22348
22377
  }
22349
22378
  } catch {
22350
22379
  content = encode2({ envoy: { topic: subject, message: raw } });
22351
22380
  }
22352
- if (!duplicate) {
22381
+ if (!duplicate && !dispatchEcho) {
22353
22382
  pi.sendMessage({ customType: "envoy-message", content, display: true }, { deliverAs: "steer", triggerTurn: true });
22354
22383
  }
22355
22384
  if (reply !== "" && subject === agentSubject(sessionID)) {
@@ -22420,17 +22449,16 @@ function envoyExtension(pi) {
22420
22449
  selfSubscribed: true
22421
22450
  });
22422
22451
  };
22423
- const registrationRequired = () => process.env.ENVOY_REGISTER_SESSION === "1" || claimedRoleTopic !== undefined;
22424
22452
  const ensureHeartbeat = (context) => {
22425
22453
  if (heartbeatRegistered)
22426
22454
  return;
22427
22455
  let heartbeatOutageNotified = false;
22428
22456
  let healing = false;
22429
22457
  context.setInterval(() => {
22430
- if (!registrationRequired())
22431
- return;
22432
22458
  const liveSessionID = context.sessionManager.getSessionId();
22433
- const drifted = liveSessionID !== "" && liveSessionID !== sessionID;
22459
+ if (liveSessionID === "")
22460
+ return;
22461
+ const drifted = liveSessionID !== sessionID;
22434
22462
  if (healing)
22435
22463
  return;
22436
22464
  healing = true;
@@ -22464,6 +22492,12 @@ function envoyExtension(pi) {
22464
22492
  sessionDirectory = context.cwd;
22465
22493
  sessionID = context.sessionManager.getSessionId();
22466
22494
  activeSessionContext = context;
22495
+ if (sessionID === "") {
22496
+ if (previousTopic !== undefined)
22497
+ closeIntentionally(previousTopic);
22498
+ ensureHeartbeat(context);
22499
+ return;
22500
+ }
22467
22501
  const currentTopic = agentSubject(sessionID);
22468
22502
  if (previousTopic !== undefined && previousTopic !== currentTopic) {
22469
22503
  closeIntentionally(previousTopic);
@@ -22473,10 +22507,8 @@ function envoyExtension(pi) {
22473
22507
  if ((context.sessionManager.getBranch?.() ?? []).length > 0) {
22474
22508
  await recoverRegisteredInterests();
22475
22509
  }
22476
- if (registrationRequired()) {
22477
- await registerSession();
22478
- ensureHeartbeat(context);
22479
- }
22510
+ await registerSession();
22511
+ ensureHeartbeat(context);
22480
22512
  };
22481
22513
  const setEnvoyRole = async (role) => {
22482
22514
  const topic = ROLE_TOPIC_PREFIX + role;
@@ -22566,11 +22598,23 @@ function envoyExtension(pi) {
22566
22598
  pi.on("session_tree", (_event, context) => rebind(undefined, context));
22567
22599
  pi.on("session_shutdown", async () => {
22568
22600
  shuttingDown = true;
22601
+ const deadline = Promise.withResolvers();
22602
+ const timer = setTimeout(deadline.resolve, 1000);
22569
22603
  try {
22570
- const drain = connection?.drain();
22571
- if (drain)
22572
- await Promise.race([drain, new Promise((resolve) => setTimeout(resolve, 1000))]);
22573
- } catch {} finally {
22604
+ const deregistration = sessionID === "" ? Promise.resolve() : client.unregisterSession(sessionID).catch(() => {
22605
+ return;
22606
+ });
22607
+ const draining = connection?.drain().catch(() => {
22608
+ return;
22609
+ }) ?? Promise.resolve();
22610
+ await Promise.race([
22611
+ Promise.allSettled([deregistration, draining]).then(() => {
22612
+ return;
22613
+ }),
22614
+ deadline.promise
22615
+ ]);
22616
+ } finally {
22617
+ clearTimeout(timer);
22574
22618
  connection = undefined;
22575
22619
  activeSessionContext = undefined;
22576
22620
  subscriptions.clear();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sjawhar/pi-legion-envoy",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "type": "module",
5
5
  "omp": {
6
6
  "extensions": [