@sjawhar/pi-legion-envoy 5.7.0 → 5.8.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
@@ -29880,6 +29880,30 @@ function actorLabel(actor, titles) {
29880
29880
  }
29881
29881
  return `${name} (as ${serviceSubjectLabel(service)})`;
29882
29882
  }
29883
+ // ../contracts/src/agent-stream.ts
29884
+ var AGENT_STREAM_SUBJECT_PREFIX = "agentstream.";
29885
+ var AGENT_STREAM_PROTOCOL = 1;
29886
+ function agentStreamFramesSubject(sessionID) {
29887
+ return `${AGENT_STREAM_SUBJECT_PREFIX}${sessionID}.frames`;
29888
+ }
29889
+ function agentStreamControlSubject(sessionID) {
29890
+ return `${AGENT_STREAM_SUBJECT_PREFIX}${sessionID}.control`;
29891
+ }
29892
+ var AGENT_STREAM_WATCH_TTL_MS = 30000;
29893
+ var AGENT_STREAM_LIMITS = {
29894
+ partChars: 16000,
29895
+ toolChars: 8000,
29896
+ historyMessages: 200,
29897
+ historyBytes: 512 * 1024,
29898
+ snapshotIntervalMs: 100
29899
+ };
29900
+ var AGENT_STREAM_TRUNCATION_SUFFIX = `
29901
+ \u2026 truncated`;
29902
+ function capAgentStreamText(text, limit) {
29903
+ if (text.length <= limit)
29904
+ return text;
29905
+ return text.slice(0, limit) + AGENT_STREAM_TRUNCATION_SUFFIX;
29906
+ }
29883
29907
  // ../contracts/src/dispatch-href.ts
