@sjawhar/pi-legion-envoy 0.43.0 → 0.44.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.
package/dist/envoy.js CHANGED
@@ -29725,6 +29725,11 @@ var ChildStatusEventPayloadSchema = object({
29725
29725
  from: string2().optional(),
29726
29726
  to: string2().optional()
29727
29727
  });
29728
+ var SubscriptionRemovedEventPayloadSchema = object({
29729
+ session_id: string2().optional(),
29730
+ by: object({ kind: string2(), id: string2().optional() }).passthrough().optional(),
29731
+ topics: array(string2()).optional()
29732
+ });
29728
29733
  // ../contracts/src/dispatch-snippet.ts
29729
29734
  var HTML_ENTITIES = [
29730
29735
  ["&lt;", "<"],
@@ -31107,6 +31112,18 @@ function renderInbound(raw, sessionID, subject) {
31107
31112
  if (frame.event.actor.kind === "session" && frame.event.actor.id === sessionID) {
31108
31113
  return { skip: true, content: "", envelope };
31109
31114
  }
31115
+ if (frame.event.type === "subscription.removed") {
31116
+ const removed = SubscriptionRemovedEventPayloadSchema.safeParse(frame.event.payload);
31117
+ if (!removed.success || removed.data.session_id !== sessionID) {
31118
+ return { skip: true, content: "", envelope };
31119
+ }
31120
+ const who = removed.data.by?.id ?? "someone";
31121
+ return {
31122
+ skip: false,
31123
+ content: `Unsubscribed from ${dispatchOwner(frame.event, subject ?? envelope.topic)} by ${who}`,
31124
+ envelope
31125
+ };
31126
+ }
31110
31127
  askQuestion = dispatchAskQuestion(frame.event);
31111
31128
  messageReplyPreview = dispatchMessageReplyPreview(frame.event);
31112
31129
  dispatchEvent = {
@@ -32387,6 +32404,46 @@ function dispatchSubscriptionTopic(details) {
32387
32404
  const { topic } = details;
32388
32405
  return typeof topic === "string" && topic.startsWith("notifications.dispatch.") ? topic : null;
32389
32406
  }
32407
+ function dispatchTopicLabel(topic) {
32408
+ if (topic.startsWith(DISPATCH_ISSUE_TOPIC_PREFIX)) {
32409
+ const [key] = topic.slice(DISPATCH_ISSUE_TOPIC_PREFIX.length).split(".", 2);
32410
+ if (key !== undefined && key !== "")
32411
+ return key;
32412
+ }
32413
+ if (topic.startsWith(DISPATCH_DOCUMENT_TOPIC_PREFIX)) {
32414
+ const [project, slug] = topic.slice(DISPATCH_DOCUMENT_TOPIC_PREFIX.length).split(".", 3);
32415
+ if (project !== undefined && project !== "" && slug !== undefined && slug !== "") {
32416
+ return `${project}/${slug}`;
32417
+ }
32418
+ }
32419
+ return topic;
32420
+ }
32421
+ function subscriptionRemovedTopics(raw, sessionID) {
32422
+ let envelope;
32423
+ try {
32424
+ envelope = JSON.parse(raw);
32425
+ } catch {
32426
+ return;
32427
+ }
32428
+ if (typeof envelope !== "object" || envelope === null)
32429
+ return;
32430
+ const { source, payload } = envelope;
32431
+ if (source !== "dispatch" || typeof payload !== "string")
32432
+ return;
32433
+ let event;
32434
+ try {
32435
+ event = JSON.parse(payload);
32436
+ } catch {
32437
+ return;
32438
+ }
32439
+ const parsedEvent = DispatchEventSchema.safeParse(event);
32440
+ if (!parsedEvent.success || parsedEvent.data.type !== "subscription.removed")
32441
+ return;
32442
+ const parsedPayload = SubscriptionRemovedEventPayloadSchema.safeParse(parsedEvent.data.payload);
32443
+ if (!parsedPayload.success || parsedPayload.data.session_id !== sessionID)
32444
+ return;
32445
+ return parsedPayload.data.topics ?? [];
32446
+ }
32390
32447
 
32391
32448
  // ../envoy-client/src/tool-contract.ts
32392
32449
  var DELIVERY_CONTRACT = "Delivery is at-least-once, possibly out of order across topics; use id for dedupe and at for freshness.";
@@ -32864,6 +32921,8 @@ function envoyExtension(pi) {
32864
32921
  };
32865
32922
  const deliver = async (subject, raw, reply) => {
32866
32923
  const rendered = renderInbound(raw, sessionID, subject);
32924
+ for (const topic of subscriptionRemovedTopics(raw, sessionID) ?? [])
32925
+ closeIntentionally(topic);
32867
32926
  const dedupeKey = rendered.envelope?.dedupe_key;
32868
32927
  const duplicate = dedupeKey !== undefined && dedupeKeys.has(dedupeKey);
32869
32928
  if (!duplicate && !rendered.skip) {
@@ -33255,8 +33314,16 @@ function envoyExtension(pi) {
33255
33314
  if (topic === null)
33256
33315
  return;
33257
33316
  try {
33258
- if (await subscribe(topic))
33317
+ const isNew = await subscribe(topic);
33318
+ if (isNew)
33259
33319
  await registerSession();
33320
+ if (isNew) {
33321
+ pi.sendMessage({
33322
+ customType: "envoy-message",
33323
+ content: `Subscribed to ${dispatchTopicLabel(topic)} (every event on this issue reaches you; envoy_unsubscribe ${topic} to stop).`,
33324
+ display: true
33325
+ }, { deliverAs: "steer", triggerTurn: false });
33326
+ }
33260
33327
  } catch (error) {
33261
33328
  activeSessionContext?.ui.notify(`envoy: dispatch reply auto-subscribe failed (${messageFor(error)}); run envoy_subscribe ${topic}`, "warning");
33262
33329
  }
package/dist/legion.js CHANGED
@@ -29724,6 +29724,11 @@ var ChildStatusEventPayloadSchema = object({
29724
29724
  from: string2().optional(),
29725
29725
  to: string2().optional()
29726
29726
  });
29727
+ var SubscriptionRemovedEventPayloadSchema = object({
29728
+ session_id: string2().optional(),
29729
+ by: object({ kind: string2(), id: string2().optional() }).passthrough().optional(),
29730
+ topics: array(string2()).optional()
29731
+ });
29727
29732
  // ../contracts/src/dispatch-snippet.ts
29728
29733
  var HTML_ENTITIES = [
29729
29734
  ["&lt;", "<"],
@@ -31554,6 +31559,18 @@ function renderInbound(raw, sessionID, subject) {
31554
31559
  if (frame.event.actor.kind === "session" && frame.event.actor.id === sessionID) {
31555
31560
  return { skip: true, content: "", envelope };
31556
31561
  }
31562
+ if (frame.event.type === "subscription.removed") {
31563
+ const removed = SubscriptionRemovedEventPayloadSchema.safeParse(frame.event.payload);
31564
+ if (!removed.success || removed.data.session_id !== sessionID) {
31565
+ return { skip: true, content: "", envelope };
31566
+ }
31567
+ const who = removed.data.by?.id ?? "someone";
31568
+ return {
31569
+ skip: false,
31570
+ content: `Unsubscribed from ${dispatchOwner(frame.event, subject ?? envelope.topic)} by ${who}`,
31571
+ envelope
31572
+ };
31573
+ }
31557
31574
  askQuestion = dispatchAskQuestion(frame.event);
31558
31575
  messageReplyPreview = dispatchMessageReplyPreview(frame.event);
31559
31576
  dispatchEvent = {
@@ -32827,6 +32844,46 @@ function dispatchSubscriptionTopic(details) {
32827
32844
  const { topic } = details;
32828
32845
  return typeof topic === "string" && topic.startsWith("notifications.dispatch.") ? topic : null;
32829
32846
  }
32847
+ function dispatchTopicLabel(topic) {
32848
+ if (topic.startsWith(DISPATCH_ISSUE_TOPIC_PREFIX)) {
32849
+ const [key] = topic.slice(DISPATCH_ISSUE_TOPIC_PREFIX.length).split(".", 2);
32850
+ if (key !== undefined && key !== "")
32851
+ return key;
32852
+ }
32853
+ if (topic.startsWith(DISPATCH_DOCUMENT_TOPIC_PREFIX)) {
32854
+ const [project, slug] = topic.slice(DISPATCH_DOCUMENT_TOPIC_PREFIX.length).split(".", 3);
32855
+ if (project !== undefined && project !== "" && slug !== undefined && slug !== "") {
32856
+ return `${project}/${slug}`;
32857
+ }
32858
+ }
32859
+ return topic;
32860
+ }
32861
+ function subscriptionRemovedTopics(raw, sessionID) {
32862
+ let envelope;
32863
+ try {
32864
+ envelope = JSON.parse(raw);
32865
+ } catch {
32866
+ return;
32867
+ }
32868
+ if (typeof envelope !== "object" || envelope === null)
32869
+ return;
32870
+ const { source, payload } = envelope;
32871
+ if (source !== "dispatch" || typeof payload !== "string")
32872
+ return;
32873
+ let event;
32874
+ try {
32875
+ event = JSON.parse(payload);
32876
+ } catch {
32877
+ return;
32878
+ }
32879
+ const parsedEvent = DispatchEventSchema.safeParse(event);
32880
+ if (!parsedEvent.success || parsedEvent.data.type !== "subscription.removed")
32881
+ return;
32882
+ const parsedPayload = SubscriptionRemovedEventPayloadSchema.safeParse(parsedEvent.data.payload);
32883
+ if (!parsedPayload.success || parsedPayload.data.session_id !== sessionID)
32884
+ return;
32885
+ return parsedPayload.data.topics ?? [];
32886
+ }
32830
32887
 
32831
32888
  // ../envoy-client/src/tool-contract.ts
32832
32889
  var DELIVERY_CONTRACT = "Delivery is at-least-once, possibly out of order across topics; use id for dedupe and at for freshness.";
@@ -33296,6 +33353,8 @@ function envoyExtension(pi) {
33296
33353
  };
33297
33354
  const deliver = async (subject, raw, reply) => {
33298
33355
  const rendered = renderInbound(raw, sessionID, subject);
33356
+ for (const topic of subscriptionRemovedTopics(raw, sessionID) ?? [])
33357
+ closeIntentionally(topic);
33299
33358
  const dedupeKey = rendered.envelope?.dedupe_key;
33300
33359
  const duplicate = dedupeKey !== undefined && dedupeKeys.has(dedupeKey);
33301
33360
  if (!duplicate && !rendered.skip) {
@@ -33687,8 +33746,16 @@ function envoyExtension(pi) {
33687
33746
  if (topic === null)
33688
33747
  return;
33689
33748
  try {
33690
- if (await subscribe(topic))
33749
+ const isNew = await subscribe(topic);
33750
+ if (isNew)
33691
33751
  await registerSession();
33752
+ if (isNew) {
33753
+ pi.sendMessage({
33754
+ customType: "envoy-message",
33755
+ content: `Subscribed to ${dispatchTopicLabel(topic)} (every event on this issue reaches you; envoy_unsubscribe ${topic} to stop).`,
33756
+ display: true
33757
+ }, { deliverAs: "steer", triggerTurn: false });
33758
+ }
33692
33759
  } catch (error) {
33693
33760
  activeSessionContext?.ui.notify(`envoy: dispatch reply auto-subscribe failed (${messageFor(error)}); run envoy_subscribe ${topic}`, "warning");
33694
33761
  }
@@ -226,9 +226,11 @@ It returns `details` `{ issue, topic, message }`. `body` is capped at 2,000 char
226
226
 
227
227
  ## What comes back
228
228
 
229
- A write result's `details.topic` subscribes the host to its owner. Issue writes use `notifications.dispatch.issue.<KEY>.>`;
229
+ A write result's `details.topic` subscribes the host to its owner, and the first such subscription on an issue or document also tells
230
+ you so (an already-subscribed write stays quiet — no repeat notice). Issue writes use `notifications.dispatch.issue.<KEY>.>`;
230
231
  project-document writes use `notifications.dispatch.document.<PROJECT>.<SLUG>.>`. The owner topic carries every Dispatch event; `notify`
231
- only controls agent wake and routed delivery. After a restart, catch up with:
232
+ only controls agent wake and routed delivery. A human may unsubscribe you from the issue or document header; you are told with a
233
+ `subscription.removed` notice when that happens. After a restart, catch up with:
232
234
 
233
235
  ```ts
234
236
  dispatch_read({ issue?, project?, artifact?, ref? })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sjawhar/pi-legion-envoy",
3
- "version": "0.43.0",
3
+ "version": "0.44.0",
4
4
  "type": "module",
5
5
  "omp": {
6
6
  "extensions": [