@sjawhar/pi-envoy 8.17.0 → 9.0.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/dispatch.js +4 -1
- package/dist/envoy.js +321 -32
- package/dist/skills/dispatch/references/messages.md +10 -1
- package/package.json +1 -1
package/dist/dispatch.js
CHANGED
|
@@ -13801,7 +13801,10 @@ var AGENT_STREAM_LIMITS = {
|
|
|
13801
13801
|
modelChars: 200,
|
|
13802
13802
|
historyMessages: 200,
|
|
13803
13803
|
historyBytes: 512 * 1024,
|
|
13804
|
-
snapshotIntervalMs: 100
|
|
13804
|
+
snapshotIntervalMs: 100,
|
|
13805
|
+
commands: 500,
|
|
13806
|
+
commandNameChars: 100,
|
|
13807
|
+
commandDescriptionChars: 200
|
|
13805
13808
|
};
|
|
13806
13809
|
// ../contracts/src/claim-holds.ts
|
|
13807
13810
|
function claimHolds(claim, titles) {
|
package/dist/envoy.js
CHANGED
|
@@ -29863,16 +29863,23 @@ function agentStreamControlSubject(sessionID) {
|
|
|
29863
29863
|
return `${AGENT_STREAM_SUBJECT_PREFIX}${sessionID}.control`;
|
|
29864
29864
|
}
|
|
29865
29865
|
var AGENT_STREAM_WATCH_TTL_MS = 30000;
|
|
29866
|
+
var AGENT_STREAM_WATCH_INTERVAL_MS = 1e4;
|
|
29866
29867
|
var AGENT_STREAM_LIMITS = {
|
|
29867
29868
|
partChars: 16000,
|
|
29868
29869
|
toolChars: 8000,
|
|
29869
29870
|
modelChars: 200,
|
|
29870
29871
|
historyMessages: 200,
|
|
29871
29872
|
historyBytes: 512 * 1024,
|
|
29872
|
-
snapshotIntervalMs: 100
|
|
29873
|
+
snapshotIntervalMs: 100,
|
|
29874
|
+
commands: 500,
|
|
29875
|
+
commandNameChars: 100,
|
|
29876
|
+
commandDescriptionChars: 200
|
|
29873
29877
|
};
|
|
29874
29878
|
var AGENT_STREAM_TRUNCATION_SUFFIX = `
|
|
29875
29879
|
\u2026 truncated`;
|
|
29880
|
+
function isTypableCommandName(name) {
|
|
29881
|
+
return /^[^\s\p{Cc}]+$/u.test(name);
|
|
29882
|
+
}
|
|
29876
29883
|
function capAgentStreamText(text, limit) {
|
|
29877
29884
|
if (text.length <= limit)
|
|
29878
29885
|
return text;
|
|
@@ -33505,7 +33512,7 @@ function sourceSession(sessionID) {
|
|
|
33505
33512
|
|
|
33506
33513
|
// ../pi-shared/src/interface.ts
|
|
33507
33514
|
import { logger } from "@oh-my-pi/pi-utils";
|
|
33508
|
-
var ENVOY_PLUGIN_INTERFACE_VERSION =
|
|
33515
|
+
var ENVOY_PLUGIN_INTERFACE_VERSION = 2;
|
|
33509
33516
|
var ENVOY_PLUGIN_INTERFACE_KEY = Symbol.for("legion.pi-shared.envoy-plugin-interface");
|
|
33510
33517
|
var LEGION_PLUGIN_LOADED_KEY = Symbol.for("legion.pi-legion.loaded");
|
|
33511
33518
|
var LEGACY_LEGION_LOADED_KEY = Symbol.for("legion.pi-envoy.legion-loaded");
|
|
@@ -33562,35 +33569,68 @@ function userMessageText(content) {
|
|
|
33562
33569
|
return pieces.join(`
|
|
33563
33570
|
`);
|
|
33564
33571
|
}
|
|
33565
|
-
function
|
|
33572
|
+
function sessionTurns(sessionID) {
|
|
33566
33573
|
const turns = envoyPluginInterface().injectedUserTurns;
|
|
33567
33574
|
const session = turns.get(sessionID) ?? { found: new Map, sent: [] };
|
|
33568
|
-
session.sent.push({ body, messageId });
|
|
33569
33575
|
turns.set(sessionID, session);
|
|
33576
|
+
return session;
|
|
33577
|
+
}
|
|
33578
|
+
function noteInjectedUserTurn(sessionID, body, messageId) {
|
|
33579
|
+
sessionTurns(sessionID).sent.push({ body, messageId, via: "text" });
|
|
33580
|
+
}
|
|
33581
|
+
function noteTypedUserTurn(sessionID, messageId) {
|
|
33582
|
+
sessionTurns(sessionID).sent.push({ messageId, via: "typed" });
|
|
33583
|
+
}
|
|
33584
|
+
function dropInjectedUserTurn(sessionID, messageId) {
|
|
33585
|
+
const session = envoyPluginInterface().injectedUserTurns.get(sessionID);
|
|
33586
|
+
if (session === undefined)
|
|
33587
|
+
return;
|
|
33588
|
+
const index = session.sent.findIndex((sent) => sent.messageId === messageId);
|
|
33589
|
+
if (index !== -1)
|
|
33590
|
+
session.sent.splice(index, 1);
|
|
33570
33591
|
}
|
|
33571
33592
|
function matchInjectedUserTurn(sessionID, message) {
|
|
33572
33593
|
if (typeof message !== "object" || message === null)
|
|
33573
33594
|
return;
|
|
33574
|
-
if (!("role" in message) || message.role !== "user")
|
|
33595
|
+
if (!("role" in message) || message.role !== "user" && message.role !== "custom") {
|
|
33575
33596
|
return;
|
|
33597
|
+
}
|
|
33576
33598
|
if (!("timestamp" in message) || typeof message.timestamp !== "number")
|
|
33577
33599
|
return;
|
|
33578
33600
|
const session = envoyPluginInterface().injectedUserTurns.get(sessionID);
|
|
33579
33601
|
if (session === undefined)
|
|
33580
33602
|
return;
|
|
33581
|
-
const
|
|
33603
|
+
const tag = messageTag(message);
|
|
33604
|
+
const memo = tag === undefined ? `${message.timestamp}` : `${message.timestamp}:${tag}`;
|
|
33605
|
+
const known = session.found.get(memo);
|
|
33582
33606
|
if (known !== undefined)
|
|
33583
33607
|
return known;
|
|
33584
|
-
|
|
33585
|
-
|
|
33608
|
+
let index = tag === undefined ? -1 : session.sent.findIndex((sent2) => sent2.messageId === tag);
|
|
33609
|
+
if (index === -1 && message.role === "user") {
|
|
33610
|
+
const text = userMessageText("content" in message ? message.content : undefined);
|
|
33611
|
+
index = session.sent.findIndex((sent2) => sent2.via === "text" && sent2.body === text);
|
|
33612
|
+
}
|
|
33586
33613
|
const sent = session.sent[index];
|
|
33587
33614
|
if (sent === undefined)
|
|
33588
33615
|
return;
|
|
33589
33616
|
session.sent.splice(index, 1);
|
|
33590
|
-
session.found.set(
|
|
33617
|
+
session.found.set(memo, sent.messageId);
|
|
33591
33618
|
return sent.messageId;
|
|
33592
33619
|
}
|
|
33620
|
+
function messageTag(message) {
|
|
33621
|
+
if (typeof message !== "object" || message === null || !("tag" in message))
|
|
33622
|
+
return;
|
|
33623
|
+
return typeof message.tag === "string" ? message.tag : undefined;
|
|
33624
|
+
}
|
|
33593
33625
|
function endInjectedUserTurns(sessionID) {
|
|
33626
|
+
const turns = envoyPluginInterface().injectedUserTurns;
|
|
33627
|
+
const typed = turns.get(sessionID)?.sent.filter((sent) => sent.via === "typed") ?? [];
|
|
33628
|
+
if (typed.length === 0)
|
|
33629
|
+
turns.delete(sessionID);
|
|
33630
|
+
else
|
|
33631
|
+
turns.set(sessionID, { found: new Map, sent: typed });
|
|
33632
|
+
}
|
|
33633
|
+
function forgetInjectedUserTurns(sessionID) {
|
|
33594
33634
|
envoyPluginInterface().injectedUserTurns.delete(sessionID);
|
|
33595
33635
|
}
|
|
33596
33636
|
|
|
@@ -33724,11 +33764,16 @@ function toolFailure(error48) {
|
|
|
33724
33764
|
}
|
|
33725
33765
|
|
|
33726
33766
|
// extensions/envoy.ts
|
|
33727
|
-
import
|
|
33767
|
+
import * as host2 from "@oh-my-pi/pi-coding-agent";
|
|
33768
|
+
import { logger as logger3, procmgr, VERSION } from "@oh-my-pi/pi-utils";
|
|
33728
33769
|
var import_nats2 = __toESM(require_mod4(), 1);
|
|
33729
33770
|
|
|
33730
33771
|
// src/agent-stream.ts
|
|
33731
33772
|
var encoder = new TextEncoder;
|
|
33773
|
+
var REPLAY_ARRAY_BYTES = 2;
|
|
33774
|
+
function jsonBytes(value) {
|
|
33775
|
+
return encoder.encode(JSON.stringify(value)).length;
|
|
33776
|
+
}
|
|
33732
33777
|
function readModel(message) {
|
|
33733
33778
|
if (typeof message.model !== "string" || message.model === "")
|
|
33734
33779
|
return;
|
|
@@ -33749,16 +33794,19 @@ function readMessage(raw) {
|
|
|
33749
33794
|
role: message.role,
|
|
33750
33795
|
timestamp: message.timestamp,
|
|
33751
33796
|
toolCallId: typeof message.toolCallId === "string" ? message.toolCallId : undefined,
|
|
33752
|
-
toolName: typeof message.toolName === "string" ? message.toolName : undefined
|
|
33797
|
+
toolName: typeof message.toolName === "string" ? message.toolName : undefined,
|
|
33798
|
+
tag: typeof message.tag === "string" ? message.tag : undefined
|
|
33753
33799
|
};
|
|
33754
33800
|
}
|
|
33755
33801
|
function keyFor(message) {
|
|
33756
33802
|
if (message.role === "toolResult") {
|
|
33757
33803
|
return message.toolCallId === undefined ? null : `t:${message.toolCallId}`;
|
|
33758
33804
|
}
|
|
33759
|
-
if (message.role
|
|
33805
|
+
if (message.role === "assistant")
|
|
33806
|
+
return `m:a${message.timestamp}`;
|
|
33807
|
+
if (message.role !== "user")
|
|
33760
33808
|
return null;
|
|
33761
|
-
return
|
|
33809
|
+
return message.tag === undefined ? `m:u${message.timestamp}` : `m:u${message.timestamp}:${message.tag}`;
|
|
33762
33810
|
}
|
|
33763
33811
|
function contentText(content) {
|
|
33764
33812
|
if (typeof content === "string")
|
|
@@ -33858,6 +33906,8 @@ class AgentStreamPublisher {
|
|
|
33858
33906
|
#publishedAt = new Map;
|
|
33859
33907
|
#seq = 0;
|
|
33860
33908
|
#watchedUntil = 0;
|
|
33909
|
+
#commands;
|
|
33910
|
+
#commandsSeq = 0;
|
|
33861
33911
|
constructor(deps) {
|
|
33862
33912
|
this.#deps = deps;
|
|
33863
33913
|
}
|
|
@@ -33872,6 +33922,46 @@ class AgentStreamPublisher {
|
|
|
33872
33922
|
this.#settled.clear();
|
|
33873
33923
|
this.#publishedAt.clear();
|
|
33874
33924
|
this.#watchedUntil = 0;
|
|
33925
|
+
this.#commands = undefined;
|
|
33926
|
+
}
|
|
33927
|
+
setCommands(subject2, commands) {
|
|
33928
|
+
const limit = AGENT_STREAM_LIMITS.historyBytes - REPLAY_ARRAY_BYTES;
|
|
33929
|
+
let bytes = jsonBytes(this.#commandsFrame([], this.#seq + 1));
|
|
33930
|
+
const seen = new Set;
|
|
33931
|
+
const kept = [];
|
|
33932
|
+
for (const command of commands) {
|
|
33933
|
+
if (kept.length >= AGENT_STREAM_LIMITS.commands)
|
|
33934
|
+
break;
|
|
33935
|
+
const name = command.name;
|
|
33936
|
+
if (name.length > AGENT_STREAM_LIMITS.commandNameChars)
|
|
33937
|
+
continue;
|
|
33938
|
+
if (!isTypableCommandName(name) || seen.has(name))
|
|
33939
|
+
continue;
|
|
33940
|
+
const listed = {
|
|
33941
|
+
name,
|
|
33942
|
+
source: command.source,
|
|
33943
|
+
...command.description !== undefined && {
|
|
33944
|
+
description: capAgentStreamText(command.description, AGENT_STREAM_LIMITS.commandDescriptionChars)
|
|
33945
|
+
},
|
|
33946
|
+
...command.terminalOnly === true && { terminalOnly: true }
|
|
33947
|
+
};
|
|
33948
|
+
const cost = jsonBytes(listed) + (kept.length === 0 ? 0 : 1);
|
|
33949
|
+
if (bytes + cost > limit)
|
|
33950
|
+
break;
|
|
33951
|
+
bytes += cost;
|
|
33952
|
+
seen.add(name);
|
|
33953
|
+
kept.push(listed);
|
|
33954
|
+
}
|
|
33955
|
+
if (JSON.stringify(kept) === JSON.stringify(this.#commands))
|
|
33956
|
+
return;
|
|
33957
|
+
this.#commands = kept;
|
|
33958
|
+
this.#seq += 1;
|
|
33959
|
+
this.#commandsSeq = this.#seq;
|
|
33960
|
+
if (this.watched)
|
|
33961
|
+
this.#deps.publish(subject2, JSON.stringify(this.#commandsFrame(kept)));
|
|
33962
|
+
}
|
|
33963
|
+
#commandsFrame(commands, seq = this.#commandsSeq) {
|
|
33964
|
+
return { commands, kind: "commands", seq, v: AGENT_STREAM_PROTOCOL };
|
|
33875
33965
|
}
|
|
33876
33966
|
record(subject2, raw, streaming, dispatchMessageId) {
|
|
33877
33967
|
const message = readMessage(raw);
|
|
@@ -33918,18 +34008,23 @@ class AgentStreamPublisher {
|
|
|
33918
34008
|
if (!this.watched)
|
|
33919
34009
|
return { frames: [], session_id: sessionID, v: AGENT_STREAM_PROTOCOL };
|
|
33920
34010
|
const ordered = [...this.#history.values()].sort((left, right) => left.at === right.at ? left.seq - right.seq : left.at - right.at);
|
|
33921
|
-
|
|
34011
|
+
const commands = this.#commands === undefined ? undefined : this.#commandsFrame(this.#commands);
|
|
34012
|
+
let used = REPLAY_ARRAY_BYTES + (commands === undefined ? 0 : jsonBytes(commands));
|
|
33922
34013
|
const kept = [];
|
|
33923
34014
|
for (let index = ordered.length - 1;index >= 0; index -= 1) {
|
|
33924
34015
|
const entry = ordered[index];
|
|
33925
34016
|
if (entry === undefined)
|
|
33926
34017
|
continue;
|
|
33927
34018
|
const frame = frameFor(entry);
|
|
33928
|
-
|
|
33929
|
-
|
|
34019
|
+
const separated = kept.length > 0 || commands !== undefined;
|
|
34020
|
+
const cost = jsonBytes(frame) + (separated ? 1 : 0);
|
|
34021
|
+
if (used + cost > AGENT_STREAM_LIMITS.historyBytes)
|
|
33930
34022
|
break;
|
|
34023
|
+
used += cost;
|
|
33931
34024
|
kept.unshift(frame);
|
|
33932
34025
|
}
|
|
34026
|
+
if (commands !== undefined)
|
|
34027
|
+
kept.push(commands);
|
|
33933
34028
|
return { frames: kept, session_id: sessionID, v: AGENT_STREAM_PROTOCOL };
|
|
33934
34029
|
}
|
|
33935
34030
|
}
|
|
@@ -34074,6 +34169,26 @@ function handledAttempts(entries) {
|
|
|
34074
34169
|
}
|
|
34075
34170
|
return handled;
|
|
34076
34171
|
}
|
|
34172
|
+
function typedInputReply(text, outcome) {
|
|
34173
|
+
const typed = /^\/\S+/.exec(text)?.[0];
|
|
34174
|
+
const command = typed ?? "This message";
|
|
34175
|
+
let reply;
|
|
34176
|
+
if ("error" in outcome)
|
|
34177
|
+
reply = `${command} was not run: ${outcome.error}`;
|
|
34178
|
+
else if ("hostVersion" in outcome) {
|
|
34179
|
+
if (typed !== undefined) {
|
|
34180
|
+
reply = `${typed} was not run: this session's Oh My Pi ${outcome.hostVersion} cannot run commands sent from Dispatch; nothing was sent.`;
|
|
34181
|
+
}
|
|
34182
|
+
} else if (outcome.handled === "terminal-only") {
|
|
34183
|
+
reply = `${command} runs only in the session's own terminal; nothing was sent.`;
|
|
34184
|
+
} else if (outcome.handled === "unavailable") {
|
|
34185
|
+
reply = `${command} was not run: this session's host cannot run typed input from Dispatch; nothing was sent.`;
|
|
34186
|
+
} else if (outcome.output?.trim())
|
|
34187
|
+
reply = outcome.output;
|
|
34188
|
+
if (reply === undefined || reply.length <= DISPATCH_BODY_MAX)
|
|
34189
|
+
return reply;
|
|
34190
|
+
return `${reply.slice(0, DISPATCH_BODY_MAX - 1)}\u2026`;
|
|
34191
|
+
}
|
|
34077
34192
|
|
|
34078
34193
|
// src/envoy-session.ts
|
|
34079
34194
|
import path3 from "path";
|
|
@@ -34161,6 +34276,53 @@ function sideTurn(context) {
|
|
|
34161
34276
|
return ({ prompt, signal }) => runEphemeralTurn({ promptText: btwPrompt(prompt), signal });
|
|
34162
34277
|
}
|
|
34163
34278
|
|
|
34279
|
+
// src/typed-pictures.ts
|
|
34280
|
+
class TypedPictures {
|
|
34281
|
+
#admissions = new Map;
|
|
34282
|
+
admit(messageId, turn) {
|
|
34283
|
+
this.#admissions.set(messageId, {
|
|
34284
|
+
addresses: turn.addresses,
|
|
34285
|
+
body: turn.body,
|
|
34286
|
+
images: turn.images,
|
|
34287
|
+
sessionID: turn.sessionID
|
|
34288
|
+
});
|
|
34289
|
+
}
|
|
34290
|
+
handOver(text) {
|
|
34291
|
+
for (const admission of this.#admissions.values()) {
|
|
34292
|
+
if (admission.images === undefined || admission.body !== text)
|
|
34293
|
+
continue;
|
|
34294
|
+
const { images } = admission;
|
|
34295
|
+
admission.images = undefined;
|
|
34296
|
+
return images;
|
|
34297
|
+
}
|
|
34298
|
+
return;
|
|
34299
|
+
}
|
|
34300
|
+
withdraw(messageId) {
|
|
34301
|
+
this.#admissions.delete(messageId);
|
|
34302
|
+
}
|
|
34303
|
+
inputPassed(messageId) {
|
|
34304
|
+
if (this.#admissions.get(messageId)?.images !== undefined)
|
|
34305
|
+
this.#admissions.delete(messageId);
|
|
34306
|
+
}
|
|
34307
|
+
started(message) {
|
|
34308
|
+
const tag = messageTag(message);
|
|
34309
|
+
if (tag === undefined)
|
|
34310
|
+
return;
|
|
34311
|
+
const admission = this.#admissions.get(tag);
|
|
34312
|
+
if (admission === undefined)
|
|
34313
|
+
return;
|
|
34314
|
+
this.#admissions.delete(tag);
|
|
34315
|
+
return admission.images === undefined ? admission.addresses : undefined;
|
|
34316
|
+
}
|
|
34317
|
+
dropOtherSessions(sessionID) {
|
|
34318
|
+
for (const [messageId, admission] of this.#admissions) {
|
|
34319
|
+
if (admission.sessionID !== sessionID && admission.images === undefined) {
|
|
34320
|
+
this.#admissions.delete(messageId);
|
|
34321
|
+
}
|
|
34322
|
+
}
|
|
34323
|
+
}
|
|
34324
|
+
}
|
|
34325
|
+
|
|
34164
34326
|
// extensions/envoy-message-renderer.ts
|
|
34165
34327
|
import { Box, Spacer, Text } from "@oh-my-pi/pi-tui";
|
|
34166
34328
|
var ENVOY_MESSAGE_COLLAPSED_LINES = 12;
|
|
@@ -34216,6 +34378,12 @@ async function copySessionID(copyToClipboard2, sessionID) {
|
|
|
34216
34378
|
}
|
|
34217
34379
|
|
|
34218
34380
|
// extensions/envoy.ts
|
|
34381
|
+
var hostBuiltinCommands;
|
|
34382
|
+
import("@oh-my-pi/pi-coding-agent/extensibility/extensions/send-user-input-handler").then((module) => {
|
|
34383
|
+
hostBuiltinCommands = module.listUserInputBuiltinCommands;
|
|
34384
|
+
}, () => {
|
|
34385
|
+
return;
|
|
34386
|
+
});
|
|
34219
34387
|
var codec2 = import_nats2.StringCodec();
|
|
34220
34388
|
var NATS_RETRY_INTERVAL_MS = 15000;
|
|
34221
34389
|
var DELIVERY_PICTURES_TIMEOUT_MS = 20000;
|
|
@@ -34264,16 +34432,16 @@ var ASK_SELF_CHECK_PROMPT = (asks) => {
|
|
|
34264
34432
|
...asks.length > askLimit ? [`+${asks.length - askLimit} more`] : []
|
|
34265
34433
|
].join(`
|
|
34266
34434
|
`);
|
|
34267
|
-
return
|
|
34435
|
+
return `Your run has just ended. ${promptAsks}
|
|
34268
34436
|
|
|
34269
|
-
|
|
34437
|
+
Are you right now waiting on a human for anything none of those asks covers? Answer with exactly one word and nothing else: WAITING or PROCEEDING. ` + "WAITING \u2014 you stopped because you need a decision, an approval, or information from a human. " + "PROCEEDING \u2014 you finished, you will carry on by yourself, or you are waiting only on tools, " + "subagents, or events.";
|
|
34270
34438
|
};
|
|
34271
34439
|
var WAITING_VERDICT = /^\W*WAITING\b/;
|
|
34272
34440
|
var ECHOED_CHOICE = /\bPROCEEDING\b/;
|
|
34273
34441
|
var isWaitingVerdict = (reply) => WAITING_VERDICT.test(reply) && !ECHOED_CHOICE.test(reply);
|
|
34274
34442
|
var ASK_SELF_CHECK_TIMEOUT_MS = 60000;
|
|
34275
34443
|
var ASK_CHECKS_PER_PERIOD = 5;
|
|
34276
|
-
var UNASKED_WAIT_REMINDER = "You just said you are waiting on a human for something no open ask in Dispatch covers.
|
|
34444
|
+
var UNASKED_WAIT_REMINDER = "You just said you are waiting on a human for something no open ask in Dispatch covers. Open it now: a decision block in the document it concerns (dispatch doc-edit with an ask block), or dispatch ask for a to-do only a human can do, naming exactly what you need and from whom.";
|
|
34277
34445
|
function isLegionManagedEntry(entry) {
|
|
34278
34446
|
if (typeof entry !== "object" || entry === null)
|
|
34279
34447
|
return false;
|
|
@@ -34367,6 +34535,7 @@ function envoyExtension(pi) {
|
|
|
34367
34535
|
let legionManagedTranscript = false;
|
|
34368
34536
|
const legionManaged = (id) => legionManagedTranscript || legionRoleClaimBridge().managedSessions.has(id);
|
|
34369
34537
|
let handledDispatchAttempts = new Set;
|
|
34538
|
+
const notedSessionIDs = new Set;
|
|
34370
34539
|
const availabilityWarningSessionIDs = new Set;
|
|
34371
34540
|
const warnAskAvailability = (context, error48) => {
|
|
34372
34541
|
const sessionID2 = context.sessionManager.getSessionId();
|
|
@@ -34431,8 +34600,12 @@ function envoyExtension(pi) {
|
|
|
34431
34600
|
legionManagedTranscript = branch.some(isLegionManagedEntry);
|
|
34432
34601
|
handledDispatchAttempts = handledAttempts(context.sessionManager.getEntries());
|
|
34433
34602
|
markPicturesShown(shownPictures(sessionID), transcriptPictures(branch));
|
|
34434
|
-
if (previousSessionID !== sessionID)
|
|
34603
|
+
if (previousSessionID !== sessionID) {
|
|
34435
34604
|
forgetShownPictures(previousSessionID);
|
|
34605
|
+
if (notedSessionIDs.delete(previousSessionID))
|
|
34606
|
+
forgetInjectedUserTurns(previousSessionID);
|
|
34607
|
+
typedPictures.dropOtherSessions(sessionID);
|
|
34608
|
+
}
|
|
34436
34609
|
};
|
|
34437
34610
|
let dispatchLedger;
|
|
34438
34611
|
const bindDispatchSession = (id) => {
|
|
@@ -34475,10 +34648,57 @@ function envoyExtension(pi) {
|
|
|
34475
34648
|
});
|
|
34476
34649
|
return connection;
|
|
34477
34650
|
};
|
|
34478
|
-
const postDispatchReply = async (delivery, result) => {
|
|
34479
|
-
await postDeliveryReply(currentDispatchConfig(),
|
|
34651
|
+
const postDispatchReply = async (delivery, result, replySessionID = sessionID) => {
|
|
34652
|
+
await postDeliveryReply(currentDispatchConfig(), replySessionID, delivery, result);
|
|
34480
34653
|
};
|
|
34481
|
-
const
|
|
34654
|
+
const typedPictures = new TypedPictures;
|
|
34655
|
+
pi.on("input", async (event) => {
|
|
34656
|
+
if (event.source !== "extension")
|
|
34657
|
+
return;
|
|
34658
|
+
const images = typedPictures.handOver(event.text);
|
|
34659
|
+
return images === undefined ? undefined : { images: [...event.images ?? [], ...images] };
|
|
34660
|
+
});
|
|
34661
|
+
const runTypedInput = (sendUserInput, delivery, replySessionID, turn, content, pictureAddresses2) => {
|
|
34662
|
+
const images = typeof content === "string" ? [] : content.filter((block) => block.type === "image");
|
|
34663
|
+
const noted = sessionID;
|
|
34664
|
+
if (images.length > 0) {
|
|
34665
|
+
typedPictures.admit(turn.messageId, {
|
|
34666
|
+
addresses: pictureAddresses2,
|
|
34667
|
+
body: turn.body,
|
|
34668
|
+
images,
|
|
34669
|
+
sessionID: noted
|
|
34670
|
+
});
|
|
34671
|
+
}
|
|
34672
|
+
noteTypedUserTurn(noted, turn.messageId);
|
|
34673
|
+
notedSessionIDs.add(noted);
|
|
34674
|
+
Promise.resolve().then(() => sendUserInput(turn.body, {
|
|
34675
|
+
...turn.mode === "aside" && { deliverAs: "aside" },
|
|
34676
|
+
tag: turn.messageId
|
|
34677
|
+
})).then((result) => {
|
|
34678
|
+
const submitted = result.handled === "prompt" || result.handled === "skill";
|
|
34679
|
+
if (submitted) {
|
|
34680
|
+
typedPictures.inputPassed(turn.messageId);
|
|
34681
|
+
} else {
|
|
34682
|
+
dropInjectedUserTurn(noted, turn.messageId);
|
|
34683
|
+
typedPictures.withdraw(turn.messageId);
|
|
34684
|
+
}
|
|
34685
|
+
return typedInputReply(turn.body, result);
|
|
34686
|
+
}, (error48) => {
|
|
34687
|
+
dropInjectedUserTurn(noted, turn.messageId);
|
|
34688
|
+
typedPictures.withdraw(turn.messageId);
|
|
34689
|
+
return typedInputReply(turn.body, { error: messageFor(error48) });
|
|
34690
|
+
}).then(async (reply) => {
|
|
34691
|
+
if (reply !== undefined)
|
|
34692
|
+
await postDispatchReply(delivery, { body: reply }, replySessionID);
|
|
34693
|
+
}).catch((error48) => {
|
|
34694
|
+
logger3.warn("envoy: the reply to a person's typed input did not reach Dispatch", {
|
|
34695
|
+
attempt: delivery.attempt,
|
|
34696
|
+
error: messageFor(error48),
|
|
34697
|
+
messageID: delivery.id
|
|
34698
|
+
});
|
|
34699
|
+
});
|
|
34700
|
+
};
|
|
34701
|
+
const acceptedUserTurn = async (rendered, acceptedSessionID) => {
|
|
34482
34702
|
if (!isUserTurnCandidate(rendered))
|
|
34483
34703
|
return;
|
|
34484
34704
|
const { delivery } = rendered;
|
|
@@ -34494,13 +34714,14 @@ function envoyExtension(pi) {
|
|
|
34494
34714
|
if (dispatch === undefined)
|
|
34495
34715
|
return;
|
|
34496
34716
|
const accepted = await dispatch.acceptMessageDelivery(delivery.id, delivery.attempt, {
|
|
34497
|
-
actor: { id:
|
|
34717
|
+
actor: { id: acceptedSessionID, kind: "session" }
|
|
34498
34718
|
});
|
|
34499
34719
|
const turn = turnFromAccept(accepted);
|
|
34500
34720
|
if (turn === undefined) {
|
|
34501
34721
|
logger3.warn("envoy: Dispatch accepted a direct message without the stored body and mode to inject; it arrives as a card", { attempt: delivery.attempt, messageID: delivery.id });
|
|
34722
|
+
return;
|
|
34502
34723
|
}
|
|
34503
|
-
return turn;
|
|
34724
|
+
return { delivery, turn };
|
|
34504
34725
|
} catch (error48) {
|
|
34505
34726
|
logger3.warn("envoy: Dispatch did not accept a direct message as this session's turn; it arrives as a card", { attempt: delivery.attempt, messageID: delivery.id, error: messageFor(error48) });
|
|
34506
34727
|
return;
|
|
@@ -34562,20 +34783,34 @@ function envoyExtension(pi) {
|
|
|
34562
34783
|
}
|
|
34563
34784
|
}
|
|
34564
34785
|
} else {
|
|
34565
|
-
const
|
|
34786
|
+
const acceptedSessionID = sessionID;
|
|
34787
|
+
const accepted = await acceptedUserTurn(rendered, acceptedSessionID);
|
|
34566
34788
|
const shown = shownPictures(sessionID);
|
|
34567
|
-
if (
|
|
34789
|
+
if (accepted === undefined) {
|
|
34568
34790
|
const card = await withDeliveredPictures(rendered.content, rendered.pictures ?? [], () => timedDispatchClient(DELIVERY_PICTURES_TIMEOUT_MS), true, shown);
|
|
34569
34791
|
pi.sendMessage({ customType: "envoy-message", content: card.content, display: true }, {
|
|
34570
34792
|
deliverAs: rendered.delivery?.mode === "aside" ? "aside" : "steer",
|
|
34571
34793
|
triggerTurn: true
|
|
34572
34794
|
});
|
|
34573
|
-
markPicturesShown(
|
|
34795
|
+
markPicturesShown(shownPictures(sessionID), card.shown);
|
|
34574
34796
|
} else {
|
|
34797
|
+
const { turn } = accepted;
|
|
34575
34798
|
const delivery = await withDeliveredPictures(turn.body, pictureAddresses(turn.body), () => timedDispatchClient(DELIVERY_PICTURES_TIMEOUT_MS), false, shown);
|
|
34576
|
-
|
|
34577
|
-
|
|
34578
|
-
|
|
34799
|
+
const sendUserInput = pi.sendUserInput;
|
|
34800
|
+
if (sendUserInput === undefined) {
|
|
34801
|
+
const refusal = typedInputReply(turn.body, { hostVersion: VERSION });
|
|
34802
|
+
if (refusal !== undefined) {
|
|
34803
|
+
await postDispatchReply(accepted.delivery, { body: refusal }, acceptedSessionID);
|
|
34804
|
+
} else {
|
|
34805
|
+
const injectionSessionID = sessionID;
|
|
34806
|
+
noteInjectedUserTurn(injectionSessionID, turn.body, turn.messageId);
|
|
34807
|
+
notedSessionIDs.add(injectionSessionID);
|
|
34808
|
+
pi.sendUserMessage(delivery.content, turn.mode === "aside" ? { deliverAs: "aside" } : undefined);
|
|
34809
|
+
markPicturesShown(shownPictures(injectionSessionID), delivery.shown);
|
|
34810
|
+
}
|
|
34811
|
+
} else {
|
|
34812
|
+
runTypedInput(sendUserInput, accepted.delivery, acceptedSessionID, turn, delivery.content, delivery.shown);
|
|
34813
|
+
}
|
|
34579
34814
|
}
|
|
34580
34815
|
}
|
|
34581
34816
|
} catch (error48) {
|
|
@@ -34649,6 +34884,39 @@ function envoyExtension(pi) {
|
|
|
34649
34884
|
});
|
|
34650
34885
|
let agentStreamControl;
|
|
34651
34886
|
let agentStreamControlSession = "";
|
|
34887
|
+
let agentStreamCommandsRefreshedOnWatchAt = -Infinity;
|
|
34888
|
+
let agentStreamViewerAttached = false;
|
|
34889
|
+
const refreshAgentStreamCommands = (session) => {
|
|
34890
|
+
if (typeof pi.sendUserInput !== "function")
|
|
34891
|
+
return;
|
|
34892
|
+
const builtins = hostBuiltinCommands?.() ?? [];
|
|
34893
|
+
const reserved = new Set(builtins.flatMap((builtin) => [builtin.name, ...builtin.aliases]));
|
|
34894
|
+
const live = host2.AgentRegistry?.global().list().find((ref) => ref.session?.sessionManager.getSessionId() === session)?.session;
|
|
34895
|
+
const commands = builtins.map((builtin) => ({
|
|
34896
|
+
description: builtin.description,
|
|
34897
|
+
name: builtin.name,
|
|
34898
|
+
source: "builtin",
|
|
34899
|
+
...builtin.terminalOnly && { terminalOnly: true }
|
|
34900
|
+
}));
|
|
34901
|
+
for (const command of [
|
|
34902
|
+
...pi.getCommands?.() ?? [],
|
|
34903
|
+
...[...live?.slashCommands ?? [], ...live?.promptTemplates ?? []].map((file2) => ({
|
|
34904
|
+
description: file2.description,
|
|
34905
|
+
name: file2.name,
|
|
34906
|
+
source: "prompt"
|
|
34907
|
+
}))
|
|
34908
|
+
]) {
|
|
34909
|
+
if (reserved.has(command.name))
|
|
34910
|
+
continue;
|
|
34911
|
+
const description = typeof command.description === "string" ? { description: command.description } : {};
|
|
34912
|
+
commands.push({
|
|
34913
|
+
name: command.name,
|
|
34914
|
+
source: command.source,
|
|
34915
|
+
...description
|
|
34916
|
+
});
|
|
34917
|
+
}
|
|
34918
|
+
agentStream.setCommands(agentStreamFramesSubject(session), commands);
|
|
34919
|
+
};
|
|
34652
34920
|
const pumpAgentStreamControl = async (subscription, session) => {
|
|
34653
34921
|
try {
|
|
34654
34922
|
for await (const message of subscription) {
|
|
@@ -34659,9 +34927,24 @@ function envoyExtension(pi) {
|
|
|
34659
34927
|
const request = parsed;
|
|
34660
34928
|
if (request.type !== "replay" && request.type !== "watch")
|
|
34661
34929
|
continue;
|
|
34930
|
+
const attached = agentStreamViewerAttached;
|
|
34931
|
+
if (request.type === "replay" || !attached || Date.now() - agentStreamCommandsRefreshedOnWatchAt >= AGENT_STREAM_WATCH_INTERVAL_MS) {
|
|
34932
|
+
try {
|
|
34933
|
+
refreshAgentStreamCommands(session);
|
|
34934
|
+
} catch (error48) {
|
|
34935
|
+
logger3.warn("envoy: could not refresh agent stream commands", {
|
|
34936
|
+
error: messageFor(error48),
|
|
34937
|
+
sessionID: session
|
|
34938
|
+
});
|
|
34939
|
+
}
|
|
34940
|
+
if (request.type === "watch" && attached) {
|
|
34941
|
+
agentStreamCommandsRefreshedOnWatchAt = Date.now();
|
|
34942
|
+
}
|
|
34943
|
+
}
|
|
34662
34944
|
const reply = message.reply;
|
|
34663
34945
|
const answer = request.type === "replay" && reply !== undefined ? JSON.stringify(agentStream.replay(session)) : undefined;
|
|
34664
34946
|
agentStream.noteViewer();
|
|
34947
|
+
agentStreamViewerAttached = true;
|
|
34665
34948
|
if (answer === undefined || reply === undefined)
|
|
34666
34949
|
continue;
|
|
34667
34950
|
connection?.publish(reply, codec2.encode(answer));
|
|
@@ -34690,8 +34973,11 @@ function envoyExtension(pi) {
|
|
|
34690
34973
|
return;
|
|
34691
34974
|
agentStreamControl?.unsubscribe();
|
|
34692
34975
|
agentStreamControl = undefined;
|
|
34693
|
-
if (agentStreamControlSession !== session)
|
|
34976
|
+
if (agentStreamControlSession !== session) {
|
|
34694
34977
|
agentStream.reset();
|
|
34978
|
+
agentStreamViewerAttached = false;
|
|
34979
|
+
agentStreamCommandsRefreshedOnWatchAt = -Infinity;
|
|
34980
|
+
}
|
|
34695
34981
|
agentStreamControlSession = session;
|
|
34696
34982
|
const active = await ensureConnection();
|
|
34697
34983
|
const subscription = active.subscribe(agentStreamControlSubject(session));
|
|
@@ -34704,6 +34990,9 @@ function envoyExtension(pi) {
|
|
|
34704
34990
|
agentStream.record(agentStreamFramesSubject(sessionID), message, streaming, matchInjectedUserTurn(sessionID, message));
|
|
34705
34991
|
};
|
|
34706
34992
|
pi.on("message_start", async (event) => {
|
|
34993
|
+
const shown = typedPictures.started(event.message);
|
|
34994
|
+
if (shown !== undefined)
|
|
34995
|
+
markPicturesShown(shownPictures(sessionID), shown);
|
|
34707
34996
|
recordAgentStreamMessage(event.message, true);
|
|
34708
34997
|
});
|
|
34709
34998
|
pi.on("message_update", async (event) => {
|
|
@@ -32,10 +32,19 @@ session with no issue (`POST /api/v1/agents/{session_id}/messages`) stays human-
|
|
|
32
32
|
A human can also message you directly from the **Agents** page, with no issue at all. On Oh My
|
|
33
33
|
Pi, a person's **Send** or **Aside** arrives as their own user message, exactly as if they had
|
|
34
34
|
typed it at your terminal: your Envoy plugin takes it only once Dispatch accepts it as a person's
|
|
35
|
-
own fresh message to you, and
|
|
35
|
+
own fresh message to you, and runs the text Dispatch stored. Answer it in the conversation as
|
|
36
36
|
you would anything typed, with no `dispatch message`; the Agents page shows your conversation
|
|
37
37
|
live, so they read your answer there.
|
|
38
38
|
|
|
39
|
+
A Send or Aside that starts with `/` runs as the command it names, as it would at your terminal:
|
|
40
|
+
a prompt template or command file arrives as the user message it expands to, a `/skill:<name>`
|
|
41
|
+
as that skill's prompt, and slash text that names no command as the words themselves; answer each
|
|
42
|
+
as you would typed. A command that runs without reaching you (`/session`, `/compact`), or that
|
|
43
|
+
did not run (one only your own terminal runs, such as `/new`), is answered on Dispatch by your
|
|
44
|
+
plugin, as your reply to that message: what the command printed, or why it did not run. You
|
|
45
|
+
receive nothing for it and write nothing for it. A command that prints and then submits a prompt
|
|
46
|
+
(`/force:<tool> <prompt>`) gets the same reply, and you answer only the prompt it submitted.
|
|
47
|
+
|
|
39
48
|
Everything else still arrives as a Dispatch frame: a **BTW**, a broadcast, a direct message on a
|
|
40
49
|
host that takes no user turn from its plugin (Claude Code), and one Dispatch did not accept.
|
|
41
50
|
That frame names no issue and its `reply_with` hint carries none either; answer it with the
|