29884
29908
  function itemFromSearch(search) {
29885
29909
  const params = new URLSearchParams(search);
@@ -34799,6 +34823,202 @@ function sourceSession(sessionID) {
34799
34823
  import { logger } from "@oh-my-pi/pi-utils";
34800
34824
  var import_nats2 = __toESM(require_mod4(), 1);
34801
34825
 
34826
+ // src/agent-stream.ts
34827
+ var encoder = new TextEncoder;
34828
+ function readMessage(raw) {
34829
+ if (typeof raw !== "object" || raw === null)
34830
+ return null;
34831
+ const message = raw;
34832
+ if (typeof message.role !== "string" || typeof message.timestamp !== "number")
34833
+ return null;
34834
+ return {
34835
+ host: message,
34836
+ isError: message.isError === true,
34837
+ role: message.role,
34838
+ timestamp: message.timestamp,
34839
+ toolCallId: typeof message.toolCallId === "string" ? message.toolCallId : undefined,
34840
+ toolName: typeof message.toolName === "string" ? message.toolName : undefined
34841
+ };
34842
+ }
34843
+ function keyFor(message) {
34844
+ if (message.role === "toolResult") {
34845
+ return message.toolCallId === undefined ? null : `t:${message.toolCallId}`;
34846
+ }
34847
+ if (message.role !== "assistant" && message.role !== "user")
34848
+ return null;
34849
+ return `m:${message.role === "assistant" ? "a" : "u"}${message.timestamp}`;
34850
+ }
34851
+ function contentText(content) {
34852
+ if (typeof content === "string")
34853
+ return content;
34854
+ if (!Array.isArray(content))
34855
+ return "";
34856
+ const pieces = [];
34857
+ for (const raw of content) {
34858
+ if (typeof raw !== "object" || raw === null)
34859
+ continue;
34860
+ const part = raw;
34861
+ if (typeof part.text === "string")
34862
+ pieces.push(part.text);
34863
+ else if (typeof part.type === "string")
34864
+ pieces.push(`[${part.type}]`);
34865
+ }
34866
+ return pieces.join(`
34867
+ `);
34868
+ }
34869
+ function boundedStrings(_key, value) {
34870
+ return typeof value === "string" ? capAgentStreamText(value, AGENT_STREAM_LIMITS.toolChars) : value;
34871
+ }
34872
+ function renderArguments(value) {
34873
+ if (value === undefined || value === null)
34874
+ return "";
34875
+ try {
34876
+ return JSON.stringify(value, boundedStrings, 2) ?? "";
34877
+ } catch {
34878
+ return "[unserialisable arguments]";
34879
+ }
34880
+ }
34881
+ function conversationParts(content) {
34882
+ const { partChars, toolChars } = AGENT_STREAM_LIMITS;
34883
+ if (typeof content === "string") {
34884
+ return content === "" ? [] : [{ text: capAgentStreamText(content, partChars), type: "text" }];
34885
+ }
34886
+ if (!Array.isArray(content))
34887
+ return [];
34888
+ const parts = [];
34889
+ for (const raw of content) {
34890
+ if (typeof raw !== "object" || raw === null)
34891
+ continue;
34892
+ const part = raw;
34893
+ if (part.type === "text" && typeof part.text === "string") {
34894
+ parts.push({ text: capAgentStreamText(part.text, partChars), type: "text" });
34895
+ } else if (part.type === "thinking" && typeof part.thinking === "string") {
34896
+ parts.push({ text: capAgentStreamText(part.thinking, partChars), type: "reasoning" });
34897
+ } else if (part.type === "toolCall" && typeof part.id === "string") {
34898
+ parts.push({
34899
+ argsText: capAgentStreamText(renderArguments(part.arguments), toolChars),
34900
+ toolCallId: part.id,
34901
+ toolName: typeof part.name === "string" ? part.name : "tool",
34902
+ type: "tool-call"
34903
+ });
34904
+ } else if (typeof part.type === "string") {
34905
+ parts.push({ text: `[${part.type}]`, type: "text" });
34906
+ }
34907
+ }
34908
+ return parts;
34909
+ }
34910
+ function frameFor(entry) {
34911
+ const { message } = entry;
34912
+ if (message.role === "toolResult" && message.toolCallId !== undefined) {
34913
+ return {
34914
+ kind: "tool-result",
34915
+ result: {
34916
+ at: message.timestamp,
34917
+ isError: message.isError,
34918
+ output: capAgentStreamText(contentText(message.host.content), AGENT_STREAM_LIMITS.toolChars),
34919
+ toolCallId: message.toolCallId,
34920
+ toolName: message.toolName ?? "tool"
34921
+ },
34922
+ seq: entry.seq,
34923
+ v: AGENT_STREAM_PROTOCOL
34924
+ };
34925
+ }
34926
+ return {
34927
+ kind: "message",
34928
+ message: {
34929
+ at: message.timestamp,
34930
+ id: entry.key.slice(2),
34931
+ parts: conversationParts(message.host.content),
34932
+ role: message.role === "assistant" ? "assistant" : "user",
34933
+ streaming: entry.streaming
34934
+ },
34935
+ seq: entry.seq,
34936
+ v: AGENT_STREAM_PROTOCOL
34937
+ };
34938
+ }
34939
+
34940
+ class AgentStreamPublisher {
34941
+ #deps;
34942
+ #history = new Map;
34943
+ #settled = new Set;
34944
+ #publishedAt = new Map;
34945
+ #seq = 0;
34946
+ #watchedUntil = 0;
34947
+ constructor(deps) {
34948
+ this.#deps = deps;
34949
+ }
34950
+ noteViewer() {
34951
+ this.#watchedUntil = this.#deps.now() + AGENT_STREAM_WATCH_TTL_MS;
34952
+ }
34953
+ get watched() {
34954
+ return this.#deps.now() < this.#watchedUntil;
34955
+ }
34956
+ reset() {
34957
+ this.#history.clear();
34958
+ this.#settled.clear();
34959
+ this.#publishedAt.clear();
34960
+ this.#watchedUntil = 0;
34961
+ }
34962
+ record(subject2, raw, streaming) {
34963
+ const message = readMessage(raw);
34964
+ if (message === null)
34965
+ return false;
34966
+ const key = keyFor(message);
34967
+ if (key === null)
34968
+ return false;
34969
+ const stillStreaming = message.role === "assistant" && streaming;
34970
+ if (stillStreaming && this.#settled.has(key))
34971
+ return false;
34972
+ this.#seq += 1;
34973
+ const entry = {
34974
+ at: message.timestamp,
34975
+ key,
34976
+ message,
34977
+ seq: this.#seq,
34978
+ streaming: stillStreaming
34979
+ };
34980
+ this.#history.set(key, entry);
34981
+ if (!stillStreaming)
34982
+ this.#settled.add(key);
34983
+ while (this.#history.size > AGENT_STREAM_LIMITS.historyMessages) {
34984
+ const oldest = this.#history.keys().next();
34985
+ if (oldest.done === true)
34986
+ break;
34987
+ this.#history.delete(oldest.value);
34988
+ this.#settled.delete(oldest.value);
34989
+ this.#publishedAt.delete(oldest.value);
34990
+ }
34991
+ if (!this.watched)
34992
+ return true;
34993
+ const now = this.#deps.now();
34994
+ const published = this.#publishedAt.get(key);
34995
+ const tooSoon = published !== undefined && now - published < AGENT_STREAM_LIMITS.snapshotIntervalMs;
34996
+ if (stillStreaming && tooSoon)
34997
+ return true;
34998
+ this.#publishedAt.set(key, now);
34999
+ this.#deps.publish(subject2, JSON.stringify(frameFor(entry)));
35000
+ return true;
35001
+ }
35002
+ replay(sessionID) {
35003
+ if (!this.watched)
35004
+ return { frames: [], session_id: sessionID, v: AGENT_STREAM_PROTOCOL };
35005
+ const ordered = [...this.#history.values()].sort((left, right) => left.at === right.at ? left.seq - right.seq : left.at - right.at);
35006
+ let budget = AGENT_STREAM_LIMITS.historyBytes;
35007
+ const kept = [];
35008
+ for (let index = ordered.length - 1;index >= 0; index -= 1) {
35009
+ const entry = ordered[index];
35010
+ if (entry === undefined)
35011
+ continue;
35012
+ const frame = frameFor(entry);
35013
+ budget -= encoder.encode(JSON.stringify(frame)).length;
35014
+ if (budget < 0)
35015
+ break;
35016
+ kept.unshift(frame);
35017
+ }
35018
+ return { frames: kept, session_id: sessionID, v: AGENT_STREAM_PROTOCOL };
35019
+ }
35020
+ }
35021
+
34802
35022
  // src/envoy-session.ts
34803
35023
  import path2 from "path";
34804
35024
  var ENVOY_SESSIONS = Symbol.for("legion.pi-envoy.envoy-session");
@@ -35371,6 +35591,75 @@ function envoyExtension(pi) {
35371
35591
  }
35372
35592
  return true;
35373
35593
  };
35594
+ const agentStream = new AgentStreamPublisher({
35595
+ now: () => Date.now(),
35596
+ publish: (subject2, payload) => connection?.publish(subject2, codec2.encode(payload))
35597
+ });
35598
+ let agentStreamControl;
35599
+ let agentStreamControlSession = "";
35600
+ const pumpAgentStreamControl = async (subscription, session) => {
35601
+ try {
35602
+ for await (const message of subscription) {
35603
+ try {
35604
+ const parsed = JSON.parse(codec2.decode(message.data));
35605
+ if (typeof parsed !== "object" || parsed === null)
35606
+ continue;
35607
+ const request = parsed;
35608
+ if (request.type !== "replay" && request.type !== "watch")
35609
+ continue;
35610
+ const reply = message.reply;
35611
+ const answer = request.type === "replay" && reply !== undefined ? JSON.stringify(agentStream.replay(session)) : undefined;
35612
+ agentStream.noteViewer();
35613
+ if (answer === undefined || reply === undefined)
35614
+ continue;
35615
+ connection?.publish(reply, codec2.encode(answer));
35616
+ } catch (error48) {
35617
+ logger.warn("envoy: agent stream control message failed", {
35618
+ error: messageFor(error48),
35619
+ sessionID: session
35620
+ });
35621
+ }
35622
+ }
35623
+ } catch {}
35624
+ if (agentStreamControl === subscription)
35625
+ agentStreamControl = undefined;
35626
+ if (shuttingDown || agentStreamControlSession !== session)
35627
+ return;
35628
+ setTimeout(() => {
35629
+ if (shuttingDown || agentStreamControlSession !== session)
35630
+ return;
35631
+ watchAgentStream(session).catch(() => {
35632
+ return;
35633
+ });
35634
+ }, RESUBSCRIBE_DELAY_MS);
35635
+ };
35636
+ const watchAgentStream = async (session) => {
35637
+ if (agentStreamControlSession === session && agentStreamControl !== undefined)
35638
+ return;
35639
+ agentStreamControl?.unsubscribe();
35640
+ agentStreamControl = undefined;
35641
+ if (agentStreamControlSession !== session)
35642
+ agentStream.reset();
35643
+ agentStreamControlSession = session;
35644
+ const active = await ensureConnection();
35645
+ const subscription = active.subscribe(agentStreamControlSubject(session));
35646
+ agentStreamControl = subscription;
35647
+ pumpAgentStreamControl(subscription, session);
35648
+ };
35649
+ const recordAgentStreamMessage = (message, streaming) => {
35650
+ if (sessionID === "")
35651
+ return;
35652
+ agentStream.record(agentStreamFramesSubject(sessionID), message, streaming);
35653
+ };
35654
+ pi.on("message_start", async (event) => {
35655
+ recordAgentStreamMessage(event.message, true);
35656
+ });
35657
+ pi.on("message_update", async (event) => {
35658
+ recordAgentStreamMessage(event.message, true);
35659
+ });
35660
+ pi.on("message_end", async (event) => {
35661
+ recordAgentStreamMessage(event.message, false);
35662
+ });
35374
35663
  const endRole = () => {
35375
35664
  claimedRoleTopic = undefined;
35376
35665
  claimedRoleSessionID = undefined;
@@ -35592,6 +35881,7 @@ function envoyExtension(pi) {
35592
35881
  }
35593
35882
  await ensureConnection();
35594
35883
  await subscribe(currentTopic);
35884
+ await watchAgentStream(sessionID);
35595
35885
  if (resumed) {
35596
35886
  await recoverRegisteredInterests();
35597
35887
  }
@@ -35756,6 +36046,8 @@ function envoyExtension(pi) {
35756
36046
  subscriptions.clear();
35757
36047
  intentionallyClosed.clear();
35758
36048
  awaitingRetry.clear();
36049
+ agentStreamControl = undefined;
36050
+ agentStreamControlSession = "";
35759
36051
  }
35760
36052
  });
35761
36053
  for (const spec of envoyToolSpecs) {
@@ -35880,7 +36172,10 @@ function envoyExtension(pi) {
35880
36172
  askCheckAbort = abort;
35881
36173
  let answered;
35882
36174
  try {
35883
- answered = askEphemeral({ prompt: ASK_SELF_CHECK_PROMPT(open.snapshot.asks), signal: abort.signal }).then((reply) => reply.replyText, (error48) => {
36175
+ answered = askEphemeral({
36176
+ prompt: ASK_SELF_CHECK_PROMPT(open.snapshot.asks),
36177
+ signal: abort.signal
36178
+ }).then((reply) => reply.replyText, (error48) => {
35884
36179
  if (!abort.signal.aborted)
35885
36180
  logSelfCheckFailure(id, error48);
35886
36181
  return;
package/dist/legion.js CHANGED
@@ -16192,7 +16192,7 @@ import { logger } from "@oh-my-pi/pi-utils";
16192
16192
  // package.json
16193
16193
  var package_default = {
16194
16194
  name: "@sjawhar/pi-legion-envoy",
16195
- version: "5.7.0",
16195
+ version: "5.8.0",
16196
16196
  type: "module",
16197
16197
  omp: {
16198
16198
  extensions: [
@@ -30003,6 +30003,30 @@ function actorLabel(actor, titles) {
30003
30003
  }
30004
30004
  return `${name} (as ${serviceSubjectLabel(service)})`;
30005
30005
  }
30006
+ // ../contracts/src/agent-stream.ts
30007
+ var AGENT_STREAM_SUBJECT_PREFIX = "agentstream.";
30008
+ var AGENT_STREAM_PROTOCOL = 1;
30009
+ function agentStreamFramesSubject(sessionID) {
30010
+ return `${AGENT_STREAM_SUBJECT_PREFIX}${sessionID}.frames`;
30011
+ }
30012
+ function agentStreamControlSubject(sessionID) {
30013
+ return `${AGENT_STREAM_SUBJECT_PREFIX}${sessionID}.control`;
30014
+ }
30015
+ var AGENT_STREAM_WATCH_TTL_MS = 30000;
30016
+ var AGENT_STREAM_LIMITS = {
30017
+ partChars: 16000,
30018
+ toolChars: 8000,
30019
+ historyMessages: 200,
30020
+ historyBytes: 512 * 1024,
30021
+ snapshotIntervalMs: 100
30022
+ };
30023
+ var AGENT_STREAM_TRUNCATION_SUFFIX = `
30024
+ \u2026 truncated`;
30025
+ function capAgentStreamText(text, limit) {
30026
+ if (text.length <= limit)
30027
+ return text;
30028
+ return text.slice(0, limit) + AGENT_STREAM_TRUNCATION_SUFFIX;
30029
+ }
30006
30030
  // ../contracts/src/dispatch-href.ts
30007
30031
  function itemFromSearch(search) {
30008
30032
  const params = new URLSearchParams(search);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sjawhar/pi-legion-envoy",
3
- "version": "5.7.0",
3
+ "version": "5.8.0",
4
4
  "type": "module",
5
5
  "omp": {
6
6
  "extensions": [