@sjawhar/pi-legion-envoy 7.6.0 → 7.6.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
CHANGED
|
@@ -35637,10 +35637,10 @@ function btwPrompt(question) {
|
|
|
35637
35637
|
].join(`
|
|
35638
35638
|
`);
|
|
35639
35639
|
}
|
|
35640
|
-
function sideTurn(
|
|
35640
|
+
function sideTurn(context) {
|
|
35641
35641
|
const runEphemeralTurn = context?.runEphemeralTurn;
|
|
35642
35642
|
if (runEphemeralTurn === undefined)
|
|
35643
|
-
return
|
|
35643
|
+
return;
|
|
35644
35644
|
return ({ prompt, signal }) => runEphemeralTurn({ promptText: btwPrompt(prompt), signal });
|
|
35645
35645
|
}
|
|
35646
35646
|
|
|
@@ -36051,7 +36051,7 @@ function envoyExtension(pi) {
|
|
|
36051
36051
|
} else if (rendered.malformedDelivery === true) {
|
|
36052
36052
|
console.warn("[envoy] dropping malformed Dispatch targeted delivery without a reply address");
|
|
36053
36053
|
} else if (rendered.delivery?.mode === "btw") {
|
|
36054
|
-
const answer = sideTurn(
|
|
36054
|
+
const answer = sideTurn(activeSessionContext);
|
|
36055
36055
|
if (shuttingDown) {
|
|
36056
36056
|
await refuse(rendered.delivery, "This OMP session is shutting down");
|
|
36057
36057
|
} else if (answer === undefined) {
|
|
@@ -36248,7 +36248,7 @@ function envoyExtension(pi) {
|
|
|
36248
36248
|
],
|
|
36249
36249
|
port: 0,
|
|
36250
36250
|
title: activeSessionContext?.sessionManager.getSessionName?.() ?? "",
|
|
36251
|
-
capabilities: sideTurn(
|
|
36251
|
+
capabilities: sideTurn(activeSessionContext) === undefined ? CAPABILITIES_WITHOUT_BTW : DELIVERY_CAPABILITIES,
|
|
36252
36252
|
driving: false,
|
|
36253
36253
|
selfSubscribed: true
|
|
36254
36254
|
});
|
|
@@ -36645,7 +36645,7 @@ function envoyExtension(pi) {
|
|
|
36645
36645
|
registerEnvoyWhoamiCommand(pi, replyAddress);
|
|
36646
36646
|
pi.on("before_agent_start", async (event, context) => {
|
|
36647
36647
|
const id = context.sessionManager.getSessionId();
|
|
36648
|
-
if (id === "" || event.prompt.trim() === "" || !context.hasUI || sideTurn(
|
|
36648
|
+
if (id === "" || event.prompt.trim() === "" || !context.hasUI || sideTurn(context) === undefined || legionManaged(id)) {
|
|
36649
36649
|
return;
|
|
36650
36650
|
}
|
|
36651
36651
|
if (await isSubagent(context))
|
|
@@ -36677,7 +36677,7 @@ function envoyExtension(pi) {
|
|
|
36677
36677
|
const id = context.sessionManager.getSessionId();
|
|
36678
36678
|
endInjectedUserTurns(sessionID);
|
|
36679
36679
|
const lastReply = event.messages?.findLast((message) => message.role === "assistant");
|
|
36680
|
-
const ask = sideTurn(
|
|
36680
|
+
const ask = sideTurn(context);
|
|
36681
36681
|
if (event.willContinue === true || lastReply?.stopReason !== "stop" || !context.hasUI || id === "" || ask === undefined || !checkOwed(id)) {
|
|
36682
36682
|
return;
|
|
36683
36683
|
}
|
|
@@ -36725,20 +36725,14 @@ function envoyExtension(pi) {
|
|
|
36725
36725
|
return;
|
|
36726
36726
|
const abort = new AbortController;
|
|
36727
36727
|
askCheckAbort = abort;
|
|
36728
|
-
|
|
36729
|
-
|
|
36730
|
-
|
|
36731
|
-
|
|
36732
|
-
|
|
36733
|
-
|
|
36734
|
-
|
|
36735
|
-
|
|
36736
|
-
return;
|
|
36737
|
-
});
|
|
36738
|
-
} catch (error48) {
|
|
36739
|
-
logSelfCheckFailure(id, error48);
|
|
36740
|
-
answered = Promise.resolve(undefined);
|
|
36741
|
-
}
|
|
36728
|
+
const answered = ask({
|
|
36729
|
+
prompt: ASK_SELF_CHECK_PROMPT(open.snapshot.asks),
|
|
36730
|
+
signal: abort.signal
|
|
36731
|
+
}).then((reply) => reply.replyText, (error48) => {
|
|
36732
|
+
if (!abort.signal.aborted)
|
|
36733
|
+
logSelfCheckFailure(id, error48);
|
|
36734
|
+
return;
|
|
36735
|
+
});
|
|
36742
36736
|
const expiry = Promise.withResolvers();
|
|
36743
36737
|
const expire = setTimeout(() => {
|
|
36744
36738
|
const timedOut = new Error(`self-check timed out after ${selfCheckTimeoutMs} ms`);
|
|
@@ -36865,7 +36859,7 @@ Warnings: ${warnings.join("; ")}`, {
|
|
|
36865
36859
|
const undelivered = source !== "" && result.holder === source && inSubagent;
|
|
36866
36860
|
const published = result.holder === undefined ? `published ${result.envelope.event_id}` : `published ${result.envelope.event_id}; holder ${result.holder}`;
|
|
36867
36861
|
return toolSuccess(undelivered ? `${published}
|
|
36868
|
-
Not delivered: that holder is the session this subagent sends as, and the listener drops a message whose source session is its recipient, so the agent that spawned you did not receive it. Reach it
|
|
36862
|
+
Not delivered: that holder is the session this subagent sends as, and the listener drops a message whose source session is its recipient, so the agent that spawned you did not receive it. Reach it with a write to its agent:// address instead.` : published, {
|
|
36869
36863
|
event_id: result.envelope.event_id,
|
|
36870
36864
|
topic,
|
|
36871
36865
|
...result.holder === undefined ? {} : { holder: result.holder },
|
|
@@ -36886,7 +36880,7 @@ Not delivered: that holder is the session this subagent sends as, and the listen
|
|
|
36886
36880
|
const address = await replyAddress(context, "", inSubagent);
|
|
36887
36881
|
const subagent = inSubagent ? {
|
|
36888
36882
|
session_id: context.sessionManager.getSessionId(),
|
|
36889
|
-
note: address === "" ? "This session is a task subagent and registers no Envoy session of its own, and this process records no top-level session its transcript traces back to, so it has no reply address at all: say who you are in the message body, and reach the agent that spawned you
|
|
36883
|
+
note: address === "" ? "This session is a task subagent and registers no Envoy session of its own, and this process records no top-level session its transcript traces back to, so it has no reply address at all: say who you are in the message body, and reach the agent that spawned you with a write to its agent:// address." : "This session is a task subagent and registers no Envoy session of its own; a reply to session_id reaches the agent that spawned it, which relays it with a write to your agent:// address. Your own envoy_publish never reaches that agent \u2014 use a write to its agent:// address for that hop."
|
|
36890
36884
|
} : undefined;
|
|
36891
36885
|
return toolSuccess(JSON.stringify({
|
|
36892
36886
|
session_id: address,
|
package/dist/legion.js
CHANGED
|
@@ -17524,7 +17524,7 @@ function endInjectedUserTurns(sessionID) {
|
|
|
17524
17524
|
// package.json
|
|
17525
17525
|
var package_default = {
|
|
17526
17526
|
name: "@sjawhar/pi-legion-envoy",
|
|
17527
|
-
version: "7.6.
|
|
17527
|
+
version: "7.6.1",
|
|
17528
17528
|
license: "Apache-2.0",
|
|
17529
17529
|
type: "module",
|
|
17530
17530
|
omp: {
|
|
@@ -18936,6 +18936,29 @@ function refusedCommand(command, commands, rule) {
|
|
|
18936
18936
|
}
|
|
18937
18937
|
return;
|
|
18938
18938
|
}
|
|
18939
|
+
var NON_FILE_WRITE_URL = /^(xd|agent|proc):\/\//iu;
|
|
18940
|
+
var READ_HEADER_TAG = /#[0-9A-Fa-f]{4}$/u;
|
|
18941
|
+
var PREFIXED_CONFLICT_URL = /^(.+):(conflict:\/\/.+)$/u;
|
|
18942
|
+
function unwrapReadHeader(path6) {
|
|
18943
|
+
const trimmed = path6.trimEnd();
|
|
18944
|
+
if (trimmed.length < 2 || !trimmed.startsWith("[") || !trimmed.endsWith("]"))
|
|
18945
|
+
return path6;
|
|
18946
|
+
const inner = trimmed.slice(1, -1);
|
|
18947
|
+
const tag = READ_HEADER_TAG.exec(inner);
|
|
18948
|
+
const target = tag === null ? inner : inner.slice(0, tag.index);
|
|
18949
|
+
if (target.length === 0 || tag === null && target.includes("#"))
|
|
18950
|
+
return path6;
|
|
18951
|
+
return target;
|
|
18952
|
+
}
|
|
18953
|
+
function writeTarget(path6) {
|
|
18954
|
+
const unwrapped = unwrapReadHeader(path6);
|
|
18955
|
+
return PREFIXED_CONFLICT_URL.exec(unwrapped)?.[2] ?? unwrapped;
|
|
18956
|
+
}
|
|
18957
|
+
function nonFileWriteScheme(toolCall) {
|
|
18958
|
+
if (toolCall.toolName !== "write" || typeof toolCall.input.path !== "string")
|
|
18959
|
+
return;
|
|
18960
|
+
return NON_FILE_WRITE_URL.exec(writeTarget(toolCall.input.path))?.[1]?.toLowerCase();
|
|
18961
|
+
}
|
|
18939
18962
|
function paneRuleRefusal(toolCall, rules) {
|
|
18940
18963
|
if (rules.length === 0)
|
|
18941
18964
|
return;
|
|
@@ -18954,8 +18977,8 @@ function paneRuleRefusal(toolCall, rules) {
|
|
|
18954
18977
|
let text;
|
|
18955
18978
|
if (toolName === "eval" && typeof input.code === "string")
|
|
18956
18979
|
text = input.code;
|
|
18957
|
-
else if (
|
|
18958
|
-
text =
|
|
18980
|
+
else if (nonFileWriteScheme(toolCall) === "proc" && typeof input.content === "string") {
|
|
18981
|
+
text = input.content;
|
|
18959
18982
|
} else
|
|
18960
18983
|
return;
|
|
18961
18984
|
for (const rule of rules) {
|
|
@@ -18972,10 +18995,6 @@ var CODE_TOOL_REFUSAL = {
|
|
|
18972
18995
|
reviewer: "the reviewer edits nothing except the final .legion/ cleanup commit via bash",
|
|
18973
18996
|
merger: "the merger only verifies and reports"
|
|
18974
18997
|
};
|
|
18975
|
-
var TOOL_DEVICE_URL = /^xd:\/\//iu;
|
|
18976
|
-
function isToolDeviceInvocation(toolCall) {
|
|
18977
|
-
return toolCall.toolName === "write" && typeof toolCall.input.path === "string" && TOOL_DEVICE_URL.test(toolCall.input.path);
|
|
18978
|
-
}
|
|
18979
18998
|
function legionExtension(pi) {
|
|
18980
18999
|
const instance = randomUUID2().slice(0, 8);
|
|
18981
19000
|
logger2.debug("extension instance loaded", { extension: import.meta.url, instance });
|
|
@@ -19056,7 +19075,7 @@ function legionExtension(pi) {
|
|
|
19056
19075
|
const active = claimSession.capability(sessionID);
|
|
19057
19076
|
const codeToolRefusal = active === undefined ? undefined : CODE_TOOL_REFUSAL[active.role];
|
|
19058
19077
|
const mutatesCode = CODE_MUTATION_TOOLS.includes(toolCall.toolName) || active?.role === "architect" && toolCall.toolName === "bash" && !isSingleLegionCommand(toolCall.input.command);
|
|
19059
|
-
if (codeToolRefusal !== undefined && mutatesCode &&
|
|
19078
|
+
if (codeToolRefusal !== undefined && mutatesCode && nonFileWriteScheme(toolCall) === undefined) {
|
|
19060
19079
|
return { block: true, reason: codeToolRefusal };
|
|
19061
19080
|
}
|
|
19062
19081
|
if (!needsGrant(toolCall))
|
|
@@ -25,11 +25,11 @@ reply. A human may reply to your message in turn — the follow-up arrives as a
|
|
|
25
25
|
`in_reply_to` names your message and whose `reply_body` quotes it; answer it the same way,
|
|
26
26
|
`dispatch_message({ issue, in_reply_to: "<their reply id>", body })`, so the exchange reads as one
|
|
27
27
|
thread. `dispatch_message` itself never carries `target` or `delivery`: agent-to-agent traffic goes
|
|
28
|
-
through Envoy or
|
|
29
|
-
(`{kind: "session", id}`), and the card shows that session as the author.
|
|
30
|
-
(any authenticated caller) lists live sessions with their capabilities
|
|
31
|
-
target only a session that advertises the mode you want. Sending to a
|
|
32
|
-
(`POST /api/v1/agents/{session_id}/messages`) stays human-only.
|
|
28
|
+
through Envoy or Oh My Pi's `agent://` messages. A bearer that targets over HTTP names its own
|
|
29
|
+
session in `actor` (`{kind: "session", id}`), and the card shows that session as the author.
|
|
30
|
+
`GET /api/v1/agents` (any authenticated caller) lists live sessions with their capabilities
|
|
31
|
+
(`aside`, `btw`, `steer`); target only a session that advertises the mode you want. Sending to a
|
|
32
|
+
session with no issue (`POST /api/v1/agents/{session_id}/messages`) stays human-only.
|
|
33
33
|
|
|
34
34
|
## Answering a direct message
|
|
35
35
|
|
|
@@ -122,8 +122,9 @@ envoy_send(
|
|
|
122
122
|
|
|
123
123
|
`envoy_whoami`'s `session_id` is the address a reply to you reaches. Inside a `task` subagent it
|
|
124
124
|
is the session that spawned you: a subagent registers no Envoy session of its own, so a peer
|
|
125
|
-
answering it reaches that session, which relays to you
|
|
126
|
-
output carries your own host session id — it is not an address, so
|
|
125
|
+
answering it reaches that session, which relays to you with a `write` to your `agent://` address.
|
|
126
|
+
The `subagent` field in that output carries your own host session id — it is not an address, so
|
|
127
|
+
never hand it to a peer.
|
|
127
128
|
|
|
128
129
|
`session_id` is empty when nothing can be reached: a process that took no Envoy identity, or a
|
|
129
130
|
subagent whose spawning session this process can no longer place (it forked away, and other
|
|
@@ -134,8 +135,8 @@ would send your peers to an agent that never spawned you.
|
|
|
134
135
|
Your own `envoy_publish` never reaches the agent that spawned you. The listener delivers nothing
|
|
135
136
|
to the session a message names as its source, and inside a subagent that source is your parent,
|
|
136
137
|
so a publish to a role it holds — or to any topic it subscribes to — is accepted and delivered to
|
|
137
|
-
nobody. Use
|
|
138
|
-
unaffected.
|
|
138
|
+
nobody. Use a `write` to its `agent://` address for that one hop. `envoy_send` to any other
|
|
139
|
+
session, including a reply, is unaffected.
|
|
139
140
|
|
|
140
141
|
### Delivery capabilities
|
|
141
142
|
|
|
@@ -62,11 +62,11 @@ workflow table. You may still use ordinary `task` subagents for your own phase w
|
|
|
62
62
|
is a Legion role.
|
|
63
63
|
Escalate a product, scope, design, cross-phase, or lifecycle decision to the owning architect with
|
|
64
64
|
`envoy_publish` to its role topic (`notifications.role.` followed by its encoded token, see
|
|
65
|
-
above), carrying the verified facts and the decision needed. `
|
|
66
|
-
inside your own process, not the architect's separate one. Never write a decision block
|
|
67
|
-
spec yourself: the architect decides whether the human must answer it and writes the block,
|
|
68
|
-
a new version of an approved root spec closes the tree's design gate. A standalone to-do
|
|
69
|
-
human can do is a `dispatch_ask`, and its replies return to your own session.
|
|
65
|
+
above), carrying the verified facts and the decision needed. A `write` to `agent://` only reaches
|
|
66
|
+
agents inside your own process, not the architect's separate one. Never write a decision block
|
|
67
|
+
into a spec yourself: the architect decides whether the human must answer it and writes the block,
|
|
68
|
+
since a new version of an approved root spec closes the tree's design gate. A standalone to-do
|
|
69
|
+
only a human can do is a `dispatch_ask`, and its replies return to your own session.
|
|
70
70
|
|
|
71
71
|
Because the same agent is always resumed for its phase, you may receive more than one
|
|
72
72
|
assignment across your lifetime: once the daemon ends your phase it suspends you, and when a later
|
|
@@ -152,9 +152,10 @@ new work.
|
|
|
152
152
|
clone, so they all share one operation log: `jj undo`, `jj abandon`, and
|
|
153
153
|
`jj op restore|revert|abandon|undo` rewrite it for every tree at once. The extension refuses them in every
|
|
154
154
|
phase-worker pane before they run — a `bash` command in any position of a pipeline or `&&`
|
|
155
|
-
chain, with or without `-R`, judged on the whole argument list
|
|
156
|
-
|
|
157
|
-
|
|
155
|
+
chain, with or without `-R`, judged on the whole argument list (a supervised service's start
|
|
156
|
+
included); `eval` code; and stdin written to a service (a `write` to `proc://<id>`) — from your
|
|
157
|
+
own tool calls and from any `task` subagent you spawn (it runs in your pane, against the same
|
|
158
|
+
log), and a `bash` command whose quoted text merely mentions `jj`
|
|
158
159
|
with one of those words (a heredoc, an echo, a commit message) is refused too: write such text
|
|
159
160
|
with the `write` tool or say "operation-log rollback" instead. `jj restore <paths>`,
|
|
160
161
|
`jj op log`, and `jj op show` stay allowed. Recover forward only: a new commit
|