@sjawhar/pi-legion-envoy 1.60.1 → 1.61.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 +56 -7
- package/dist/legion.js +350 -27
- package/package.json +2 -2
package/dist/envoy.js
CHANGED
|
@@ -29849,6 +29849,29 @@ var AskFollowerEventPayloadSchema = object({
|
|
|
29849
29849
|
session_id: string2().optional(),
|
|
29850
29850
|
by: object({ kind: string2(), id: string2().optional() }).passthrough().optional()
|
|
29851
29851
|
});
|
|
29852
|
+
// ../contracts/src/dispatch-href.ts
|
|
29853
|
+
function itemFromSearch(search) {
|
|
29854
|
+
const params = new URLSearchParams(search);
|
|
29855
|
+
const ask = params.get("ask");
|
|
29856
|
+
const comment = params.get("comment");
|
|
29857
|
+
if (ask === null && comment === null) {
|
|
29858
|
+
return;
|
|
29859
|
+
}
|
|
29860
|
+
if (ask !== null && comment !== null) {
|
|
29861
|
+
return null;
|
|
29862
|
+
}
|
|
29863
|
+
const raw = ask ?? comment ?? "";
|
|
29864
|
+
if (raw === "" || raw.includes("/")) {
|
|
29865
|
+
return null;
|
|
29866
|
+
}
|
|
29867
|
+
let id;
|
|
29868
|
+
try {
|
|
29869
|
+
id = decodeURIComponent(raw);
|
|
29870
|
+
} catch {
|
|
29871
|
+
return null;
|
|
29872
|
+
}
|
|
29873
|
+
return ask === null ? { id, kind: "comment" } : { id, kind: "ask" };
|
|
29874
|
+
}
|
|
29852
29875
|
// ../contracts/src/dispatch-snippet.ts
|
|
29853
29876
|
var HTML_ENTITIES = [
|
|
29854
29877
|
["<", "<"],
|
|
@@ -30449,6 +30472,10 @@ var handoffMessageSchema = object({
|
|
|
30449
30472
|
// ../contracts/src/subject.ts
|
|
30450
30473
|
var AGENT_TOPIC_PREFIX = "notifications.agent.";
|
|
30451
30474
|
var ROLE_TOPIC_PREFIX = "notifications.role.";
|
|
30475
|
+
var LEGION_NOTICE_TOPIC_PREFIX = "notifications.legion.";
|
|
30476
|
+
function legionNoticeSubject(project, issue) {
|
|
30477
|
+
return `${LEGION_NOTICE_TOPIC_PREFIX}${project}.${issue}`;
|
|
30478
|
+
}
|
|
30452
30479
|
var DISPATCH_TOPIC_PREFIX = "notifications.dispatch.";
|
|
30453
30480
|
var DISPATCH_ISSUE_TOPIC_PREFIX = `${DISPATCH_TOPIC_PREFIX}issue.`;
|
|
30454
30481
|
var DISPATCH_DOCUMENT_TOPIC_PREFIX = `${DISPATCH_TOPIC_PREFIX}document.`;
|
|
@@ -33004,6 +33031,13 @@ function dispatchRefFromUrl(value, serverUrl) {
|
|
|
33004
33031
|
if (issuePage) {
|
|
33005
33032
|
const [, key, page, artifact, ask, comment, message] = issuePage;
|
|
33006
33033
|
const version = versionOf("v");
|
|
33034
|
+
if (page === "spec" || artifact !== undefined) {
|
|
33035
|
+
const item = itemFromSearch(url.search);
|
|
33036
|
+
if (item === null)
|
|
33037
|
+
return;
|
|
33038
|
+
if (item !== undefined)
|
|
33039
|
+
return `dispatch://${key}/${item.kind}/${item.id}`;
|
|
33040
|
+
}
|
|
33007
33041
|
if (page === "spec" && version !== "")
|
|
33008
33042
|
return `dispatch://${key}/artifact/spec${version}`;
|
|
33009
33043
|
if (page !== undefined)
|
|
@@ -33024,13 +33058,10 @@ function dispatchRefFromUrl(value, serverUrl) {
|
|
|
33024
33058
|
return;
|
|
33025
33059
|
const [, project, slug] = documentPage;
|
|
33026
33060
|
const document = `dispatch://${project}/artifact/${decodeURIComponent(slug ?? "")}${versionOf("version")}`;
|
|
33027
|
-
const
|
|
33028
|
-
if (
|
|
33029
|
-
return
|
|
33030
|
-
|
|
33031
|
-
if (comment !== null)
|
|
33032
|
-
return `${document}/comment/${comment}`;
|
|
33033
|
-
return document;
|
|
33061
|
+
const item = itemFromSearch(url.search);
|
|
33062
|
+
if (item === null)
|
|
33063
|
+
return;
|
|
33064
|
+
return item === undefined ? document : `${document}/${item.kind}/${item.id}`;
|
|
33034
33065
|
}
|
|
33035
33066
|
var refGrammarProblem = "ref must be a valid dispatch:// reference such as dispatch://KEY-1, " + "dispatch://KEY-1/ask/<uuid>, dispatch://KEY-1/comment/<uuid>, " + "dispatch://KEY-1/message/<uuid>, dispatch://KEY-1/artifact/<slug>, or " + "dispatch://PROJECT/artifact/<document-ref> (an artifact id, slug, or filename); " + "a dashboard URL on this Dispatch server is accepted too";
|
|
33036
33067
|
var ownerRequiredProblem = "issue is required; supply issue or set LEGION_ISSUE";
|
|
@@ -34629,6 +34660,13 @@ async function claimEnvoyRole(sessionID, role, context) {
|
|
|
34629
34660
|
throw new Error("Envoy has no bound instance for a role claim");
|
|
34630
34661
|
await instance.claim(sessionID, role, context);
|
|
34631
34662
|
}
|
|
34663
|
+
async function subscribeLegionNotice(sessionID, topic, context) {
|
|
34664
|
+
const bridge = legionRoleClaimBridge();
|
|
34665
|
+
const instance = bridge.instances.findLast((candidate) => candidate.sessionID() === sessionID) ?? bridge.instances.at(-1);
|
|
34666
|
+
if (instance === undefined)
|
|
34667
|
+
throw new Error("Envoy has no bound instance for a notice subscription");
|
|
34668
|
+
await instance.subscribe(sessionID, topic, context);
|
|
34669
|
+
}
|
|
34632
34670
|
function onEnvoyRoleRegained(listener) {
|
|
34633
34671
|
legionRoleClaimBridge().regained = listener;
|
|
34634
34672
|
}
|
|
@@ -35163,8 +35201,19 @@ function envoyExtension(pi) {
|
|
|
35163
35201
|
await registerSession();
|
|
35164
35202
|
await setEnvoyRole(role);
|
|
35165
35203
|
};
|
|
35204
|
+
const subscribeNotice = async (targetSessionID, topic, callerContext) => {
|
|
35205
|
+
const context = callerContext ?? activeSessionContext;
|
|
35206
|
+
if (context === undefined || context.sessionManager.getSessionId() !== targetSessionID) {
|
|
35207
|
+
throw new Error(`Envoy has no active session for Legion notice subscription: ${targetSessionID}`);
|
|
35208
|
+
}
|
|
35209
|
+
if (sessionID !== targetSessionID)
|
|
35210
|
+
await establishSession(context);
|
|
35211
|
+
await subscribe(topic);
|
|
35212
|
+
await registerSession();
|
|
35213
|
+
};
|
|
35166
35214
|
const claimInstance = {
|
|
35167
35215
|
claim,
|
|
35216
|
+
subscribe: subscribeNotice,
|
|
35168
35217
|
sessionID: () => activeSessionContext?.sessionManager.getSessionId() ?? sessionID
|
|
35169
35218
|
};
|
|
35170
35219
|
bridge.instances.push(claimInstance);
|
package/dist/legion.js
CHANGED
|
@@ -16113,7 +16113,7 @@ var require_mod4 = __commonJS(function(exports) {
|
|
|
16113
16113
|
|
|
16114
16114
|
// extensions/legion.ts
|
|
16115
16115
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
16116
|
-
import
|
|
16116
|
+
import path5 from "path";
|
|
16117
16117
|
|
|
16118
16118
|
// ../envoy-client/src/defaults.ts
|
|
16119
16119
|
var DEFAULT_ENVOY_URL = "http://127.0.0.1:9020";
|
|
@@ -16144,7 +16144,7 @@ import { logger } from "@oh-my-pi/pi-utils";
|
|
|
16144
16144
|
// package.json
|
|
16145
16145
|
var package_default = {
|
|
16146
16146
|
name: "@sjawhar/pi-legion-envoy",
|
|
16147
|
-
version: "1.
|
|
16147
|
+
version: "1.61.0",
|
|
16148
16148
|
type: "module",
|
|
16149
16149
|
omp: {
|
|
16150
16150
|
extensions: [
|
|
@@ -16157,7 +16157,7 @@ var package_default = {
|
|
|
16157
16157
|
},
|
|
16158
16158
|
legion: {
|
|
16159
16159
|
daemonApiVersion: 8,
|
|
16160
|
-
goDaemonApiVersion:
|
|
16160
|
+
goDaemonApiVersion: 3
|
|
16161
16161
|
},
|
|
16162
16162
|
repository: {
|
|
16163
16163
|
type: "git",
|
|
@@ -29921,6 +29921,29 @@ var AskFollowerEventPayloadSchema = object({
|
|
|
29921
29921
|
session_id: string2().optional(),
|
|
29922
29922
|
by: object({ kind: string2(), id: string2().optional() }).passthrough().optional()
|
|
29923
29923
|
});
|
|
29924
|
+
// ../contracts/src/dispatch-href.ts
|
|
29925
|
+
function itemFromSearch(search) {
|
|
29926
|
+
const params = new URLSearchParams(search);
|
|
29927
|
+
const ask = params.get("ask");
|
|
29928
|
+
const comment = params.get("comment");
|
|
29929
|
+
if (ask === null && comment === null) {
|
|
29930
|
+
return;
|
|
29931
|
+
}
|
|
29932
|
+
if (ask !== null && comment !== null) {
|
|
29933
|
+
return null;
|
|
29934
|
+
}
|
|
29935
|
+
const raw = ask ?? comment ?? "";
|
|
29936
|
+
if (raw === "" || raw.includes("/")) {
|
|
29937
|
+
return null;
|
|
29938
|
+
}
|
|
29939
|
+
let id;
|
|
29940
|
+
try {
|
|
29941
|
+
id = decodeURIComponent(raw);
|
|
29942
|
+
} catch {
|
|
29943
|
+
return null;
|
|
29944
|
+
}
|
|
29945
|
+
return ask === null ? { id, kind: "comment" } : { id, kind: "ask" };
|
|
29946
|
+
}
|
|
29924
29947
|
// ../contracts/src/dispatch-snippet.ts
|
|
29925
29948
|
var HTML_ENTITIES = [
|
|
29926
29949
|
["<", "<"],
|
|
@@ -30521,6 +30544,10 @@ var handoffMessageSchema = object({
|
|
|
30521
30544
|
// ../contracts/src/subject.ts
|
|
30522
30545
|
var AGENT_TOPIC_PREFIX = "notifications.agent.";
|
|
30523
30546
|
var ROLE_TOPIC_PREFIX = "notifications.role.";
|
|
30547
|
+
var LEGION_NOTICE_TOPIC_PREFIX = "notifications.legion.";
|
|
30548
|
+
function legionNoticeSubject(project, issue) {
|
|
30549
|
+
return `${LEGION_NOTICE_TOPIC_PREFIX}${project}.${issue}`;
|
|
30550
|
+
}
|
|
30524
30551
|
var DISPATCH_TOPIC_PREFIX = "notifications.dispatch.";
|
|
30525
30552
|
var DISPATCH_ISSUE_TOPIC_PREFIX = `${DISPATCH_TOPIC_PREFIX}issue.`;
|
|
30526
30553
|
var DISPATCH_DOCUMENT_TOPIC_PREFIX = `${DISPATCH_TOPIC_PREFIX}document.`;
|
|
@@ -31160,6 +31187,13 @@ async function claimEnvoyRole(sessionID, role, context) {
|
|
|
31160
31187
|
throw new Error("Envoy has no bound instance for a role claim");
|
|
31161
31188
|
await instance.claim(sessionID, role, context);
|
|
31162
31189
|
}
|
|
31190
|
+
async function subscribeLegionNotice(sessionID, topic, context) {
|
|
31191
|
+
const bridge = legionRoleClaimBridge();
|
|
31192
|
+
const instance = bridge.instances.findLast((candidate) => candidate.sessionID() === sessionID) ?? bridge.instances.at(-1);
|
|
31193
|
+
if (instance === undefined)
|
|
31194
|
+
throw new Error("Envoy has no bound instance for a notice subscription");
|
|
31195
|
+
await instance.subscribe(sessionID, topic, context);
|
|
31196
|
+
}
|
|
31163
31197
|
function onEnvoyRoleRegained(listener) {
|
|
31164
31198
|
legionRoleClaimBridge().regained = listener;
|
|
31165
31199
|
}
|
|
@@ -31254,6 +31288,9 @@ async function writeGrantFile(file, grantId) {
|
|
|
31254
31288
|
}
|
|
31255
31289
|
}
|
|
31256
31290
|
|
|
31291
|
+
// src/legion/go-bootstrap.ts
|
|
31292
|
+
import path3 from "path";
|
|
31293
|
+
|
|
31257
31294
|
// ../contracts/src/legion-go-api.ts
|
|
31258
31295
|
var nonEmptyString2 = string2().min(1);
|
|
31259
31296
|
var timestamp = exports_iso.datetime({ offset: true });
|
|
@@ -31323,10 +31360,18 @@ var legionGoSlotView = strictObject({
|
|
|
31323
31360
|
index: number2().int().nonnegative(),
|
|
31324
31361
|
admittedAt: timestamp
|
|
31325
31362
|
});
|
|
31363
|
+
var legionGoPendingStatusWrite = strictObject({
|
|
31364
|
+
issue: nonEmptyString2,
|
|
31365
|
+
payload: record(string2(), unknown()),
|
|
31366
|
+
attempts: number2().int().nonnegative(),
|
|
31367
|
+
nextAt: timestamp,
|
|
31368
|
+
lastError: string2().optional()
|
|
31369
|
+
});
|
|
31326
31370
|
var legionGoIssue = strictObject({
|
|
31327
31371
|
key: nonEmptyString2,
|
|
31328
31372
|
generation: number2().int().nonnegative(),
|
|
31329
31373
|
phase: _enum2(LEGION_GO_PHASES),
|
|
31374
|
+
status: nonEmptyString2,
|
|
31330
31375
|
architect: legionGoClaimView.optional(),
|
|
31331
31376
|
workers: partialRecord(_enum2(LEGION_ROLES), legionGoPhaseView),
|
|
31332
31377
|
pullRequest: legionGoPullRequestView.optional(),
|
|
@@ -31348,7 +31393,8 @@ var legionGoAdmission = strictObject({
|
|
|
31348
31393
|
var LegionGoStateResponse = strictObject({
|
|
31349
31394
|
daemon: goDaemonInfo,
|
|
31350
31395
|
admission: legionGoAdmission,
|
|
31351
|
-
issues: record(string2(), legionGoIssue)
|
|
31396
|
+
issues: record(string2(), legionGoIssue),
|
|
31397
|
+
pendingStatusWrites: array(legionGoPendingStatusWrite)
|
|
31352
31398
|
});
|
|
31353
31399
|
var LegionGoRegisterResponse = strictObject({
|
|
31354
31400
|
claimToken: nonEmptyString2,
|
|
@@ -31358,9 +31404,10 @@ var LegionGoRegisterResponse = strictObject({
|
|
|
31358
31404
|
generation: number2().int().positive(),
|
|
31359
31405
|
secret: nonEmptyString2
|
|
31360
31406
|
});
|
|
31361
|
-
var LegionGoErrorResponse =
|
|
31362
|
-
error: nonEmptyString2
|
|
31363
|
-
})
|
|
31407
|
+
var LegionGoErrorResponse = union([
|
|
31408
|
+
strictObject({ error: nonEmptyString2 }),
|
|
31409
|
+
strictObject({ code: nonEmptyString2, error: nonEmptyString2 })
|
|
31410
|
+
]);
|
|
31364
31411
|
var legionGoDeliveryView = strictObject({
|
|
31365
31412
|
id: nonEmptyString2,
|
|
31366
31413
|
task: nonEmptyString2,
|
|
@@ -31389,18 +31436,81 @@ var LegionGoOperatorClaimResponse = strictObject({
|
|
|
31389
31436
|
var LegionGoOperatorClaimsResponse = strictObject({
|
|
31390
31437
|
claims: array(LegionGoOperatorClaimResponse)
|
|
31391
31438
|
});
|
|
31439
|
+
var LegionGoGrantResponse = strictObject({
|
|
31440
|
+
grantId: nonEmptyString2,
|
|
31441
|
+
expiresAt: timestamp
|
|
31442
|
+
});
|
|
31443
|
+
var LegionGoGitHubTokenResponse = strictObject({
|
|
31444
|
+
token: nonEmptyString2,
|
|
31445
|
+
appLogin: string2().endsWith("[bot]")
|
|
31446
|
+
});
|
|
31447
|
+
var LegionGoGitCredentialResponse = strictObject({
|
|
31448
|
+
username: literal("x-access-token"),
|
|
31449
|
+
password: nonEmptyString2
|
|
31450
|
+
});
|
|
31451
|
+
var LegionGoEmptyResponse = strictObject({});
|
|
31452
|
+
var LegionGoWaveReleaseResponse = strictObject({
|
|
31453
|
+
released: array(nonEmptyString2)
|
|
31454
|
+
});
|
|
31455
|
+
var LegionGoGrantRequest = strictObject({
|
|
31456
|
+
sessionId: nonEmptyString2,
|
|
31457
|
+
secret: nonEmptyString2,
|
|
31458
|
+
tree: nonEmptyString2,
|
|
31459
|
+
issue: nonEmptyString2
|
|
31460
|
+
});
|
|
31461
|
+
var LegionGoGrantCredentialRequest = strictObject({
|
|
31462
|
+
grantId: nonEmptyString2
|
|
31463
|
+
});
|
|
31464
|
+
var LegionGoHandoffCompleteRequest = strictObject({
|
|
31465
|
+
grantId: nonEmptyString2,
|
|
31466
|
+
summary: nonEmptyString2,
|
|
31467
|
+
verdict: string2(),
|
|
31468
|
+
ready: boolean2(),
|
|
31469
|
+
commit: nonEmptyString2
|
|
31470
|
+
});
|
|
31471
|
+
var LegionGoIssueStatusRequest = strictObject({
|
|
31472
|
+
grantId: nonEmptyString2,
|
|
31473
|
+
issue: nonEmptyString2,
|
|
31474
|
+
status: _enum2(["todo", "backlog", "icebox"])
|
|
31475
|
+
});
|
|
31476
|
+
var LegionGoGateRegisterRequest = strictObject({
|
|
31477
|
+
grantId: nonEmptyString2,
|
|
31478
|
+
issue: nonEmptyString2,
|
|
31479
|
+
artifactId: uuid2(),
|
|
31480
|
+
version: number2().int().positive()
|
|
31481
|
+
});
|
|
31482
|
+
var LegionGoWaveReleaseRequest = strictObject({
|
|
31483
|
+
grantId: nonEmptyString2,
|
|
31484
|
+
issues: array(nonEmptyString2)
|
|
31485
|
+
});
|
|
31486
|
+
var LegionGoPhaseBackwardRequest = strictObject({
|
|
31487
|
+
grantId: nonEmptyString2,
|
|
31488
|
+
to: _enum2(LEGION_GO_PHASES),
|
|
31489
|
+
reason: nonEmptyString2
|
|
31490
|
+
});
|
|
31491
|
+
var LegionGoPhaseRetryRequest = strictObject({
|
|
31492
|
+
grantId: nonEmptyString2,
|
|
31493
|
+
issue: nonEmptyString2,
|
|
31494
|
+
decision: _enum2(["retry", "escalate"])
|
|
31495
|
+
});
|
|
31496
|
+
var LegionGoSignOffRequest = strictObject({
|
|
31497
|
+
grantId: nonEmptyString2,
|
|
31498
|
+
issue: nonEmptyString2
|
|
31499
|
+
});
|
|
31392
31500
|
|
|
31393
31501
|
// src/legion/go-daemon-client.ts
|
|
31394
31502
|
class LegionGoDaemonApiError extends Error {
|
|
31395
31503
|
method;
|
|
31396
31504
|
path;
|
|
31397
31505
|
status;
|
|
31506
|
+
code;
|
|
31398
31507
|
detail;
|
|
31399
|
-
constructor(method, path, status, detail) {
|
|
31400
|
-
super(`${method} ${path} failed with ${status}: ${detail}`);
|
|
31508
|
+
constructor(method, path, status, code, detail) {
|
|
31509
|
+
super(`${method} ${path} failed with ${status}${code === undefined ? "" : ` ${code}`}: ${detail}`);
|
|
31401
31510
|
this.method = method;
|
|
31402
31511
|
this.path = path;
|
|
31403
31512
|
this.status = status;
|
|
31513
|
+
this.code = code;
|
|
31404
31514
|
this.detail = detail;
|
|
31405
31515
|
this.name = "LegionGoDaemonApiError";
|
|
31406
31516
|
}
|
|
@@ -31444,7 +31554,11 @@ function createLegionGoDaemonClient(baseUrl, fetchFn = fetch) {
|
|
|
31444
31554
|
if (response.ok)
|
|
31445
31555
|
return { status: response.status, text };
|
|
31446
31556
|
const refusal = parseStrictly(LegionGoErrorResponse, text);
|
|
31447
|
-
|
|
31557
|
+
if ("value" in refusal) {
|
|
31558
|
+
const code = "code" in refusal.value && typeof refusal.value.code === "string" ? refusal.value.code : undefined;
|
|
31559
|
+
throw new LegionGoDaemonApiError(method, path, response.status, code, refusal.value.error);
|
|
31560
|
+
}
|
|
31561
|
+
throw new LegionGoDaemonApiError(method, path, response.status, undefined, `${JSON.stringify(text)} is not the Go daemon's refusal shape (${refusal.problem})`);
|
|
31448
31562
|
};
|
|
31449
31563
|
const read = async (method, path, schema, body) => {
|
|
31450
31564
|
const { status, text } = await call(method, path, body);
|
|
@@ -31454,6 +31568,7 @@ function createLegionGoDaemonClient(baseUrl, fetchFn = fetch) {
|
|
|
31454
31568
|
}
|
|
31455
31569
|
return parsed.value;
|
|
31456
31570
|
};
|
|
31571
|
+
const post = (path, request, response, body) => read("POST", path, response, request.parse(body));
|
|
31457
31572
|
const acknowledge = async (path, body) => {
|
|
31458
31573
|
const { status, text } = await call("POST", path, body);
|
|
31459
31574
|
if (status !== 204 || text !== "") {
|
|
@@ -31464,7 +31579,18 @@ function createLegionGoDaemonClient(baseUrl, fetchFn = fetch) {
|
|
|
31464
31579
|
state: () => read("GET", "/legion/v1/state", LegionGoStateResponse),
|
|
31465
31580
|
register: (input) => read("POST", "/legion/v1/claims/register", LegionGoRegisterResponse, input),
|
|
31466
31581
|
ready: (input) => acknowledge("/legion/v1/claims/ready", input),
|
|
31467
|
-
exit: (input) => acknowledge("/legion/v1/claims/exit", input)
|
|
31582
|
+
exit: (input) => acknowledge("/legion/v1/claims/exit", input),
|
|
31583
|
+
grant: (input) => post("/legion/v1/grants", LegionGoGrantRequest, LegionGoGrantResponse, input),
|
|
31584
|
+
githubToken: (input) => post("/legion/v1/gh-token", LegionGoGrantCredentialRequest, LegionGoGitHubTokenResponse, input),
|
|
31585
|
+
gitCredential: (input) => post("/legion/v1/git-credential", LegionGoGrantCredentialRequest, LegionGoGitCredentialResponse, input),
|
|
31586
|
+
provisioningCredential: (input) => post("/legion/v1/provisioning-credential", LegionGoGrantCredentialRequest, LegionGoGitHubTokenResponse, input),
|
|
31587
|
+
handoffComplete: (input) => post("/legion/v1/handoff/complete", LegionGoHandoffCompleteRequest, LegionGoEmptyResponse, input),
|
|
31588
|
+
issueStatus: (input) => post("/legion/v1/issues/status", LegionGoIssueStatusRequest, LegionGoEmptyResponse, input),
|
|
31589
|
+
gateRegister: (input) => post("/legion/v1/gates/register", LegionGoGateRegisterRequest, LegionGoEmptyResponse, input),
|
|
31590
|
+
waveRelease: (input) => post("/legion/v1/waves/release", LegionGoWaveReleaseRequest, LegionGoWaveReleaseResponse, input),
|
|
31591
|
+
phaseBackward: (input) => post("/legion/v1/phase/backward", LegionGoPhaseBackwardRequest, LegionGoEmptyResponse, input),
|
|
31592
|
+
phaseRetry: (input) => post("/legion/v1/phase/retry", LegionGoPhaseRetryRequest, LegionGoEmptyResponse, input),
|
|
31593
|
+
signOff: (input) => post("/legion/v1/signoff", LegionGoSignOffRequest, LegionGoEmptyResponse, input)
|
|
31468
31594
|
};
|
|
31469
31595
|
}
|
|
31470
31596
|
|
|
@@ -31569,7 +31695,7 @@ async function bootstrapGoClaim(context, state) {
|
|
|
31569
31695
|
if (existing !== undefined)
|
|
31570
31696
|
return existing;
|
|
31571
31697
|
const bootToken = requiredSecret(process.env, "LEGION_BOOT_TOKEN");
|
|
31572
|
-
const daemon =
|
|
31698
|
+
const daemon = state.daemon();
|
|
31573
31699
|
const bootstrap = (async () => {
|
|
31574
31700
|
const { sessionFile, agentId } = await state.persistedTranscript(context);
|
|
31575
31701
|
state.recordBootstrappedSession(sessionFile);
|
|
@@ -31587,7 +31713,9 @@ async function bootstrapGoClaim(context, state) {
|
|
|
31587
31713
|
generation: claim.generation
|
|
31588
31714
|
};
|
|
31589
31715
|
try {
|
|
31590
|
-
|
|
31716
|
+
const stateDir = requiredEnvironment(process.env, "LEGION_STATE_DIR");
|
|
31717
|
+
await exportJjSessionAttribution(sessionFile, stateDir);
|
|
31718
|
+
process.env.LEGION_GRANT_FILE = path3.join(stateDir, "secrets", `${claim.claimToken}-grant`);
|
|
31591
31719
|
state.setCapability({
|
|
31592
31720
|
kind: "phase-worker",
|
|
31593
31721
|
sessionID,
|
|
@@ -31598,6 +31726,7 @@ async function bootstrapGoClaim(context, state) {
|
|
|
31598
31726
|
secret: claim.secret
|
|
31599
31727
|
});
|
|
31600
31728
|
await claimEnvoyRole(sessionID, claim.claimToken, context);
|
|
31729
|
+
await subscribeLegionNotice(sessionID, legionNoticeSubject(requiredEnvironment(process.env, "LEGION_PROJECT"), claim.role === "architect" ? claim.tree : claim.issue), context);
|
|
31601
31730
|
await callGoReadyWithRetry("claims/ready", () => daemon.ready(ready));
|
|
31602
31731
|
onEnvoyRoleRegained(async (role, reason) => {
|
|
31603
31732
|
if (role !== claim.claimToken)
|
|
@@ -31623,9 +31752,6 @@ async function bootstrapGoClaim(context, state) {
|
|
|
31623
31752
|
}
|
|
31624
31753
|
}
|
|
31625
31754
|
|
|
31626
|
-
// src/legion/tools.ts
|
|
31627
|
-
import { randomUUID as randomUUID2 } from "crypto";
|
|
31628
|
-
|
|
31629
31755
|
// src/tool-result.ts
|
|
31630
31756
|
function toolSuccess(text, details = {}) {
|
|
31631
31757
|
return { content: [{ type: "text", text }], details };
|
|
@@ -31634,8 +31760,167 @@ function toolFailure(error) {
|
|
|
31634
31760
|
return { content: [{ type: "text", text: messageFor(error) }], details: {}, isError: true };
|
|
31635
31761
|
}
|
|
31636
31762
|
|
|
31637
|
-
// src/legion/tools.ts
|
|
31763
|
+
// src/legion/go-tools.ts
|
|
31764
|
+
var OPERATIONS = {
|
|
31765
|
+
architect: [
|
|
31766
|
+
"register_gate",
|
|
31767
|
+
"release_children",
|
|
31768
|
+
"request_backward_move",
|
|
31769
|
+
"retry_or_escalate",
|
|
31770
|
+
"sign_off",
|
|
31771
|
+
"read_record"
|
|
31772
|
+
],
|
|
31773
|
+
"phase-worker": ["request_backward_move", "read_record"]
|
|
31774
|
+
};
|
|
31775
|
+
var OPERATION_FIELDS = {
|
|
31776
|
+
register_gate: ["issue", "artifactId", "version"],
|
|
31777
|
+
release_children: ["issues"],
|
|
31778
|
+
request_backward_move: ["to", "reason"],
|
|
31779
|
+
retry_or_escalate: ["issue", "decision"],
|
|
31780
|
+
sign_off: ["issue"],
|
|
31781
|
+
read_record: ["issue"]
|
|
31782
|
+
};
|
|
31638
31783
|
var jsonSuccess = (details) => toolSuccess(JSON.stringify(details), details);
|
|
31784
|
+
function toolSchema(pi) {
|
|
31785
|
+
const z = pi.zod;
|
|
31786
|
+
return z.object({
|
|
31787
|
+
op: z.enum([
|
|
31788
|
+
"register_gate",
|
|
31789
|
+
"release_children",
|
|
31790
|
+
"request_backward_move",
|
|
31791
|
+
"retry_or_escalate",
|
|
31792
|
+
"sign_off",
|
|
31793
|
+
"read_record"
|
|
31794
|
+
]),
|
|
31795
|
+
issue: z.string().optional(),
|
|
31796
|
+
artifactId: z.string().optional(),
|
|
31797
|
+
version: z.number().optional(),
|
|
31798
|
+
issues: z.array(z.string()).optional(),
|
|
31799
|
+
to: z.enum(LEGION_GO_PHASES).optional(),
|
|
31800
|
+
reason: z.string().optional(),
|
|
31801
|
+
decision: z.enum(["retry", "escalate"]).optional()
|
|
31802
|
+
});
|
|
31803
|
+
}
|
|
31804
|
+
function requiredString(parameters, operation, name) {
|
|
31805
|
+
const value = parameters[name];
|
|
31806
|
+
if (typeof value !== "string" || value.trim() === "")
|
|
31807
|
+
throw new Error(`${operation} requires ${name}`);
|
|
31808
|
+
return value;
|
|
31809
|
+
}
|
|
31810
|
+
function assertOperationInput(parameters, operation) {
|
|
31811
|
+
const fields = OPERATION_FIELDS[operation];
|
|
31812
|
+
if (fields === undefined)
|
|
31813
|
+
throw new Error(`Unsupported Go Legion operation: ${operation}`);
|
|
31814
|
+
for (const name of Object.keys(parameters)) {
|
|
31815
|
+
if (name !== "op" && !fields.includes(name)) {
|
|
31816
|
+
throw new Error(`${operation} does not accept field "${name}"`);
|
|
31817
|
+
}
|
|
31818
|
+
}
|
|
31819
|
+
}
|
|
31820
|
+
async function grantFor(daemon, session) {
|
|
31821
|
+
if (session.secret === undefined) {
|
|
31822
|
+
throw new Error("Go Legion session has no registered claim capability");
|
|
31823
|
+
}
|
|
31824
|
+
return (await daemon.grant({
|
|
31825
|
+
sessionId: session.sessionId,
|
|
31826
|
+
secret: session.secret,
|
|
31827
|
+
tree: session.tree,
|
|
31828
|
+
issue: session.issue
|
|
31829
|
+
})).grantId;
|
|
31830
|
+
}
|
|
31831
|
+
function recordFrom(state, issue) {
|
|
31832
|
+
const record = state.issues[issue];
|
|
31833
|
+
if (record === undefined)
|
|
31834
|
+
throw new Error(`The Go daemon has no record for ${issue}`);
|
|
31835
|
+
return record;
|
|
31836
|
+
}
|
|
31837
|
+
function createGoLegionTool(deps) {
|
|
31838
|
+
const { pi, daemon, session } = deps;
|
|
31839
|
+
return {
|
|
31840
|
+
name: "legion",
|
|
31841
|
+
label: "legion",
|
|
31842
|
+
description: "Perform the workflow operation the Go Legion daemon assigned this role. The daemon advances phases; this tool cannot spawn workers.",
|
|
31843
|
+
defaultInactive: true,
|
|
31844
|
+
parameters: toolSchema(pi),
|
|
31845
|
+
execute: async (_id, parameters, _signal, _onUpdate, context) => {
|
|
31846
|
+
try {
|
|
31847
|
+
const active = session(context);
|
|
31848
|
+
const operation = requiredString(parameters, "legion", "op");
|
|
31849
|
+
if (!OPERATIONS[active.kind].includes(operation)) {
|
|
31850
|
+
throw new Error(`${operation} is not available to a ${active.kind} session`);
|
|
31851
|
+
}
|
|
31852
|
+
assertOperationInput(parameters, operation);
|
|
31853
|
+
const client = daemon();
|
|
31854
|
+
switch (operation) {
|
|
31855
|
+
case "read_record": {
|
|
31856
|
+
const issue = requiredString(parameters, operation, "issue");
|
|
31857
|
+
return jsonSuccess({ record: recordFrom(await client.state(), issue) });
|
|
31858
|
+
}
|
|
31859
|
+
case "register_gate": {
|
|
31860
|
+
const version = parameters.version;
|
|
31861
|
+
if (typeof version !== "number" || !Number.isSafeInteger(version) || version < 1) {
|
|
31862
|
+
throw new Error("register_gate requires a positive integer version");
|
|
31863
|
+
}
|
|
31864
|
+
const grantId = await grantFor(client, active);
|
|
31865
|
+
await client.gateRegister({
|
|
31866
|
+
grantId,
|
|
31867
|
+
issue: requiredString(parameters, operation, "issue"),
|
|
31868
|
+
artifactId: requiredString(parameters, operation, "artifactId"),
|
|
31869
|
+
version
|
|
31870
|
+
});
|
|
31871
|
+
return jsonSuccess({});
|
|
31872
|
+
}
|
|
31873
|
+
case "release_children": {
|
|
31874
|
+
const issues = parameters.issues;
|
|
31875
|
+
if (!Array.isArray(issues) || !issues.every((issue) => typeof issue === "string")) {
|
|
31876
|
+
throw new Error("release_children requires issues");
|
|
31877
|
+
}
|
|
31878
|
+
const grantId = await grantFor(client, active);
|
|
31879
|
+
return jsonSuccess(await client.waveRelease({ grantId, issues }));
|
|
31880
|
+
}
|
|
31881
|
+
case "request_backward_move": {
|
|
31882
|
+
const grantId = await grantFor(client, active);
|
|
31883
|
+
await client.phaseBackward({
|
|
31884
|
+
grantId,
|
|
31885
|
+
to: requiredString(parameters, operation, "to"),
|
|
31886
|
+
reason: requiredString(parameters, operation, "reason")
|
|
31887
|
+
});
|
|
31888
|
+
return jsonSuccess({});
|
|
31889
|
+
}
|
|
31890
|
+
case "retry_or_escalate": {
|
|
31891
|
+
const decision = parameters.decision;
|
|
31892
|
+
if (decision !== "retry" && decision !== "escalate") {
|
|
31893
|
+
throw new Error("retry_or_escalate requires decision retry or escalate");
|
|
31894
|
+
}
|
|
31895
|
+
const grantId = await grantFor(client, active);
|
|
31896
|
+
await client.phaseRetry({
|
|
31897
|
+
grantId,
|
|
31898
|
+
issue: requiredString(parameters, operation, "issue"),
|
|
31899
|
+
decision
|
|
31900
|
+
});
|
|
31901
|
+
return jsonSuccess({});
|
|
31902
|
+
}
|
|
31903
|
+
case "sign_off": {
|
|
31904
|
+
const grantId = await grantFor(client, active);
|
|
31905
|
+
await client.signOff({
|
|
31906
|
+
grantId,
|
|
31907
|
+
issue: requiredString(parameters, operation, "issue")
|
|
31908
|
+
});
|
|
31909
|
+
return jsonSuccess({});
|
|
31910
|
+
}
|
|
31911
|
+
default:
|
|
31912
|
+
throw new Error(`Unsupported Go Legion operation: ${operation}`);
|
|
31913
|
+
}
|
|
31914
|
+
} catch (error) {
|
|
31915
|
+
return toolFailure(error);
|
|
31916
|
+
}
|
|
31917
|
+
}
|
|
31918
|
+
};
|
|
31919
|
+
}
|
|
31920
|
+
|
|
31921
|
+
// src/legion/tools.ts
|
|
31922
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
31923
|
+
var jsonSuccess2 = (details) => toolSuccess(JSON.stringify(details), details);
|
|
31639
31924
|
function isDispatchArtifactId(value) {
|
|
31640
31925
|
return LegionDaemonApi.GatesRegister.request.shape.artifactId.safeParse(value).success;
|
|
31641
31926
|
}
|
|
@@ -31703,7 +31988,7 @@ function createLegionTool(deps) {
|
|
|
31703
31988
|
issue: stringInput("issue"),
|
|
31704
31989
|
status
|
|
31705
31990
|
});
|
|
31706
|
-
return
|
|
31991
|
+
return jsonSuccess2({});
|
|
31707
31992
|
}
|
|
31708
31993
|
case "register_gate": {
|
|
31709
31994
|
const version = parameters.version;
|
|
@@ -31722,14 +32007,14 @@ function createLegionTool(deps) {
|
|
|
31722
32007
|
artifactId,
|
|
31723
32008
|
version
|
|
31724
32009
|
});
|
|
31725
|
-
return
|
|
32010
|
+
return jsonSuccess2({});
|
|
31726
32011
|
}
|
|
31727
32012
|
case "release_wave": {
|
|
31728
32013
|
const issues = parameters.issues;
|
|
31729
32014
|
if (!Array.isArray(issues) || !issues.every((issue) => typeof issue === "string")) {
|
|
31730
32015
|
throw new Error("release_wave requires issues");
|
|
31731
32016
|
}
|
|
31732
|
-
return
|
|
32017
|
+
return jsonSuccess2(await daemon.releaseWave({
|
|
31733
32018
|
tree: architect.tree,
|
|
31734
32019
|
issues,
|
|
31735
32020
|
sessionId,
|
|
@@ -31750,7 +32035,7 @@ function createLegionTool(deps) {
|
|
|
31750
32035
|
sessionId,
|
|
31751
32036
|
secret: architect.secret
|
|
31752
32037
|
});
|
|
31753
|
-
return
|
|
32038
|
+
return jsonSuccess2({});
|
|
31754
32039
|
}
|
|
31755
32040
|
case "spawn_worker": {
|
|
31756
32041
|
const role = parameters.role;
|
|
@@ -31758,7 +32043,7 @@ function createLegionTool(deps) {
|
|
|
31758
32043
|
throw new Error("spawn_worker requires a valid Legion role");
|
|
31759
32044
|
}
|
|
31760
32045
|
const requestId = randomUUID2();
|
|
31761
|
-
return
|
|
32046
|
+
return jsonSuccess2(await daemon.spawnWorker({
|
|
31762
32047
|
tree: architect.tree,
|
|
31763
32048
|
sessionId,
|
|
31764
32049
|
secret: architect.secret,
|
|
@@ -31780,7 +32065,7 @@ function createLegionTool(deps) {
|
|
|
31780
32065
|
|
|
31781
32066
|
// src/subagent-session.ts
|
|
31782
32067
|
import fs from "fs";
|
|
31783
|
-
import
|
|
32068
|
+
import path4 from "path";
|
|
31784
32069
|
import * as host from "@oh-my-pi/pi-coding-agent";
|
|
31785
32070
|
var LEGION_BOOTSTRAPPED_SESSION = Symbol.for("legion.pi-envoy.bootstrapped-session");
|
|
31786
32071
|
var bootstrappedSessionStore = globalThis;
|
|
@@ -31791,7 +32076,7 @@ async function isSubagentSession(context) {
|
|
|
31791
32076
|
await context.sessionManager.ensureOnDisk();
|
|
31792
32077
|
const sessionFile = context.sessionManager.getSessionFile();
|
|
31793
32078
|
if (sessionFile !== undefined && fs.existsSync(sessionFile)) {
|
|
31794
|
-
return fs.existsSync(`${
|
|
32079
|
+
return fs.existsSync(`${path4.dirname(sessionFile)}.jsonl`);
|
|
31795
32080
|
}
|
|
31796
32081
|
const bootstrapped = bootstrappedSessionStore[LEGION_BOOTSTRAPPED_SESSION];
|
|
31797
32082
|
return bootstrapped !== undefined && bootstrapped !== sessionFile;
|
|
@@ -31877,7 +32162,7 @@ async function persistedTranscript(context) {
|
|
|
31877
32162
|
if (!sessionFile?.endsWith(".jsonl")) {
|
|
31878
32163
|
throw new Error("Legion session must have a persisted transcript");
|
|
31879
32164
|
}
|
|
31880
|
-
const agentId =
|
|
32165
|
+
const agentId = path5.basename(sessionFile, ".jsonl");
|
|
31881
32166
|
if (!agentId)
|
|
31882
32167
|
throw new Error("Legion session transcript has no agent id");
|
|
31883
32168
|
return { sessionFile, agentId };
|
|
@@ -32052,6 +32337,11 @@ function legionExtension(pi) {
|
|
|
32052
32337
|
});
|
|
32053
32338
|
return daemonClient;
|
|
32054
32339
|
};
|
|
32340
|
+
let goDaemonClient;
|
|
32341
|
+
const goRoleDaemon = () => {
|
|
32342
|
+
goDaemonClient ??= createLegionGoDaemonClient(requiredEnvironment(process.env, "LEGION_DAEMON_URL"));
|
|
32343
|
+
return goDaemonClient;
|
|
32344
|
+
};
|
|
32055
32345
|
const rerunReadyAfterRegain = async (endpoint, role, reason, call) => {
|
|
32056
32346
|
try {
|
|
32057
32347
|
await callReadyWithRetry(`${endpoint} after role regain`, call);
|
|
@@ -32263,7 +32553,7 @@ function legionExtension(pi) {
|
|
|
32263
32553
|
if (await checkSubagentSession(context))
|
|
32264
32554
|
return;
|
|
32265
32555
|
if (process.env.LEGION_DAEMON_API === "go") {
|
|
32266
|
-
|
|
32556
|
+
await bootstrapGoClaim(context, {
|
|
32267
32557
|
capability: () => capability,
|
|
32268
32558
|
setCapability: (next) => {
|
|
32269
32559
|
capability = next;
|
|
@@ -32272,10 +32562,14 @@ function legionExtension(pi) {
|
|
|
32272
32562
|
setBootstrap: (next) => {
|
|
32273
32563
|
bootstrap = next;
|
|
32274
32564
|
},
|
|
32565
|
+
daemon: goRoleDaemon,
|
|
32275
32566
|
exitProcess,
|
|
32276
32567
|
persistedTranscript,
|
|
32277
32568
|
recordBootstrappedSession
|
|
32278
32569
|
});
|
|
32570
|
+
registerGoTools();
|
|
32571
|
+
await activateLegionTool();
|
|
32572
|
+
return;
|
|
32279
32573
|
}
|
|
32280
32574
|
const classification = classifySession(process.env);
|
|
32281
32575
|
switch (classification.kind) {
|
|
@@ -32350,7 +32644,12 @@ function legionExtension(pi) {
|
|
|
32350
32644
|
}
|
|
32351
32645
|
return;
|
|
32352
32646
|
}
|
|
32353
|
-
return wrapBashWithGrant(() =>
|
|
32647
|
+
return wrapBashWithGrant(() => process.env.LEGION_DAEMON_API === "go" ? goRoleDaemon().grant({
|
|
32648
|
+
tree: active.tree,
|
|
32649
|
+
issue: active.issue,
|
|
32650
|
+
sessionId: sessionID,
|
|
32651
|
+
secret: active.secret
|
|
32652
|
+
}) : roleDaemon().grant({
|
|
32354
32653
|
tree: active.tree,
|
|
32355
32654
|
issue: active.issue,
|
|
32356
32655
|
sessionId: sessionID,
|
|
@@ -32401,6 +32700,30 @@ function legionExtension(pi) {
|
|
|
32401
32700
|
architectToolsRegistered = true;
|
|
32402
32701
|
pi.registerTool(createLegionTool({ pi, roleDaemon, architectSession }));
|
|
32403
32702
|
};
|
|
32703
|
+
let goToolsRegistered = false;
|
|
32704
|
+
const registerGoTools = () => {
|
|
32705
|
+
if (goToolsRegistered)
|
|
32706
|
+
return;
|
|
32707
|
+
goToolsRegistered = true;
|
|
32708
|
+
pi.registerTool(createGoLegionTool({
|
|
32709
|
+
pi,
|
|
32710
|
+
daemon: goRoleDaemon,
|
|
32711
|
+
session: (context) => {
|
|
32712
|
+
const sessionID = context.sessionManager.getSessionId();
|
|
32713
|
+
const active = capability;
|
|
32714
|
+
if (active === undefined || active.sessionID !== sessionID) {
|
|
32715
|
+
throw new Error("Go Legion tool is available only to this session's registered claim");
|
|
32716
|
+
}
|
|
32717
|
+
return {
|
|
32718
|
+
kind: active.role === "architect" ? "architect" : "phase-worker",
|
|
32719
|
+
sessionId: active.sessionID,
|
|
32720
|
+
tree: active.tree,
|
|
32721
|
+
issue: active.issue,
|
|
32722
|
+
secret: active.secret
|
|
32723
|
+
};
|
|
32724
|
+
}
|
|
32725
|
+
}));
|
|
32726
|
+
};
|
|
32404
32727
|
pi.registerCommand("legion-claim-controller", {
|
|
32405
32728
|
description: "Claim the Legion controller role and register daemon authority for this session",
|
|
32406
32729
|
handler: async (_args, context) => controllerSession.claim(context)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sjawhar/pi-legion-envoy",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.61.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"omp": {
|
|
6
6
|
"extensions": [
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
},
|
|
14
14
|
"legion": {
|
|
15
15
|
"daemonApiVersion": 8,
|
|
16
|
-
"goDaemonApiVersion":
|
|
16
|
+
"goDaemonApiVersion": 3
|
|
17
17
|
},
|
|
18
18
|
"repository": {
|
|
19
19
|
"type": "git",
|