@sjawhar/pi-legion-envoy 5.29.0 → 5.30.1
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 +149 -9
- package/dist/legion.js +102 -2
- package/dist/skills/dispatch/references/messages.md +13 -4
- package/package.json +1 -1
package/dist/envoy.js
CHANGED
|
@@ -29796,6 +29796,7 @@ var DispatchTargetedMessagePayloadSchema = MessageEventPayloadSchema.extend({
|
|
|
29796
29796
|
body: exports_external.string(),
|
|
29797
29797
|
target: exports_external.string(),
|
|
29798
29798
|
in_reply_to: exports_external.string().nullable(),
|
|
29799
|
+
broadcast_id: exports_external.string().nullish(),
|
|
29799
29800
|
deliveries: exports_external.array(exports_external.unknown()),
|
|
29800
29801
|
created_at: exports_external.string()
|
|
29801
29802
|
});
|
|
@@ -32030,7 +32031,8 @@ function renderInbound(raw, sessionID, subject2) {
|
|
|
32030
32031
|
replyPath: dispatchReplyPath("message", message2.data.id),
|
|
32031
32032
|
replyFields: {},
|
|
32032
32033
|
issueKey: frame.event.issue_key,
|
|
32033
|
-
body: message2.data.body
|
|
32034
|
+
body: message2.data.body,
|
|
32035
|
+
...typeof message2.data.broadcast_id === "string" ? { broadcastId: message2.data.broadcast_id } : {}
|
|
32034
32036
|
};
|
|
32035
32037
|
dispatchReply = {
|
|
32036
32038
|
tool: "dispatch_message",
|
|
@@ -32578,6 +32580,9 @@ class DispatchClient {
|
|
|
32578
32580
|
async getMessageThread(id, session) {
|
|
32579
32581
|
return this.#json("GET", ["api", "v1", "messages", id], undefined, { session });
|
|
32580
32582
|
}
|
|
32583
|
+
async acceptMessageDelivery(id, attempt, input) {
|
|
32584
|
+
return this.#json("POST", ["api", "v1", "messages", id, "deliveries", String(attempt), "accept"], input);
|
|
32585
|
+
}
|
|
32581
32586
|
async artifact(issue2, input) {
|
|
32582
32587
|
const artifactPath = ["api", "v1", "issues", await this.#resolveIssue(issue2), "artifacts"];
|
|
32583
32588
|
if ("content" in input)
|
|
@@ -35297,7 +35302,7 @@ function conversationParts(content) {
|
|
|
35297
35302
|
return parts;
|
|
35298
35303
|
}
|
|
35299
35304
|
function frameFor(entry) {
|
|
35300
|
-
const { message } = entry;
|
|
35305
|
+
const { dispatchMessageId, message } = entry;
|
|
35301
35306
|
if (message.role === "toolResult" && message.toolCallId !== undefined) {
|
|
35302
35307
|
return {
|
|
35303
35308
|
kind: "tool-result",
|
|
@@ -35319,7 +35324,8 @@ function frameFor(entry) {
|
|
|
35319
35324
|
id: entry.key.slice(2),
|
|
35320
35325
|
parts: conversationParts(message.host.content),
|
|
35321
35326
|
role: message.role === "assistant" ? "assistant" : "user",
|
|
35322
|
-
streaming: entry.streaming
|
|
35327
|
+
streaming: entry.streaming,
|
|
35328
|
+
...dispatchMessageId === undefined ? {} : { dispatchMessageId }
|
|
35323
35329
|
},
|
|
35324
35330
|
seq: entry.seq,
|
|
35325
35331
|
v: AGENT_STREAM_PROTOCOL
|
|
@@ -35348,7 +35354,7 @@ class AgentStreamPublisher {
|
|
|
35348
35354
|
this.#publishedAt.clear();
|
|
35349
35355
|
this.#watchedUntil = 0;
|
|
35350
35356
|
}
|
|
35351
|
-
record(subject2, raw, streaming) {
|
|
35357
|
+
record(subject2, raw, streaming, dispatchMessageId) {
|
|
35352
35358
|
const message = readMessage(raw);
|
|
35353
35359
|
if (message === null)
|
|
35354
35360
|
return false;
|
|
@@ -35361,6 +35367,7 @@ class AgentStreamPublisher {
|
|
|
35361
35367
|
this.#seq += 1;
|
|
35362
35368
|
const entry = {
|
|
35363
35369
|
at: message.timestamp,
|
|
35370
|
+
dispatchMessageId: dispatchMessageId ?? this.#history.get(key)?.dispatchMessageId,
|
|
35364
35371
|
key,
|
|
35365
35372
|
message,
|
|
35366
35373
|
seq: this.#seq,
|
|
@@ -35431,6 +35438,101 @@ function withDispatchFirst(messages, context) {
|
|
|
35431
35438
|
];
|
|
35432
35439
|
}
|
|
35433
35440
|
|
|
35441
|
+
// src/dispatch-user-turn.ts
|
|
35442
|
+
function isUserTurnCandidate(rendered) {
|
|
35443
|
+
const { delivery } = rendered;
|
|
35444
|
+
return rendered.envelope?.source === "dispatch" && delivery?.resource === "message" && delivery.issueKey === null && delivery.broadcastId === undefined && rendered.dispatchActor?.kind === "user" && (delivery.mode === "aside" || delivery.mode === "steer");
|
|
35445
|
+
}
|
|
35446
|
+
var AcceptedDeliverySchema = exports_external.object({
|
|
35447
|
+
message_id: exports_external.string(),
|
|
35448
|
+
body: exports_external.string(),
|
|
35449
|
+
delivery: exports_external.enum(["aside", "steer"])
|
|
35450
|
+
});
|
|
35451
|
+
function turnFromAccept(accepted) {
|
|
35452
|
+
const parsed = AcceptedDeliverySchema.safeParse(accepted);
|
|
35453
|
+
if (!parsed.success)
|
|
35454
|
+
return;
|
|
35455
|
+
return { body: parsed.data.body, messageId: parsed.data.message_id, mode: parsed.data.delivery };
|
|
35456
|
+
}
|
|
35457
|
+
var HANDLED_ATTEMPT_ENTRY = "envoy-dispatch-handled-attempt";
|
|
35458
|
+
function handledAttemptKey(messageId, attempt) {
|
|
35459
|
+
return `${messageId}#${attempt}`;
|
|
35460
|
+
}
|
|
35461
|
+
function handledAttempts(entries) {
|
|
35462
|
+
const handled = new Set;
|
|
35463
|
+
for (const entry of entries) {
|
|
35464
|
+
if (typeof entry !== "object" || entry === null)
|
|
35465
|
+
continue;
|
|
35466
|
+
if (!("type" in entry) || entry.type !== "custom")
|
|
35467
|
+
continue;
|
|
35468
|
+
if (!("customType" in entry) || entry.customType !== HANDLED_ATTEMPT_ENTRY)
|
|
35469
|
+
continue;
|
|
35470
|
+
if (!("data" in entry) || typeof entry.data !== "object" || entry.data === null)
|
|
35471
|
+
continue;
|
|
35472
|
+
const { data } = entry;
|
|
35473
|
+
if (!("message_id" in data) || typeof data.message_id !== "string")
|
|
35474
|
+
continue;
|
|
35475
|
+
if (!("attempt" in data) || typeof data.attempt !== "number")
|
|
35476
|
+
continue;
|
|
35477
|
+
handled.add(handledAttemptKey(data.message_id, data.attempt));
|
|
35478
|
+
}
|
|
35479
|
+
return handled;
|
|
35480
|
+
}
|
|
35481
|
+
function userMessageText(content) {
|
|
35482
|
+
if (typeof content === "string")
|
|
35483
|
+
return content;
|
|
35484
|
+
if (!Array.isArray(content))
|
|
35485
|
+
return "";
|
|
35486
|
+
const pieces = [];
|
|
35487
|
+
for (const part of content) {
|
|
35488
|
+
if (typeof part !== "object" || part === null)
|
|
35489
|
+
continue;
|
|
35490
|
+
if ("type" in part && part.type === "text" && "text" in part && typeof part.text === "string") {
|
|
35491
|
+
pieces.push(part.text);
|
|
35492
|
+
}
|
|
35493
|
+
}
|
|
35494
|
+
return pieces.join(`
|
|
35495
|
+
`);
|
|
35496
|
+
}
|
|
35497
|
+
var INJECTED_USER_TURNS = Symbol.for("legion.pi-envoy.injected-user-turns");
|
|
35498
|
+
function injectedUserTurns() {
|
|
35499
|
+
const store = globalThis;
|
|
35500
|
+
const turns = store[INJECTED_USER_TURNS] ?? new Map;
|
|
35501
|
+
store[INJECTED_USER_TURNS] = turns;
|
|
35502
|
+
return turns;
|
|
35503
|
+
}
|
|
35504
|
+
function noteInjectedUserTurn(sessionID, body, messageId) {
|
|
35505
|
+
const turns = injectedUserTurns();
|
|
35506
|
+
const session = turns.get(sessionID) ?? { found: new Map, sent: [] };
|
|
35507
|
+
session.sent.push({ body, messageId });
|
|
35508
|
+
turns.set(sessionID, session);
|
|
35509
|
+
}
|
|
35510
|
+
function matchInjectedUserTurn(sessionID, message) {
|
|
35511
|
+
if (typeof message !== "object" || message === null)
|
|
35512
|
+
return;
|
|
35513
|
+
if (!("role" in message) || message.role !== "user")
|
|
35514
|
+
return;
|
|
35515
|
+
if (!("timestamp" in message) || typeof message.timestamp !== "number")
|
|
35516
|
+
return;
|
|
35517
|
+
const session = injectedUserTurns().get(sessionID);
|
|
35518
|
+
if (session === undefined)
|
|
35519
|
+
return;
|
|
35520
|
+
const known = session.found.get(message.timestamp);
|
|
35521
|
+
if (known !== undefined)
|
|
35522
|
+
return known;
|
|
35523
|
+
const text = userMessageText("content" in message ? message.content : undefined);
|
|
35524
|
+
const index = session.sent.findIndex((sent2) => sent2.body === text);
|
|
35525
|
+
const sent = session.sent[index];
|
|
35526
|
+
if (sent === undefined)
|
|
35527
|
+
return;
|
|
35528
|
+
session.sent.splice(index, 1);
|
|
35529
|
+
session.found.set(message.timestamp, sent.messageId);
|
|
35530
|
+
return sent.messageId;
|
|
35531
|
+
}
|
|
35532
|
+
function endInjectedUserTurns(sessionID) {
|
|
35533
|
+
injectedUserTurns().delete(sessionID);
|
|
35534
|
+
}
|
|
35535
|
+
|
|
35434
35536
|
// src/envoy-session.ts
|
|
35435
35537
|
import path2 from "path";
|
|
35436
35538
|
var ENVOY_SESSIONS = Symbol.for("legion.pi-envoy.envoy-session");
|
|
@@ -35687,6 +35789,7 @@ var NATS_RETRY_INTERVAL_MS = 15000;
|
|
|
35687
35789
|
var CAPABILITIES_WITHOUT_BTW = DELIVERY_CAPABILITIES.filter((capability) => capability !== "btw");
|
|
35688
35790
|
var ROLE_CLAIM_ENTRY = "envoy-role-claim";
|
|
35689
35791
|
var OPEN_ASKS_TIMEOUT_MS = 3000;
|
|
35792
|
+
var USER_TURN_ACCEPT_TIMEOUT_MS = 1e4;
|
|
35690
35793
|
var LEGION_MANAGED_ENTRY = "legion-managed-session";
|
|
35691
35794
|
var ASK_REMINDER_MESSAGE = "dispatch-ask-reminder";
|
|
35692
35795
|
var ASK_PROMPT_LINE_SEPARATOR = /\r\n|[\r\n\u0085\u2028\u2029]/;
|
|
@@ -35816,6 +35919,7 @@ function envoyExtension(pi) {
|
|
|
35816
35919
|
let runSeq = 0;
|
|
35817
35920
|
let legionManagedTranscript = false;
|
|
35818
35921
|
const legionManaged = (id) => legionManagedTranscript || legionRoleClaimBridge().managedSessions.has(id);
|
|
35922
|
+
let handledDispatchAttempts = new Set;
|
|
35819
35923
|
const availabilityWarningSessionIDs = new Set;
|
|
35820
35924
|
const warnAskAvailability = (context, error48) => {
|
|
35821
35925
|
const sessionID2 = context.sessionManager.getSessionId();
|
|
@@ -35873,6 +35977,7 @@ function envoyExtension(pi) {
|
|
|
35873
35977
|
awarenessGeneration++;
|
|
35874
35978
|
}
|
|
35875
35979
|
legionManagedTranscript = branch.some(isLegionManagedEntry);
|
|
35980
|
+
handledDispatchAttempts = handledAttempts(context.sessionManager.getEntries());
|
|
35876
35981
|
};
|
|
35877
35982
|
pi.on("resources_discover", async () => ({ skillPaths: [SKILLS_DIRECTORY] }));
|
|
35878
35983
|
registerEnvoyMessageRenderer(pi);
|
|
@@ -35892,6 +35997,34 @@ function envoyExtension(pi) {
|
|
|
35892
35997
|
const postDispatchReply = async (delivery, result) => {
|
|
35893
35998
|
await postDeliveryReply(currentDispatchConfig(), sessionID, delivery, result);
|
|
35894
35999
|
};
|
|
36000
|
+
const acceptedUserTurn = async (rendered) => {
|
|
36001
|
+
if (!isUserTurnCandidate(rendered))
|
|
36002
|
+
return;
|
|
36003
|
+
const { delivery } = rendered;
|
|
36004
|
+
const key = handledAttemptKey(delivery.id, delivery.attempt);
|
|
36005
|
+
if (handledDispatchAttempts.has(key)) {
|
|
36006
|
+
logger.warn("envoy: a frame names a Dispatch attempt this session already delivered; it arrives as a card", { attempt: delivery.attempt, messageID: delivery.id });
|
|
36007
|
+
return;
|
|
36008
|
+
}
|
|
36009
|
+
handledDispatchAttempts.add(key);
|
|
36010
|
+
pi.appendEntry(HANDLED_ATTEMPT_ENTRY, { attempt: delivery.attempt, message_id: delivery.id });
|
|
36011
|
+
try {
|
|
36012
|
+
const config2 = activeDispatchConfig(process.env, { cwd: process.cwd() });
|
|
36013
|
+
if (config2 === null)
|
|
36014
|
+
return;
|
|
36015
|
+
const accepted = await new DispatchClient(config2.url, config2.token, fetch, AbortSignal.timeout(USER_TURN_ACCEPT_TIMEOUT_MS)).acceptMessageDelivery(delivery.id, delivery.attempt, {
|
|
36016
|
+
actor: { id: sessionID, kind: "session" }
|
|
36017
|
+
});
|
|
36018
|
+
const turn = turnFromAccept(accepted);
|
|
36019
|
+
if (turn === undefined) {
|
|
36020
|
+
logger.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 });
|
|
36021
|
+
}
|
|
36022
|
+
return turn;
|
|
36023
|
+
} catch (error48) {
|
|
36024
|
+
logger.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) });
|
|
36025
|
+
return;
|
|
36026
|
+
}
|
|
36027
|
+
};
|
|
35895
36028
|
const deliver = async (subject2, raw, reply) => {
|
|
35896
36029
|
const rendered = renderInbound(raw, sessionID, subject2);
|
|
35897
36030
|
const directSubject = agentSubject(sessionID);
|
|
@@ -35950,10 +36083,16 @@ function envoyExtension(pi) {
|
|
|
35950
36083
|
}
|
|
35951
36084
|
}
|
|
35952
36085
|
} else {
|
|
35953
|
-
|
|
35954
|
-
|
|
35955
|
-
|
|
35956
|
-
|
|
36086
|
+
const turn = await acceptedUserTurn(rendered);
|
|
36087
|
+
if (turn === undefined) {
|
|
36088
|
+
pi.sendMessage({ customType: "envoy-message", content: rendered.content, display: true }, {
|
|
36089
|
+
deliverAs: rendered.delivery?.mode === "aside" ? "aside" : "steer",
|
|
36090
|
+
triggerTurn: true
|
|
36091
|
+
});
|
|
36092
|
+
} else {
|
|
36093
|
+
noteInjectedUserTurn(sessionID, turn.body, turn.messageId);
|
|
36094
|
+
pi.sendUserMessage(turn.body, turn.mode === "aside" ? { deliverAs: "aside" } : undefined);
|
|
36095
|
+
}
|
|
35957
36096
|
}
|
|
35958
36097
|
} catch (error48) {
|
|
35959
36098
|
console.warn(`[envoy] failed to deliver envelope ${envelope2?.event_id ?? "unknown"}`, error48);
|
|
@@ -36079,7 +36218,7 @@ function envoyExtension(pi) {
|
|
|
36079
36218
|
const recordAgentStreamMessage = (message, streaming) => {
|
|
36080
36219
|
if (sessionID === "")
|
|
36081
36220
|
return;
|
|
36082
|
-
agentStream.record(agentStreamFramesSubject(sessionID), message, streaming);
|
|
36221
|
+
agentStream.record(agentStreamFramesSubject(sessionID), message, streaming, matchInjectedUserTurn(sessionID, message));
|
|
36083
36222
|
};
|
|
36084
36223
|
pi.on("message_start", async (event) => {
|
|
36085
36224
|
recordAgentStreamMessage(event.message, true);
|
|
@@ -36556,6 +36695,7 @@ function envoyExtension(pi) {
|
|
|
36556
36695
|
}
|
|
36557
36696
|
pi.on("agent_end", async (event, context) => {
|
|
36558
36697
|
const id = context.sessionManager.getSessionId();
|
|
36698
|
+
endInjectedUserTurns(sessionID);
|
|
36559
36699
|
const lastReply = event.messages?.findLast((message) => message.role === "assistant");
|
|
36560
36700
|
const ask = sideTurn(pi, context);
|
|
36561
36701
|
if (event.willContinue === true || lastReply?.stopReason !== "stop" || !context.hasUI || id === "" || ask === undefined || !checkOwed(id)) {
|
package/dist/legion.js
CHANGED
|
@@ -29965,6 +29965,7 @@ var DispatchTargetedMessagePayloadSchema = MessageEventPayloadSchema.extend({
|
|
|
29965
29965
|
body: exports_external.string(),
|
|
29966
29966
|
target: exports_external.string(),
|
|
29967
29967
|
in_reply_to: exports_external.string().nullable(),
|
|
29968
|
+
broadcast_id: exports_external.string().nullish(),
|
|
29968
29969
|
deliveries: exports_external.array(exports_external.unknown()),
|
|
29969
29970
|
created_at: exports_external.string()
|
|
29970
29971
|
});
|
|
@@ -31484,6 +31485,9 @@ class DispatchClient {
|
|
|
31484
31485
|
async getMessageThread(id, session) {
|
|
31485
31486
|
return this.#json("GET", ["api", "v1", "messages", id], undefined, { session });
|
|
31486
31487
|
}
|
|
31488
|
+
async acceptMessageDelivery(id, attempt, input) {
|
|
31489
|
+
return this.#json("POST", ["api", "v1", "messages", id, "deliveries", String(attempt), "accept"], input);
|
|
31490
|
+
}
|
|
31487
31491
|
async artifact(issue2, input) {
|
|
31488
31492
|
const artifactPath = ["api", "v1", "issues", await this.#resolveIssue(issue2), "artifacts"];
|
|
31489
31493
|
if ("content" in input)
|
|
@@ -33672,7 +33676,7 @@ import { logger } from "@oh-my-pi/pi-utils";
|
|
|
33672
33676
|
// package.json
|
|
33673
33677
|
var package_default = {
|
|
33674
33678
|
name: "@sjawhar/pi-legion-envoy",
|
|
33675
|
-
version: "5.
|
|
33679
|
+
version: "5.30.1",
|
|
33676
33680
|
type: "module",
|
|
33677
33681
|
omp: {
|
|
33678
33682
|
extensions: [
|
|
@@ -33717,6 +33721,101 @@ var package_default = {
|
|
|
33717
33721
|
}
|
|
33718
33722
|
};
|
|
33719
33723
|
|
|
33724
|
+
// src/dispatch-user-turn.ts
|
|
33725
|
+
function isUserTurnCandidate(rendered) {
|
|
33726
|
+
const { delivery } = rendered;
|
|
33727
|
+
return rendered.envelope?.source === "dispatch" && delivery?.resource === "message" && delivery.issueKey === null && delivery.broadcastId === undefined && rendered.dispatchActor?.kind === "user" && (delivery.mode === "aside" || delivery.mode === "steer");
|
|
33728
|
+
}
|
|
33729
|
+
var AcceptedDeliverySchema = exports_external.object({
|
|
33730
|
+
message_id: exports_external.string(),
|
|
33731
|
+
body: exports_external.string(),
|
|
33732
|
+
delivery: exports_external.enum(["aside", "steer"])
|
|
33733
|
+
});
|
|
33734
|
+
function turnFromAccept(accepted) {
|
|
33735
|
+
const parsed = AcceptedDeliverySchema.safeParse(accepted);
|
|
33736
|
+
if (!parsed.success)
|
|
33737
|
+
return;
|
|
33738
|
+
return { body: parsed.data.body, messageId: parsed.data.message_id, mode: parsed.data.delivery };
|
|
33739
|
+
}
|
|
33740
|
+
var HANDLED_ATTEMPT_ENTRY = "envoy-dispatch-handled-attempt";
|
|
33741
|
+
function handledAttemptKey(messageId, attempt) {
|
|
33742
|
+
return `${messageId}#${attempt}`;
|
|
33743
|
+
}
|
|
33744
|
+
function handledAttempts(entries) {
|
|
33745
|
+
const handled = new Set;
|
|
33746
|
+
for (const entry of entries) {
|
|
33747
|
+
if (typeof entry !== "object" || entry === null)
|
|
33748
|
+
continue;
|
|
33749
|
+
if (!("type" in entry) || entry.type !== "custom")
|
|
33750
|
+
continue;
|
|
33751
|
+
if (!("customType" in entry) || entry.customType !== HANDLED_ATTEMPT_ENTRY)
|
|
33752
|
+
continue;
|
|
33753
|
+
if (!("data" in entry) || typeof entry.data !== "object" || entry.data === null)
|
|
33754
|
+
continue;
|
|
33755
|
+
const { data } = entry;
|
|
33756
|
+
if (!("message_id" in data) || typeof data.message_id !== "string")
|
|
33757
|
+
continue;
|
|
33758
|
+
if (!("attempt" in data) || typeof data.attempt !== "number")
|
|
33759
|
+
continue;
|
|
33760
|
+
handled.add(handledAttemptKey(data.message_id, data.attempt));
|
|
33761
|
+
}
|
|
33762
|
+
return handled;
|
|
33763
|
+
}
|
|
33764
|
+
function userMessageText(content) {
|
|
33765
|
+
if (typeof content === "string")
|
|
33766
|
+
return content;
|
|
33767
|
+
if (!Array.isArray(content))
|
|
33768
|
+
return "";
|
|
33769
|
+
const pieces = [];
|
|
33770
|
+
for (const part of content) {
|
|
33771
|
+
if (typeof part !== "object" || part === null)
|
|
33772
|
+
continue;
|
|
33773
|
+
if ("type" in part && part.type === "text" && "text" in part && typeof part.text === "string") {
|
|
33774
|
+
pieces.push(part.text);
|
|
33775
|
+
}
|
|
33776
|
+
}
|
|
33777
|
+
return pieces.join(`
|
|
33778
|
+
`);
|
|
33779
|
+
}
|
|
33780
|
+
var INJECTED_USER_TURNS = Symbol.for("legion.pi-envoy.injected-user-turns");
|
|
33781
|
+
function injectedUserTurns() {
|
|
33782
|
+
const store = globalThis;
|
|
33783
|
+
const turns = store[INJECTED_USER_TURNS] ?? new Map;
|
|
33784
|
+
store[INJECTED_USER_TURNS] = turns;
|
|
33785
|
+
return turns;
|
|
33786
|
+
}
|
|
33787
|
+
function noteInjectedUserTurn(sessionID, body, messageId) {
|
|
33788
|
+
const turns = injectedUserTurns();
|
|
33789
|
+
const session = turns.get(sessionID) ?? { found: new Map, sent: [] };
|
|
33790
|
+
session.sent.push({ body, messageId });
|
|
33791
|
+
turns.set(sessionID, session);
|
|
33792
|
+
}
|
|
33793
|
+
function matchInjectedUserTurn(sessionID, message) {
|
|
33794
|
+
if (typeof message !== "object" || message === null)
|
|
33795
|
+
return;
|
|
33796
|
+
if (!("role" in message) || message.role !== "user")
|
|
33797
|
+
return;
|
|
33798
|
+
if (!("timestamp" in message) || typeof message.timestamp !== "number")
|
|
33799
|
+
return;
|
|
33800
|
+
const session = injectedUserTurns().get(sessionID);
|
|
33801
|
+
if (session === undefined)
|
|
33802
|
+
return;
|
|
33803
|
+
const known = session.found.get(message.timestamp);
|
|
33804
|
+
if (known !== undefined)
|
|
33805
|
+
return known;
|
|
33806
|
+
const text = userMessageText("content" in message ? message.content : undefined);
|
|
33807
|
+
const index = session.sent.findIndex((sent2) => sent2.body === text);
|
|
33808
|
+
const sent = session.sent[index];
|
|
33809
|
+
if (sent === undefined)
|
|
33810
|
+
return;
|
|
33811
|
+
session.sent.splice(index, 1);
|
|
33812
|
+
session.found.set(message.timestamp, sent.messageId);
|
|
33813
|
+
return sent.messageId;
|
|
33814
|
+
}
|
|
33815
|
+
function endInjectedUserTurns(sessionID) {
|
|
33816
|
+
injectedUserTurns().delete(sessionID);
|
|
33817
|
+
}
|
|
33818
|
+
|
|
33720
33819
|
// src/legion/classify.ts
|
|
33721
33820
|
function classifySession(env) {
|
|
33722
33821
|
if (env.LEGION_CONTROLLER !== undefined && env.LEGION_TREE !== undefined) {
|
|
@@ -35838,7 +35937,8 @@ function legionExtension(pi) {
|
|
|
35838
35937
|
pi.on("message_start", async (event, context) => {
|
|
35839
35938
|
if (!phaseWorkerSession(context))
|
|
35840
35939
|
return;
|
|
35841
|
-
const
|
|
35940
|
+
const injected = matchInjectedUserTurn(context.sessionManager.getSessionId(), event.message) !== undefined;
|
|
35941
|
+
const kind = injected ? "inbound-event" : inboundKind(event.message);
|
|
35842
35942
|
if (kind !== undefined)
|
|
35843
35943
|
advancePhaseStall({ kind });
|
|
35844
35944
|
});
|
|
@@ -6,7 +6,8 @@ human messages you directly from the Agents page.
|
|
|
6
6
|
## Targeted agent messages
|
|
7
7
|
|
|
8
8
|
A human — or any bearer caller over HTTP, such as a test rig — can target the issue message at a
|
|
9
|
-
live Envoy session or role as **BTW**, **Aside**, or **Steer
|
|
9
|
+
live Envoy session or role as **BTW**, **Aside**, or **Steer** (the dashboard calls a steer
|
|
10
|
+
**Send**). The incoming Dispatch frame names
|
|
10
11
|
the issue and includes a `reply_with` hint (`{ tool, args }`, ready to issue on any host); reply on the same open issue with the existing
|
|
11
12
|
tool, never a new targeted send:
|
|
12
13
|
|
|
@@ -32,9 +33,17 @@ target only a session that advertises the mode you want. Sending to a session wi
|
|
|
32
33
|
|
|
33
34
|
## Answering a direct message
|
|
34
35
|
|
|
35
|
-
A human can also message you directly from the **Agents** page, with no issue at all.
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
A human can also message you directly from the **Agents** page, with no issue at all. On Oh My
|
|
37
|
+
Pi, a person's **Send** or **Aside** arrives as their own user message, exactly as if they had
|
|
38
|
+
typed it at your terminal: your Envoy plugin takes it only once Dispatch accepts it as a person's
|
|
39
|
+
own fresh message to you, and injects the text Dispatch stored. Answer it in the conversation as
|
|
40
|
+
you would anything typed, with no `dispatch_message`; the Agents page shows your conversation
|
|
41
|
+
live, so they read your answer there.
|
|
42
|
+
|
|
43
|
+
Everything else still arrives as a Dispatch frame: a **BTW**, a broadcast, a direct message on a
|
|
44
|
+
host that takes no user turn from its plugin (Claude Code), and one Dispatch did not accept.
|
|
45
|
+
That frame names no issue and its `reply_with` hint carries none either; answer it with the
|
|
46
|
+
message's bare id in `in_reply_to`, alone:
|
|
38
47
|
|
|
39
48
|
```ts
|
|
40
49
|
dispatch_message({
|