@sjawhar/pi-legion-envoy 1.33.0 → 1.34.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/envoy.js +37 -8
- package/dist/legion.js +37 -8
- package/dist/skills/dispatch/SKILL.md +27 -4
- package/dist/skills/envoy/SKILL.md +10 -0
- package/package.json +1 -1
package/dist/envoy.js
CHANGED
|
@@ -29649,6 +29649,7 @@ function date4(params) {
|
|
|
29649
29649
|
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/classic/external.js
|
|
29650
29650
|
config(en_default());
|
|
29651
29651
|
// ../contracts/src/dispatch-api.ts
|
|
29652
|
+
var DELIVERY_CAPABILITIES = ["aside", "btw", "steer"];
|
|
29652
29653
|
function searchOwnerOf(result) {
|
|
29653
29654
|
return result.owner ?? { kind: "issue", key: result.issue.key };
|
|
29654
29655
|
}
|
|
@@ -29731,6 +29732,8 @@ var CommentEventPayloadSchema = object({
|
|
|
29731
29732
|
ask_id: string2().nullish(),
|
|
29732
29733
|
ask_question: string2().optional(),
|
|
29733
29734
|
ask_state: _enum2(["open", "answered", "resolved"]).optional(),
|
|
29735
|
+
ask_waiting_on: _enum2(["human", "agent"]).optional(),
|
|
29736
|
+
turn: _enum2(["human", "agent"]).nullish(),
|
|
29734
29737
|
anchor: object({ block_id: string2().nullable().optional(), quote: string2().optional() }).nullish(),
|
|
29735
29738
|
suggestion: object({ replace_with: string2().optional() }).nullish(),
|
|
29736
29739
|
author: object({ kind: string2(), id: string2() }).optional(),
|
|
@@ -29757,7 +29760,7 @@ var DispatchTargetedMessagePayloadSchema = MessageEventPayloadSchema.extend({
|
|
|
29757
29760
|
var MessageDeliveryEventPayloadSchema = object({
|
|
29758
29761
|
message_id: string2().optional(),
|
|
29759
29762
|
attempt: number2().int().positive().optional(),
|
|
29760
|
-
delivery: _enum2(
|
|
29763
|
+
delivery: _enum2(DELIVERY_CAPABILITIES).optional(),
|
|
29761
29764
|
session_id: string2().optional(),
|
|
29762
29765
|
target: string2().optional(),
|
|
29763
29766
|
title: string2().optional(),
|
|
@@ -29848,6 +29851,16 @@ function documentOwnerValidation(requireArtifact, alwaysRequireArtifact = false)
|
|
|
29848
29851
|
message: alwaysRequireArtifact ? "Exactly one of issue and project is required; artifact or ref must name the document." : "Exactly one of issue and project is required; with project, artifact names the document."
|
|
29849
29852
|
};
|
|
29850
29853
|
}
|
|
29854
|
+
var commentValidation = (() => {
|
|
29855
|
+
const owner = documentOwnerValidation(true);
|
|
29856
|
+
return {
|
|
29857
|
+
check: (value) => {
|
|
29858
|
+
const input = value;
|
|
29859
|
+
return owner.check(value) && (input.turn === undefined || typeof input.reply_to_ask === "string");
|
|
29860
|
+
},
|
|
29861
|
+
message: `${owner.message} turn requires reply_to_ask.`
|
|
29862
|
+
};
|
|
29863
|
+
})();
|
|
29851
29864
|
var SPEC_SECTIONS = [
|
|
29852
29865
|
"Summary",
|
|
29853
29866
|
"Decisions needed",
|
|
@@ -29943,9 +29956,10 @@ var dispatchToolSpecs = [
|
|
|
29943
29956
|
occurrence: z.number({ int: true, min: 0 }).describe("Optional zero-based occurrence of quote.").optional(),
|
|
29944
29957
|
body: z.string({ max: 2000 }).describe("Review comment, at most 2,000 characters."),
|
|
29945
29958
|
reply_to: z.string().describe("Full id of a comment to reply to; replying to any comment in a thread continues that " + "thread (an ask's clarification thread included).").optional(),
|
|
29946
|
-
reply_to_ask: z.string().describe("Optional ask id to reply to, threading this comment under that question. Mutually " + "exclusive with reply_to.").optional()
|
|
29959
|
+
reply_to_ask: z.string().describe("Optional ask id to reply to, threading this comment under that question. Mutually " + "exclusive with reply_to.").optional(),
|
|
29960
|
+
turn: z.enum(["agent", "human"]).describe("Only with reply_to_ask: who holds the turn after this reply. agent: a progress note - " + "you keep the turn and the ask stays 'Waiting on agents' for the human; human (default): " + "you need the human to act - the ask returns to 'Waiting on you'.").optional()
|
|
29947
29961
|
}),
|
|
29948
|
-
validation:
|
|
29962
|
+
validation: commentValidation
|
|
29949
29963
|
},
|
|
29950
29964
|
{
|
|
29951
29965
|
name: "dispatch_suggest",
|
|
@@ -31103,7 +31117,7 @@ var TolerantInboundEnvelopeSchema = object({
|
|
|
31103
31117
|
}).passthrough();
|
|
31104
31118
|
var DispatchDeliveryRequestSchema = object({
|
|
31105
31119
|
attempt: number2().int().positive(),
|
|
31106
|
-
mode: _enum2(
|
|
31120
|
+
mode: _enum2(DELIVERY_CAPABILITIES)
|
|
31107
31121
|
});
|
|
31108
31122
|
var DispatchTargetedFrameSchema = object({
|
|
31109
31123
|
event: DispatchEventSchema,
|
|
@@ -32595,21 +32609,34 @@ async function executeDispatchTool(input) {
|
|
|
32595
32609
|
if (replyTo !== undefined && replyToAsk !== undefined) {
|
|
32596
32610
|
throw new Error("reply_to and reply_to_ask cannot both be set");
|
|
32597
32611
|
}
|
|
32612
|
+
const requestedTurn = optionalString(args, "turn");
|
|
32613
|
+
if (requestedTurn !== undefined && replyToAsk === undefined) {
|
|
32614
|
+
throw new Error("turn requires reply_to_ask");
|
|
32615
|
+
}
|
|
32616
|
+
if (requestedTurn !== undefined && requestedTurn !== "agent" && requestedTurn !== "human") {
|
|
32617
|
+
throw new Error("turn must be agent or human");
|
|
32618
|
+
}
|
|
32598
32619
|
const commentInput = {
|
|
32599
32620
|
body: stringArg(args, "body"),
|
|
32600
32621
|
...anchored === undefined ? {} : { anchor: anchored },
|
|
32601
32622
|
...replyTo === undefined ? {} : { reply_to: replyTo },
|
|
32602
32623
|
...replyToAsk === undefined ? {} : { ask_id: replyToAsk },
|
|
32624
|
+
...requestedTurn === undefined ? {} : { turn: requestedTurn },
|
|
32603
32625
|
actor
|
|
32604
32626
|
};
|
|
32605
32627
|
const comment = resolved?.owner.kind === "project" ? await client.artifactComment(resolved.artifact.id, commentInput) : await client.comment(issue(), commentInput);
|
|
32628
|
+
const askState = comment.turn === null ? "" : ` (ask now waiting on ${comment.turn})`;
|
|
32606
32629
|
return {
|
|
32607
|
-
text: `Posted comment ${comment.id}`,
|
|
32630
|
+
text: `Posted comment ${comment.id}${askState}`,
|
|
32608
32631
|
details: resolved === undefined ? {
|
|
32609
32632
|
issue: comment.issue_key,
|
|
32610
32633
|
topic: dispatchIssueSubject(issue(), ">"),
|
|
32611
|
-
comment: comment.id
|
|
32612
|
-
|
|
32634
|
+
comment: comment.id,
|
|
32635
|
+
...comment.turn === null ? {} : { ask_waiting_on: comment.turn }
|
|
32636
|
+
} : writeResultDetails(resolved, {
|
|
32637
|
+
comment: comment.id,
|
|
32638
|
+
...comment.turn === null ? {} : { ask_waiting_on: comment.turn }
|
|
32639
|
+
})
|
|
32613
32640
|
};
|
|
32614
32641
|
}
|
|
32615
32642
|
case "dispatch_suggest": {
|
|
@@ -33290,6 +33317,8 @@ async function copySessionID(copyToClipboard, sessionID) {
|
|
|
33290
33317
|
// extensions/envoy.ts
|
|
33291
33318
|
var codec2 = import_nats.StringCodec();
|
|
33292
33319
|
var NATS_RETRY_INTERVAL_MS = 15000;
|
|
33320
|
+
var ALL_CAPABILITIES = DELIVERY_CAPABILITIES;
|
|
33321
|
+
var CAPABILITIES_WITHOUT_BTW = DELIVERY_CAPABILITIES.filter((capability) => capability !== "btw");
|
|
33293
33322
|
var ROLE_CLAIM_ENTRY = "envoy-role-claim";
|
|
33294
33323
|
var OPEN_ASKS_TIMEOUT_MS = 3000;
|
|
33295
33324
|
function isRoleClaimEntry(entry) {
|
|
@@ -33548,7 +33577,7 @@ function envoyExtension(pi) {
|
|
|
33548
33577
|
topics: [...new Set([agentSubject(sessionID), ...subscriptions.keys()])],
|
|
33549
33578
|
port: 0,
|
|
33550
33579
|
title: activeSessionContext?.sessionManager.getSessionName?.() ?? "",
|
|
33551
|
-
capabilities: typeof pi.askEphemeral === "function" ?
|
|
33580
|
+
capabilities: typeof pi.askEphemeral === "function" ? ALL_CAPABILITIES : CAPABILITIES_WITHOUT_BTW,
|
|
33552
33581
|
driving: false,
|
|
33553
33582
|
selfSubscribed: true
|
|
33554
33583
|
});
|
package/dist/legion.js
CHANGED
|
@@ -29649,6 +29649,7 @@ function date4(params) {
|
|
|
29649
29649
|
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/classic/external.js
|
|
29650
29650
|
config(en_default());
|
|
29651
29651
|
// ../contracts/src/dispatch-api.ts
|
|
29652
|
+
var DELIVERY_CAPABILITIES = ["aside", "btw", "steer"];
|
|
29652
29653
|
function searchOwnerOf(result) {
|
|
29653
29654
|
return result.owner ?? { kind: "issue", key: result.issue.key };
|
|
29654
29655
|
}
|
|
@@ -29731,6 +29732,8 @@ var CommentEventPayloadSchema = object({
|
|
|
29731
29732
|
ask_id: string2().nullish(),
|
|
29732
29733
|
ask_question: string2().optional(),
|
|
29733
29734
|
ask_state: _enum2(["open", "answered", "resolved"]).optional(),
|
|
29735
|
+
ask_waiting_on: _enum2(["human", "agent"]).optional(),
|
|
29736
|
+
turn: _enum2(["human", "agent"]).nullish(),
|
|
29734
29737
|
anchor: object({ block_id: string2().nullable().optional(), quote: string2().optional() }).nullish(),
|
|
29735
29738
|
suggestion: object({ replace_with: string2().optional() }).nullish(),
|
|
29736
29739
|
author: object({ kind: string2(), id: string2() }).optional(),
|
|
@@ -29757,7 +29760,7 @@ var DispatchTargetedMessagePayloadSchema = MessageEventPayloadSchema.extend({
|
|
|
29757
29760
|
var MessageDeliveryEventPayloadSchema = object({
|
|
29758
29761
|
message_id: string2().optional(),
|
|
29759
29762
|
attempt: number2().int().positive().optional(),
|
|
29760
|
-
delivery: _enum2(
|
|
29763
|
+
delivery: _enum2(DELIVERY_CAPABILITIES).optional(),
|
|
29761
29764
|
session_id: string2().optional(),
|
|
29762
29765
|
target: string2().optional(),
|
|
29763
29766
|
title: string2().optional(),
|
|
@@ -29848,6 +29851,16 @@ function documentOwnerValidation(requireArtifact, alwaysRequireArtifact = false)
|
|
|
29848
29851
|
message: alwaysRequireArtifact ? "Exactly one of issue and project is required; artifact or ref must name the document." : "Exactly one of issue and project is required; with project, artifact names the document."
|
|
29849
29852
|
};
|
|
29850
29853
|
}
|
|
29854
|
+
var commentValidation = (() => {
|
|
29855
|
+
const owner = documentOwnerValidation(true);
|
|
29856
|
+
return {
|
|
29857
|
+
check: (value) => {
|
|
29858
|
+
const input = value;
|
|
29859
|
+
return owner.check(value) && (input.turn === undefined || typeof input.reply_to_ask === "string");
|
|
29860
|
+
},
|
|
29861
|
+
message: `${owner.message} turn requires reply_to_ask.`
|
|
29862
|
+
};
|
|
29863
|
+
})();
|
|
29851
29864
|
var SPEC_SECTIONS = [
|
|
29852
29865
|
"Summary",
|
|
29853
29866
|
"Decisions needed",
|
|
@@ -29943,9 +29956,10 @@ var dispatchToolSpecs = [
|
|
|
29943
29956
|
occurrence: z.number({ int: true, min: 0 }).describe("Optional zero-based occurrence of quote.").optional(),
|
|
29944
29957
|
body: z.string({ max: 2000 }).describe("Review comment, at most 2,000 characters."),
|
|
29945
29958
|
reply_to: z.string().describe("Full id of a comment to reply to; replying to any comment in a thread continues that " + "thread (an ask's clarification thread included).").optional(),
|
|
29946
|
-
reply_to_ask: z.string().describe("Optional ask id to reply to, threading this comment under that question. Mutually " + "exclusive with reply_to.").optional()
|
|
29959
|
+
reply_to_ask: z.string().describe("Optional ask id to reply to, threading this comment under that question. Mutually " + "exclusive with reply_to.").optional(),
|
|
29960
|
+
turn: z.enum(["agent", "human"]).describe("Only with reply_to_ask: who holds the turn after this reply. agent: a progress note - " + "you keep the turn and the ask stays 'Waiting on agents' for the human; human (default): " + "you need the human to act - the ask returns to 'Waiting on you'.").optional()
|
|
29947
29961
|
}),
|
|
29948
|
-
validation:
|
|
29962
|
+
validation: commentValidation
|
|
29949
29963
|
},
|
|
29950
29964
|
{
|
|
29951
29965
|
name: "dispatch_suggest",
|
|
@@ -31616,7 +31630,7 @@ var TolerantInboundEnvelopeSchema = object({
|
|
|
31616
31630
|
}).passthrough();
|
|
31617
31631
|
var DispatchDeliveryRequestSchema = object({
|
|
31618
31632
|
attempt: number2().int().positive(),
|
|
31619
|
-
mode: _enum2(
|
|
31633
|
+
mode: _enum2(DELIVERY_CAPABILITIES)
|
|
31620
31634
|
});
|
|
31621
31635
|
var DispatchTargetedFrameSchema = object({
|
|
31622
31636
|
event: DispatchEventSchema,
|
|
@@ -33086,21 +33100,34 @@ async function executeDispatchTool(input) {
|
|
|
33086
33100
|
if (replyTo !== undefined && replyToAsk !== undefined) {
|
|
33087
33101
|
throw new Error("reply_to and reply_to_ask cannot both be set");
|
|
33088
33102
|
}
|
|
33103
|
+
const requestedTurn = optionalString(args, "turn");
|
|
33104
|
+
if (requestedTurn !== undefined && replyToAsk === undefined) {
|
|
33105
|
+
throw new Error("turn requires reply_to_ask");
|
|
33106
|
+
}
|
|
33107
|
+
if (requestedTurn !== undefined && requestedTurn !== "agent" && requestedTurn !== "human") {
|
|
33108
|
+
throw new Error("turn must be agent or human");
|
|
33109
|
+
}
|
|
33089
33110
|
const commentInput = {
|
|
33090
33111
|
body: stringArg(args, "body"),
|
|
33091
33112
|
...anchored === undefined ? {} : { anchor: anchored },
|
|
33092
33113
|
...replyTo === undefined ? {} : { reply_to: replyTo },
|
|
33093
33114
|
...replyToAsk === undefined ? {} : { ask_id: replyToAsk },
|
|
33115
|
+
...requestedTurn === undefined ? {} : { turn: requestedTurn },
|
|
33094
33116
|
actor
|
|
33095
33117
|
};
|
|
33096
33118
|
const comment = resolved?.owner.kind === "project" ? await client.artifactComment(resolved.artifact.id, commentInput) : await client.comment(issue(), commentInput);
|
|
33119
|
+
const askState = comment.turn === null ? "" : ` (ask now waiting on ${comment.turn})`;
|
|
33097
33120
|
return {
|
|
33098
|
-
text: `Posted comment ${comment.id}`,
|
|
33121
|
+
text: `Posted comment ${comment.id}${askState}`,
|
|
33099
33122
|
details: resolved === undefined ? {
|
|
33100
33123
|
issue: comment.issue_key,
|
|
33101
33124
|
topic: dispatchIssueSubject(issue(), ">"),
|
|
33102
|
-
comment: comment.id
|
|
33103
|
-
|
|
33125
|
+
comment: comment.id,
|
|
33126
|
+
...comment.turn === null ? {} : { ask_waiting_on: comment.turn }
|
|
33127
|
+
} : writeResultDetails(resolved, {
|
|
33128
|
+
comment: comment.id,
|
|
33129
|
+
...comment.turn === null ? {} : { ask_waiting_on: comment.turn }
|
|
33130
|
+
})
|
|
33104
33131
|
};
|
|
33105
33132
|
}
|
|
33106
33133
|
case "dispatch_suggest": {
|
|
@@ -33773,6 +33800,8 @@ async function copySessionID(copyToClipboard, sessionID) {
|
|
|
33773
33800
|
// extensions/envoy.ts
|
|
33774
33801
|
var codec2 = import_nats.StringCodec();
|
|
33775
33802
|
var NATS_RETRY_INTERVAL_MS = 15000;
|
|
33803
|
+
var ALL_CAPABILITIES = DELIVERY_CAPABILITIES;
|
|
33804
|
+
var CAPABILITIES_WITHOUT_BTW = DELIVERY_CAPABILITIES.filter((capability) => capability !== "btw");
|
|
33776
33805
|
var ROLE_CLAIM_ENTRY = "envoy-role-claim";
|
|
33777
33806
|
var OPEN_ASKS_TIMEOUT_MS = 3000;
|
|
33778
33807
|
function isRoleClaimEntry(entry) {
|
|
@@ -34031,7 +34060,7 @@ function envoyExtension(pi) {
|
|
|
34031
34060
|
topics: [...new Set([agentSubject(sessionID), ...subscriptions.keys()])],
|
|
34032
34061
|
port: 0,
|
|
34033
34062
|
title: activeSessionContext?.sessionManager.getSessionName?.() ?? "",
|
|
34034
|
-
capabilities: typeof pi.askEphemeral === "function" ?
|
|
34063
|
+
capabilities: typeof pi.askEphemeral === "function" ? ALL_CAPABILITIES : CAPABILITIES_WITHOUT_BTW,
|
|
34035
34064
|
driving: false,
|
|
34036
34065
|
selfSubscribed: true
|
|
34037
34066
|
});
|
|
@@ -42,6 +42,16 @@ agent's `envoy.json` to set the GitHub OAuth callback origin: the value must
|
|
|
42
42
|
be the exact URL humans type in their browser, and the GitHub App callback is
|
|
43
43
|
`<DISPATCH_SERVER_URL>/auth/callback`.
|
|
44
44
|
|
|
45
|
+
### Finding a route
|
|
46
|
+
|
|
47
|
+
The tools cover the everyday surface. For anything else, ask the server: `GET /api/v1` (no
|
|
48
|
+
credential) returns every route as `{method, path, auth, description}` sorted by path — `auth`
|
|
49
|
+
is `public`, `any` (a human or a bearer), `human` (a bearer gets `403 HUMAN_ONLY`), or `bearer`.
|
|
50
|
+
A path Dispatch does not serve under `/api` or `/v1` answers
|
|
51
|
+
`404 {"code":"NOT_FOUND","error":"no route for GET /v1/issues","hint":"GET /api/v1 lists every
|
|
52
|
+
route"}`; when you see that, you typed the path wrong — read the index rather than guessing. Every
|
|
53
|
+
`/api/v1` error body carries a `code`; branch on the code, never on the text.
|
|
54
|
+
|
|
45
55
|
## Writing a spec
|
|
46
56
|
|
|
47
57
|
A spec has two readers: the human who decides reads the top; the implementer who builds reads the
|
|
@@ -194,6 +204,14 @@ an answer: the human did not understand the question or needs more before choosi
|
|
|
194
204
|
in the same thread with `dispatch_comment({ reply_to_ask })`, or reword the question itself with `dispatch_edit_ask` when the wording
|
|
195
205
|
was the problem; either puts the ask back in front of them. Do not open a second ask.
|
|
196
206
|
|
|
207
|
+
Every reply to an open ask says whose turn it is next, and the Inbox files the ask by that, not by who spoke last. Your plain reply
|
|
208
|
+
(`turn` omitted, or `turn: "human"`) hands the turn to the human: the ask returns to their `Waiting on you`. When you are not done
|
|
209
|
+
yet — "dispatched two auditors, back with results", "checking the release branch, back shortly", any working-on-it note — reply with
|
|
210
|
+
`turn: "agent"`: the note lands in the thread, the ask stays under `Waiting on agents`, and the human is not told to act. Use
|
|
211
|
+
`turn: "agent"` for every progress note and `turn: "human"` (the default) only when you need them. A human's reply always hands the
|
|
212
|
+
turn to you. The result names the state (`ask now waiting on agent` / `human`), the delivered `comment.created` carries it as
|
|
213
|
+
`ask_waiting_on`, and every ask read carries it as `waiting_on`.
|
|
214
|
+
|
|
197
215
|
## Approval of a spec
|
|
198
216
|
|
|
199
217
|
Approval is a property of a document, not a question you phrase: a human approves a specific version, the way a pull-request
|
|
@@ -300,7 +318,7 @@ an ask block without a question or with a blank option is rejected with `INVALID
|
|
|
300
318
|
Add feedback with:
|
|
301
319
|
|
|
302
320
|
```ts
|
|
303
|
-
dispatch_comment({ issue?, project?, artifact?, ref?, quote?, occurrence?, body, reply_to?, reply_to_ask? })
|
|
321
|
+
dispatch_comment({ issue?, project?, artifact?, ref?, quote?, occurrence?, body, reply_to?, reply_to_ask?, turn? })
|
|
304
322
|
```
|
|
305
323
|
|
|
306
324
|
It returns issue or project-document owner details plus `comment` and, for writes, `topic`.
|
|
@@ -309,8 +327,10 @@ It returns issue or project-document owner details plus `comment` and, for write
|
|
|
309
327
|
for display. Omit both for a floating issue comment. A reply (`reply_to`/`reply_to_ask`) takes no
|
|
310
328
|
`quote`; it belongs to its parent's anchor. Reply to any comment in a thread; the server keeps
|
|
311
329
|
threads flat. A reply to a resolved thread reopens it. Use `reply_to_ask` to reply directly under a
|
|
312
|
-
question asked with `dispatch_ask
|
|
313
|
-
|
|
330
|
+
question asked with `dispatch_ask`; `turn` (only with `reply_to_ask`) says who holds the turn after
|
|
331
|
+
the reply — `agent` for a progress note that keeps the ask waiting on you, `human` (the default) when
|
|
332
|
+
the human needs to act; see [Asking](#asking). Comments are edited only by their author from the
|
|
333
|
+
dashboard. A delivered `comment.created` event carries the comment `id`; reply to it with
|
|
314
334
|
`dispatch_comment({ reply_to: <id> })`.
|
|
315
335
|
|
|
316
336
|
Propose an exact replacement instead of describing it:
|
|
@@ -395,7 +415,10 @@ dispatch_message({
|
|
|
395
415
|
delivery can post its answer automatically; use this call when the frame asks the primary agent to
|
|
396
416
|
reply. `dispatch_message` itself never carries `target` or `delivery`: agent-to-agent traffic goes
|
|
397
417
|
through Envoy or the hub. A bearer that targets over HTTP names its own session in `actor`
|
|
398
|
-
(`{kind: "session", id}`), and the card shows that session as the author.
|
|
418
|
+
(`{kind: "session", id}`), and the card shows that session as the author. `GET /api/v1/agents`
|
|
419
|
+
(any authenticated caller) lists live sessions with their capabilities (`aside`, `btw`, `steer`);
|
|
420
|
+
target only a session that advertises the mode you want. Sending to a session with no issue
|
|
421
|
+
(`POST /api/v1/agents/{session_id}/messages`) stays human-only.
|
|
399
422
|
|
|
400
423
|
## What comes back
|
|
401
424
|
|
|
@@ -90,6 +90,16 @@ envoy_send(
|
|
|
90
90
|
)
|
|
91
91
|
```
|
|
92
92
|
|
|
93
|
+
### Delivery capabilities
|
|
94
|
+
|
|
95
|
+
Each session row from `envoy_sessions` carries `capabilities`, the targeted-delivery modes that
|
|
96
|
+
session's host honours: `aside` (a message queued beside the model's work), `btw` (an ephemeral
|
|
97
|
+
question the host answers without disturbing the current turn), and `steer` (an interjection at
|
|
98
|
+
the next tool boundary). An OMP session advertises `aside`, `btw`, and `steer` (`aside` and
|
|
99
|
+
`steer` on a host without `askEphemeral`); a Claude Code session advertises `aside` only, because
|
|
100
|
+
its channel notifications queue for the next turn. Target a session only with a mode it
|
|
101
|
+
advertises.
|
|
102
|
+
|
|
93
103
|
## Waiting for CI or a merge
|
|
94
104
|
|
|
95
105
|
Subscribe to `notifications.github.example-org.example-repo.pr.42.>` and end the turn. The single
|