@sjawhar/pi-legion-envoy 1.27.1 → 1.27.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.
Files changed (3) hide show
  1. package/dist/envoy.js +23 -203
  2. package/dist/legion.js +23 -203
  3. package/package.json +1 -1
package/dist/envoy.js CHANGED
@@ -32402,12 +32402,23 @@ async function executeDispatchTool(input) {
32402
32402
  if (!input.config.enabled || !configUrl || !configToken) {
32403
32403
  throw new Error("Dispatch is disabled; resolve both DISPATCH_URL and DISPATCH_TOKEN");
32404
32404
  }
32405
+ const baseFetch = input.fetchImpl ?? fetch;
32406
+ const fetchImpl = Object.assign(async (...args) => {
32407
+ try {
32408
+ return await baseFetch(...args);
32409
+ } catch (error) {
32410
+ if (error instanceof TypeError) {
32411
+ throw new Error(`Dispatch at ${configUrl} is unreachable: ${error.message}. If the Dispatch URL changed, restart this agent process so it picks up the new configuration.`);
32412
+ }
32413
+ throw error;
32414
+ }
32415
+ }, { preconnect: baseFetch.preconnect });
32405
32416
  if (input.tool === "dispatch_open_asks") {
32406
32417
  const sessionId = input.sessionId?.trim();
32407
32418
  if (!sessionId)
32408
32419
  throw new Error("host session id is required for dispatch_open_asks");
32409
32420
  toolSchema(input.tool).parse(input.args);
32410
- const client = new DispatchClient(configUrl, configToken, input.fetchImpl, input.signal);
32421
+ const client = new DispatchClient(configUrl, configToken, fetchImpl, input.signal);
32411
32422
  const response = await client.openAsks(sessionId);
32412
32423
  return { text: formatOpenAsksSummary(response, configUrl), details: { ...response } };
32413
32424
  }
@@ -32416,7 +32427,7 @@ async function executeDispatchTool(input) {
32416
32427
  const ownerArguments = await resolveOwnerArguments(input.tool, input.args, input.cwd, env, exec);
32417
32428
  const args = toolSchema(input.tool).parse(ownerArguments.args);
32418
32429
  const actor = toolActor(await resolveOrigin(env, exec, input.cwd), input);
32419
- const client = new DispatchClient(configUrl, configToken, input.fetchImpl, input.signal);
32430
+ const client = new DispatchClient(configUrl, configToken, fetchImpl, input.signal);
32420
32431
  const owner = ownerArguments.owner?.kind === "issue" ? {
32421
32432
  kind: "issue",
32422
32433
  issue: await ensureIssue(client, ownerArguments.owner.issue, actor)
@@ -33255,59 +33266,7 @@ async function copySessionID(copyToClipboard, sessionID) {
33255
33266
  var codec2 = import_nats.StringCodec();
33256
33267
  var NATS_RETRY_INTERVAL_MS = 15000;
33257
33268
  var ROLE_CLAIM_ENTRY = "envoy-role-claim";
33258
- var ASK_AWARENESS_ENTRY = "dispatch-ask-awareness";
33259
- var LEGION_MANAGED_ENTRY = "legion-managed-session";
33260
33269
  var OPEN_ASKS_TIMEOUT_MS = 3000;
33261
- var OPEN_ASKS_REMINDER = "You have no unanswered asks in Dispatch. If you are waiting for human input, open an ask. Otherwise ignore this reminder and continue with any remaining work. Do not reply just to acknowledge this reminder.";
33262
- function isAskAwarenessEntry(entry) {
33263
- if (typeof entry !== "object" || entry === null)
33264
- return false;
33265
- if (!("type" in entry) || entry.type !== "custom")
33266
- return false;
33267
- if (!("customType" in entry) || entry.customType !== ASK_AWARENESS_ENTRY)
33268
- return false;
33269
- if (!("data" in entry) || typeof entry.data !== "object" || entry.data === null)
33270
- return false;
33271
- const data = entry.data;
33272
- return "session_id" in data && typeof data.session_id === "string" && "period" in data && typeof data.period === "number" && "baseline_as_of" in data && (typeof data.baseline_as_of === "string" || data.baseline_as_of === null) && "checked" in data && typeof data.checked === "boolean" && "fired" in data && typeof data.fired === "boolean" && "saw_ask" in data && typeof data.saw_ask === "boolean";
33273
- }
33274
- function isLegionManagedEntry(entry) {
33275
- if (typeof entry !== "object" || entry === null)
33276
- return false;
33277
- if (!("type" in entry) || entry.type !== "custom")
33278
- return false;
33279
- if (!("customType" in entry) || entry.customType !== LEGION_MANAGED_ENTRY)
33280
- return false;
33281
- if (!("data" in entry) || typeof entry.data !== "object" || entry.data === null)
33282
- return false;
33283
- return "session_id" in entry.data && typeof entry.data.session_id === "string";
33284
- }
33285
- function emptyAskAwareness(sessionID) {
33286
- return {
33287
- session_id: sessionID,
33288
- period: 0,
33289
- baseline_as_of: null,
33290
- checked: false,
33291
- fired: false,
33292
- saw_ask: false
33293
- };
33294
- }
33295
- function transcriptAskAwareness(branch, sessionID) {
33296
- let state;
33297
- for (const entry of branch) {
33298
- if (isAskAwarenessEntry(entry) && entry.data.session_id === sessionID)
33299
- state = entry.data;
33300
- }
33301
- return state;
33302
- }
33303
- function transcriptMarksLegionSession(branch, sessionID) {
33304
- return branch.some((entry) => isLegionManagedEntry(entry) && entry.data.session_id === sessionID);
33305
- }
33306
- function isHumanTurnInitiator(message) {
33307
- if (message.attribution !== "user")
33308
- return false;
33309
- return message.role === "user" || message.role === "custom" && (message.customType === "skill-prompt" || message.customType === "collab-prompt");
33310
- }
33311
33270
  function isRoleClaimEntry(entry) {
33312
33271
  if (typeof entry !== "object" || entry === null)
33313
33272
  return false;
@@ -33327,15 +33286,13 @@ function legionRoleClaimBridge() {
33327
33286
  let bridge = store[LEGION_ROLE_CLAIM_BRIDGE];
33328
33287
  if (bridge === undefined) {
33329
33288
  const ready = Promise.withResolvers();
33330
- bridge = { ready, managedSessions: new Set };
33289
+ bridge = { ready };
33331
33290
  store[LEGION_ROLE_CLAIM_BRIDGE] = bridge;
33332
33291
  }
33333
33292
  return bridge;
33334
33293
  }
33335
33294
  async function claimEnvoyRole(sessionID, role, context) {
33336
33295
  const bridge = legionRoleClaimBridge();
33337
- bridge.managedSessions.add(sessionID);
33338
- bridge.markManagedSession?.(sessionID, context);
33339
33296
  const claim = bridge.claim ?? await bridge.ready.promise;
33340
33297
  await claim(sessionID, role, context);
33341
33298
  }
@@ -33384,87 +33341,26 @@ function envoyExtension(pi) {
33384
33341
  let claimedRoleTopic;
33385
33342
  let activeSessionContext;
33386
33343
  const inbox = [];
33387
- let askAwareness = emptyAskAwareness("");
33388
- let candidateBaseline;
33389
- let awarenessGeneration = 0;
33390
- let availabilityWarningPeriod;
33391
- let agentRunning = false;
33392
- let agentWillContinue = false;
33393
- let legionManagedSessionID = "";
33394
- let terminalInputUnsubscribe;
33395
- const persistAskAwareness = (state) => {
33396
- askAwareness = state;
33397
- pi.appendEntry(ASK_AWARENESS_ENTRY, state);
33398
- };
33344
+ const availabilityWarningSessionIDs = new Set;
33399
33345
  const warnAskAvailability = (context, error) => {
33400
- const key = `${askAwareness.session_id}:${askAwareness.period}`;
33401
- if (availabilityWarningPeriod === key)
33346
+ const sessionID = context.sessionManager.getSessionId();
33347
+ if (availabilityWarningSessionIDs.has(sessionID))
33402
33348
  return;
33403
- availabilityWarningPeriod = key;
33404
- context.ui.notify(`envoy: Dispatch open-ask check unavailable (${messageFor(error)}); reminder skipped`, "warning");
33349
+ availabilityWarningSessionIDs.add(sessionID);
33350
+ context.ui.notify(`envoy: Dispatch open-ask check unavailable (${messageFor(error)}); summary unavailable`, "warning");
33405
33351
  };
33406
- const queryOpenAsks = async (requestedSessionID, since, signal) => {
33352
+ const queryOpenAsks = async (requestedSessionID) => {
33407
33353
  const config = activeDispatchConfig();
33408
33354
  if (config === null)
33409
33355
  return null;
33410
- const deadline = AbortSignal.timeout(OPEN_ASKS_TIMEOUT_MS);
33411
- const requestSignal = signal === undefined ? deadline : AbortSignal.any([signal, deadline]);
33412
- return new DispatchClient(config.url, config.token, fetch, requestSignal).openAsks(requestedSessionID, since);
33413
- };
33414
- const setReminderBaseline = (requestedSessionID, period, asOf) => {
33415
- candidateBaseline = { sessionID: requestedSessionID, asOf };
33416
- if (askAwareness.session_id !== requestedSessionID || askAwareness.period !== period || askAwareness.checked || askAwareness.fired || askAwareness.baseline_as_of !== null) {
33417
- return;
33418
- }
33419
- persistAskAwareness({ ...askAwareness, baseline_as_of: asOf });
33420
- };
33421
- const refreshReminderBaseline = async (requestedSessionID, period, context) => {
33422
- try {
33423
- const snapshot = await queryOpenAsks(requestedSessionID);
33424
- if (snapshot === null)
33425
- return;
33426
- setReminderBaseline(requestedSessionID, period, snapshot.as_of);
33427
- } catch (error) {
33428
- warnAskAvailability(context, error);
33429
- }
33430
- };
33431
- const armAskReminder = async (context, refreshBaseline = false) => {
33432
- const id = context.sessionManager.getSessionId();
33433
- if (id === "" || activeDispatchConfig() === null)
33434
- return;
33435
- awarenessGeneration++;
33436
- availabilityWarningPeriod = undefined;
33437
- const baseline = !agentRunning && !refreshBaseline && candidateBaseline !== undefined && candidateBaseline.sessionID === id ? candidateBaseline.asOf : null;
33438
- const previousPeriod = askAwareness.session_id === id ? askAwareness.period : 0;
33439
- persistAskAwareness({
33440
- session_id: id,
33441
- period: previousPeriod + 1,
33442
- baseline_as_of: baseline,
33443
- checked: false,
33444
- fired: false,
33445
- saw_ask: false
33446
- });
33447
- if (agentRunning || refreshBaseline) {
33448
- await refreshReminderBaseline(id, askAwareness.period, context);
33449
- }
33450
- };
33451
- const cancelPendingAskReminder = () => {
33452
- awarenessGeneration++;
33453
- };
33454
- const installTerminalInputCancellation = (context) => {
33455
- terminalInputUnsubscribe?.();
33456
- terminalInputUnsubscribe = context.ui.onTerminalInput(cancelPendingAskReminder);
33356
+ const snapshot = await new DispatchClient(config.url, config.token, fetch, AbortSignal.timeout(OPEN_ASKS_TIMEOUT_MS)).openAsks(requestedSessionID);
33357
+ availabilityWarningSessionIDs.delete(requestedSessionID);
33358
+ return snapshot;
33457
33359
  };
33458
33360
  const restoreLocalSessionState = (context) => {
33459
33361
  sessionDirectory = context.cwd;
33460
33362
  sessionID = context.sessionManager.getSessionId();
33461
33363
  activeSessionContext = context;
33462
- candidateBaseline = undefined;
33463
- const branch = context.sessionManager.getBranch?.() ?? [];
33464
- askAwareness = transcriptAskAwareness(branch, sessionID) ?? emptyAskAwareness(sessionID);
33465
- const persistedLegionSession = transcriptMarksLegionSession(branch, sessionID);
33466
- const managed = persistedLegionSession || legionRoleClaimBridge().managedSessions.has(sessionID);
33467
- legionManagedSessionID = managed ? sessionID : "";
33468
33364
  };
33469
33365
  pi.on("resources_discover", async () => ({ skillPaths: [SKILLS_DIRECTORY] }));
33470
33366
  registerEnvoyMessageRenderer(pi);
@@ -33505,13 +33401,6 @@ function envoyExtension(pi) {
33505
33401
  const dedupeKey = rendered.envelope?.dedupe_key;
33506
33402
  const duplicate = dedupeKey !== undefined && dedupeKeys.has(dedupeKey);
33507
33403
  if (!duplicate && !rendered.skip) {
33508
- if (rendered.dispatchActor?.kind === "user" && activeSessionContext !== undefined) {
33509
- try {
33510
- await armAskReminder(activeSessionContext, true);
33511
- } catch (error) {
33512
- warnAskAvailability(activeSessionContext, error);
33513
- }
33514
- }
33515
33404
  const envelope = rendered.envelope;
33516
33405
  if (envelope !== undefined) {
33517
33406
  inbox.unshift({
@@ -33831,17 +33720,6 @@ function envoyExtension(pi) {
33831
33720
  return run;
33832
33721
  };
33833
33722
  const bridge = legionRoleClaimBridge();
33834
- bridge.markManagedSession = (targetSessionID, context) => {
33835
- bridge.managedSessions.add(targetSessionID);
33836
- if (targetSessionID !== sessionID)
33837
- return;
33838
- if (legionManagedSessionID === targetSessionID)
33839
- return;
33840
- legionManagedSessionID = targetSessionID;
33841
- if (context !== undefined || activeSessionContext !== undefined) {
33842
- pi.appendEntry(LEGION_MANAGED_ENTRY, { session_id: targetSessionID });
33843
- }
33844
- };
33845
33723
  const claim = async (targetSessionID, role, callerContext) => {
33846
33724
  const context = callerContext ?? activeSessionContext;
33847
33725
  if (context === undefined || context.sessionManager.getSessionId() !== targetSessionID) {
@@ -33855,7 +33733,6 @@ function envoyExtension(pi) {
33855
33733
  bridge.claim = claim;
33856
33734
  bridge.ready.resolve(claim);
33857
33735
  pi.on("session_start", async (_event, context) => {
33858
- installTerminalInputCancellation(context);
33859
33736
  const previousSessionID = sessionID;
33860
33737
  restoreLocalSessionState(context);
33861
33738
  if (dispatchConfig.error !== null) {
@@ -33891,7 +33768,6 @@ function envoyExtension(pi) {
33891
33768
  }
33892
33769
  });
33893
33770
  const rebind = async (reason, context) => {
33894
- installTerminalInputCancellation(context);
33895
33771
  const previousID = sessionID;
33896
33772
  restoreLocalSessionState(context);
33897
33773
  if (defaults.natsUrls.length === 0)
@@ -33941,8 +33817,6 @@ function envoyExtension(pi) {
33941
33817
  ]);
33942
33818
  } finally {
33943
33819
  clearTimeout(timer);
33944
- terminalInputUnsubscribe?.();
33945
- terminalInputUnsubscribe = undefined;
33946
33820
  connection = undefined;
33947
33821
  activeSessionContext = undefined;
33948
33822
  subscriptions.clear();
@@ -33996,7 +33870,6 @@ function envoyExtension(pi) {
33996
33870
  const snapshot = await queryOpenAsks(id);
33997
33871
  if (snapshot === null)
33998
33872
  return;
33999
- setReminderBaseline(id, askAwareness.session_id === id ? askAwareness.period : 0, snapshot.as_of);
34000
33873
  return {
34001
33874
  message: {
34002
33875
  customType: "dispatch-open-asks",
@@ -34018,60 +33891,7 @@ ${formatOpenAsksSummary(snapshot, currentDispatchConfig().url)}`,
34018
33891
  };
34019
33892
  }
34020
33893
  });
34021
- pi.on("agent_start", async () => {
34022
- agentRunning = true;
34023
- agentWillContinue = false;
34024
- });
34025
- pi.on("agent_end", async (event) => {
34026
- agentRunning = false;
34027
- agentWillContinue = event.willContinue === true;
34028
- });
34029
- pi.on("input", async () => {
34030
- cancelPendingAskReminder();
34031
- });
34032
- pi.on("message_start", async (event, context) => {
34033
- if (!isHumanTurnInitiator(event.message))
34034
- return;
34035
- try {
34036
- await armAskReminder(context);
34037
- } catch (error) {
34038
- warnAskAvailability(context, error);
34039
- }
34040
- });
34041
- pi.on("session_stop", async (event, context) => {
34042
- if (event.stop_hook_active || event.signal.aborted || shuttingDown || agentWillContinue || event.last_assistant_message?.role !== "assistant" || event.session_id !== sessionID || legionManagedSessionID === event.session_id || askAwareness.session_id !== event.session_id || askAwareness.period === 0 || askAwareness.checked || askAwareness.fired) {
34043
- return;
34044
- }
34045
- const baseline = askAwareness.baseline_as_of;
34046
- if (baseline === null)
34047
- return;
34048
- if (context.ui.getEditorText().trim() !== "")
34049
- return;
34050
- const claimed = { ...askAwareness, checked: true };
34051
- const generation = awarenessGeneration;
34052
- persistAskAwareness(claimed);
34053
- let snapshot;
34054
- try {
34055
- snapshot = await queryOpenAsks(event.session_id, baseline, event.signal);
34056
- } catch (error) {
34057
- if (!event.signal.aborted && generation === awarenessGeneration) {
34058
- warnAskAvailability(context, error);
34059
- }
34060
- return;
34061
- }
34062
- if (snapshot === null || event.signal.aborted || generation !== awarenessGeneration || shuttingDown || agentWillContinue || context.sessionManager.getSessionId() !== event.session_id || sessionID !== event.session_id || legionManagedSessionID === event.session_id || context.ui.getEditorText().trim() !== "" || askAwareness.session_id !== claimed.session_id || askAwareness.period !== claimed.period || !askAwareness.checked) {
34063
- return;
34064
- }
34065
- if (snapshot.count > 0 || snapshot.opened_since || askAwareness.saw_ask) {
34066
- return;
34067
- }
34068
- persistAskAwareness({ ...askAwareness, fired: true });
34069
- return { continue: true, additionalContext: OPEN_ASKS_REMINDER };
34070
- });
34071
33894
  pi.on("tool_result", async (event) => {
34072
- if (!event.isError && event.toolName === "dispatch_ask" && typeof event.details === "object" && event.details !== null && "ask" in event.details && typeof event.details.ask === "string" && askAwareness.session_id === sessionID && askAwareness.period > 0 && !askAwareness.saw_ask) {
34073
- persistAskAwareness({ ...askAwareness, saw_ask: true });
34074
- }
34075
33895
  if (event.isError)
34076
33896
  return;
34077
33897
  const topic = dispatchSubscriptionTopic(event.details);
package/dist/legion.js CHANGED
@@ -32833,12 +32833,23 @@ async function executeDispatchTool(input) {
32833
32833
  if (!input.config.enabled || !configUrl || !configToken) {
32834
32834
  throw new Error("Dispatch is disabled; resolve both DISPATCH_URL and DISPATCH_TOKEN");
32835
32835
  }
32836
+ const baseFetch = input.fetchImpl ?? fetch;
32837
+ const fetchImpl = Object.assign(async (...args) => {
32838
+ try {
32839
+ return await baseFetch(...args);
32840
+ } catch (error) {
32841
+ if (error instanceof TypeError) {
32842
+ throw new Error(`Dispatch at ${configUrl} is unreachable: ${error.message}. If the Dispatch URL changed, restart this agent process so it picks up the new configuration.`);
32843
+ }
32844
+ throw error;
32845
+ }
32846
+ }, { preconnect: baseFetch.preconnect });
32836
32847
  if (input.tool === "dispatch_open_asks") {
32837
32848
  const sessionId = input.sessionId?.trim();
32838
32849
  if (!sessionId)
32839
32850
  throw new Error("host session id is required for dispatch_open_asks");
32840
32851
  toolSchema(input.tool).parse(input.args);
32841
- const client = new DispatchClient(configUrl, configToken, input.fetchImpl, input.signal);
32852
+ const client = new DispatchClient(configUrl, configToken, fetchImpl, input.signal);
32842
32853
  const response = await client.openAsks(sessionId);
32843
32854
  return { text: formatOpenAsksSummary(response, configUrl), details: { ...response } };
32844
32855
  }
@@ -32847,7 +32858,7 @@ async function executeDispatchTool(input) {
32847
32858
  const ownerArguments = await resolveOwnerArguments(input.tool, input.args, input.cwd, env, exec);
32848
32859
  const args = toolSchema(input.tool).parse(ownerArguments.args);
32849
32860
  const actor = toolActor(await resolveOrigin(env, exec, input.cwd), input);
32850
- const client = new DispatchClient(configUrl, configToken, input.fetchImpl, input.signal);
32861
+ const client = new DispatchClient(configUrl, configToken, fetchImpl, input.signal);
32851
32862
  const owner = ownerArguments.owner?.kind === "issue" ? {
32852
32863
  kind: "issue",
32853
32864
  issue: await ensureIssue(client, ownerArguments.owner.issue, actor)
@@ -33678,59 +33689,7 @@ async function copySessionID(copyToClipboard, sessionID) {
33678
33689
  var codec2 = import_nats.StringCodec();
33679
33690
  var NATS_RETRY_INTERVAL_MS = 15000;
33680
33691
  var ROLE_CLAIM_ENTRY = "envoy-role-claim";
33681
- var ASK_AWARENESS_ENTRY = "dispatch-ask-awareness";
33682
- var LEGION_MANAGED_ENTRY = "legion-managed-session";
33683
33692
  var OPEN_ASKS_TIMEOUT_MS = 3000;
33684
- var OPEN_ASKS_REMINDER = "You have no unanswered asks in Dispatch. If you are waiting for human input, open an ask. Otherwise ignore this reminder and continue with any remaining work. Do not reply just to acknowledge this reminder.";
33685
- function isAskAwarenessEntry(entry) {
33686
- if (typeof entry !== "object" || entry === null)
33687
- return false;
33688
- if (!("type" in entry) || entry.type !== "custom")
33689
- return false;
33690
- if (!("customType" in entry) || entry.customType !== ASK_AWARENESS_ENTRY)
33691
- return false;
33692
- if (!("data" in entry) || typeof entry.data !== "object" || entry.data === null)
33693
- return false;
33694
- const data = entry.data;
33695
- return "session_id" in data && typeof data.session_id === "string" && "period" in data && typeof data.period === "number" && "baseline_as_of" in data && (typeof data.baseline_as_of === "string" || data.baseline_as_of === null) && "checked" in data && typeof data.checked === "boolean" && "fired" in data && typeof data.fired === "boolean" && "saw_ask" in data && typeof data.saw_ask === "boolean";
33696
- }
33697
- function isLegionManagedEntry(entry) {
33698
- if (typeof entry !== "object" || entry === null)
33699
- return false;
33700
- if (!("type" in entry) || entry.type !== "custom")
33701
- return false;
33702
- if (!("customType" in entry) || entry.customType !== LEGION_MANAGED_ENTRY)
33703
- return false;
33704
- if (!("data" in entry) || typeof entry.data !== "object" || entry.data === null)
33705
- return false;
33706
- return "session_id" in entry.data && typeof entry.data.session_id === "string";
33707
- }
33708
- function emptyAskAwareness(sessionID) {
33709
- return {
33710
- session_id: sessionID,
33711
- period: 0,
33712
- baseline_as_of: null,
33713
- checked: false,
33714
- fired: false,
33715
- saw_ask: false
33716
- };
33717
- }
33718
- function transcriptAskAwareness(branch, sessionID) {
33719
- let state;
33720
- for (const entry of branch) {
33721
- if (isAskAwarenessEntry(entry) && entry.data.session_id === sessionID)
33722
- state = entry.data;
33723
- }
33724
- return state;
33725
- }
33726
- function transcriptMarksLegionSession(branch, sessionID) {
33727
- return branch.some((entry) => isLegionManagedEntry(entry) && entry.data.session_id === sessionID);
33728
- }
33729
- function isHumanTurnInitiator(message) {
33730
- if (message.attribution !== "user")
33731
- return false;
33732
- return message.role === "user" || message.role === "custom" && (message.customType === "skill-prompt" || message.customType === "collab-prompt");
33733
- }
33734
33693
  function isRoleClaimEntry(entry) {
33735
33694
  if (typeof entry !== "object" || entry === null)
33736
33695
  return false;
@@ -33750,15 +33709,13 @@ function legionRoleClaimBridge() {
33750
33709
  let bridge = store[LEGION_ROLE_CLAIM_BRIDGE];
33751
33710
  if (bridge === undefined) {
33752
33711
  const ready = Promise.withResolvers();
33753
- bridge = { ready, managedSessions: new Set };
33712
+ bridge = { ready };
33754
33713
  store[LEGION_ROLE_CLAIM_BRIDGE] = bridge;
33755
33714
  }
33756
33715
  return bridge;
33757
33716
  }
33758
33717
  async function claimEnvoyRole(sessionID, role, context) {
33759
33718
  const bridge = legionRoleClaimBridge();
33760
- bridge.managedSessions.add(sessionID);
33761
- bridge.markManagedSession?.(sessionID, context);
33762
33719
  const claim = bridge.claim ?? await bridge.ready.promise;
33763
33720
  await claim(sessionID, role, context);
33764
33721
  }
@@ -33807,87 +33764,26 @@ function envoyExtension(pi) {
33807
33764
  let claimedRoleTopic;
33808
33765
  let activeSessionContext;
33809
33766
  const inbox = [];
33810
- let askAwareness = emptyAskAwareness("");
33811
- let candidateBaseline;
33812
- let awarenessGeneration = 0;
33813
- let availabilityWarningPeriod;
33814
- let agentRunning = false;
33815
- let agentWillContinue = false;
33816
- let legionManagedSessionID = "";
33817
- let terminalInputUnsubscribe;
33818
- const persistAskAwareness = (state) => {
33819
- askAwareness = state;
33820
- pi.appendEntry(ASK_AWARENESS_ENTRY, state);
33821
- };
33767
+ const availabilityWarningSessionIDs = new Set;
33822
33768
  const warnAskAvailability = (context, error) => {
33823
- const key = `${askAwareness.session_id}:${askAwareness.period}`;
33824
- if (availabilityWarningPeriod === key)
33769
+ const sessionID = context.sessionManager.getSessionId();
33770
+ if (availabilityWarningSessionIDs.has(sessionID))
33825
33771
  return;
33826
- availabilityWarningPeriod = key;
33827
- context.ui.notify(`envoy: Dispatch open-ask check unavailable (${messageFor(error)}); reminder skipped`, "warning");
33772
+ availabilityWarningSessionIDs.add(sessionID);
33773
+ context.ui.notify(`envoy: Dispatch open-ask check unavailable (${messageFor(error)}); summary unavailable`, "warning");
33828
33774
  };
33829
- const queryOpenAsks = async (requestedSessionID, since, signal) => {
33775
+ const queryOpenAsks = async (requestedSessionID) => {
33830
33776
  const config = activeDispatchConfig();
33831
33777
  if (config === null)
33832
33778
  return null;
33833
- const deadline = AbortSignal.timeout(OPEN_ASKS_TIMEOUT_MS);
33834
- const requestSignal = signal === undefined ? deadline : AbortSignal.any([signal, deadline]);
33835
- return new DispatchClient(config.url, config.token, fetch, requestSignal).openAsks(requestedSessionID, since);
33836
- };
33837
- const setReminderBaseline = (requestedSessionID, period, asOf) => {
33838
- candidateBaseline = { sessionID: requestedSessionID, asOf };
33839
- if (askAwareness.session_id !== requestedSessionID || askAwareness.period !== period || askAwareness.checked || askAwareness.fired || askAwareness.baseline_as_of !== null) {
33840
- return;
33841
- }
33842
- persistAskAwareness({ ...askAwareness, baseline_as_of: asOf });
33843
- };
33844
- const refreshReminderBaseline = async (requestedSessionID, period, context) => {
33845
- try {
33846
- const snapshot = await queryOpenAsks(requestedSessionID);
33847
- if (snapshot === null)
33848
- return;
33849
- setReminderBaseline(requestedSessionID, period, snapshot.as_of);
33850
- } catch (error) {
33851
- warnAskAvailability(context, error);
33852
- }
33853
- };
33854
- const armAskReminder = async (context, refreshBaseline = false) => {
33855
- const id = context.sessionManager.getSessionId();
33856
- if (id === "" || activeDispatchConfig() === null)
33857
- return;
33858
- awarenessGeneration++;
33859
- availabilityWarningPeriod = undefined;
33860
- const baseline = !agentRunning && !refreshBaseline && candidateBaseline !== undefined && candidateBaseline.sessionID === id ? candidateBaseline.asOf : null;
33861
- const previousPeriod = askAwareness.session_id === id ? askAwareness.period : 0;
33862
- persistAskAwareness({
33863
- session_id: id,
33864
- period: previousPeriod + 1,
33865
- baseline_as_of: baseline,
33866
- checked: false,
33867
- fired: false,
33868
- saw_ask: false
33869
- });
33870
- if (agentRunning || refreshBaseline) {
33871
- await refreshReminderBaseline(id, askAwareness.period, context);
33872
- }
33873
- };
33874
- const cancelPendingAskReminder = () => {
33875
- awarenessGeneration++;
33876
- };
33877
- const installTerminalInputCancellation = (context) => {
33878
- terminalInputUnsubscribe?.();
33879
- terminalInputUnsubscribe = context.ui.onTerminalInput(cancelPendingAskReminder);
33779
+ const snapshot = await new DispatchClient(config.url, config.token, fetch, AbortSignal.timeout(OPEN_ASKS_TIMEOUT_MS)).openAsks(requestedSessionID);
33780
+ availabilityWarningSessionIDs.delete(requestedSessionID);
33781
+ return snapshot;
33880
33782
  };
33881
33783
  const restoreLocalSessionState = (context) => {
33882
33784
  sessionDirectory = context.cwd;
33883
33785
  sessionID = context.sessionManager.getSessionId();
33884
33786
  activeSessionContext = context;
33885
- candidateBaseline = undefined;
33886
- const branch = context.sessionManager.getBranch?.() ?? [];
33887
- askAwareness = transcriptAskAwareness(branch, sessionID) ?? emptyAskAwareness(sessionID);
33888
- const persistedLegionSession = transcriptMarksLegionSession(branch, sessionID);
33889
- const managed = persistedLegionSession || legionRoleClaimBridge().managedSessions.has(sessionID);
33890
- legionManagedSessionID = managed ? sessionID : "";
33891
33787
  };
33892
33788
  pi.on("resources_discover", async () => ({ skillPaths: [SKILLS_DIRECTORY] }));
33893
33789
  registerEnvoyMessageRenderer(pi);
@@ -33928,13 +33824,6 @@ function envoyExtension(pi) {
33928
33824
  const dedupeKey = rendered.envelope?.dedupe_key;
33929
33825
  const duplicate = dedupeKey !== undefined && dedupeKeys.has(dedupeKey);
33930
33826
  if (!duplicate && !rendered.skip) {
33931
- if (rendered.dispatchActor?.kind === "user" && activeSessionContext !== undefined) {
33932
- try {
33933
- await armAskReminder(activeSessionContext, true);
33934
- } catch (error) {
33935
- warnAskAvailability(activeSessionContext, error);
33936
- }
33937
- }
33938
33827
  const envelope = rendered.envelope;
33939
33828
  if (envelope !== undefined) {
33940
33829
  inbox.unshift({
@@ -34254,17 +34143,6 @@ function envoyExtension(pi) {
34254
34143
  return run;
34255
34144
  };
34256
34145
  const bridge = legionRoleClaimBridge();
34257
- bridge.markManagedSession = (targetSessionID, context) => {
34258
- bridge.managedSessions.add(targetSessionID);
34259
- if (targetSessionID !== sessionID)
34260
- return;
34261
- if (legionManagedSessionID === targetSessionID)
34262
- return;
34263
- legionManagedSessionID = targetSessionID;
34264
- if (context !== undefined || activeSessionContext !== undefined) {
34265
- pi.appendEntry(LEGION_MANAGED_ENTRY, { session_id: targetSessionID });
34266
- }
34267
- };
34268
34146
  const claim = async (targetSessionID, role, callerContext) => {
34269
34147
  const context = callerContext ?? activeSessionContext;
34270
34148
  if (context === undefined || context.sessionManager.getSessionId() !== targetSessionID) {
@@ -34278,7 +34156,6 @@ function envoyExtension(pi) {
34278
34156
  bridge.claim = claim;
34279
34157
  bridge.ready.resolve(claim);
34280
34158
  pi.on("session_start", async (_event, context) => {
34281
- installTerminalInputCancellation(context);
34282
34159
  const previousSessionID = sessionID;
34283
34160
  restoreLocalSessionState(context);
34284
34161
  if (dispatchConfig.error !== null) {
@@ -34314,7 +34191,6 @@ function envoyExtension(pi) {
34314
34191
  }
34315
34192
  });
34316
34193
  const rebind = async (reason, context) => {
34317
- installTerminalInputCancellation(context);
34318
34194
  const previousID = sessionID;
34319
34195
  restoreLocalSessionState(context);
34320
34196
  if (defaults.natsUrls.length === 0)
@@ -34364,8 +34240,6 @@ function envoyExtension(pi) {
34364
34240
  ]);
34365
34241
  } finally {
34366
34242
  clearTimeout(timer);
34367
- terminalInputUnsubscribe?.();
34368
- terminalInputUnsubscribe = undefined;
34369
34243
  connection = undefined;
34370
34244
  activeSessionContext = undefined;
34371
34245
  subscriptions.clear();
@@ -34419,7 +34293,6 @@ function envoyExtension(pi) {
34419
34293
  const snapshot = await queryOpenAsks(id);
34420
34294
  if (snapshot === null)
34421
34295
  return;
34422
- setReminderBaseline(id, askAwareness.session_id === id ? askAwareness.period : 0, snapshot.as_of);
34423
34296
  return {
34424
34297
  message: {
34425
34298
  customType: "dispatch-open-asks",
@@ -34441,60 +34314,7 @@ ${formatOpenAsksSummary(snapshot, currentDispatchConfig().url)}`,
34441
34314
  };
34442
34315
  }
34443
34316
  });
34444
- pi.on("agent_start", async () => {
34445
- agentRunning = true;
34446
- agentWillContinue = false;
34447
- });
34448
- pi.on("agent_end", async (event) => {
34449
- agentRunning = false;
34450
- agentWillContinue = event.willContinue === true;
34451
- });
34452
- pi.on("input", async () => {
34453
- cancelPendingAskReminder();
34454
- });
34455
- pi.on("message_start", async (event, context) => {
34456
- if (!isHumanTurnInitiator(event.message))
34457
- return;
34458
- try {
34459
- await armAskReminder(context);
34460
- } catch (error) {
34461
- warnAskAvailability(context, error);
34462
- }
34463
- });
34464
- pi.on("session_stop", async (event, context) => {
34465
- if (event.stop_hook_active || event.signal.aborted || shuttingDown || agentWillContinue || event.last_assistant_message?.role !== "assistant" || event.session_id !== sessionID || legionManagedSessionID === event.session_id || askAwareness.session_id !== event.session_id || askAwareness.period === 0 || askAwareness.checked || askAwareness.fired) {
34466
- return;
34467
- }
34468
- const baseline = askAwareness.baseline_as_of;
34469
- if (baseline === null)
34470
- return;
34471
- if (context.ui.getEditorText().trim() !== "")
34472
- return;
34473
- const claimed = { ...askAwareness, checked: true };
34474
- const generation = awarenessGeneration;
34475
- persistAskAwareness(claimed);
34476
- let snapshot;
34477
- try {
34478
- snapshot = await queryOpenAsks(event.session_id, baseline, event.signal);
34479
- } catch (error) {
34480
- if (!event.signal.aborted && generation === awarenessGeneration) {
34481
- warnAskAvailability(context, error);
34482
- }
34483
- return;
34484
- }
34485
- if (snapshot === null || event.signal.aborted || generation !== awarenessGeneration || shuttingDown || agentWillContinue || context.sessionManager.getSessionId() !== event.session_id || sessionID !== event.session_id || legionManagedSessionID === event.session_id || context.ui.getEditorText().trim() !== "" || askAwareness.session_id !== claimed.session_id || askAwareness.period !== claimed.period || !askAwareness.checked) {
34486
- return;
34487
- }
34488
- if (snapshot.count > 0 || snapshot.opened_since || askAwareness.saw_ask) {
34489
- return;
34490
- }
34491
- persistAskAwareness({ ...askAwareness, fired: true });
34492
- return { continue: true, additionalContext: OPEN_ASKS_REMINDER };
34493
- });
34494
34317
  pi.on("tool_result", async (event) => {
34495
- if (!event.isError && event.toolName === "dispatch_ask" && typeof event.details === "object" && event.details !== null && "ask" in event.details && typeof event.details.ask === "string" && askAwareness.session_id === sessionID && askAwareness.period > 0 && !askAwareness.saw_ask) {
34496
- persistAskAwareness({ ...askAwareness, saw_ask: true });
34497
- }
34498
34318
  if (event.isError)
34499
34319
  return;
34500
34320
  const topic = dispatchSubscriptionTopic(event.details);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sjawhar/pi-legion-envoy",
3
- "version": "1.27.1",
3
+ "version": "1.27.2",
4
4
  "type": "module",
5
5
  "omp": {
6
6
  "extensions": [