@coreplane/switchboard 1.236.0 → 1.238.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/assets/deploy/cloudflare-resident/worker.ts +210 -74
- package/dist/assets/deploy/cloudflare-sandbox/worker.ts +36 -10
- package/dist/assets/package-lock.json +3 -3
- package/dist/assets/package.json +1 -1
- package/dist/assets/source.json +3 -3
- package/dist/assets/src/core/authz/authorize.ts +25 -6
- package/dist/assets/src/core/authz/policy.ts +9 -0
- package/dist/assets/src/core/authz/types.ts +15 -1
- package/dist/assets/src/core/coordinator/driver.ts +5 -1
- package/dist/assets/src/core/runEvents.ts +10 -6
- package/dist/assets/src/core/ship/coordinator.ts +23 -1
- package/dist/assets/src/execution/residentDepsStore.ts +14 -2
- package/dist/assets/src/execution/residentDiskBudget.ts +45 -11
- package/dist/assets/src/execution/residentHead.ts +34 -8
- package/dist/assets/src/execution/residentRebind.ts +88 -31
- package/dist/assets/src/execution/sandboxLifecycle.ts +33 -0
- package/dist/assets/src/execution/snapshotRetention.ts +62 -0
- package/dist/assets/src/mcp/registry.ts +9 -1
- package/dist/assets/web/dist/.vite/manifest.json +408 -408
- package/dist/assets/web/dist/assets/AppShell-MYHNxJks.js +1 -0
- package/dist/assets/web/dist/assets/{CostsPage-BfJsol9y.js → CostsPage-DKk9OjB0.js} +1 -1
- package/dist/assets/web/dist/assets/{DeliveryPage-CvInfu7y.js → DeliveryPage-Kz6js49i.js} +1 -1
- package/dist/assets/web/dist/assets/{NotFoundPage-d37EP8hS.js → NotFoundPage-BXLrQQzy.js} +1 -1
- package/dist/assets/web/dist/assets/{ResidentDetailPage-DQSD0SZD.js → ResidentDetailPage-CrfCd8fx.js} +1 -1
- package/dist/assets/web/dist/assets/ResidentsIndexPage-BdW0k2h3.js +1 -0
- package/dist/assets/web/dist/assets/{RunFoldRow-Bngn4GjF.js → RunFoldRow-CbTd9SiS.js} +1 -1
- package/dist/assets/web/dist/assets/{RunRoutePage-a-tMrirB.js → RunRoutePage-br0H-8Pz.js} +3 -3
- package/dist/assets/web/dist/assets/RunsIndexPage-Bnzb15zH.js +1 -0
- package/dist/assets/web/dist/assets/{RunsTabs-CyCdOhJ2.js → RunsTabs-XpCqeWu4.js} +1 -1
- package/dist/assets/web/dist/assets/{ScheduledPage-ByCmyrDs.js → ScheduledPage-NLhjzHAz.js} +1 -1
- package/dist/assets/web/dist/assets/SettingsPage-D-i0uVw-.js +1 -0
- package/dist/assets/web/dist/assets/{StatusDot-D-BPSPpw.js → StatusDot-DkXALvKf.js} +1 -1
- package/dist/assets/web/dist/assets/{Tooltip-DPDx5tXT.js → Tooltip-DtLIyi5P.js} +1 -1
- package/dist/assets/web/dist/assets/{UnitRoutePage-0eiekTHQ.js → UnitRoutePage-DGXukEAH.js} +1 -1
- package/dist/assets/web/dist/assets/{angular-html-Ni1Xil2G.js → angular-html-DYtV9uIN.js} +1 -1
- package/dist/assets/web/dist/assets/{angular-ts-oCgo6p05.js → angular-ts-DF2P25Zp.js} +1 -1
- package/dist/assets/web/dist/assets/{apl-CiWYl0gh.js → apl-Ff96wxko.js} +1 -1
- package/dist/assets/web/dist/assets/{astro-B-VeKZ_J.js → astro-LbBnZna-.js} +1 -1
- package/dist/assets/web/dist/assets/{blade-BIWEISKd.js → blade-DltuxeBW.js} +1 -1
- package/dist/assets/web/dist/assets/{c-DHxA5Byp.js → c-DBg7yuPx.js} +1 -1
- package/dist/assets/web/dist/assets/{chapel-D4qGLw-Q.js → chapel-C83hUAds.js} +1 -1
- package/dist/assets/web/dist/assets/{cobol-BznX5Q-u.js → cobol-DB0yV_2D.js} +1 -1
- package/dist/assets/web/dist/assets/{coffee-CVr8Idlj.js → coffee-Bpih6Kzl.js} +1 -1
- package/dist/assets/web/dist/assets/{cpp-wrk6NfW4.js → cpp-CU00ZeRn.js} +1 -1
- package/dist/assets/web/dist/assets/{crystal-CGYWMeH1.js → crystal-f8ImJkux.js} +1 -1
- package/dist/assets/web/dist/assets/{css-BgGytMsy.js → css-4VrC4ISo.js} +1 -1
- package/dist/assets/web/dist/assets/{dist-DSkVB16z.js → dist-DtA7JiH5.js} +2 -2
- package/dist/assets/web/dist/assets/{durationTone-DYogEPEk.js → durationTone-CYMux25J.js} +1 -1
- package/dist/assets/web/dist/assets/{edge-CdR6AU9v.js → edge-BwFDzfS_.js} +1 -1
- package/dist/assets/web/dist/assets/{elixir-CQRb3PZx.js → elixir-wwIYIpJm.js} +1 -1
- package/dist/assets/web/dist/assets/{elm-CpuA6W8B.js → elm-DyDuM3v3.js} +1 -1
- package/dist/assets/web/dist/assets/{erb-CvlrblqH.js → erb-DFJBEVrh.js} +1 -1
- package/dist/assets/web/dist/assets/{git-rebase-COnovCVc.js → git-rebase-CuDv-44X.js} +1 -1
- package/dist/assets/web/dist/assets/{glimmer-js-C-DS4g_J.js → glimmer-js-ZFnrm4J3.js} +1 -1
- package/dist/assets/web/dist/assets/{glimmer-ts-DTkorWRI.js → glimmer-ts-XY4Yqshp.js} +1 -1
- package/dist/assets/web/dist/assets/{glsl-CH51-JW7.js → glsl-xnR8efx0.js} +1 -1
- package/dist/assets/web/dist/assets/{graphql-BO-XymNv.js → graphql-D-gO5tnP.js} +1 -1
- package/dist/assets/web/dist/assets/{hack-C-N0oicf.js → hack-CSxbW4ax.js} +1 -1
- package/dist/assets/web/dist/assets/{haml-BfmdtWoP.js → haml-D4ar42QT.js} +1 -1
- package/dist/assets/web/dist/assets/{handlebars-BqhO_te-.js → handlebars-CMLbDE7o.js} +1 -1
- package/dist/assets/web/dist/assets/{html-DUEZlTKV.js → html-CVekU4Fs.js} +1 -1
- package/dist/assets/web/dist/assets/{html-derivative-DOUJTS8A.js → html-derivative-St3HHw_N.js} +1 -1
- package/dist/assets/web/dist/assets/{http-j7UH0Cqv.js → http-Mon0DWHS.js} +1 -1
- package/dist/assets/web/dist/assets/{hurl-D6ZaBLJk.js → hurl-Cbl5IvBs.js} +1 -1
- package/dist/assets/web/dist/assets/{indexRow-BjXBzI5Z.js → indexRow-FLJu5mR9.js} +1 -1
- package/dist/assets/web/dist/assets/{java-dugWx3wi.js → java-Bprck1l-.js} +1 -1
- package/dist/assets/web/dist/assets/{javascript-CK4GC4JO.js → javascript-C73G1-UR.js} +1 -1
- package/dist/assets/web/dist/assets/{jinja-DrLKIyKG.js → jinja-C4DuRxYJ.js} +1 -1
- package/dist/assets/web/dist/assets/{jison-CS1Th-TO.js → jison-B2U03GEC.js} +1 -1
- package/dist/assets/web/dist/assets/{json-CkBqdVys.js → json-CvTZUDzx.js} +1 -1
- package/dist/assets/web/dist/assets/{jsx-CCxdg7n3.js → jsx-DMWKCzwo.js} +1 -1
- package/dist/assets/web/dist/assets/{julia-BalKW2hA.js → julia-Czn7bV_3.js} +1 -1
- package/dist/assets/web/dist/assets/{just-BKw1G40D.js → just-BBxpNfvC.js} +1 -1
- package/dist/assets/web/dist/assets/{latex-Dfad6ZN-.js → latex-Bw9yg5mr.js} +1 -1
- package/dist/assets/web/dist/assets/{liquid-f_CMGBvd.js → liquid-BRCjaydE.js} +1 -1
- package/dist/assets/web/dist/assets/{lua-BcMK_uKM.js → lua-DUu2KIC0.js} +1 -1
- package/dist/assets/web/dist/assets/main-CFHF2RvQ.css +1 -0
- package/dist/assets/web/dist/assets/{main-DR8iKpNt.js → main-Dht0in4v.js} +2 -2
- package/dist/assets/web/dist/assets/{marko-B0oNLx2i.js → marko-CZJZIlSQ.js} +1 -1
- package/dist/assets/web/dist/assets/{mdc-Bc_MmIyq.js → mdc-BmClq6fd.js} +1 -1
- package/dist/assets/web/dist/assets/{nginx-Cmxv8RIt.js → nginx-Bmt3xSEm.js} +1 -1
- package/dist/assets/web/dist/assets/{nim-BtgRfaY0.js → nim-Da-i0utK.js} +1 -1
- package/dist/assets/web/dist/assets/{org-Dl9EzlHh.js → org-BvAre2qP.js} +1 -1
- package/dist/assets/web/dist/assets/{perl-BmF7x7BB.js → perl-v9WwFFtB.js} +1 -1
- package/dist/assets/web/dist/assets/{php-rH6CVptQ.js → php-DTU4Zyze.js} +1 -1
- package/dist/assets/web/dist/assets/{pug-CIN5Ccck.js → pug-DhTwEqTO.js} +1 -1
- package/dist/assets/web/dist/assets/{qml-BpLS3RFr.js → qml-BUD9AC_c.js} +1 -1
- package/dist/assets/web/dist/assets/{r-BUMF3B-H.js → r-CAnu56n7.js} +1 -1
- package/dist/assets/web/dist/assets/{razor-B7sTBPdT.js → razor-Dn0P4gAq.js} +1 -1
- package/dist/assets/web/dist/assets/{regexp-h2SvwrsJ.js → regexp-CZyBRaVW.js} +1 -1
- package/dist/assets/web/dist/assets/residentsModel-By1fThvk.js +1 -0
- package/dist/assets/web/dist/assets/{rst-DLOWEsD2.js → rst-Ckyd5UsY.js} +1 -1
- package/dist/assets/web/dist/assets/{ruby-Cl_-I4k3.js → ruby-BBQ62fsx.js} +1 -1
- package/dist/assets/web/dist/assets/{sas-Cm4M-BvV.js → sas-CW35hMxG.js} +1 -1
- package/dist/assets/web/dist/assets/{scss-CEFCEQUF.js → scss-CpV8pF4J.js} +1 -1
- package/dist/assets/web/dist/assets/{shellscript-CE6zb5eS.js → shellscript-CRJvsmE1.js} +1 -1
- package/dist/assets/web/dist/assets/{shellsession-B4fDVzrM.js → shellsession-CyriUZxf.js} +1 -1
- package/dist/assets/web/dist/assets/{soy-DwXCMpjE.js → soy-D5QzYUeg.js} +1 -1
- package/dist/assets/web/dist/assets/{sql-DrnAKnyD.js → sql-CUBf_Mse.js} +1 -1
- package/dist/assets/web/dist/assets/{stata-BzlwPWD6.js → stata-B-4n4ITz.js} +1 -1
- package/dist/assets/web/dist/assets/{surrealql-CeUieZXd.js → surrealql-RuWZBLtr.js} +1 -1
- package/dist/assets/web/dist/assets/{svelte-BERIpIKR.js → svelte-CO5YzCsK.js} +1 -1
- package/dist/assets/web/dist/assets/{templ-6FSuUDwg.js → templ-DbkWVaBF.js} +1 -1
- package/dist/assets/web/dist/assets/{tex-SWCyMwNX.js → tex-IuxoEcId.js} +1 -1
- package/dist/assets/web/dist/assets/{ts-tags-VWj7gWgY.js → ts-tags-g6dGQPjd.js} +1 -1
- package/dist/assets/web/dist/assets/{tsx-B6dzfL8L.js → tsx-WnXwiyH2.js} +1 -1
- package/dist/assets/web/dist/assets/{twig-BDXsr482.js → twig-Lx1G7PnY.js} +1 -1
- package/dist/assets/web/dist/assets/{typescript-s100Dt8y.js → typescript-BQd7biLF.js} +1 -1
- package/dist/assets/web/dist/assets/{typst-WfyEAUdt.js → typst-D2lXPHK7.js} +1 -1
- package/dist/assets/web/dist/assets/{vue-CkXOOJ-6.js → vue-4wVQD9y9.js} +1 -1
- package/dist/assets/web/dist/assets/{vue-html-CEpzdcau.js → vue-html-DZPaRte4.js} +1 -1
- package/dist/assets/web/dist/assets/{vue-vine-D9dsfiFA.js → vue-vine-DbmZCmu-.js} +1 -1
- package/dist/assets/web/dist/assets/{xml-iBDofbJq.js → xml-DV16szyo.js} +1 -1
- package/dist/assets/web/dist/assets/{xsl-CqbJF4hD.js → xsl-eo5-1sk0.js} +1 -1
- package/dist/assets/web/dist/assets/{yaml-D9i6OxYe.js → yaml-BXdX8HZf.js} +1 -1
- package/dist/cli.js +1143 -542
- package/package.json +1 -1
- package/dist/assets/web/dist/assets/AppShell-DZdz_sl7.js +0 -1
- package/dist/assets/web/dist/assets/ResidentsIndexPage-CZUGLen6.js +0 -1
- package/dist/assets/web/dist/assets/RunsIndexPage-s0VKmRda.js +0 -1
- package/dist/assets/web/dist/assets/SettingsPage-DhKEokGK.js +0 -1
- package/dist/assets/web/dist/assets/main-B77vAZVg.css +0 -1
- package/dist/assets/web/dist/assets/residentsModel-VSJEepL1.js +0 -1
package/dist/cli.js
CHANGED
|
@@ -697,7 +697,7 @@ function validatePolicy(rules) {
|
|
|
697
697
|
}
|
|
698
698
|
}
|
|
699
699
|
}
|
|
700
|
-
var grant, MEMBER_OF, IS_SELF, OWNER_OF, ALL_CHANNELS, POLICY, ACTOR_KINDS, CONDITION_KINDS, REQUIRED_ATTRIBUTE, PLACEHOLDER, PolicyError;
|
|
700
|
+
var grant, MEMBER_OF, IS_SELF, ACTS_AS_PERSON, OWNER_OF, ALL_CHANNELS, POLICY, ACTOR_KINDS, CONDITION_KINDS, REQUIRED_ATTRIBUTE, PLACEHOLDER, PolicyError;
|
|
701
701
|
var init_policy = __esm({
|
|
702
702
|
"../../src/core/authz/policy.ts"() {
|
|
703
703
|
"use strict";
|
|
@@ -705,6 +705,7 @@ var init_policy = __esm({
|
|
|
705
705
|
grant = (g) => ({ kind: "has-grant", grant: g });
|
|
706
706
|
MEMBER_OF = { kind: "member-of" };
|
|
707
707
|
IS_SELF = { kind: "is-self" };
|
|
708
|
+
ACTS_AS_PERSON = { kind: "acts-as-person" };
|
|
708
709
|
OWNER_OF = { kind: "owner-of" };
|
|
709
710
|
ALL_CHANNELS = { kind: "all-channels" };
|
|
710
711
|
POLICY = [
|
|
@@ -799,6 +800,13 @@ var init_policy = __esm({
|
|
|
799
800
|
// ── mcp (external MCP servers live in the three config tiers) ────────────
|
|
800
801
|
{ action: "mcp:read", resource: "command", when: [grant("mcp:read")] },
|
|
801
802
|
{ action: "mcp:write", resource: "command", when: [grant("mcp:write")] },
|
|
803
|
+
// A person always has their own tier to write (`mcp add|connect|remove`
|
|
804
|
+
// for themselves), as `config set me` already is theirs: the row admits an
|
|
805
|
+
// actor whose `self` names a chat identity — every chat user (who holds
|
|
806
|
+
// `mcp:write` by baseline anyway) and a dashboard session linked to its
|
|
807
|
+
// person (record 0042) — never an unlinked browser session or a credential.
|
|
808
|
+
// Which tier the write reaches stays the handler's question below.
|
|
809
|
+
{ action: "mcp:write", resource: "command", actorKinds: ["user"], when: [ACTS_AS_PERSON] },
|
|
802
810
|
// A CHANNEL's servers: the channel-config right for a person; a credential
|
|
803
811
|
// an admin minted with `mcp:write` manages any tier it can name.
|
|
804
812
|
{ action: "mcp:write", resource: "config-scope", resourceKind: "channel", when: [grant("config:write")] },
|
|
@@ -864,6 +872,7 @@ var init_policy = __esm({
|
|
|
864
872
|
"has-grant",
|
|
865
873
|
"member-of",
|
|
866
874
|
"is-self",
|
|
875
|
+
"acts-as-person",
|
|
867
876
|
"owner-of",
|
|
868
877
|
"all-channels"
|
|
869
878
|
];
|
|
@@ -918,13 +927,20 @@ function principalOf(actor) {
|
|
|
918
927
|
while (current.onBehalfOf) current = current.onBehalfOf;
|
|
919
928
|
return current;
|
|
920
929
|
}
|
|
930
|
+
function selfIdsOf(actor) {
|
|
931
|
+
const principal = principalOf(actor);
|
|
932
|
+
return principal.self && principal.self.length > 0 ? principal.self : [principal.id];
|
|
933
|
+
}
|
|
934
|
+
function actsAsPerson(selfIds) {
|
|
935
|
+
return selfIds.some((id) => id.startsWith("slack:"));
|
|
936
|
+
}
|
|
921
937
|
function deny(reason2) {
|
|
922
938
|
return { allow: false, reason: reason2 };
|
|
923
939
|
}
|
|
924
940
|
function isKnownActorKind(kind) {
|
|
925
941
|
return ACTOR_KINDS.includes(kind);
|
|
926
942
|
}
|
|
927
|
-
function evaluateCondition(condition, grants,
|
|
943
|
+
function evaluateCondition(condition, grants, selfIds, attributes) {
|
|
928
944
|
switch (condition.kind) {
|
|
929
945
|
case "has-grant": {
|
|
930
946
|
const grant2 = resolveGrant(condition.grant, attributes);
|
|
@@ -933,7 +949,9 @@ function evaluateCondition(condition, grants, selfId, attributes) {
|
|
|
933
949
|
case "member-of":
|
|
934
950
|
return attributes.channelId !== void 0 && holds(grants.channels, attributes.channelId) || attributes.channelVisibility === "public";
|
|
935
951
|
case "is-self":
|
|
936
|
-
return attributes.userId !== void 0 && attributes.userId
|
|
952
|
+
return attributes.userId !== void 0 && selfIds.includes(attributes.userId);
|
|
953
|
+
case "acts-as-person":
|
|
954
|
+
return actsAsPerson(selfIds);
|
|
937
955
|
case "owner-of":
|
|
938
956
|
return attributes.repo !== void 0 && holds(grants.repos, attributes.repo);
|
|
939
957
|
case "all-channels":
|
|
@@ -954,10 +972,10 @@ function authorizeWith(rules, actor, action, resource) {
|
|
|
954
972
|
);
|
|
955
973
|
if (forOrigin.length === 0) return deny("origin-visibility");
|
|
956
974
|
const grants = effectiveGrants(actor);
|
|
957
|
-
const
|
|
975
|
+
const selfIds = selfIdsOf(actor);
|
|
958
976
|
let reason2;
|
|
959
977
|
for (const rule of forOrigin) {
|
|
960
|
-
const failed = rule.when.find((condition) => !evaluateCondition(condition, grants,
|
|
978
|
+
const failed = rule.when.find((condition) => !evaluateCondition(condition, grants, selfIds, attributes));
|
|
961
979
|
if (!failed) return ALLOW;
|
|
962
980
|
reason2 ??= FAILURE_REASON[failed.kind];
|
|
963
981
|
}
|
|
@@ -975,6 +993,7 @@ var init_authorize = __esm({
|
|
|
975
993
|
ALLOW = Object.freeze({ allow: true });
|
|
976
994
|
FAILURE_REASON = {
|
|
977
995
|
"has-grant": "missing-grant",
|
|
996
|
+
"acts-as-person": "not-person",
|
|
978
997
|
"member-of": "not-member",
|
|
979
998
|
"is-self": "not-self",
|
|
980
999
|
"owner-of": "not-owner",
|
|
@@ -2540,6 +2559,9 @@ function unitSlug(unit) {
|
|
|
2540
2559
|
function unitBranch(planId, slug) {
|
|
2541
2560
|
return `plan/${planId}/${slug}`;
|
|
2542
2561
|
}
|
|
2562
|
+
function isUnitBranch(ref) {
|
|
2563
|
+
return /^plan\/[^/]+\/[^/]+$/.test(ref);
|
|
2564
|
+
}
|
|
2543
2565
|
function parsePlanBranch(branch2) {
|
|
2544
2566
|
const m = PLAN_BRANCH.exec(branch2);
|
|
2545
2567
|
return m ? { planId: m[1], unitSlug: m[2] } : void 0;
|
|
@@ -3247,7 +3269,7 @@ function isOutcome(v) {
|
|
|
3247
3269
|
function isMcpServerEntry(v) {
|
|
3248
3270
|
if (!v || typeof v !== "object" || Array.isArray(v)) return false;
|
|
3249
3271
|
const e = v;
|
|
3250
|
-
return isStr(e.url, MCP_URL_MAX) && (e.agents === void 0 || Array.isArray(e.agents) && e.agents.length > 0 && e.agents.length <= MCP_AGENTS_MAX && e.agents.every((a) => isStr(a, 32))) && (e.auth === "none" || e.auth === "bearer" || e.auth === "oauth") && (e.tokenEnv === void 0 || isStr(e.tokenEnv, 128)) && (e.headersEnv === void 0 || isHeadersEnv(e.headersEnv)) && (e.addedBy === void 0 || isStr(e.addedBy, 260)) && (e.addedAt === void 0 || isNum(e.addedAt));
|
|
3272
|
+
return isStr(e.url, MCP_URL_MAX) && (e.agents === void 0 || Array.isArray(e.agents) && e.agents.length > 0 && e.agents.length <= MCP_AGENTS_MAX && e.agents.every((a) => isStr(a, 32))) && (e.auth === "none" || e.auth === "bearer" || e.auth === "oauth") && (e.tokenEnv === void 0 || isStr(e.tokenEnv, 128)) && (e.headersEnv === void 0 || isHeadersEnv(e.headersEnv)) && (e.addedBy === void 0 || isStr(e.addedBy, 260)) && (e.addedAt === void 0 || isNum(e.addedAt)) && (e.promotedFrom === void 0 || isStr(e.promotedFrom, 260));
|
|
3251
3273
|
}
|
|
3252
3274
|
function isSealedCredential(v) {
|
|
3253
3275
|
if (!v || typeof v !== "object") return false;
|
|
@@ -3273,7 +3295,8 @@ function serverView(scopeKey, name, entry, opts) {
|
|
|
3273
3295
|
state,
|
|
3274
3296
|
source: opts.source,
|
|
3275
3297
|
...entry.addedBy ? { addedBy: entry.addedBy } : {},
|
|
3276
|
-
...entry.addedAt !== void 0 ? { addedAt: entry.addedAt } : {}
|
|
3298
|
+
...entry.addedAt !== void 0 ? { addedAt: entry.addedAt } : {},
|
|
3299
|
+
...entry.promotedFrom ? { promotedFrom: entry.promotedFrom } : {}
|
|
3277
3300
|
};
|
|
3278
3301
|
}
|
|
3279
3302
|
function safeUrl(raw) {
|
|
@@ -4134,6 +4157,15 @@ var init_config2 = __esm({
|
|
|
4134
4157
|
* caller's right to read each channel is decided; `config show --channel`
|
|
4135
4158
|
* is the read that carries the values.
|
|
4136
4159
|
*/
|
|
4160
|
+
/** Every channel and every user with a tier of its own — a static block or a
|
|
4161
|
+
* runtime override — sorted: what `mcp list --all` walks (record 0042). */
|
|
4162
|
+
mcpTierIds() {
|
|
4163
|
+
const ids = (stat, runtime) => [.../* @__PURE__ */ new Set([...Object.keys(stat ?? {}), ...Object.keys(runtime)])].sort();
|
|
4164
|
+
return {
|
|
4165
|
+
channels: ids(this.config.channels, this.overrides.channels),
|
|
4166
|
+
users: ids(this.config.users, this.overrides.users)
|
|
4167
|
+
};
|
|
4168
|
+
}
|
|
4137
4169
|
channelsWithScope() {
|
|
4138
4170
|
const ids = /* @__PURE__ */ new Set([...Object.keys(this.config.channels ?? {}), ...Object.keys(this.overrides.channels)]);
|
|
4139
4171
|
const rows = [];
|
|
@@ -10044,10 +10076,15 @@ function wakePause(ms, signal2) {
|
|
|
10044
10076
|
});
|
|
10045
10077
|
}
|
|
10046
10078
|
function reboundOf(value) {
|
|
10079
|
+
const move = returnedOf(value);
|
|
10080
|
+
return move !== void 0 && move.pr !== void 0 ? { from: move.from, to: move.to, pr: move.pr } : void 0;
|
|
10081
|
+
}
|
|
10082
|
+
function returnedOf(value) {
|
|
10047
10083
|
if (typeof value !== "object" || value === null) return void 0;
|
|
10048
10084
|
const v = value;
|
|
10049
|
-
if (typeof v.from !== "string" || typeof v.to !== "string" ||
|
|
10050
|
-
if (
|
|
10085
|
+
if (typeof v.from !== "string" || typeof v.to !== "string" || !v.from || !v.to) return void 0;
|
|
10086
|
+
if (v.pr === void 0) return { from: v.from, to: v.to };
|
|
10087
|
+
if (typeof v.pr !== "number" || !Number.isSafeInteger(v.pr) || v.pr <= 0) return void 0;
|
|
10051
10088
|
return { from: v.from, to: v.to, pr: v.pr };
|
|
10052
10089
|
}
|
|
10053
10090
|
function rebindRefusedOf(value) {
|
|
@@ -10288,7 +10325,7 @@ var init_resident = __esm({
|
|
|
10288
10325
|
const trace = sanitizeGraftedSteps(data.trace);
|
|
10289
10326
|
const rebound = reboundOf(data.rebound);
|
|
10290
10327
|
const rebindRefused = rebindRefusedOf(data.rebindRefused);
|
|
10291
|
-
const returned =
|
|
10328
|
+
const returned = returnedOf(data.returned);
|
|
10292
10329
|
this.lastBinding = {
|
|
10293
10330
|
ref: data.ref,
|
|
10294
10331
|
sha: data.sha,
|
|
@@ -10858,7 +10895,7 @@ function rebindLabel(binding) {
|
|
|
10858
10895
|
if (binding.rebound) return ` \xB7 rebound to ${binding.rebound.to} (this thread's PR #${binding.rebound.pr})`;
|
|
10859
10896
|
if (binding.returned) {
|
|
10860
10897
|
const r = binding.returned;
|
|
10861
|
-
return ` \xB7 returned to ${r.to} (the branch of this thread's PR #${r.pr} is gone)`;
|
|
10898
|
+
return r.pr !== void 0 ? ` \xB7 returned to ${r.to} (the branch of this thread's PR #${r.pr} is gone)` : ` \xB7 returned to ${r.to} (this thread's branch ${r.from} is gone)`;
|
|
10862
10899
|
}
|
|
10863
10900
|
if (binding.rebindRefused) {
|
|
10864
10901
|
const r = binding.rebindRefused;
|
|
@@ -15232,6 +15269,7 @@ var init_commandRegistry = __esm({
|
|
|
15232
15269
|
commandId: cmd.id,
|
|
15233
15270
|
callerKind: caller.kind,
|
|
15234
15271
|
callerId: caller.id,
|
|
15272
|
+
...caller.actor.asUser ? { asUser: caller.actor.asUser.id } : {},
|
|
15235
15273
|
effect: cmd.effect,
|
|
15236
15274
|
outcome: res.ok ? "ok" : res.error,
|
|
15237
15275
|
...reason2 === void 0 ? {} : { reason: reason2 },
|
|
@@ -15416,8 +15454,14 @@ function assertMayEditChannel(caller, channel) {
|
|
|
15416
15454
|
if (!mayEditChannel(caller, channel))
|
|
15417
15455
|
throw new CommandError("unauthorized", "Channel config changes are restricted.");
|
|
15418
15456
|
}
|
|
15419
|
-
function
|
|
15420
|
-
if (caller.kind
|
|
15457
|
+
function meIdOf(caller) {
|
|
15458
|
+
if (caller.kind !== "access") return caller.id;
|
|
15459
|
+
return caller.actor.self?.find((id) => id.startsWith("slack:"));
|
|
15460
|
+
}
|
|
15461
|
+
function meIdOrRefuse(caller) {
|
|
15462
|
+
const id = meIdOf(caller);
|
|
15463
|
+
if (id === void 0) throw new CommandError("unauthorized", ME_ON_ACCESS_MESSAGE);
|
|
15464
|
+
return id;
|
|
15421
15465
|
}
|
|
15422
15466
|
async function mayReadChannel(caller, channel, deps) {
|
|
15423
15467
|
const origin = caller.origin?.channelId;
|
|
@@ -15477,7 +15521,7 @@ var init_config4 = __esm({
|
|
|
15477
15521
|
const channel = targetChannel(caller, options.channel);
|
|
15478
15522
|
await assertMayReadChannel(caller, channel, deps);
|
|
15479
15523
|
const description = {
|
|
15480
|
-
...await deps.config.describeConfig(channel, caller.id),
|
|
15524
|
+
...await deps.config.describeConfig(channel, meIdOf(caller) ?? caller.id),
|
|
15481
15525
|
channelConfigRestricted: !mayEditChannel(caller, channel)
|
|
15482
15526
|
};
|
|
15483
15527
|
return description;
|
|
@@ -15570,8 +15614,7 @@ var init_config4 = __esm({
|
|
|
15570
15614
|
assertMayEditChannel(caller, channel);
|
|
15571
15615
|
effective = await deps.config.setChannelOverride(channel, patch);
|
|
15572
15616
|
} else {
|
|
15573
|
-
|
|
15574
|
-
effective = await deps.config.setUserOverride(caller.id, patch);
|
|
15617
|
+
effective = await deps.config.setUserOverride(meIdOrRefuse(caller), patch);
|
|
15575
15618
|
}
|
|
15576
15619
|
return { scope: args.scope, effective: summarizeScope(effective) };
|
|
15577
15620
|
}
|
|
@@ -15590,8 +15633,7 @@ var init_config4 = __esm({
|
|
|
15590
15633
|
assertMayEditChannel(caller, channel);
|
|
15591
15634
|
await deps.config.clearChannelOverride(channel);
|
|
15592
15635
|
} else {
|
|
15593
|
-
|
|
15594
|
-
await deps.config.clearUserOverride(caller.id);
|
|
15636
|
+
await deps.config.clearUserOverride(meIdOrRefuse(caller));
|
|
15595
15637
|
}
|
|
15596
15638
|
return { scope: args.scope, cleared: true };
|
|
15597
15639
|
}
|
|
@@ -15633,7 +15675,7 @@ ${quote(current ?? "")}`;
|
|
|
15633
15675
|
handler: async ({ args, options, caller, deps }) => {
|
|
15634
15676
|
const channel = args.scope === "channel" ? targetChannel(caller, options.channel) : void 0;
|
|
15635
15677
|
if (args.text === void 0 && channel !== void 0) await assertMayReadChannel(caller, channel, deps);
|
|
15636
|
-
const scopes = await deps.config.scopes(channel ?? caller.origin?.channelId ?? "", caller.id);
|
|
15678
|
+
const scopes = await deps.config.scopes(channel ?? caller.origin?.channelId ?? "", meIdOf(caller) ?? caller.id);
|
|
15637
15679
|
const current = (args.scope === "channel" ? scopes.channel : scopes.user).instructions?.trim();
|
|
15638
15680
|
if (args.text === void 0)
|
|
15639
15681
|
return { scope: args.scope, action: "show", ...current ? { instructions: current } : {} };
|
|
@@ -15650,8 +15692,7 @@ ${quote(current ?? "")}`;
|
|
|
15650
15692
|
assertMayEditChannel(caller, channel);
|
|
15651
15693
|
effective = await deps.config.setChannelOverride(channel, patch);
|
|
15652
15694
|
} else {
|
|
15653
|
-
|
|
15654
|
-
effective = await deps.config.setUserOverride(caller.id, patch);
|
|
15695
|
+
effective = await deps.config.setUserOverride(meIdOrRefuse(caller), patch);
|
|
15655
15696
|
}
|
|
15656
15697
|
if (text.length === 0) {
|
|
15657
15698
|
const fromStatic = effective.instructions?.trim();
|
|
@@ -15782,7 +15823,25 @@ function anyOf(alternatives) {
|
|
|
15782
15823
|
if (flat.length === 1) return flat[0];
|
|
15783
15824
|
return { kind: "or", of: flat };
|
|
15784
15825
|
}
|
|
15785
|
-
function
|
|
15826
|
+
function allOf(parts) {
|
|
15827
|
+
const flat = [];
|
|
15828
|
+
for (const p of parts) {
|
|
15829
|
+
if (p.kind === "none") return NONE;
|
|
15830
|
+
if (p.kind === "all") continue;
|
|
15831
|
+
if (p.kind === "and") flat.push(...p.of);
|
|
15832
|
+
else flat.push(p);
|
|
15833
|
+
}
|
|
15834
|
+
if (flat.length === 0) return ALL;
|
|
15835
|
+
if (flat.length === 1) return flat[0];
|
|
15836
|
+
return { kind: "and", of: flat };
|
|
15837
|
+
}
|
|
15838
|
+
function selfPredicate(selfIds) {
|
|
15839
|
+
return anyOf(selfIds.map((userId) => ({ kind: "user-is", userId })));
|
|
15840
|
+
}
|
|
15841
|
+
function ownedBy(actor) {
|
|
15842
|
+
return selfPredicate(selfIdsOf(actor));
|
|
15843
|
+
}
|
|
15844
|
+
function compileCondition(condition, grants, selfIds) {
|
|
15786
15845
|
switch (condition.kind) {
|
|
15787
15846
|
case "has-grant": {
|
|
15788
15847
|
if (grants.actions === "all") return ALL;
|
|
@@ -15793,7 +15852,9 @@ function compileCondition(condition, grants, selfId) {
|
|
|
15793
15852
|
if (grants.channels === "all") return ALL;
|
|
15794
15853
|
return anyOf([grants.channels.size === 0 ? NONE : { kind: "channels-in", channelIds: grants.channels }, PUBLIC]);
|
|
15795
15854
|
case "is-self":
|
|
15796
|
-
return
|
|
15855
|
+
return selfPredicate(selfIds);
|
|
15856
|
+
case "acts-as-person":
|
|
15857
|
+
return actsAsPerson(selfIds) ? ALL : NONE;
|
|
15797
15858
|
case "owner-of":
|
|
15798
15859
|
if (grants.repos === "all") return ALL;
|
|
15799
15860
|
return grants.repos.size === 0 ? NONE : { kind: "repos-in", repos: grants.repos };
|
|
@@ -15802,17 +15863,9 @@ function compileCondition(condition, grants, selfId) {
|
|
|
15802
15863
|
}
|
|
15803
15864
|
return NONE;
|
|
15804
15865
|
}
|
|
15805
|
-
function compileRule(rule, grants,
|
|
15866
|
+
function compileRule(rule, grants, selfIds) {
|
|
15806
15867
|
if (rule.originVisibility) return NONE;
|
|
15807
|
-
|
|
15808
|
-
for (const condition of rule.when) {
|
|
15809
|
-
const part = compileCondition(condition, grants, selfId);
|
|
15810
|
-
if (part.kind === "none") return NONE;
|
|
15811
|
-
if (part.kind !== "all") parts.push(part);
|
|
15812
|
-
}
|
|
15813
|
-
if (parts.length === 0) return ALL;
|
|
15814
|
-
if (parts.length === 1) return parts[0];
|
|
15815
|
-
return { kind: "and", of: parts };
|
|
15868
|
+
return allOf(rule.when.map((condition) => compileCondition(condition, grants, selfIds)));
|
|
15816
15869
|
}
|
|
15817
15870
|
function predicateWith(rules, actor, action, resourceType, kind) {
|
|
15818
15871
|
if (RESOURCE_KINDS[resourceType] && kind === void 0) {
|
|
@@ -15822,12 +15875,12 @@ function predicateWith(rules, actor, action, resourceType, kind) {
|
|
|
15822
15875
|
if (!target) throw new TypeError(`authz predicate: no target for ${resourceType}${kind ? `/${kind}` : ""}`);
|
|
15823
15876
|
if (!isKnownActorKind(actor.kind)) return NONE;
|
|
15824
15877
|
const grants = effectiveGrants(actor);
|
|
15825
|
-
const
|
|
15878
|
+
const selfIds = selfIdsOf(actor);
|
|
15826
15879
|
const alternatives = [];
|
|
15827
15880
|
for (const rule of rules) {
|
|
15828
15881
|
if (rule.action !== action || ruleTarget(rule) !== target) continue;
|
|
15829
15882
|
if (rule.actorKinds && !rule.actorKinds.includes(actor.kind)) continue;
|
|
15830
|
-
alternatives.push(compileRule(rule, grants,
|
|
15883
|
+
alternatives.push(compileRule(rule, grants, selfIds));
|
|
15831
15884
|
}
|
|
15832
15885
|
return anyOf(alternatives);
|
|
15833
15886
|
}
|
|
@@ -18938,6 +18991,25 @@ var init_service = __esm({
|
|
|
18938
18991
|
for (const r of resolved) out.push(await this.view(r));
|
|
18939
18992
|
return out;
|
|
18940
18993
|
}
|
|
18994
|
+
/** Every tier there is — the org, every channel, every user (static and runtime) — for an
|
|
18995
|
+
* actor with org rights; anyone else is refused (record 0042: a non-admin never sees another
|
|
18996
|
+
* person's tier). Each row carries `addedByName` when the lookup answers. */
|
|
18997
|
+
async listAll(actor) {
|
|
18998
|
+
if (!actor.orgAdmin)
|
|
18999
|
+
throw new McpServiceError(
|
|
19000
|
+
"unauthorized",
|
|
19001
|
+
"listing every tier's MCP servers takes admin rights (repo-management rights). `mcp list` shows the org's, this channel's and your own."
|
|
19002
|
+
);
|
|
19003
|
+
const { channels, users } = this.opts.config.mcpTierIds();
|
|
19004
|
+
const targets = [
|
|
19005
|
+
{ kind: "org" },
|
|
19006
|
+
...channels.map((id) => ({ kind: "channel", id })),
|
|
19007
|
+
...users.map((id) => ({ kind: "user", id }))
|
|
19008
|
+
];
|
|
19009
|
+
const out = [];
|
|
19010
|
+
for (const target of targets) for (const r of this.tierEntries(target)) out.push(await this.view(r));
|
|
19011
|
+
return out;
|
|
19012
|
+
}
|
|
18941
19013
|
async add(actor, target, input) {
|
|
18942
19014
|
const scopeKey = mcpScopeKey(target.kind, target.id);
|
|
18943
19015
|
try {
|
|
@@ -19052,6 +19124,55 @@ var init_service = __esm({
|
|
|
19052
19124
|
return { ...view, probe: { ok: false, error: (err2 instanceof Error ? err2.message : String(err2)).slice(0, 300) } };
|
|
19053
19125
|
}
|
|
19054
19126
|
}
|
|
19127
|
+
/** `mcp promote <name> --from <user>` (record 0042): copy a person's runtime entry — `url`,
|
|
19128
|
+
* `auth`, `agents` (widened only when the admin passes some) — into the org tier under the
|
|
19129
|
+
* same name, `addedBy` the admin and `promotedFrom` the person, and for bearer/oauth mint a
|
|
19130
|
+
* fresh ORG connect ticket exactly as `mcp add --scope org` would. The person's sealed
|
|
19131
|
+
* credential is never read, unsealed or written: it is theirs, and the org's calls must not
|
|
19132
|
+
* run as them. The personal entry stays (shadowed by the org tier at run time). */
|
|
19133
|
+
async promote(actor, name, from, opts = {}) {
|
|
19134
|
+
const org = this.target(actor, "org", void 0);
|
|
19135
|
+
const source2 = this.tierEntries({ kind: "user", id: from }).find((r) => r.name === name);
|
|
19136
|
+
if (!source2)
|
|
19137
|
+
throw new McpServiceError(
|
|
19138
|
+
"not_found",
|
|
19139
|
+
`no MCP server named "${name}" in the user scope of ${from} (see \`mcp list --all\`)`
|
|
19140
|
+
);
|
|
19141
|
+
if (source2.source === "config")
|
|
19142
|
+
throw new McpServiceError(
|
|
19143
|
+
"conflict",
|
|
19144
|
+
`"${name}" is pinned in config.yaml under users.${from} \u2014 move it to defaults.mcpServers there`
|
|
19145
|
+
);
|
|
19146
|
+
const runtime = this.opts.config.runtimeScope("org");
|
|
19147
|
+
if (runtime.mcpServers?.[name] || this.opts.config.isStaticMcpServer("org", void 0, name))
|
|
19148
|
+
throw new McpServiceError(
|
|
19149
|
+
"conflict",
|
|
19150
|
+
`an org MCP server named "${name}" already exists \u2014 remove it first or pick another name`
|
|
19151
|
+
);
|
|
19152
|
+
if (Object.keys(runtime.mcpServers ?? {}).length >= MCP_SERVERS_PER_SCOPE_MAX)
|
|
19153
|
+
throw new McpServiceError("invalid_input", `the org scope already has ${MCP_SERVERS_PER_SCOPE_MAX} servers`);
|
|
19154
|
+
const agents = this.checkAgents("org", opts.agents ?? source2.entry.agents);
|
|
19155
|
+
if (source2.entry.auth !== "none") this.requireCredentialSupport(source2.entry.auth);
|
|
19156
|
+
const entry = {
|
|
19157
|
+
url: source2.entry.url,
|
|
19158
|
+
agents,
|
|
19159
|
+
auth: source2.entry.auth,
|
|
19160
|
+
addedBy: actor.id,
|
|
19161
|
+
addedAt: this.now(),
|
|
19162
|
+
promotedFrom: from
|
|
19163
|
+
};
|
|
19164
|
+
await this.writeServers(org, { ...runtime.mcpServers, [name]: entry });
|
|
19165
|
+
const view = serverView("org", name, entry, { hasCredential: false, source: "runtime" });
|
|
19166
|
+
if (entry.auth === "none") return { server: view, promotedFrom: from };
|
|
19167
|
+
const ticket = await this.mintTicket(mcpCredentialKey("org", name), actor);
|
|
19168
|
+
return {
|
|
19169
|
+
server: view,
|
|
19170
|
+
promotedFrom: from,
|
|
19171
|
+
connectUrl: this.connectUrl(ticket),
|
|
19172
|
+
expiresAt: ticket.expiresAt,
|
|
19173
|
+
expiresInMinutes: TICKET_MINUTES
|
|
19174
|
+
};
|
|
19175
|
+
}
|
|
19055
19176
|
// ---- the connect page ----------------------------------------------------------
|
|
19056
19177
|
/** GET: binding an unbound ticket is a compare-and-swap on its state; when
|
|
19057
19178
|
* another opener won the race the decision is re-planned against the
|
|
@@ -19421,7 +19542,7 @@ var init_service = __esm({
|
|
|
19421
19542
|
return `${(this.opts.publicBaseUrl ?? "").replace(/\/+$/, "")}/mcp/connect/${ticket.nonce}`;
|
|
19422
19543
|
}
|
|
19423
19544
|
async mintTicket(serverId, actor) {
|
|
19424
|
-
const email = this.opts.resolveEmail ? await this.opts.resolveEmail(actor.id).catch(() => void 0) : void 0;
|
|
19545
|
+
const email = actor.email ?? (this.opts.resolveEmail ? await this.opts.resolveEmail(actor.id).catch(() => void 0) : void 0);
|
|
19425
19546
|
const ticket = newTicket({
|
|
19426
19547
|
nonce: this.nonce(),
|
|
19427
19548
|
serverId,
|
|
@@ -19478,7 +19599,9 @@ var init_service = __esm({
|
|
|
19478
19599
|
}
|
|
19479
19600
|
async view(r) {
|
|
19480
19601
|
const hasCredential = r.entry.auth !== "none" && !r.entry.tokenEnv ? await this.viaSecrets(() => this.opts.secrets.getCredential(mcpCredentialKey(r.scopeKey, r.name))) !== null : false;
|
|
19481
|
-
|
|
19602
|
+
const view = serverView(r.scopeKey, r.name, r.entry, { hasCredential, source: r.source });
|
|
19603
|
+
const name = view.addedBy && this.opts.resolveName ? await this.opts.resolveName(view.addedBy).catch(() => void 0) : void 0;
|
|
19604
|
+
return name ? { ...view, addedByName: name } : view;
|
|
19482
19605
|
}
|
|
19483
19606
|
async specFor(r) {
|
|
19484
19607
|
const id = mcpCredentialKey(r.scopeKey, r.name);
|
|
@@ -19576,7 +19699,11 @@ async function serviceOf(deps) {
|
|
|
19576
19699
|
}
|
|
19577
19700
|
function actorOf(caller, channel) {
|
|
19578
19701
|
return {
|
|
19579
|
-
|
|
19702
|
+
// The person, for a dashboard session linked to one (record 0042): its
|
|
19703
|
+
// own tier, `addedBy` and the connect ticket's email are the person's.
|
|
19704
|
+
id: meIdOf(caller) ?? caller.id,
|
|
19705
|
+
// A dashboard session's own email binds the tickets it mints, linked or not.
|
|
19706
|
+
...caller.email ? { email: caller.email } : {},
|
|
19580
19707
|
orgAdmin: authorize(caller.actor, "mcp:write", { type: "config-scope", kind: "org" }).allow,
|
|
19581
19708
|
channelAdmin: authorize(caller.actor, "mcp:write", { type: "config-scope", kind: "channel", id: channel ?? "" }).allow
|
|
19582
19709
|
};
|
|
@@ -19585,7 +19712,7 @@ function channelOf2(caller, option) {
|
|
|
19585
19712
|
return option ?? caller.origin?.channelId;
|
|
19586
19713
|
}
|
|
19587
19714
|
async function writeTarget(svc, caller, actor, word, channel) {
|
|
19588
|
-
if ((word ?? "me") === "me" && caller
|
|
19715
|
+
if ((word ?? "me") === "me" && meIdOf(caller) === void 0)
|
|
19589
19716
|
throw new CommandError("unauthorized", MCP_ME_ON_ACCESS_MESSAGE);
|
|
19590
19717
|
return via(() => svc.target(actor, word ?? "me", channel));
|
|
19591
19718
|
}
|
|
@@ -19627,7 +19754,10 @@ function renderServerLine(r) {
|
|
|
19627
19754
|
const state = r.state === "connected" ? "\u2705 connected" : r.state === "static" ? "\u2705 static (bot env)" : "\u23F3 awaiting credential";
|
|
19628
19755
|
const agents = Array.isArray(r.agents) ? r.agents.join(", ") : "";
|
|
19629
19756
|
const pinned = r.source === "config" ? " \xB7 pinned in config.yaml" : "";
|
|
19630
|
-
|
|
19757
|
+
const by = typeof r.addedBy === "string" ? ` \xB7 added by ${typeof r.addedByName === "string" ? r.addedByName : r.addedBy}` : "";
|
|
19758
|
+
const from = typeof r.promotedFrom === "string" ? ` \xB7 promoted from ${r.promotedFrom}` : "";
|
|
19759
|
+
const owner = r.scope === "user" && typeof r.scopeKey === "string" ? ` \xB7 tier of ${r.scopeKey.slice("user:".length)}` : "";
|
|
19760
|
+
return `\u2022 \`${String(r.name)}\` (${String(r.scope)}) ${state} \u2014 ${String(r.url)} \xB7 agents: ${agents} \xB7 auth: ${String(r.auth)}${pinned}${by}${from}${owner}`;
|
|
19631
19761
|
}
|
|
19632
19762
|
function renderList(output) {
|
|
19633
19763
|
const o = output;
|
|
@@ -19667,13 +19797,14 @@ function renderShow(output) {
|
|
|
19667
19797
|
function registerMcpCommands(registry) {
|
|
19668
19798
|
for (const cmd of MCP_COMMANDS) registry.register(cmd);
|
|
19669
19799
|
}
|
|
19670
|
-
var defineCommand10, serverName, serverUrl, scopeOption, channelOption2, nameArg, MCP_ME_ON_ACCESS_MESSAGE, mcpList, mcpAdd, mcpConnect, mcpShow, mcpRemove, MCP_COMMANDS;
|
|
19800
|
+
var defineCommand10, serverName, serverUrl, scopeOption, channelOption2, nameArg, USER_ID_PATTERN, MCP_ME_ON_ACCESS_MESSAGE, mcpList, mcpPromote, mcpAdd, mcpConnect, mcpShow, mcpRemove, MCP_COMMANDS;
|
|
19671
19801
|
var init_mcp = __esm({
|
|
19672
19802
|
"../../src/core/commands/mcp.ts"() {
|
|
19673
19803
|
"use strict";
|
|
19674
19804
|
init_config3();
|
|
19675
19805
|
init_service();
|
|
19676
19806
|
init_authorize();
|
|
19807
|
+
init_config4();
|
|
19677
19808
|
init_commandRegistry();
|
|
19678
19809
|
defineCommand10 = commandDefiner();
|
|
19679
19810
|
serverName = z13.string().refine((s) => MCP_SERVER_NAME_PATTERN.test(s), "expected a slug: lowercase letters, digits, dashes (\u2264 32 chars)");
|
|
@@ -19683,22 +19814,53 @@ var init_mcp = __esm({
|
|
|
19683
19814
|
);
|
|
19684
19815
|
channelOption2 = z13.string().optional().describe("target channel for `--scope channel` and for `list` (default: the channel you are speaking in)");
|
|
19685
19816
|
nameArg = { name: "name", schema: serverName, describe: "the server's name (see `mcp list`)" };
|
|
19817
|
+
USER_ID_PATTERN = /^[a-z]+:[A-Za-z0-9_.@-]{1,200}$/;
|
|
19686
19818
|
MCP_ME_ON_ACCESS_MESSAGE = "Personal MCP servers are added in chat (`mcp add <name> --url \u2026`): your runs are requested as your chat user, not as this browser session, so a server added here for yourself would reach no run. Pass --scope org or --scope channel.";
|
|
19687
19819
|
mcpList = defineCommand10({
|
|
19688
19820
|
id: "mcp.list",
|
|
19689
19821
|
enabledWhen: (caps) => caps.mcp,
|
|
19690
|
-
options: z13.object({
|
|
19822
|
+
options: z13.object({
|
|
19823
|
+
channel: channelOption2,
|
|
19824
|
+
all: flag.optional().describe("every tier there is \u2014 the org's, every channel's, every user's (admins)")
|
|
19825
|
+
}),
|
|
19691
19826
|
action: "mcp:read",
|
|
19692
19827
|
effect: "read",
|
|
19693
|
-
describe: "External MCP servers your runs in this channel can use \u2014 org-wide, this channel's, and your own \u2014 with state and agents; never a credential.",
|
|
19828
|
+
describe: "External MCP servers your runs in this channel can use \u2014 org-wide, this channel's, and your own \u2014 with state and agents; never a credential. `--all` (admins): every tier.",
|
|
19694
19829
|
render: renderList,
|
|
19695
19830
|
handler: async ({ options, caller, deps }) => {
|
|
19696
19831
|
const svc = await serviceOf(deps);
|
|
19697
19832
|
const channel = channelOf2(caller, options.channel);
|
|
19698
|
-
const
|
|
19833
|
+
const actor = actorOf(caller, channel);
|
|
19834
|
+
const servers = await via(() => options.all ? svc.listAll(actor) : svc.list(actor, channel));
|
|
19699
19835
|
return { servers };
|
|
19700
19836
|
}
|
|
19701
19837
|
});
|
|
19838
|
+
mcpPromote = defineCommand10({
|
|
19839
|
+
id: "mcp.promote",
|
|
19840
|
+
enabledWhen: (caps) => caps.mcp,
|
|
19841
|
+
args: [nameArg],
|
|
19842
|
+
options: z13.object({
|
|
19843
|
+
from: z13.string().refine((s) => USER_ID_PATTERN.test(s), "expected a platform-namespaced user id (slack:U\u2026)").describe("whose personal server to promote (`slack:U\u2026`, as `mcp list --all` shows it)"),
|
|
19844
|
+
agents: z13.string().transform(
|
|
19845
|
+
(s) => s.split(",").map((a) => a.trim()).filter(Boolean)
|
|
19846
|
+
).optional().describe("widen the agents (default: the personal entry's); an org server may name coding/review/ship")
|
|
19847
|
+
}),
|
|
19848
|
+
action: "mcp:write",
|
|
19849
|
+
// Org-only by definition (there is no `me` alternative to point at), so the gate is the
|
|
19850
|
+
// table's, at the door: the same `mcp:write` on `config-scope { org }` that `mcp add --scope org` asks.
|
|
19851
|
+
resource: () => ({ type: "config-scope", kind: "org" }),
|
|
19852
|
+
effect: "write",
|
|
19853
|
+
describe: "Re-issue a person's MCP server in the org tier (admins): the same name, URL and auth, added by you; a bearer/oauth server gets a fresh org connect link for you to complete \u2014 the person's credential is never copied.",
|
|
19854
|
+
render: renderAdd,
|
|
19855
|
+
settle: (output, { deps }) => settleConnect(deps, output),
|
|
19856
|
+
handler: async ({ args, options, caller, deps }) => {
|
|
19857
|
+
const svc = await serviceOf(deps);
|
|
19858
|
+
const actor = actorOf(caller, channelOf2(caller, void 0));
|
|
19859
|
+
return await via(
|
|
19860
|
+
() => svc.promote(actor, args.name, options.from, options.agents ? { agents: options.agents } : {})
|
|
19861
|
+
);
|
|
19862
|
+
}
|
|
19863
|
+
});
|
|
19702
19864
|
mcpAdd = defineCommand10({
|
|
19703
19865
|
id: "mcp.add",
|
|
19704
19866
|
enabledWhen: (caps) => caps.mcp,
|
|
@@ -19796,7 +19958,7 @@ var init_mcp = __esm({
|
|
|
19796
19958
|
return await via(() => svc.remove(actor, target, args.name));
|
|
19797
19959
|
}
|
|
19798
19960
|
});
|
|
19799
|
-
MCP_COMMANDS = [mcpList, mcpAdd, mcpConnect, mcpShow, mcpRemove];
|
|
19961
|
+
MCP_COMMANDS = [mcpList, mcpAdd, mcpConnect, mcpShow, mcpRemove, mcpPromote];
|
|
19800
19962
|
}
|
|
19801
19963
|
});
|
|
19802
19964
|
|
|
@@ -22669,14 +22831,16 @@ var init_runs = __esm({
|
|
|
22669
22831
|
sinceMs: z18.coerce.number().int().nonnegative().optional().describe("only runs started at or after this epoch ms"),
|
|
22670
22832
|
limit: positiveInt3.max(RUN_LIST_MAX_LIMIT).optional().describe(`page size (max ${RUN_LIST_MAX_LIMIT})`),
|
|
22671
22833
|
before: z18.coerce.number().int().nonnegative().optional().describe("page cursor: runs finished before this epoch ms"),
|
|
22672
|
-
beforeId: runId2.optional().describe("page cursor tie-breaker: the last id of the previous page")
|
|
22834
|
+
beforeId: runId2.optional().describe("page cursor tie-breaker: the last id of the previous page"),
|
|
22835
|
+
mine: flag.optional().describe("only the runs you requested \u2014 for a dashboard session, the Slack user its email names (record 0042)")
|
|
22673
22836
|
}),
|
|
22674
22837
|
action: "runs:read",
|
|
22675
22838
|
effect: "read",
|
|
22676
22839
|
describe: "List runs (live and persisted, newest first) \u2014 metadata only, never message text.",
|
|
22677
22840
|
handler: async ({ options, caller, deps }) => {
|
|
22678
|
-
const
|
|
22679
|
-
const
|
|
22841
|
+
const { thread, parent, mine, ...rest } = options;
|
|
22842
|
+
const readable = predicateFor(caller.actor, "runs:read", "run");
|
|
22843
|
+
const visibleTo = mine ? allOf([readable, ownedBy(caller.actor)]) : readable;
|
|
22680
22844
|
return asJson3(
|
|
22681
22845
|
await (await deps.runs()).listRuns({
|
|
22682
22846
|
...rest,
|
|
@@ -25768,7 +25932,13 @@ async function fetchPullRequestFacts(pr) {
|
|
|
25768
25932
|
...typeof data.title === "string" && data.title ? { title: data.title } : {},
|
|
25769
25933
|
..."auto_merge" in data ? { autoMergeEnabled: data.auto_merge !== null } : {},
|
|
25770
25934
|
..."mergeable" in data ? { mergeable: data.mergeable === null ? null : data.mergeable === true } : {},
|
|
25771
|
-
...typeof data.mergeable_state === "string" && data.mergeable_state ? { mergeableState: data.mergeable_state } : {}
|
|
25935
|
+
...typeof data.mergeable_state === "string" && data.mergeable_state ? { mergeableState: data.mergeable_state } : {},
|
|
25936
|
+
// The merge commit rides only a merged row: on an open pull request GitHub
|
|
25937
|
+
// reports a test-merge sha under `merge_commit_sha`.
|
|
25938
|
+
...typeof data.merged_at === "string" && data.merged_at ? {
|
|
25939
|
+
mergedAt: data.merged_at,
|
|
25940
|
+
...typeof data.merge_commit_sha === "string" && /^[0-9a-f]{40}$/.test(data.merge_commit_sha) ? { mergeCommitSha: data.merge_commit_sha } : {}
|
|
25941
|
+
} : {}
|
|
25772
25942
|
};
|
|
25773
25943
|
}
|
|
25774
25944
|
async function mergePullRequest(pr, opts) {
|
|
@@ -26121,7 +26291,12 @@ async function resolveRepoContext(msg, history = [], probe, residentSlugs, recor
|
|
|
26121
26291
|
} else {
|
|
26122
26292
|
out.prUnpostable = { number: inherited.number, reason: head.reason };
|
|
26123
26293
|
if (inherited.source === "record" && head.reason === "closed" && head.sha !== void 0) {
|
|
26124
|
-
out.closedRecordPr = {
|
|
26294
|
+
out.closedRecordPr = {
|
|
26295
|
+
number: inherited.number,
|
|
26296
|
+
headSha: head.sha,
|
|
26297
|
+
...head.ref !== void 0 ? { headRef: head.ref } : {},
|
|
26298
|
+
merged: head.merged
|
|
26299
|
+
};
|
|
26125
26300
|
}
|
|
26126
26301
|
}
|
|
26127
26302
|
}
|
|
@@ -26134,11 +26309,21 @@ function ownPrOf(ctx) {
|
|
|
26134
26309
|
}
|
|
26135
26310
|
function recordPrOf(ctx) {
|
|
26136
26311
|
if (ctx.prFromRecord && ctx.pr !== void 0 && ctx.headSha !== void 0) {
|
|
26137
|
-
return {
|
|
26312
|
+
return {
|
|
26313
|
+
number: ctx.pr,
|
|
26314
|
+
headSha: ctx.headSha,
|
|
26315
|
+
...ctx.refFromPr && ctx.ref !== void 0 ? { headBranch: ctx.ref } : {},
|
|
26316
|
+
state: "open"
|
|
26317
|
+
};
|
|
26138
26318
|
}
|
|
26139
26319
|
if (ctx.closedRecordPr !== void 0) {
|
|
26140
|
-
const { number, headSha, merged } = ctx.closedRecordPr;
|
|
26141
|
-
return {
|
|
26320
|
+
const { number, headSha, headRef, merged } = ctx.closedRecordPr;
|
|
26321
|
+
return {
|
|
26322
|
+
number,
|
|
26323
|
+
headSha,
|
|
26324
|
+
...headRef !== void 0 ? { headBranch: headRef } : {},
|
|
26325
|
+
state: merged ? "merged" : "closed"
|
|
26326
|
+
};
|
|
26142
26327
|
}
|
|
26143
26328
|
return void 0;
|
|
26144
26329
|
}
|
|
@@ -26150,8 +26335,14 @@ function inheritedPr(thread, records, repo) {
|
|
|
26150
26335
|
}
|
|
26151
26336
|
async function openPrHeadSha(pr) {
|
|
26152
26337
|
const head = await prHead(pr).catch(() => void 0);
|
|
26153
|
-
if (head?.state === "closed")
|
|
26154
|
-
return {
|
|
26338
|
+
if (head?.state === "closed") {
|
|
26339
|
+
return {
|
|
26340
|
+
reason: "closed",
|
|
26341
|
+
...head.sha ? { sha: head.sha } : {},
|
|
26342
|
+
...head.ref ? { ref: head.ref } : {},
|
|
26343
|
+
merged: head.merged
|
|
26344
|
+
};
|
|
26345
|
+
}
|
|
26155
26346
|
if (head?.state === "open" && head.sha) {
|
|
26156
26347
|
return {
|
|
26157
26348
|
sha: head.sha,
|
|
@@ -26999,7 +27190,7 @@ var init_types5 = __esm({
|
|
|
26999
27190
|
|
|
27000
27191
|
// ../../src/core/dispatch/commandRun.ts
|
|
27001
27192
|
function isInlineRunCommand(id) {
|
|
27002
|
-
return id.startsWith("friction.") || id === "memory.forget" || /^repo\.(onboard|offboard|rebuild|reconfigure|test|build)$/.test(id) || /^mcp\.(add|connect|remove)$/.test(id);
|
|
27193
|
+
return id.startsWith("friction.") || id === "memory.forget" || /^repo\.(onboard|offboard|rebuild|reconfigure|test|build)$/.test(id) || /^mcp\.(add|connect|remove|promote)$/.test(id);
|
|
27003
27194
|
}
|
|
27004
27195
|
async function runChatCommand(deps, msg, io, parsed, ending, trace, opts = {}) {
|
|
27005
27196
|
const commands = deps.commands;
|
|
@@ -32512,6 +32703,14 @@ async function attachRound(deps, ctx) {
|
|
|
32512
32703
|
return attached;
|
|
32513
32704
|
});
|
|
32514
32705
|
}
|
|
32706
|
+
async function reattachWorkspace2(deps, ctx) {
|
|
32707
|
+
try {
|
|
32708
|
+
return { kind: "attached", round: await attachRound(deps, ctx) };
|
|
32709
|
+
} catch (err2) {
|
|
32710
|
+
if (err2 instanceof WorkspaceReattachRefusedError) return { kind: "reattach_refused", why: err2.why };
|
|
32711
|
+
throw err2;
|
|
32712
|
+
}
|
|
32713
|
+
}
|
|
32515
32714
|
async function attachWorkspace(deps, ctx) {
|
|
32516
32715
|
const { msg, io, refuse: refuse3, card, shell, closeLines, clock: clock4, agent, profile, repoCtx, root, reattach } = ctx;
|
|
32517
32716
|
let round2;
|
|
@@ -32908,7 +33107,7 @@ function openThroughSeam(harness, deps, run2) {
|
|
|
32908
33107
|
}
|
|
32909
33108
|
return harness.open(deps, run2);
|
|
32910
33109
|
}
|
|
32911
|
-
var isRecord3, relaunchesOf, isPiFacts, HarnessInterruptedError, HarnessMismatchError;
|
|
33110
|
+
var isRecord3, relaunchesOf, isPiFacts, HarnessInterruptedError, HarnessMismatchError, RELAUNCH_CEILING, HarnessContainerReplacedError;
|
|
32912
33111
|
var init_contract4 = __esm({
|
|
32913
33112
|
"../../src/core/harness/contract.ts"() {
|
|
32914
33113
|
"use strict";
|
|
@@ -32939,6 +33138,114 @@ var init_contract4 = __esm({
|
|
|
32939
33138
|
expected;
|
|
32940
33139
|
found;
|
|
32941
33140
|
};
|
|
33141
|
+
RELAUNCH_CEILING = 2;
|
|
33142
|
+
HarnessContainerReplacedError = class extends HarnessInterruptedError {
|
|
33143
|
+
constructor(message3, said2, was, now, record2) {
|
|
33144
|
+
super(message3, "container replaced under the run", "container_replaced");
|
|
33145
|
+
this.said = said2;
|
|
33146
|
+
this.was = was;
|
|
33147
|
+
this.now = now;
|
|
33148
|
+
this.record = record2;
|
|
33149
|
+
this.name = "HarnessContainerReplacedError";
|
|
33150
|
+
}
|
|
33151
|
+
said;
|
|
33152
|
+
was;
|
|
33153
|
+
now;
|
|
33154
|
+
record;
|
|
33155
|
+
};
|
|
33156
|
+
}
|
|
33157
|
+
});
|
|
33158
|
+
|
|
33159
|
+
// ../../src/core/dispatch/relaunch.ts
|
|
33160
|
+
async function prepareRelaunch(deps, ctx) {
|
|
33161
|
+
const { replaced, facts: facts2, harness } = ctx;
|
|
33162
|
+
const refuse3 = (why, reason2, refusal = "container_replaced") => ({
|
|
33163
|
+
kind: "refused",
|
|
33164
|
+
interruption: new RelaunchRefusedError(why, reason2, refusal)
|
|
33165
|
+
});
|
|
33166
|
+
if (harness.history !== "authored-session")
|
|
33167
|
+
return refuse3(
|
|
33168
|
+
`the ${harness.name} harness keeps its own store, which went with the container, so nothing here can rebuild its process; the run restarts from its request`,
|
|
33169
|
+
"container replaced under the run; the harness keeps its own store; restarting from the request"
|
|
33170
|
+
);
|
|
33171
|
+
if (facts2 === void 0)
|
|
33172
|
+
return refuse3(
|
|
33173
|
+
"the row carries no harness facts to count a relaunch on; the run restarts from its request",
|
|
33174
|
+
"container replaced under the run before its process left facts; restarting from the request"
|
|
33175
|
+
);
|
|
33176
|
+
const relaunches = facts2.relaunches;
|
|
33177
|
+
if (relaunches >= RELAUNCH_CEILING)
|
|
33178
|
+
return refuse3(
|
|
33179
|
+
`the container was replaced under the run again and the relaunch ceiling is ${RELAUNCH_CEILING} (${relaunches} relaunches already), so pi is not started a ${ordinal(relaunches + 2)} time; the run restarts from its request`,
|
|
33180
|
+
`relaunch ceiling: ${relaunches} relaunches already; restarting from the request`
|
|
33181
|
+
);
|
|
33182
|
+
let round2;
|
|
33183
|
+
if (ctx.binding !== void 0) {
|
|
33184
|
+
const reattached = await reattachWorkspace2(deps, {
|
|
33185
|
+
threadKey: ctx.threadKey,
|
|
33186
|
+
agent: ctx.agent,
|
|
33187
|
+
profile: ctx.profile,
|
|
33188
|
+
repoCtx: ctx.repoCtx,
|
|
33189
|
+
root: ctx.root,
|
|
33190
|
+
clock: ctx.clock,
|
|
33191
|
+
reattach: ctx.binding
|
|
33192
|
+
});
|
|
33193
|
+
if (reattached.kind === "reattach_refused")
|
|
33194
|
+
return refuse3(
|
|
33195
|
+
`the run's workspace could not be re-attached in the replacement container (${reattached.why}); the run restarts from its request as a new run in this thread`,
|
|
33196
|
+
"workspace lost with the replaced container; restarting from the request",
|
|
33197
|
+
"workspace_lost"
|
|
33198
|
+
);
|
|
33199
|
+
round2 = reattached.round;
|
|
33200
|
+
}
|
|
33201
|
+
const counted = { ...facts2, relaunches: relaunches + 1 };
|
|
33202
|
+
let written = counted;
|
|
33203
|
+
let bearer;
|
|
33204
|
+
if (deps.runBearers) {
|
|
33205
|
+
const rotated = deps.runBearers.rotate(ctx.runId, (secretHash) => {
|
|
33206
|
+
written = { ...counted, bearerHash: secretHash };
|
|
33207
|
+
ctx.saveFacts(written);
|
|
33208
|
+
});
|
|
33209
|
+
if (!rotated.ok)
|
|
33210
|
+
return refuse3(
|
|
33211
|
+
`the run's bearer could not be rotated for the relaunch (${rotated.reason}); the run restarts from its request`,
|
|
33212
|
+
`the run's bearer could not be rotated (${rotated.reason}); restarting from the request`
|
|
33213
|
+
);
|
|
33214
|
+
bearer = rotated.token;
|
|
33215
|
+
} else {
|
|
33216
|
+
ctx.saveFacts(written);
|
|
33217
|
+
}
|
|
33218
|
+
const { deadline, ...record2 } = replaced.record;
|
|
33219
|
+
return {
|
|
33220
|
+
kind: "relaunch",
|
|
33221
|
+
resume: {
|
|
33222
|
+
...record2,
|
|
33223
|
+
remainingMs: Math.max(0, deadline - ctx.clock()),
|
|
33224
|
+
facts: written,
|
|
33225
|
+
relaunch: {
|
|
33226
|
+
...replaced.was !== void 0 ? { from: replaced.was } : {},
|
|
33227
|
+
...replaced.now !== void 0 ? { to: replaced.now } : {}
|
|
33228
|
+
}
|
|
33229
|
+
},
|
|
33230
|
+
...bearer !== void 0 ? { bearer } : {},
|
|
33231
|
+
...round2 !== void 0 ? { round: round2 } : {}
|
|
33232
|
+
};
|
|
33233
|
+
}
|
|
33234
|
+
function ordinal(n2) {
|
|
33235
|
+
return n2 === 2 ? "second" : n2 === 3 ? "third" : n2 === 4 ? "fourth" : `${n2}th`;
|
|
33236
|
+
}
|
|
33237
|
+
var RelaunchRefusedError;
|
|
33238
|
+
var init_relaunch = __esm({
|
|
33239
|
+
"../../src/core/dispatch/relaunch.ts"() {
|
|
33240
|
+
"use strict";
|
|
33241
|
+
init_contract4();
|
|
33242
|
+
init_provision();
|
|
33243
|
+
RelaunchRefusedError = class extends HarnessInterruptedError {
|
|
33244
|
+
constructor(message3, reason2, refusal) {
|
|
33245
|
+
super(message3, reason2, refusal);
|
|
33246
|
+
this.name = "RelaunchRefusedError";
|
|
33247
|
+
}
|
|
33248
|
+
};
|
|
32942
33249
|
}
|
|
32943
33250
|
});
|
|
32944
33251
|
|
|
@@ -32980,7 +33287,7 @@ function startScript(start3) {
|
|
|
32980
33287
|
`mkfifo -m 600 ${shellQuote(paths.fifo)}`,
|
|
32981
33288
|
keepLog ? `: >> ${shellQuote(paths.log)}` : `: > ${shellQuote(paths.log)}`,
|
|
32982
33289
|
keepLog ? `: >> ${shellQuote(paths.errLog)}` : `: > ${shellQuote(paths.errLog)}`,
|
|
32983
|
-
`setsid -f sh -c ${shellQuote(inner)}`,
|
|
33290
|
+
`setsid -f sh -c ${shellQuote(inner)} ${DETACHED_STDIO}`,
|
|
32984
33291
|
`sleep 0.3`,
|
|
32985
33292
|
`cat ${shellQuote(paths.pidFile)}`
|
|
32986
33293
|
].join(" && ");
|
|
@@ -33087,7 +33394,7 @@ function stdoutOf(operation, out) {
|
|
|
33087
33394
|
const stdout = cut >= 0 ? out.slice(0, cut) : out;
|
|
33088
33395
|
return stdout === "(no output)" ? "" : stdout;
|
|
33089
33396
|
}
|
|
33090
|
-
var PORT_ARG, HARNESS_PORT_ENV, HarnessContainerError, WRITE_CHUNK_CHARS, INLINE_LINE_CHARS, LOG_READ_BYTES, COMMAND_WORD, FREE_PORT_JS, IDENTITY_WORD, METHOD_WORD, PATH_WORD, HEADER_NAME, OP_TIMEOUT_MS, CURL_MAX_TIME_S, secretHeaderEnv, STDERR_MARK, RUNTIME_WORD, HarnessContainerRuntimeReplacedError, ExecHarnessContainer;
|
|
33397
|
+
var PORT_ARG, HARNESS_PORT_ENV, HarnessContainerError, WRITE_CHUNK_CHARS, INLINE_LINE_CHARS, LOG_READ_BYTES, COMMAND_WORD, FREE_PORT_JS, DETACHED_STDIO, IDENTITY_WORD, METHOD_WORD, PATH_WORD, HEADER_NAME, OP_TIMEOUT_MS, CURL_MAX_TIME_S, secretHeaderEnv, STDERR_MARK, RUNTIME_WORD, HarnessContainerRuntimeReplacedError, ExecHarnessContainer;
|
|
33091
33398
|
var init_container = __esm({
|
|
33092
33399
|
"../../src/core/harness/container.ts"() {
|
|
33093
33400
|
"use strict";
|
|
@@ -33109,6 +33416,7 @@ var init_container = __esm({
|
|
|
33109
33416
|
LOG_READ_BYTES = 48 * 1024;
|
|
33110
33417
|
COMMAND_WORD = /^[A-Za-z0-9_./-]+$/;
|
|
33111
33418
|
FREE_PORT_JS = 'const s=require("net").createServer();s.listen(0,"127.0.0.1",()=>{console.log(s.address().port);s.close()})';
|
|
33419
|
+
DETACHED_STDIO = "</dev/null >/dev/null 2>&1";
|
|
33112
33420
|
IDENTITY_WORD = /^[A-Za-z0-9-]{1,64}$/;
|
|
33113
33421
|
METHOD_WORD = /^[A-Z]+$/;
|
|
33114
33422
|
PATH_WORD = /^\/\S*$/;
|
|
@@ -34251,12 +34559,32 @@ var init_mirror = __esm({
|
|
|
34251
34559
|
seedEchoPending = false;
|
|
34252
34560
|
mirroredTail;
|
|
34253
34561
|
iteration = 0;
|
|
34562
|
+
/** The rows written since the mirror started, and the compaction entries
|
|
34563
|
+
* among them by position — kept whether or not the ledger is wired, so
|
|
34564
|
+
* the harness holds this generation's copy of the record it wrote
|
|
34565
|
+
* (`written`), what a relaunch under a living bot rebuilds from. Bounded
|
|
34566
|
+
* by the run as the ledger's transcript and pi's own session file are — one
|
|
34567
|
+
* row per turn under the run's turn guard, the same bytes both already
|
|
34568
|
+
* hold — and never trimmed: a rebuilt session renders every row, with each
|
|
34569
|
+
* compaction where it sat, so a row dropped here would be a row the record
|
|
34570
|
+
* clause lost. */
|
|
34571
|
+
rows = [];
|
|
34572
|
+
compactionRows = [];
|
|
34254
34573
|
/** The highest ledger inbox seq folded in so far (run-history item 40). */
|
|
34255
34574
|
inboxConsumedSeq = 0;
|
|
34256
34575
|
/** Whether a report is owed: the mirror is wired and something happened. */
|
|
34257
34576
|
get wired() {
|
|
34258
34577
|
return this.deps.onStep !== void 0;
|
|
34259
34578
|
}
|
|
34579
|
+
/** The transcript rows this mirror has written since it started (the rows
|
|
34580
|
+
* after the seed or the resumed transcript it was constructed on), and the
|
|
34581
|
+
* compaction entries among them, each `before` counted from the first of
|
|
34582
|
+
* these rows: what a relaunch under a living bot rebuilds from
|
|
34583
|
+
* (harness-pi item 8), this generation's copy of the ledger's rows. A row
|
|
34584
|
+
* met again on a re-attach (`alreadyHeld`) was the caller's and is not here. */
|
|
34585
|
+
get written() {
|
|
34586
|
+
return { messages: [...this.rows], compactions: [...this.compactionRows] };
|
|
34587
|
+
}
|
|
34260
34588
|
/** The row (the run's own index) the calls under way ride in — the last
|
|
34261
34589
|
* assistant turn the ledger holds — for the `tool_call` stamp (run-history
|
|
34262
34590
|
* item 53); undefined before any assistant turn, or when the transcript's
|
|
@@ -34277,6 +34605,19 @@ var init_mirror = __esm({
|
|
|
34277
34605
|
expectSeedEcho() {
|
|
34278
34606
|
this.seedEchoPending = true;
|
|
34279
34607
|
}
|
|
34608
|
+
/** The settlement turn a rebuilt process's session starts on (harness-pi
|
|
34609
|
+
* item 8) — one tool result per call in flight at the death: the relay's
|
|
34610
|
+
* answer, the still-running note or the restart note — primed as the user
|
|
34611
|
+
* content pending for the next step, the write-through's own convention
|
|
34612
|
+
* (the results since the last assistant turn are the one user turn the next
|
|
34613
|
+
* step carries). The ledger's next rows are then the session's,
|
|
34614
|
+
* `[user(settlements + the continue echo), assistant]` from the seed index,
|
|
34615
|
+
* so a rebuild from the ledger at the next death hands the model a
|
|
34616
|
+
* `tool_result` for every `tool_use` and the two records agree on what the
|
|
34617
|
+
* model saw. Never on a re-attach, where pi's own log carries the results. */
|
|
34618
|
+
prime(parts) {
|
|
34619
|
+
this.pendingUser.push(...parts);
|
|
34620
|
+
}
|
|
34280
34621
|
/** The row about to be written, against the transcript's tail — compared
|
|
34281
34622
|
* once, with the first row the mirror meets: equal means the ledger holds
|
|
34282
34623
|
* it already (read again, harness-pi item 8), together with the results
|
|
@@ -34295,7 +34636,6 @@ var init_mirror = __esm({
|
|
|
34295
34636
|
* there — so the harness can move the row's offset past it. A result or a
|
|
34296
34637
|
* steer's text waits in memory for the next step, and the answer is no. */
|
|
34297
34638
|
async onMessage(message3, turn) {
|
|
34298
|
-
if (!this.deps.onStep) return false;
|
|
34299
34639
|
const chat = chatMessageOf(message3);
|
|
34300
34640
|
if (!chat) return false;
|
|
34301
34641
|
if (chat.content.length === 0) return false;
|
|
@@ -34307,7 +34647,7 @@ var init_mirror = __esm({
|
|
|
34307
34647
|
this.pendingUser.push(...chat.content);
|
|
34308
34648
|
return false;
|
|
34309
34649
|
}
|
|
34310
|
-
if (this.alreadyHeld({ turn: chat })) return
|
|
34650
|
+
if (this.alreadyHeld({ turn: chat })) return this.wired;
|
|
34311
34651
|
const turns = [];
|
|
34312
34652
|
if (this.pendingUser.length > 0) turns.push({ role: "user", content: this.pendingUser });
|
|
34313
34653
|
this.pendingUser = [];
|
|
@@ -34324,6 +34664,8 @@ var init_mirror = __esm({
|
|
|
34324
34664
|
};
|
|
34325
34665
|
this.idx += turns.length;
|
|
34326
34666
|
this.lastAssistantIdx = this.idx - 1;
|
|
34667
|
+
this.rows.push(...turns);
|
|
34668
|
+
if (!this.deps.onStep) return false;
|
|
34327
34669
|
await this.deps.onStep(report2);
|
|
34328
34670
|
return true;
|
|
34329
34671
|
}
|
|
@@ -34333,8 +34675,7 @@ var init_mirror = __esm({
|
|
|
34333
34675
|
* the summary where pi wrote it and the index moves past it. Answers as
|
|
34334
34676
|
* `onMessage` does: the ledger holds everything up to the entry. */
|
|
34335
34677
|
async onCompaction(entry, turn) {
|
|
34336
|
-
if (
|
|
34337
|
-
if (this.alreadyHeld({ compaction: entry })) return true;
|
|
34678
|
+
if (this.alreadyHeld({ compaction: entry })) return this.wired;
|
|
34338
34679
|
const turns = [];
|
|
34339
34680
|
if (this.pendingUser.length > 0) turns.push({ role: "user", content: this.pendingUser });
|
|
34340
34681
|
this.pendingUser = [];
|
|
@@ -34349,6 +34690,9 @@ var init_mirror = __esm({
|
|
|
34349
34690
|
inboxConsumedSeq: this.inboxConsumedSeq
|
|
34350
34691
|
};
|
|
34351
34692
|
this.idx += turns.length + 1;
|
|
34693
|
+
this.rows.push(...turns);
|
|
34694
|
+
this.compactionRows.push({ before: this.rows.length, entry });
|
|
34695
|
+
if (!this.deps.onStep) return false;
|
|
34352
34696
|
await this.deps.onStep(report2);
|
|
34353
34697
|
return true;
|
|
34354
34698
|
}
|
|
@@ -34507,6 +34851,426 @@ var init_process = __esm({
|
|
|
34507
34851
|
}
|
|
34508
34852
|
});
|
|
34509
34853
|
|
|
34854
|
+
// ../../src/execution/tracingExecutor.ts
|
|
34855
|
+
var TracingExecutor;
|
|
34856
|
+
var init_tracingExecutor = __esm({
|
|
34857
|
+
"../../src/execution/tracingExecutor.ts"() {
|
|
34858
|
+
"use strict";
|
|
34859
|
+
TracingExecutor = class {
|
|
34860
|
+
constructor(inner, span, backend) {
|
|
34861
|
+
this.inner = inner;
|
|
34862
|
+
this.span = span;
|
|
34863
|
+
this.backend = backend;
|
|
34864
|
+
const innerRelease = inner.release?.bind(inner);
|
|
34865
|
+
if (innerRelease) this.release = (mode) => this.timed("exec.release", (s) => innerRelease(mode, { span: s }));
|
|
34866
|
+
const innerMoveTo = inner.moveTo?.bind(inner);
|
|
34867
|
+
if (innerMoveTo) this.moveTo = (sha) => this.timed("exec.move_to", (s) => innerMoveTo(sha, { span: s }));
|
|
34868
|
+
const innerReadBytes = inner.readBytes?.bind(inner);
|
|
34869
|
+
if (innerReadBytes)
|
|
34870
|
+
this.readBytes = (path) => this.timed("exec.read_bytes", (s) => innerReadBytes(path, { span: s }));
|
|
34871
|
+
}
|
|
34872
|
+
inner;
|
|
34873
|
+
span;
|
|
34874
|
+
backend;
|
|
34875
|
+
release;
|
|
34876
|
+
moveTo;
|
|
34877
|
+
readBytes;
|
|
34878
|
+
/** Each op under its own `exec.*` span, handed to the inner executor as
|
|
34879
|
+
* `opts.span` so its HTTP calls become `http.client` children (item 21). */
|
|
34880
|
+
timed(name, fn, extra = {}) {
|
|
34881
|
+
return this.span.span(name, (s) => fn(s), {
|
|
34882
|
+
attrs: {
|
|
34883
|
+
...this.backend !== void 0 ? { backend: this.backend } : {},
|
|
34884
|
+
...extra.timeoutMs !== void 0 ? { timeoutMs: extra.timeoutMs } : {}
|
|
34885
|
+
}
|
|
34886
|
+
});
|
|
34887
|
+
}
|
|
34888
|
+
exec(command2, opts) {
|
|
34889
|
+
return this.timed("exec.exec", (s) => this.inner.exec(command2, { ...opts, span: s }), {
|
|
34890
|
+
timeoutMs: opts?.timeoutMs
|
|
34891
|
+
});
|
|
34892
|
+
}
|
|
34893
|
+
readFile(path) {
|
|
34894
|
+
return this.timed("exec.read_file", (s) => this.inner.readFile(path, { span: s }));
|
|
34895
|
+
}
|
|
34896
|
+
writeFile(path, content) {
|
|
34897
|
+
return this.timed("exec.write_file", (s) => this.inner.writeFile(path, content, { span: s }));
|
|
34898
|
+
}
|
|
34899
|
+
};
|
|
34900
|
+
}
|
|
34901
|
+
});
|
|
34902
|
+
|
|
34903
|
+
// ../../src/core/chatMessage.ts
|
|
34904
|
+
function capToolResultContent(content, cap = MAX_TOOL_RESULT_CHARS) {
|
|
34905
|
+
const capText = (text) => text.length > cap ? `${text.slice(0, cap)}
|
|
34906
|
+
\u2026[tool result truncated: ${text.length - cap} of ${text.length} characters cut \u2014 ask for a narrower slice]` : text;
|
|
34907
|
+
if (typeof content === "string") return capText(content);
|
|
34908
|
+
return content.map((p) => p.type === "text" ? { ...p, text: capText(p.text) } : p);
|
|
34909
|
+
}
|
|
34910
|
+
var MAX_TOOL_RESULT_CHARS;
|
|
34911
|
+
var init_chatMessage = __esm({
|
|
34912
|
+
"../../src/core/chatMessage.ts"() {
|
|
34913
|
+
"use strict";
|
|
34914
|
+
MAX_TOOL_RESULT_CHARS = 12e4;
|
|
34915
|
+
}
|
|
34916
|
+
});
|
|
34917
|
+
|
|
34918
|
+
// ../../src/core/harness/pi/toolRules.ts
|
|
34919
|
+
import { isAbsolute as isAbsolute5, relative, resolve as resolve10 } from "node:path";
|
|
34920
|
+
function reachFor(identity2) {
|
|
34921
|
+
if (identity2 === "write") return CODING_REACH;
|
|
34922
|
+
if (identity2 === "read") return READ_REACH;
|
|
34923
|
+
return NONE_REACH;
|
|
34924
|
+
}
|
|
34925
|
+
function judgeToolCall(tool, input, ctx) {
|
|
34926
|
+
const bundle = PI_TOOL_BUNDLES[tool];
|
|
34927
|
+
if (bundle === void 0) return outside(`${tool} is not in any bundle the ${ctx.identity} identity reaches`);
|
|
34928
|
+
if (!reachFor(ctx.identity).has(bundle)) {
|
|
34929
|
+
return outside(
|
|
34930
|
+
ctx.identity === "none" ? `${tool} is the \`${bundle}\` bundle: a run without a workspace (identity none) has none of pi's own tools` : `${tool} is the \`${bundle}\` bundle, outside the ${ctx.identity} identity's reach`
|
|
34931
|
+
);
|
|
34932
|
+
}
|
|
34933
|
+
const args = isRecord6(input) ? input : {};
|
|
34934
|
+
if (tool === "bash") return judgeBash(args.command, ctx);
|
|
34935
|
+
if (bundle === "files" || bundle === "write-files") return judgePath(args.path, ctx);
|
|
34936
|
+
return allowed;
|
|
34937
|
+
}
|
|
34938
|
+
function judgeBash(command2, ctx) {
|
|
34939
|
+
if (typeof command2 !== "string") return refused3("malformed \u2014 bash without a string command");
|
|
34940
|
+
if (CREDENTIAL_FILE.test(command2)) return refused3("credential \u2014 reads the executor's credential store");
|
|
34941
|
+
if (ENV_DUMP.test(command2)) return refused3("credential \u2014 dumps the process environment");
|
|
34942
|
+
if (CREDENTIAL_VAR.test(command2)) return refused3("credential \u2014 expands a credential variable");
|
|
34943
|
+
if (MERGE_OR_APPROVE.test(command2)) {
|
|
34944
|
+
return refused3("merge/approve \u2014 a coding run never merges or approves a pull request");
|
|
34945
|
+
}
|
|
34946
|
+
if (ctx.identity !== "write") {
|
|
34947
|
+
if (ANY_PUSH.test(command2)) return refused3("read-only \u2014 a read-identity run never pushes");
|
|
34948
|
+
if (GH_WRITE_VERB.test(command2) || GH_API_WRITE.test(command2) || CURL_GITHUB_WRITE.test(command2)) {
|
|
34949
|
+
return refused3("read-only \u2014 a read-identity run never writes to GitHub");
|
|
34950
|
+
}
|
|
34951
|
+
return allowed;
|
|
34952
|
+
}
|
|
34953
|
+
for (const match of command2.matchAll(GIT_PUSH)) {
|
|
34954
|
+
const verdict = judgePush(match[1], ctx);
|
|
34955
|
+
if (verdict.verdict !== "allowed") return verdict;
|
|
34956
|
+
}
|
|
34957
|
+
return allowed;
|
|
34958
|
+
}
|
|
34959
|
+
function judgePush(tail, ctx) {
|
|
34960
|
+
const words = tail.split(/\s+/).filter((w) => w.length > 0 && !w.startsWith("-"));
|
|
34961
|
+
const [remote, refspec] = words;
|
|
34962
|
+
if (remote !== void 0 && remote !== "origin") {
|
|
34963
|
+
return refused3(`repo:use \u2014 push to remote \`${remote}\`, not the run's repository (origin)`);
|
|
34964
|
+
}
|
|
34965
|
+
if (refspec === void 0) return allowed;
|
|
34966
|
+
const destination = refspec.replace(/^\+/, "").split(":").pop() ?? refspec;
|
|
34967
|
+
if (ctx.branch !== void 0) {
|
|
34968
|
+
const branch3 = destination === "HEAD" ? ctx.branch : destination.replace(/^refs\/heads\//, "");
|
|
34969
|
+
if (branch3 !== ctx.branch) return refused3(`repo:use \u2014 push to \`${branch3}\`, not the run's branch ${ctx.branch}`);
|
|
34970
|
+
return allowed;
|
|
34971
|
+
}
|
|
34972
|
+
const branch2 = destination.replace(/^refs\/heads\//, "");
|
|
34973
|
+
if (branch2 !== "HEAD" && (ctx.protectedBranches ?? []).includes(branch2)) {
|
|
34974
|
+
return refused3(
|
|
34975
|
+
`repo:use \u2014 push to \`${branch2}\`, the branch this run's pull request targets; push your own branch`
|
|
34976
|
+
);
|
|
34977
|
+
}
|
|
34978
|
+
return allowed;
|
|
34979
|
+
}
|
|
34980
|
+
function judgePath(path, ctx) {
|
|
34981
|
+
if (path === void 0) return allowed;
|
|
34982
|
+
if (typeof path !== "string") return refused3("malformed \u2014 a path tool without a string path");
|
|
34983
|
+
if (CREDENTIAL_FILE.test(path)) return refused3("credential \u2014 reads the executor's credential store");
|
|
34984
|
+
const rel = relative(ctx.checkout, resolve10(ctx.checkout, path));
|
|
34985
|
+
if (rel.startsWith("..") || isAbsolute5(rel)) return refused3(`path \u2014 \`${path}\` resolves outside the checkout`);
|
|
34986
|
+
return allowed;
|
|
34987
|
+
}
|
|
34988
|
+
var PI_TOOL_BUNDLES, CODING_REACH, READ_REACH, NONE_REACH, allowed, refused3, outside, isRecord6, CREDENTIAL_FILE, ENV_DUMP, CREDENTIAL_VAR, MERGE_OR_APPROVE, GIT_THEN_PUSH, GIT_PUSH, ANY_PUSH, GH_WRITE_VERB, WRITE_METHOD, GH_API_WRITE, CURL_GITHUB_WRITE;
|
|
34989
|
+
var init_toolRules = __esm({
|
|
34990
|
+
"../../src/core/harness/pi/toolRules.ts"() {
|
|
34991
|
+
"use strict";
|
|
34992
|
+
PI_TOOL_BUNDLES = {
|
|
34993
|
+
bash: "shell",
|
|
34994
|
+
read: "files",
|
|
34995
|
+
grep: "files",
|
|
34996
|
+
find: "files",
|
|
34997
|
+
ls: "files",
|
|
34998
|
+
write: "write-files",
|
|
34999
|
+
edit: "write-files",
|
|
35000
|
+
submit_pr_description: "pr",
|
|
35001
|
+
submit_verdict: "verdict"
|
|
35002
|
+
};
|
|
35003
|
+
CODING_REACH = /* @__PURE__ */ new Set([
|
|
35004
|
+
"shell",
|
|
35005
|
+
"files",
|
|
35006
|
+
"write-files",
|
|
35007
|
+
"web",
|
|
35008
|
+
"github-read",
|
|
35009
|
+
"github-write",
|
|
35010
|
+
"pr"
|
|
35011
|
+
]);
|
|
35012
|
+
READ_REACH = /* @__PURE__ */ new Set(["shell", "files", "web", "search", "github-read", "verdict"]);
|
|
35013
|
+
NONE_REACH = /* @__PURE__ */ new Set();
|
|
35014
|
+
allowed = { verdict: "allowed" };
|
|
35015
|
+
refused3 = (reason2) => ({ verdict: "refused", reason: reason2 });
|
|
35016
|
+
outside = (reason2) => ({ verdict: "outside-profile", reason: reason2 });
|
|
35017
|
+
isRecord6 = (v) => typeof v === "object" && v !== null;
|
|
35018
|
+
CREDENTIAL_FILE = /(^|[\s/'"`])(\.git\/)?(github-credentials|\.git-credentials)($|[\s'"`|;&])/;
|
|
35019
|
+
ENV_DUMP = /(^|[;&|(]\s*)((printenv|env)(\s+-[A-Za-z0-9-]+)*|export\s+-p|declare\s+-[a-zA-Z]*[px][a-zA-Z]*|set)\s*($|[|;&>)])|\/proc\/(self|\d+)\/environ|process\.env(?![.[\w])/;
|
|
35020
|
+
CREDENTIAL_VAR = /\$\{?[A-Z][A-Z0-9_]*(_API_KEY|_TOKEN|_SECRET|_BEARER|_PASSWORD)\b|printenv\s+[A-Z][A-Z0-9_]*(_API_KEY|_TOKEN|_SECRET|_BEARER|_PASSWORD)\b/;
|
|
35021
|
+
MERGE_OR_APPROVE = /\bgh\s+pr\s+(merge|review)\b|\/pulls\/\d+\/(merge|reviews)\b/;
|
|
35022
|
+
GIT_THEN_PUSH = String.raw`\bgit(?:\s+(?:-C\s+\S+|--git-dir=\S+|--work-tree=\S+|-c\s+\S+|--no-pager))*\s+push\b`;
|
|
35023
|
+
GIT_PUSH = new RegExp(`${GIT_THEN_PUSH}([^;&|]*)`, "g");
|
|
35024
|
+
ANY_PUSH = new RegExp(GIT_THEN_PUSH);
|
|
35025
|
+
GH_WRITE_VERB = /\bgh\s+(pr|issue)\s+(comment|create|edit|close|reopen|ready|lock|unlock|review|merge|delete|transfer|pin|unpin)\b/;
|
|
35026
|
+
WRITE_METHOD = "(POST|post|PATCH|patch|PUT|put|DELETE|delete)";
|
|
35027
|
+
GH_API_WRITE = new RegExp(
|
|
35028
|
+
`\\bgh\\s+api\\b(?=[^;&|]*(?:\\s(?:-X|--method)[\\s=]*${WRITE_METHOD}\\b|\\s(?:-f|-F|--field|--raw-field|--input)[\\s=]))`
|
|
35029
|
+
);
|
|
35030
|
+
CURL_GITHUB_WRITE = new RegExp(
|
|
35031
|
+
`\\bcurl\\b(?=[^;&|]*api\\.github\\.com)(?=[^;&|]*(?:\\s(?:-X|--request)[\\s=]*${WRITE_METHOD}\\b|\\s(?:-d|--data(?:-\\w+)?|--json|-T|--upload-file)[\\s=]))`
|
|
35032
|
+
);
|
|
35033
|
+
}
|
|
35034
|
+
});
|
|
35035
|
+
|
|
35036
|
+
// ../../src/core/harness/pi/relay.ts
|
|
35037
|
+
function stillRunningNote(tool) {
|
|
35038
|
+
return `This ${tool} call is still running in the bot: the process that made it was relaunched while the call was in flight, and the call keeps running there rather than being re-run \u2014 do not run it again; its result is kept on the relay under the same call id.`;
|
|
35039
|
+
}
|
|
35040
|
+
async function relayToolCall(harness, calls, ask, opts = {}) {
|
|
35041
|
+
const answer = calls.join(ask.toolCallId, () => runRelayedTool(harness, ask, { signal: calls.signal }));
|
|
35042
|
+
const window = new AbortController();
|
|
35043
|
+
const sleep2 = opts.sleep ?? sleepUnlessAborted;
|
|
35044
|
+
const progress = await Promise.race([
|
|
35045
|
+
answer.then((a) => ({ done: true, answer: a })),
|
|
35046
|
+
sleep2(opts.windowMs ?? RELAY_POLL_WINDOW_MS, window.signal).then(() => ({ done: false }))
|
|
35047
|
+
]);
|
|
35048
|
+
window.abort();
|
|
35049
|
+
return progress;
|
|
35050
|
+
}
|
|
35051
|
+
function relayedToolDefinitions(harness) {
|
|
35052
|
+
return harness.tools.map(({ name, description, inputSchema }) => ({ name, description, inputSchema }));
|
|
35053
|
+
}
|
|
35054
|
+
function authorizeToolCall(harness, ask) {
|
|
35055
|
+
harness.gateSaw(ask.toolCallId);
|
|
35056
|
+
const blocked = harness.toolsBlocked();
|
|
35057
|
+
const refuse3 = (reason2) => {
|
|
35058
|
+
harness.emit({
|
|
35059
|
+
type: "run_note",
|
|
35060
|
+
kind: "tool_refused",
|
|
35061
|
+
summary: redactAndCap(`${ask.tool} refused: ${reason2}`, 300)
|
|
35062
|
+
});
|
|
35063
|
+
return { allow: false, reason: reason2 };
|
|
35064
|
+
};
|
|
35065
|
+
if (blocked !== void 0) return refuse3(blocked);
|
|
35066
|
+
if (harness.tools.some((t) => t.name === ask.tool)) return { allow: true };
|
|
35067
|
+
const verdict = judgeToolCall(ask.tool, ask.input, harness.rules);
|
|
35068
|
+
if (verdict.verdict === "allowed") return { allow: true };
|
|
35069
|
+
return refuse3(verdict.reason);
|
|
35070
|
+
}
|
|
35071
|
+
async function runRelayedTool(harness, ask, opts = {}) {
|
|
35072
|
+
const tool = harness.tools.find((t) => t.name === ask.tool);
|
|
35073
|
+
if (!tool) return { content: [{ type: "text", text: `Unknown tool: ${ask.tool}` }], isError: true };
|
|
35074
|
+
const span = harness.toolSpan(ask.toolCallId);
|
|
35075
|
+
const base = harness.toolContext;
|
|
35076
|
+
const readConversation = base.conversation;
|
|
35077
|
+
const ctx = {
|
|
35078
|
+
...span ? {
|
|
35079
|
+
...base,
|
|
35080
|
+
span,
|
|
35081
|
+
executor: new TracingExecutor(base.executor, span, harness.backend),
|
|
35082
|
+
publish: (e) => harness.emit(withSpanId(e, span.id)),
|
|
35083
|
+
...base.github?.api.withSpan ? { github: { ...base.github, api: base.github.api.withSpan(span) } } : {}
|
|
35084
|
+
} : { ...base, publish: (e) => harness.emit(e) },
|
|
35085
|
+
...opts.signal && !base.signal ? { signal: opts.signal } : {},
|
|
35086
|
+
...readConversation && harness.callSeen ? { conversation: () => harness.callSeen(ask.toolCallId).then(() => readConversation()) } : {},
|
|
35087
|
+
// After the spreads, so the call's own id can never be shadowed by one on the base context.
|
|
35088
|
+
callId: ask.toolCallId
|
|
35089
|
+
};
|
|
35090
|
+
try {
|
|
35091
|
+
const input = typeof ask.input === "object" && ask.input !== null ? ask.input : {};
|
|
35092
|
+
const output = await tool.run(input, ctx);
|
|
35093
|
+
return { content: piContentOf(capToolResultContent(output)), isError: false };
|
|
35094
|
+
} catch (err2) {
|
|
35095
|
+
return errorAnswerOf(err2);
|
|
35096
|
+
}
|
|
35097
|
+
}
|
|
35098
|
+
function errorAnswerOf(err2) {
|
|
35099
|
+
const message3 = err2 instanceof Error ? err2.message : String(err2);
|
|
35100
|
+
return { content: piContentOf(capToolResultContent(`Error: ${message3}`)), isError: true };
|
|
35101
|
+
}
|
|
35102
|
+
function piContentOf(content) {
|
|
35103
|
+
if (typeof content === "string") return [{ type: "text", text: content }];
|
|
35104
|
+
return content.map(
|
|
35105
|
+
(p) => p.type === "text" ? { type: "text", text: p.text } : p.type === "image" ? { type: "image", data: p.data, mimeType: p.mediaType } : { type: "text", text: `[document ${p.name ?? "document"} (${p.mediaType})]` }
|
|
35106
|
+
);
|
|
35107
|
+
}
|
|
35108
|
+
function withSpanId(e, spanId) {
|
|
35109
|
+
switch (e.type) {
|
|
35110
|
+
case "tool_call":
|
|
35111
|
+
case "tool_result":
|
|
35112
|
+
case "run_note":
|
|
35113
|
+
case "assistant":
|
|
35114
|
+
case "skill_use":
|
|
35115
|
+
return { ...e, spanId };
|
|
35116
|
+
default:
|
|
35117
|
+
return e;
|
|
35118
|
+
}
|
|
35119
|
+
}
|
|
35120
|
+
var HarnessRegistry, RELAY_POLL_WINDOW_MS, RelayedCalls;
|
|
35121
|
+
var init_relay = __esm({
|
|
35122
|
+
"../../src/core/harness/pi/relay.ts"() {
|
|
35123
|
+
"use strict";
|
|
35124
|
+
init_tracingExecutor();
|
|
35125
|
+
init_chatMessage();
|
|
35126
|
+
init_awaitChildren();
|
|
35127
|
+
init_runEvents();
|
|
35128
|
+
init_toolRules();
|
|
35129
|
+
HarnessRegistry = class {
|
|
35130
|
+
live = /* @__PURE__ */ new Map();
|
|
35131
|
+
/** The run drives a pi from here, its relayed calls kept beside it; the
|
|
35132
|
+
* returned function forgets it and ends whatever call still runs. */
|
|
35133
|
+
register(harness) {
|
|
35134
|
+
return this.hold({ harness, calls: new RelayedCalls() });
|
|
35135
|
+
}
|
|
35136
|
+
/** A process relaunched under the same run — in a replacement container,
|
|
35137
|
+
* with a rotated bearer (harness-pi item 8) — takes the run's registration
|
|
35138
|
+
* over, and the run's relayed calls stay: a call in flight keeps running
|
|
35139
|
+
* under the same end signal, and an ask by its id through the new
|
|
35140
|
+
* registration joins it and reads its answer. The earlier registration's
|
|
35141
|
+
* forget is a no-op from here; the returned one forgets the run and ends
|
|
35142
|
+
* the calls. Refuses by name a run that is not registered. */
|
|
35143
|
+
replace(harness) {
|
|
35144
|
+
const previous = this.live.get(harness.runId);
|
|
35145
|
+
if (!previous) throw new Error(`run ${harness.runId} is not registered on the relay: nothing to replace`);
|
|
35146
|
+
return this.hold({ harness, calls: previous.calls });
|
|
35147
|
+
}
|
|
35148
|
+
/** The run forgotten whichever registration holds it, its calls ended: the
|
|
35149
|
+
* run loop's, for a run whose process was found gone with its container
|
|
35150
|
+
* and is not relaunched — the harness left the registration standing so a
|
|
35151
|
+
* relaunch could take it over (`replace`) with the calls kept, and a
|
|
35152
|
+
* refusal has to end them. A run not registered is nothing to forget. */
|
|
35153
|
+
forget(runId3) {
|
|
35154
|
+
const entry = this.live.get(runId3);
|
|
35155
|
+
if (!entry) return;
|
|
35156
|
+
this.live.delete(runId3);
|
|
35157
|
+
entry.calls.end();
|
|
35158
|
+
}
|
|
35159
|
+
hold(entry) {
|
|
35160
|
+
this.live.set(entry.harness.runId, entry);
|
|
35161
|
+
return () => {
|
|
35162
|
+
if (this.live.get(entry.harness.runId) !== entry) return;
|
|
35163
|
+
this.live.delete(entry.harness.runId);
|
|
35164
|
+
entry.calls.end();
|
|
35165
|
+
};
|
|
35166
|
+
}
|
|
35167
|
+
get(runId3) {
|
|
35168
|
+
return this.live.get(runId3)?.harness;
|
|
35169
|
+
}
|
|
35170
|
+
/** The run's relayed calls, for as long as it is registered. */
|
|
35171
|
+
calls(runId3) {
|
|
35172
|
+
return this.live.get(runId3)?.calls;
|
|
35173
|
+
}
|
|
35174
|
+
size() {
|
|
35175
|
+
return this.live.size;
|
|
35176
|
+
}
|
|
35177
|
+
};
|
|
35178
|
+
RELAY_POLL_WINDOW_MS = 3e4;
|
|
35179
|
+
RelayedCalls = class {
|
|
35180
|
+
calls = /* @__PURE__ */ new Map();
|
|
35181
|
+
/** The ids whose call is still running: joined by `join`, forgotten as each answers. */
|
|
35182
|
+
running = /* @__PURE__ */ new Set();
|
|
35183
|
+
/** The ids answered from the record (`settle`), never run here. */
|
|
35184
|
+
settledIds = /* @__PURE__ */ new Set();
|
|
35185
|
+
ending = new AbortController();
|
|
35186
|
+
get size() {
|
|
35187
|
+
return this.calls.size;
|
|
35188
|
+
}
|
|
35189
|
+
/** Aborted when the run ends: the signal every call here runs under. */
|
|
35190
|
+
get signal() {
|
|
35191
|
+
return this.ending.signal;
|
|
35192
|
+
}
|
|
35193
|
+
/** The call's answer as a promise: started by `start` on the first ask, the same promise after. */
|
|
35194
|
+
join(callId, start3) {
|
|
35195
|
+
let answer = this.calls.get(callId);
|
|
35196
|
+
if (!answer) {
|
|
35197
|
+
answer = start3();
|
|
35198
|
+
this.calls.set(callId, answer);
|
|
35199
|
+
this.running.add(callId);
|
|
35200
|
+
const done = () => void this.running.delete(callId);
|
|
35201
|
+
answer.then(done, done);
|
|
35202
|
+
}
|
|
35203
|
+
return answer;
|
|
35204
|
+
}
|
|
35205
|
+
/** The ids of the calls still running, in the order they were started. */
|
|
35206
|
+
inFlight() {
|
|
35207
|
+
return [...this.running];
|
|
35208
|
+
}
|
|
35209
|
+
/** A call asked here whose answer already landed: the answer, or nothing
|
|
35210
|
+
* for a call never asked, one still running (`awaitInFlight` reads those)
|
|
35211
|
+
* or one settled from the record (`settle`: the record's answer, not the
|
|
35212
|
+
* bot's). What a relaunch reads for a call the record names beside the
|
|
35213
|
+
* calls it awaits (harness-pi item 8): an answer that landed after the
|
|
35214
|
+
* process died and before it could ask again is carried into the rebuilt
|
|
35215
|
+
* session too, never lost to a restart note. A `start` that rejected is
|
|
35216
|
+
* the error answer `runRelayedTool` would have given. */
|
|
35217
|
+
async answered(callId) {
|
|
35218
|
+
const answer = this.calls.get(callId);
|
|
35219
|
+
if (!answer || this.running.has(callId) || this.settledIds.has(callId)) return void 0;
|
|
35220
|
+
return answer.then(
|
|
35221
|
+
(a) => a,
|
|
35222
|
+
(err2) => errorAnswerOf(err2)
|
|
35223
|
+
);
|
|
35224
|
+
}
|
|
35225
|
+
/** Every call still running, awaited together up to one window — the relay
|
|
35226
|
+
* window a request waits, by default — for a relaunch that rebuilds the
|
|
35227
|
+
* process's session (harness-pi item 8): a call that answers inside it is
|
|
35228
|
+
* `done` with its answer, the result the rebuilt session carries; one still
|
|
35229
|
+
* running after it is not, keeps running here for an ask by the same id,
|
|
35230
|
+
* and the rebuilt session carries the still-running note in its place
|
|
35231
|
+
* (`stillRunningNote`). A call whose `start` rejected is `done` with an
|
|
35232
|
+
* error answer, as `runRelayedTool` answers its own throws, so one
|
|
35233
|
+
* straggler's exception never loses the others' answers. Nothing is ended
|
|
35234
|
+
* or re-run. Answers at once with nothing in flight. */
|
|
35235
|
+
async awaitInFlight(opts = {}) {
|
|
35236
|
+
const ids = this.inFlight();
|
|
35237
|
+
if (ids.length === 0) return [];
|
|
35238
|
+
const window = new AbortController();
|
|
35239
|
+
const sleep2 = opts.sleep ?? sleepUnlessAborted;
|
|
35240
|
+
const closed = sleep2(opts.windowMs ?? RELAY_POLL_WINDOW_MS, window.signal).then(() => ({
|
|
35241
|
+
done: false
|
|
35242
|
+
}));
|
|
35243
|
+
const progress = await Promise.all(
|
|
35244
|
+
ids.map(async (callId) => {
|
|
35245
|
+
const answered = this.calls.get(callId).then(
|
|
35246
|
+
(answer) => ({ done: true, answer }),
|
|
35247
|
+
(err2) => ({ done: true, answer: errorAnswerOf(err2) })
|
|
35248
|
+
);
|
|
35249
|
+
return { callId, ...await Promise.race([answered, closed]) };
|
|
35250
|
+
})
|
|
35251
|
+
);
|
|
35252
|
+
window.abort();
|
|
35253
|
+
return progress;
|
|
35254
|
+
}
|
|
35255
|
+
/** The call's answer is known before it is asked (harness-pi item 8): the
|
|
35256
|
+
* record's settlement for a call in flight when the previous generation
|
|
35257
|
+
* died, whose extension asks again with the same id. Every ask reads it and
|
|
35258
|
+
* the tool never runs; a call already joined keeps the answer it has. */
|
|
35259
|
+
settle(callId, answer) {
|
|
35260
|
+
if (this.calls.has(callId)) return;
|
|
35261
|
+
this.calls.set(callId, Promise.resolve(answer));
|
|
35262
|
+
this.settledIds.add(callId);
|
|
35263
|
+
}
|
|
35264
|
+
end() {
|
|
35265
|
+
this.ending.abort();
|
|
35266
|
+
this.calls.clear();
|
|
35267
|
+
this.running.clear();
|
|
35268
|
+
this.settledIds.clear();
|
|
35269
|
+
}
|
|
35270
|
+
};
|
|
35271
|
+
}
|
|
35272
|
+
});
|
|
35273
|
+
|
|
34510
35274
|
// ../../src/core/harness/pi/transport.ts
|
|
34511
35275
|
var NEWLINE, PiRpcTransport;
|
|
34512
35276
|
var init_transport = __esm({
|
|
@@ -34642,16 +35406,25 @@ function promptOf(seed) {
|
|
|
34642
35406
|
function settlementText(s) {
|
|
34643
35407
|
return s.action === "synthetic" ? s.text : `The bot restarted while this ${s.toolUse.name} call was in flight; its result was lost \u2014 re-check its effects before re-running it.`;
|
|
34644
35408
|
}
|
|
34645
|
-
function settlementResults(settlements) {
|
|
35409
|
+
function settlementResults(settlements, settled = /* @__PURE__ */ new Map()) {
|
|
34646
35410
|
if (settlements.length === 0) return void 0;
|
|
34647
35411
|
return {
|
|
34648
35412
|
role: "user",
|
|
34649
|
-
content: settlements.map((s) =>
|
|
34650
|
-
|
|
34651
|
-
|
|
34652
|
-
|
|
34653
|
-
|
|
34654
|
-
|
|
35413
|
+
content: settlements.map((s) => {
|
|
35414
|
+
const relay = settled.get(s.toolUse.id);
|
|
35415
|
+
return {
|
|
35416
|
+
type: "tool_result",
|
|
35417
|
+
toolUseId: s.toolUse.id,
|
|
35418
|
+
content: relay?.text ?? settlementText(s),
|
|
35419
|
+
...relay?.isError ?? true ? { isError: true } : {}
|
|
35420
|
+
};
|
|
35421
|
+
})
|
|
35422
|
+
};
|
|
35423
|
+
}
|
|
35424
|
+
function relaySettlementOf(answer) {
|
|
35425
|
+
return {
|
|
35426
|
+
text: answer.content.map((c) => c.type === "text" ? c.text : "[image]").join("\n"),
|
|
35427
|
+
isError: answer.isError
|
|
34655
35428
|
};
|
|
34656
35429
|
}
|
|
34657
35430
|
function ledgerTailOf(resume) {
|
|
@@ -34782,12 +35555,36 @@ async function runPiHarnessOpen(deps, run2) {
|
|
|
34782
35555
|
await deps.sleep(seenTick);
|
|
34783
35556
|
}
|
|
34784
35557
|
};
|
|
34785
|
-
const
|
|
35558
|
+
const relaunch = run2.resume?.relaunch;
|
|
35559
|
+
const forget = relaunch !== void 0 && deps.registry.get(run2.runId) !== void 0 ? deps.registry.replace(live) : deps.registry.register(live);
|
|
34786
35560
|
const calls = deps.registry.calls(run2.runId);
|
|
34787
35561
|
for (const s of run2.resume?.settlements ?? []) calls?.settle(s.toolUse.id, settlementAnswer(s));
|
|
34788
35562
|
let pid;
|
|
34789
35563
|
let transport;
|
|
34790
35564
|
let facts2;
|
|
35565
|
+
const recordBase = {
|
|
35566
|
+
messages: run2.resume?.messages ?? run2.messages,
|
|
35567
|
+
compactions: run2.resume?.compactions ?? []
|
|
35568
|
+
};
|
|
35569
|
+
const relaySettled = /* @__PURE__ */ new Map();
|
|
35570
|
+
let stillRunning = 0;
|
|
35571
|
+
const recordNow = () => {
|
|
35572
|
+
const written = mirror.written;
|
|
35573
|
+
const messages = [...recordBase.messages, ...written.messages];
|
|
35574
|
+
const last = messages.at(-1);
|
|
35575
|
+
const calls2 = last?.role === "assistant" ? last.content.filter((p) => p.type === "tool_use") : [];
|
|
35576
|
+
return {
|
|
35577
|
+
messages,
|
|
35578
|
+
compactions: [
|
|
35579
|
+
...recordBase.compactions,
|
|
35580
|
+
...written.compactions.map((c) => ({ ...c, before: recordBase.messages.length + c.before }))
|
|
35581
|
+
],
|
|
35582
|
+
settlements: calls2.map((toolUse) => ({ toolUse, action: "synthetic", text: replacedCallNote(toolUse.name) })),
|
|
35583
|
+
turn: bridge.turns,
|
|
35584
|
+
inboxConsumedSeq: mirror.inboxConsumedSeq,
|
|
35585
|
+
deadline
|
|
35586
|
+
};
|
|
35587
|
+
};
|
|
34791
35588
|
let mirrored = 0;
|
|
34792
35589
|
const save = () => {
|
|
34793
35590
|
if (facts2) {
|
|
@@ -34814,8 +35611,8 @@ async function runPiHarnessOpen(deps, run2) {
|
|
|
34814
35611
|
hardStopped ? "the run was hard-stopped" : bypass ? "the run was stopped: a tool call bypassed the gate" : "the run ended"
|
|
34815
35612
|
);
|
|
34816
35613
|
save();
|
|
34817
|
-
forget();
|
|
34818
35614
|
if (replaced !== void 0) return;
|
|
35615
|
+
forget();
|
|
34819
35616
|
if (pid !== void 0) await container.kill(pid).catch(() => {
|
|
34820
35617
|
});
|
|
34821
35618
|
if (paths !== void 0) await container.remove(paths).catch(() => {
|
|
@@ -34827,7 +35624,7 @@ async function runPiHarnessOpen(deps, run2) {
|
|
|
34827
35624
|
let elsewhere;
|
|
34828
35625
|
const here = await container.identity();
|
|
34829
35626
|
const honoured = (hash) => hash !== void 0 && (deps.bearers === void 0 || deps.bearers.adopt(run2.runId, hash));
|
|
34830
|
-
if (recorded !== void 0) {
|
|
35627
|
+
if (recorded !== void 0 && relaunch === void 0) {
|
|
34831
35628
|
const located2 = await locatePi(recorded, container, here);
|
|
34832
35629
|
if (located2 === "another-container") {
|
|
34833
35630
|
elsewhere = `pi is elsewhere: the row's pi (pid ${recorded.pid}) ran in container ${recorded.container}, not the one this run was handed (${here}), so it was neither probed nor ended here`;
|
|
@@ -34862,7 +35659,7 @@ async function runPiHarnessOpen(deps, run2) {
|
|
|
34862
35659
|
);
|
|
34863
35660
|
} else {
|
|
34864
35661
|
paths = piRunPathsAt(await container.makeRoot(piRunPaths(run2.runId).dir));
|
|
34865
|
-
if (recorded?.root !== void 0 && recorded.root !== paths.dir && elsewhere === void 0)
|
|
35662
|
+
if (recorded?.root !== void 0 && recorded.root !== paths.dir && elsewhere === void 0 && relaunch === void 0)
|
|
34866
35663
|
await container.remove(piRunPathsAt(recorded.root)).catch(() => {
|
|
34867
35664
|
});
|
|
34868
35665
|
const spec = {
|
|
@@ -34878,12 +35675,33 @@ async function runPiHarnessOpen(deps, run2) {
|
|
|
34878
35675
|
};
|
|
34879
35676
|
let session;
|
|
34880
35677
|
if (run2.resume) {
|
|
34881
|
-
const
|
|
35678
|
+
const wanted = new Map(run2.resume.settlements.map((s) => [s.toolUse.id, s.toolUse.name]));
|
|
35679
|
+
const running = calls?.inFlight().filter((id) => wanted.has(id)) ?? [];
|
|
35680
|
+
if (running.length > 0)
|
|
35681
|
+
run2.onProgress?.(
|
|
35682
|
+
`waiting up to ${Math.round(RELAY_POLL_WINDOW_MS / 1e3)} s for ${running.length} relayed call(s) still running in the bot before pi restarts`
|
|
35683
|
+
);
|
|
35684
|
+
for (const p of await calls?.awaitInFlight({ sleep: (ms) => deps.sleep(ms) }) ?? []) {
|
|
35685
|
+
const tool = wanted.get(p.callId);
|
|
35686
|
+
if (tool === void 0) continue;
|
|
35687
|
+
relaySettled.set(
|
|
35688
|
+
p.callId,
|
|
35689
|
+
p.done ? relaySettlementOf(p.answer) : { text: stillRunningNote(tool), isError: false }
|
|
35690
|
+
);
|
|
35691
|
+
if (!p.done) stillRunning++;
|
|
35692
|
+
}
|
|
35693
|
+
for (const [callId] of wanted) {
|
|
35694
|
+
if (relaySettled.has(callId)) continue;
|
|
35695
|
+
const answer2 = await calls?.answered(callId);
|
|
35696
|
+
if (answer2 !== void 0) relaySettled.set(callId, relaySettlementOf(answer2));
|
|
35697
|
+
}
|
|
35698
|
+
const settled2 = settlementResults(run2.resume.settlements, relaySettled);
|
|
34882
35699
|
session = {
|
|
34883
35700
|
stem: "resumed",
|
|
34884
35701
|
messages: settled2 ? [...run2.resume.messages, settled2] : run2.resume.messages,
|
|
34885
35702
|
compactions: run2.resume.compactions ?? []
|
|
34886
35703
|
};
|
|
35704
|
+
if (settled2) mirror.prime(settled2.content);
|
|
34887
35705
|
} else {
|
|
34888
35706
|
const earlier2 = splitSeed(run2.messages).session;
|
|
34889
35707
|
if (earlier2.length > 0) session = { stem: "seed", messages: earlier2, compactions: [] };
|
|
@@ -34908,7 +35726,20 @@ async function runPiHarnessOpen(deps, run2) {
|
|
|
34908
35726
|
)
|
|
34909
35727
|
);
|
|
34910
35728
|
}
|
|
34911
|
-
if (run2.resume) {
|
|
35729
|
+
if (run2.resume && relaunch !== void 0) {
|
|
35730
|
+
const inFlight = run2.resume.settlements.length;
|
|
35731
|
+
const answered = relaySettled.size - stillRunning;
|
|
35732
|
+
const lost = inFlight - relaySettled.size;
|
|
35733
|
+
const settledHow = inFlight === 0 ? "nothing was in flight" : `${inFlight} call(s) were in flight: ` + [
|
|
35734
|
+
...answered > 0 ? [`${answered} answered on the relay`] : [],
|
|
35735
|
+
...stillRunning > 0 ? [`${stillRunning} still running there`] : [],
|
|
35736
|
+
...lost > 0 ? [`${lost} lost with the container, each answered with a restart note`] : []
|
|
35737
|
+
].join(", ");
|
|
35738
|
+
note(
|
|
35739
|
+
"resumed",
|
|
35740
|
+
`relaunched after the container was replaced (${relaunch.from ?? "unknown"} \u2192 ${relaunch.to ?? "unknown"}): the row's pi (pid ${recorded?.pid ?? "unknown"}) went with the old container and was neither probed nor ended here; pi restarted in the container the run holds on the mirrored transcript \u2014 ${settledHow}; ${Math.round(remainingMs / 6e4)} min of budget left`
|
|
35741
|
+
);
|
|
35742
|
+
} else if (run2.resume) {
|
|
34912
35743
|
const lost = run2.resume.settlements.length;
|
|
34913
35744
|
const how = elsewhere !== void 0 ? `${elsewhere}, and pi restarted` : ended !== void 0 && recorded !== void 0 ? `the row ${ended} (pid ${recorded.pid}), so it was ended and pi restarted` : "pi restarted";
|
|
34914
35745
|
note(
|
|
@@ -35066,7 +35897,8 @@ ${stagedLine}` : prompt;
|
|
|
35066
35897
|
return new PiContainerReplacedError(
|
|
35067
35898
|
said2.message,
|
|
35068
35899
|
facts2?.container,
|
|
35069
|
-
await container.identity().catch(() => void 0)
|
|
35900
|
+
await container.identity().catch(() => void 0),
|
|
35901
|
+
recordNow()
|
|
35070
35902
|
);
|
|
35071
35903
|
};
|
|
35072
35904
|
check();
|
|
@@ -35336,6 +36168,7 @@ var init_harness = __esm({
|
|
|
35336
36168
|
init_mirror();
|
|
35337
36169
|
init_process();
|
|
35338
36170
|
init_protocol();
|
|
36171
|
+
init_relay();
|
|
35339
36172
|
init_transport();
|
|
35340
36173
|
FINALE_TIMEOUT_MS = 3 * 6e4;
|
|
35341
36174
|
CALL_SEEN_WAIT_MS = 3e3;
|
|
@@ -35355,21 +36188,17 @@ var init_harness = __esm({
|
|
|
35355
36188
|
this.name = "GateBypassed";
|
|
35356
36189
|
}
|
|
35357
36190
|
};
|
|
35358
|
-
PiContainerReplacedError = class extends
|
|
35359
|
-
constructor(said2, was, now) {
|
|
36191
|
+
PiContainerReplacedError = class extends HarnessContainerReplacedError {
|
|
36192
|
+
constructor(said2, was, now, record2) {
|
|
35360
36193
|
super(
|
|
35361
|
-
`the container running pi was replaced (${was ?? "unknown"} \u2192 ${now ?? "unknown"}; the executor said: ${redactAndCap(said2.replace(/\s+/g, " ").trim(), 240)})
|
|
35362
|
-
|
|
35363
|
-
|
|
36194
|
+
`the container running pi was replaced (${was ?? "unknown"} \u2192 ${now ?? "unknown"}; the executor said: ${redactAndCap(said2.replace(/\s+/g, " ").trim(), 240)})`,
|
|
36195
|
+
said2,
|
|
36196
|
+
was,
|
|
36197
|
+
now,
|
|
36198
|
+
record2
|
|
35364
36199
|
);
|
|
35365
|
-
this.said = said2;
|
|
35366
|
-
this.was = was;
|
|
35367
|
-
this.now = now;
|
|
35368
36200
|
this.name = "PiContainerReplacedError";
|
|
35369
36201
|
}
|
|
35370
|
-
said;
|
|
35371
|
-
was;
|
|
35372
|
-
now;
|
|
35373
36202
|
};
|
|
35374
36203
|
POLICY_REFUSAL_REPLY = "the model refused this request under its usage policy \u2014 rephrase it and the thread continues";
|
|
35375
36204
|
ModelPolicyRefusedError = class extends Error {
|
|
@@ -35834,7 +36663,8 @@ async function runCodingPrPostStep(input) {
|
|
|
35834
36663
|
const base = prDescription ? await resolveBaseRefLazy(candidates, repo, input.fetchRepoInfo) : resolveBaseRef(candidates, void 0);
|
|
35835
36664
|
const pushedBranch = branch2 !== void 0 && branch2 !== base && pushed;
|
|
35836
36665
|
const ownPr = target.ownPr;
|
|
35837
|
-
const
|
|
36666
|
+
const onOwnPrBranch = ownPr?.headBranch !== void 0 && branch2 === ownPr.headBranch;
|
|
36667
|
+
const pushedPastOwnPr = ownPr !== void 0 && onOwnPrBranch && pushed && headSha !== void 0 && !sameCommit(headSha, ownPr.headSha) ? headSha : void 0;
|
|
35838
36668
|
if (prDescription && ownPr !== void 0 && ownPr.state !== "open" && pushedPastOwnPr !== void 0 && branch2 !== void 0) {
|
|
35839
36669
|
const url = `https://github.com/${repo}/pull/${ownPr.number}`;
|
|
35840
36670
|
console.log(
|
|
@@ -35848,7 +36678,7 @@ async function runCodingPrPostStep(input) {
|
|
|
35848
36678
|
});
|
|
35849
36679
|
return `\u26A0\uFE0F A PR description was submitted and \`${branch2}\`${branchNote} was pushed to \`${pushedPastOwnPr.slice(0, 7)}\`, but this thread's pull request ${url} is ${ownPr.state} and that branch is its head \u2014 the new commits need a new pull request, so nothing was edited: compare & open manually: ${compareUrl}`;
|
|
35850
36680
|
}
|
|
35851
|
-
if (prDescription && ownPr !== void 0 && (branch2 === base || ownPr.state !== "open")) {
|
|
36681
|
+
if (prDescription && ownPr !== void 0 && (branch2 === base || ownPr.state !== "open" && onOwnPrBranch)) {
|
|
35852
36682
|
const { number, headSha: prHead2, state } = ownPr;
|
|
35853
36683
|
const url = `https://github.com/${repo}/pull/${number}`;
|
|
35854
36684
|
const standing = state === "open" ? "" : ` (${state})`;
|
|
@@ -36199,7 +37029,6 @@ async function runLoop(deps, ctx) {
|
|
|
36199
37029
|
mcpForRun,
|
|
36200
37030
|
run: run2,
|
|
36201
37031
|
registry,
|
|
36202
|
-
round: round2,
|
|
36203
37032
|
admitted,
|
|
36204
37033
|
ledgerRun,
|
|
36205
37034
|
resume,
|
|
@@ -36226,7 +37055,9 @@ async function runLoop(deps, ctx) {
|
|
|
36226
37055
|
seed
|
|
36227
37056
|
} = ctx;
|
|
36228
37057
|
const agent = budgetedAgent(ctx.agent, profile);
|
|
36229
|
-
const {
|
|
37058
|
+
const { round: round2 } = ctx;
|
|
37059
|
+
let { executor } = round2.selection;
|
|
37060
|
+
const { binding } = round2.selection;
|
|
36230
37061
|
const coordinatorBase = (async () => {
|
|
36231
37062
|
if (coordinator === void 0) return void 0;
|
|
36232
37063
|
if (coordinator.base !== void 0 || repoCtx.baseRef !== void 0) return coordinator.base;
|
|
@@ -36420,6 +37251,12 @@ async function runLoop(deps, ctx) {
|
|
|
36420
37251
|
const postedBefore = resume ? reviewPostedBefore(resume.events) : void 0;
|
|
36421
37252
|
if (postedBefore) reviewHead = postedBefore.head;
|
|
36422
37253
|
const facts2 = resume ? harnessFactsOf(resume.row.state.harness) : void 0;
|
|
37254
|
+
let lastFacts = facts2;
|
|
37255
|
+
const saveFacts = (h) => {
|
|
37256
|
+
lastFacts = h;
|
|
37257
|
+
ledgerRun?.setState({ harness: h });
|
|
37258
|
+
};
|
|
37259
|
+
let workspaceBinding = workspaceBindingFor(round2.selection, profile.machine);
|
|
36423
37260
|
try {
|
|
36424
37261
|
if (finish2) {
|
|
36425
37262
|
onEvent({
|
|
@@ -36490,11 +37327,21 @@ async function runLoop(deps, ctx) {
|
|
|
36490
37327
|
)
|
|
36491
37328
|
)
|
|
36492
37329
|
];
|
|
36493
|
-
|
|
37330
|
+
let bearer = ctx.bearer;
|
|
37331
|
+
let harnessResume = reentry ? {
|
|
37332
|
+
messages: reentry.messages,
|
|
37333
|
+
compactions: reentry.compactions,
|
|
37334
|
+
settlements: reentry.settlements,
|
|
37335
|
+
remainingMs: reentry.remainingMs,
|
|
37336
|
+
turn: reentry.turn,
|
|
37337
|
+
inboxConsumedSeq: reentry.inboxConsumedSeq,
|
|
37338
|
+
...facts2 ? { facts: facts2 } : {}
|
|
37339
|
+
} : void 0;
|
|
37340
|
+
const openRun = () => openThroughSeam(
|
|
36494
37341
|
harnessDeps.harness,
|
|
36495
37342
|
{
|
|
36496
37343
|
container: harnessDeps.containerFor?.(executor, profile.machine) ?? harnessContainerFor(executor, profile.machine),
|
|
36497
|
-
bearer
|
|
37344
|
+
bearer,
|
|
36498
37345
|
harnessUrl,
|
|
36499
37346
|
registry: harnessDeps.registry,
|
|
36500
37347
|
...deps.runBearers ? { bearers: deps.runBearers } : {},
|
|
@@ -36525,24 +37372,55 @@ async function runLoop(deps, ctx) {
|
|
|
36525
37372
|
...stageFollowUps ? { stageFollowUps } : {},
|
|
36526
37373
|
onEvent,
|
|
36527
37374
|
onProgress,
|
|
36528
|
-
...ledgerRun ? {
|
|
36529
|
-
|
|
36530
|
-
|
|
36531
|
-
saveFacts: (h) => ledgerRun.setState({ harness: h })
|
|
36532
|
-
} : {},
|
|
36533
|
-
...reentry ? {
|
|
36534
|
-
resume: {
|
|
36535
|
-
messages: reentry.messages,
|
|
36536
|
-
compactions: reentry.compactions,
|
|
36537
|
-
settlements: reentry.settlements,
|
|
36538
|
-
remainingMs: reentry.remainingMs,
|
|
36539
|
-
turn: reentry.turn,
|
|
36540
|
-
inboxConsumedSeq: reentry.inboxConsumedSeq,
|
|
36541
|
-
...facts2 ? { facts: facts2 } : {}
|
|
36542
|
-
}
|
|
36543
|
-
} : {}
|
|
37375
|
+
...ledgerRun ? { onStep: ledgerRun.step.bind(ledgerRun), logIndexOf: ledgerRun.logIndexOf.bind(ledgerRun) } : {},
|
|
37376
|
+
saveFacts,
|
|
37377
|
+
...harnessResume ? { resume: harnessResume } : {}
|
|
36544
37378
|
}
|
|
36545
37379
|
);
|
|
37380
|
+
for (; ; ) {
|
|
37381
|
+
try {
|
|
37382
|
+
harnessSession = await openRun();
|
|
37383
|
+
break;
|
|
37384
|
+
} catch (err2) {
|
|
37385
|
+
if (!(err2 instanceof HarnessContainerReplacedError)) throw err2;
|
|
37386
|
+
let decision;
|
|
37387
|
+
try {
|
|
37388
|
+
decision = await prepareRelaunch(deps, {
|
|
37389
|
+
runId: run2.id,
|
|
37390
|
+
threadKey: msg.threadKey,
|
|
37391
|
+
agent: ctx.agent,
|
|
37392
|
+
profile,
|
|
37393
|
+
repoCtx,
|
|
37394
|
+
root,
|
|
37395
|
+
clock: clock4,
|
|
37396
|
+
harness: harnessDeps.harness,
|
|
37397
|
+
replaced: err2,
|
|
37398
|
+
facts: lastFacts,
|
|
37399
|
+
binding: workspaceBinding,
|
|
37400
|
+
saveFacts
|
|
37401
|
+
});
|
|
37402
|
+
} catch (failed) {
|
|
37403
|
+
harnessDeps.registry.forget(run2.id);
|
|
37404
|
+
throw failed;
|
|
37405
|
+
}
|
|
37406
|
+
if (decision.kind === "refused") {
|
|
37407
|
+
harnessDeps.registry.forget(run2.id);
|
|
37408
|
+
onEvent({ type: "run_note", kind: "sandbox_restarted", summary: decision.interruption.message });
|
|
37409
|
+
throw decision.interruption;
|
|
37410
|
+
}
|
|
37411
|
+
if (decision.round !== void 0) {
|
|
37412
|
+
executor = decision.round.selection.executor;
|
|
37413
|
+
toolContext.executor = executor;
|
|
37414
|
+
const rebound = workspaceBindingFor(decision.round.selection, profile.machine);
|
|
37415
|
+
if (rebound !== void 0) {
|
|
37416
|
+
workspaceBinding = rebound;
|
|
37417
|
+
ledgerRun?.setState({ binding: rebound });
|
|
37418
|
+
}
|
|
37419
|
+
}
|
|
37420
|
+
if (decision.bearer !== void 0) bearer = decision.bearer;
|
|
37421
|
+
harnessResume = decision.resume;
|
|
37422
|
+
}
|
|
37423
|
+
}
|
|
36546
37424
|
answer = harnessSession.answer;
|
|
36547
37425
|
}
|
|
36548
37426
|
if (isPrReview && repoCtx.repo && repoCtx.pr !== void 0 && run2.control.requested !== "hard" && postedBefore === void 0) {
|
|
@@ -36851,7 +37729,9 @@ var init_runLoop = __esm({
|
|
|
36851
37729
|
init_provider();
|
|
36852
37730
|
init_toolsets();
|
|
36853
37731
|
init_contract4();
|
|
37732
|
+
init_relaunch();
|
|
36854
37733
|
init_botHostContainer();
|
|
37734
|
+
init_factory();
|
|
36855
37735
|
init_container();
|
|
36856
37736
|
init_harness();
|
|
36857
37737
|
init_windDown();
|
|
@@ -37004,7 +37884,7 @@ async function shipPreflight(input) {
|
|
|
37004
37884
|
`\u{1F6AB} Nothing to ship: give ship a task (\`agent:ship in ${repo}: <task>\`), or name an open ship PR by URL to resume its review loop.`
|
|
37005
37885
|
);
|
|
37006
37886
|
}
|
|
37007
|
-
const ref = repoCtx.ref && !repoCtx.refFromPr && repoCtx.ref.toLowerCase() !== repo.toLowerCase() ? repoCtx.ref : void 0;
|
|
37887
|
+
const ref = repoCtx.ref && !repoCtx.refFromPr && !isUnitBranch(repoCtx.ref) && repoCtx.ref.toLowerCase() !== repo.toLowerCase() ? repoCtx.ref : void 0;
|
|
37008
37888
|
return {
|
|
37009
37889
|
ok: true,
|
|
37010
37890
|
entry: { repo, base: resolveBaseRef([ref], info?.defaultBranch) }
|
|
@@ -37137,7 +38017,7 @@ function planWhere(p, units, mergedBefore) {
|
|
|
37137
38017
|
async function handOffToCoordinator(deps, input) {
|
|
37138
38018
|
const log = deps.log ?? console.log;
|
|
37139
38019
|
const planned = await plan(deps, input);
|
|
37140
|
-
if (!planned.ok) return
|
|
38020
|
+
if (!planned.ok) return refused4(planned.reply);
|
|
37141
38021
|
const p = planned.planned;
|
|
37142
38022
|
const planOf = () => ({
|
|
37143
38023
|
id: p.planId,
|
|
@@ -37167,15 +38047,15 @@ async function handOffToCoordinator(deps, input) {
|
|
|
37167
38047
|
const status3 = await deps.status(latest.id);
|
|
37168
38048
|
const where = `plan \`${p.planId}\``;
|
|
37169
38049
|
if (status3.kind === "unanswered")
|
|
37170
|
-
return
|
|
38050
|
+
return refused4(
|
|
37171
38051
|
`\u26A0\uFE0F The plan runner could not tell whether \`${latest.id}\` still runs: ${status3.reason}. Nothing ran; re-issue the request to try again.`
|
|
37172
38052
|
);
|
|
37173
38053
|
if (status3.kind === "status" && RUNNING.has(status3.status))
|
|
37174
|
-
return
|
|
38054
|
+
return refused4(
|
|
37175
38055
|
`\u{1F6AB} A runner for ${where} is still running (\`${latest.id}\`, status: ${status3.status}): wait for it to end \u2014 or terminate it in the Workflows dashboard \u2014 before re-issuing.`
|
|
37176
38056
|
);
|
|
37177
38057
|
if (status3.kind === "status" && !ENDED.has(status3.status))
|
|
37178
|
-
return
|
|
38058
|
+
return refused4(
|
|
37179
38059
|
`\u{1F6AB} A runner for ${where} (\`${latest.id}\`) is in a state the bot does not read as ended (${status3.status}) \u2014 a person decides.`
|
|
37180
38060
|
);
|
|
37181
38061
|
const merged = /* @__PURE__ */ new Set();
|
|
@@ -37187,7 +38067,7 @@ async function handOffToCoordinator(deps, input) {
|
|
|
37187
38067
|
}
|
|
37188
38068
|
const remaining = p.selected.filter((u) => !merged.has(u));
|
|
37189
38069
|
if (remaining.length === 0)
|
|
37190
|
-
return
|
|
38070
|
+
return refused4(
|
|
37191
38071
|
`\u{1F6AB} Every unit of ${where} this request names is merged already (${p.selected.join(", ")}) \u2014 nothing left to run.`
|
|
37192
38072
|
);
|
|
37193
38073
|
const mergedBefore = p.selected.filter((u) => merged.has(u));
|
|
@@ -37222,16 +38102,16 @@ async function start(deps, input, instance, units, where, write) {
|
|
|
37222
38102
|
const put = write === "replace" ? await deps.instances.replace(instance) : await deps.instances.put(instance);
|
|
37223
38103
|
if (!put.ok) {
|
|
37224
38104
|
if (put.reason === "unavailable")
|
|
37225
|
-
return
|
|
38105
|
+
return refused4(
|
|
37226
38106
|
"\u26A0\uFE0F The plan runner needs run history on the state Worker (`runHistory.worker`): the instance record could not be written, so nothing ran."
|
|
37227
38107
|
);
|
|
37228
|
-
return
|
|
38108
|
+
return refused4(
|
|
37229
38109
|
`\u{1F6AB} A runner for \`${instance.id}\` was just recorded by another request \u2014 re-issue in a minute if it did not start.`
|
|
37230
38110
|
);
|
|
37231
38111
|
}
|
|
37232
38112
|
const rows = await deps.instances.putUnits(units);
|
|
37233
38113
|
if (!rows.ok)
|
|
37234
|
-
return
|
|
38114
|
+
return refused4(
|
|
37235
38115
|
"\u26A0\uFE0F The plan runner needs run history on the state Worker: the unit rows could not be written, so nothing ran."
|
|
37236
38116
|
);
|
|
37237
38117
|
let answer;
|
|
@@ -37248,26 +38128,26 @@ async function start(deps, input, instance, units, where, write) {
|
|
|
37248
38128
|
}
|
|
37249
38129
|
case "duplicate": {
|
|
37250
38130
|
const status3 = answer.status !== void 0 ? `, status: ${answer.status}` : "";
|
|
37251
|
-
return
|
|
38131
|
+
return refused4(
|
|
37252
38132
|
`\u{1F6AB} A Workflow instance \`${instance.id}\` already exists on the platform${status3} but the state Worker knew nothing of it \u2014 a person decides; re-issuing will not resume it.`
|
|
37253
38133
|
);
|
|
37254
38134
|
}
|
|
37255
38135
|
case "failed":
|
|
37256
38136
|
case "unanswered":
|
|
37257
38137
|
log(`[ship] ${input.msg.threadKey}: the plan runner ${instance.id} could not be started \u2014 ${answer.reason}`);
|
|
37258
|
-
return
|
|
38138
|
+
return refused4(
|
|
37259
38139
|
`\u26A0\uFE0F The plan runner could not be started: ${answer.reason}. Nothing ran; re-issue the request to try again.`
|
|
37260
38140
|
);
|
|
37261
38141
|
}
|
|
37262
38142
|
}
|
|
37263
|
-
var
|
|
38143
|
+
var refused4, describe6, RUNNING, ENDED;
|
|
37264
38144
|
var init_handOff = __esm({
|
|
37265
38145
|
"../../src/core/coordinator/handOff.ts"() {
|
|
37266
38146
|
"use strict";
|
|
37267
38147
|
init_contract();
|
|
37268
38148
|
init_preflight();
|
|
37269
38149
|
init_coordinator();
|
|
37270
|
-
|
|
38150
|
+
refused4 = (reply) => ({ status: "aborted", reply });
|
|
37271
38151
|
describe6 = (err2) => err2 instanceof Error ? err2.message : String(err2);
|
|
37272
38152
|
RUNNING = /* @__PURE__ */ new Set(["queued", "running", "paused", "waiting", "waitingForPause"]);
|
|
37273
38153
|
ENDED = /* @__PURE__ */ new Set(["complete", "errored", "terminated"]);
|
|
@@ -37867,12 +38747,12 @@ function continuable(run2) {
|
|
|
37867
38747
|
return run2.finished && run2.session !== void 0 && run2.agent !== void 0;
|
|
37868
38748
|
}
|
|
37869
38749
|
function stickyAgentOf(runs) {
|
|
37870
|
-
const newest = runs
|
|
38750
|
+
const newest = runs.find(addressed);
|
|
37871
38751
|
if (newest === void 0 || !continuable(newest)) return void 0;
|
|
37872
38752
|
return newest.agent;
|
|
37873
38753
|
}
|
|
37874
38754
|
function threadRouteOf(runs, agent) {
|
|
37875
|
-
const newest = runs
|
|
38755
|
+
const newest = runs.find(addressed);
|
|
37876
38756
|
if (newest === void 0 || !continuable(newest) || newest.agent !== agent || newest.route === void 0)
|
|
37877
38757
|
return void 0;
|
|
37878
38758
|
const { preset, reason: reason2, model } = newest.route;
|
|
@@ -37896,11 +38776,12 @@ function threadPrOf(runs) {
|
|
|
37896
38776
|
if (run2 === void 0) return void 0;
|
|
37897
38777
|
return { repo: run2.repo, number: run2.pr.number, at: run2.finishedAt ?? run2.startedAt };
|
|
37898
38778
|
}
|
|
37899
|
-
var THREAD_READ_LIMIT;
|
|
38779
|
+
var THREAD_READ_LIMIT, addressed;
|
|
37900
38780
|
var init_thread = __esm({
|
|
37901
38781
|
"../../src/core/dispatch/thread.ts"() {
|
|
37902
38782
|
"use strict";
|
|
37903
38783
|
THREAD_READ_LIMIT = 8;
|
|
38784
|
+
addressed = (run2) => run2.parentInstanceId === void 0;
|
|
37904
38785
|
}
|
|
37905
38786
|
});
|
|
37906
38787
|
|
|
@@ -39988,391 +40869,6 @@ var init_piHarness = __esm({
|
|
|
39988
40869
|
}
|
|
39989
40870
|
});
|
|
39990
40871
|
|
|
39991
|
-
// ../../src/execution/tracingExecutor.ts
|
|
39992
|
-
var TracingExecutor;
|
|
39993
|
-
var init_tracingExecutor = __esm({
|
|
39994
|
-
"../../src/execution/tracingExecutor.ts"() {
|
|
39995
|
-
"use strict";
|
|
39996
|
-
TracingExecutor = class {
|
|
39997
|
-
constructor(inner, span, backend) {
|
|
39998
|
-
this.inner = inner;
|
|
39999
|
-
this.span = span;
|
|
40000
|
-
this.backend = backend;
|
|
40001
|
-
const innerRelease = inner.release?.bind(inner);
|
|
40002
|
-
if (innerRelease) this.release = (mode) => this.timed("exec.release", (s) => innerRelease(mode, { span: s }));
|
|
40003
|
-
const innerMoveTo = inner.moveTo?.bind(inner);
|
|
40004
|
-
if (innerMoveTo) this.moveTo = (sha) => this.timed("exec.move_to", (s) => innerMoveTo(sha, { span: s }));
|
|
40005
|
-
const innerReadBytes = inner.readBytes?.bind(inner);
|
|
40006
|
-
if (innerReadBytes)
|
|
40007
|
-
this.readBytes = (path) => this.timed("exec.read_bytes", (s) => innerReadBytes(path, { span: s }));
|
|
40008
|
-
}
|
|
40009
|
-
inner;
|
|
40010
|
-
span;
|
|
40011
|
-
backend;
|
|
40012
|
-
release;
|
|
40013
|
-
moveTo;
|
|
40014
|
-
readBytes;
|
|
40015
|
-
/** Each op under its own `exec.*` span, handed to the inner executor as
|
|
40016
|
-
* `opts.span` so its HTTP calls become `http.client` children (item 21). */
|
|
40017
|
-
timed(name, fn, extra = {}) {
|
|
40018
|
-
return this.span.span(name, (s) => fn(s), {
|
|
40019
|
-
attrs: {
|
|
40020
|
-
...this.backend !== void 0 ? { backend: this.backend } : {},
|
|
40021
|
-
...extra.timeoutMs !== void 0 ? { timeoutMs: extra.timeoutMs } : {}
|
|
40022
|
-
}
|
|
40023
|
-
});
|
|
40024
|
-
}
|
|
40025
|
-
exec(command2, opts) {
|
|
40026
|
-
return this.timed("exec.exec", (s) => this.inner.exec(command2, { ...opts, span: s }), {
|
|
40027
|
-
timeoutMs: opts?.timeoutMs
|
|
40028
|
-
});
|
|
40029
|
-
}
|
|
40030
|
-
readFile(path) {
|
|
40031
|
-
return this.timed("exec.read_file", (s) => this.inner.readFile(path, { span: s }));
|
|
40032
|
-
}
|
|
40033
|
-
writeFile(path, content) {
|
|
40034
|
-
return this.timed("exec.write_file", (s) => this.inner.writeFile(path, content, { span: s }));
|
|
40035
|
-
}
|
|
40036
|
-
};
|
|
40037
|
-
}
|
|
40038
|
-
});
|
|
40039
|
-
|
|
40040
|
-
// ../../src/core/chatMessage.ts
|
|
40041
|
-
function capToolResultContent(content, cap = MAX_TOOL_RESULT_CHARS) {
|
|
40042
|
-
const capText = (text) => text.length > cap ? `${text.slice(0, cap)}
|
|
40043
|
-
\u2026[tool result truncated: ${text.length - cap} of ${text.length} characters cut \u2014 ask for a narrower slice]` : text;
|
|
40044
|
-
if (typeof content === "string") return capText(content);
|
|
40045
|
-
return content.map((p) => p.type === "text" ? { ...p, text: capText(p.text) } : p);
|
|
40046
|
-
}
|
|
40047
|
-
var MAX_TOOL_RESULT_CHARS;
|
|
40048
|
-
var init_chatMessage = __esm({
|
|
40049
|
-
"../../src/core/chatMessage.ts"() {
|
|
40050
|
-
"use strict";
|
|
40051
|
-
MAX_TOOL_RESULT_CHARS = 12e4;
|
|
40052
|
-
}
|
|
40053
|
-
});
|
|
40054
|
-
|
|
40055
|
-
// ../../src/core/harness/pi/toolRules.ts
|
|
40056
|
-
import { isAbsolute as isAbsolute5, relative, resolve as resolve10 } from "node:path";
|
|
40057
|
-
function reachFor(identity2) {
|
|
40058
|
-
if (identity2 === "write") return CODING_REACH;
|
|
40059
|
-
if (identity2 === "read") return READ_REACH;
|
|
40060
|
-
return NONE_REACH;
|
|
40061
|
-
}
|
|
40062
|
-
function judgeToolCall(tool, input, ctx) {
|
|
40063
|
-
const bundle = PI_TOOL_BUNDLES[tool];
|
|
40064
|
-
if (bundle === void 0) return outside(`${tool} is not in any bundle the ${ctx.identity} identity reaches`);
|
|
40065
|
-
if (!reachFor(ctx.identity).has(bundle)) {
|
|
40066
|
-
return outside(
|
|
40067
|
-
ctx.identity === "none" ? `${tool} is the \`${bundle}\` bundle: a run without a workspace (identity none) has none of pi's own tools` : `${tool} is the \`${bundle}\` bundle, outside the ${ctx.identity} identity's reach`
|
|
40068
|
-
);
|
|
40069
|
-
}
|
|
40070
|
-
const args = isRecord6(input) ? input : {};
|
|
40071
|
-
if (tool === "bash") return judgeBash(args.command, ctx);
|
|
40072
|
-
if (bundle === "files" || bundle === "write-files") return judgePath(args.path, ctx);
|
|
40073
|
-
return allowed;
|
|
40074
|
-
}
|
|
40075
|
-
function judgeBash(command2, ctx) {
|
|
40076
|
-
if (typeof command2 !== "string") return refused4("malformed \u2014 bash without a string command");
|
|
40077
|
-
if (CREDENTIAL_FILE.test(command2)) return refused4("credential \u2014 reads the executor's credential store");
|
|
40078
|
-
if (ENV_DUMP.test(command2)) return refused4("credential \u2014 dumps the process environment");
|
|
40079
|
-
if (CREDENTIAL_VAR.test(command2)) return refused4("credential \u2014 expands a credential variable");
|
|
40080
|
-
if (MERGE_OR_APPROVE.test(command2)) {
|
|
40081
|
-
return refused4("merge/approve \u2014 a coding run never merges or approves a pull request");
|
|
40082
|
-
}
|
|
40083
|
-
if (ctx.identity !== "write") {
|
|
40084
|
-
if (ANY_PUSH.test(command2)) return refused4("read-only \u2014 a read-identity run never pushes");
|
|
40085
|
-
if (GH_WRITE_VERB.test(command2) || GH_API_WRITE.test(command2) || CURL_GITHUB_WRITE.test(command2)) {
|
|
40086
|
-
return refused4("read-only \u2014 a read-identity run never writes to GitHub");
|
|
40087
|
-
}
|
|
40088
|
-
return allowed;
|
|
40089
|
-
}
|
|
40090
|
-
for (const match of command2.matchAll(GIT_PUSH)) {
|
|
40091
|
-
const verdict = judgePush(match[1], ctx);
|
|
40092
|
-
if (verdict.verdict !== "allowed") return verdict;
|
|
40093
|
-
}
|
|
40094
|
-
return allowed;
|
|
40095
|
-
}
|
|
40096
|
-
function judgePush(tail, ctx) {
|
|
40097
|
-
const words = tail.split(/\s+/).filter((w) => w.length > 0 && !w.startsWith("-"));
|
|
40098
|
-
const [remote, refspec] = words;
|
|
40099
|
-
if (remote !== void 0 && remote !== "origin") {
|
|
40100
|
-
return refused4(`repo:use \u2014 push to remote \`${remote}\`, not the run's repository (origin)`);
|
|
40101
|
-
}
|
|
40102
|
-
if (refspec === void 0) return allowed;
|
|
40103
|
-
const destination = refspec.replace(/^\+/, "").split(":").pop() ?? refspec;
|
|
40104
|
-
if (ctx.branch !== void 0) {
|
|
40105
|
-
const branch3 = destination === "HEAD" ? ctx.branch : destination.replace(/^refs\/heads\//, "");
|
|
40106
|
-
if (branch3 !== ctx.branch) return refused4(`repo:use \u2014 push to \`${branch3}\`, not the run's branch ${ctx.branch}`);
|
|
40107
|
-
return allowed;
|
|
40108
|
-
}
|
|
40109
|
-
const branch2 = destination.replace(/^refs\/heads\//, "");
|
|
40110
|
-
if (branch2 !== "HEAD" && (ctx.protectedBranches ?? []).includes(branch2)) {
|
|
40111
|
-
return refused4(
|
|
40112
|
-
`repo:use \u2014 push to \`${branch2}\`, the branch this run's pull request targets; push your own branch`
|
|
40113
|
-
);
|
|
40114
|
-
}
|
|
40115
|
-
return allowed;
|
|
40116
|
-
}
|
|
40117
|
-
function judgePath(path, ctx) {
|
|
40118
|
-
if (path === void 0) return allowed;
|
|
40119
|
-
if (typeof path !== "string") return refused4("malformed \u2014 a path tool without a string path");
|
|
40120
|
-
if (CREDENTIAL_FILE.test(path)) return refused4("credential \u2014 reads the executor's credential store");
|
|
40121
|
-
const rel = relative(ctx.checkout, resolve10(ctx.checkout, path));
|
|
40122
|
-
if (rel.startsWith("..") || isAbsolute5(rel)) return refused4(`path \u2014 \`${path}\` resolves outside the checkout`);
|
|
40123
|
-
return allowed;
|
|
40124
|
-
}
|
|
40125
|
-
var PI_TOOL_BUNDLES, CODING_REACH, READ_REACH, NONE_REACH, allowed, refused4, outside, isRecord6, CREDENTIAL_FILE, ENV_DUMP, CREDENTIAL_VAR, MERGE_OR_APPROVE, GIT_THEN_PUSH, GIT_PUSH, ANY_PUSH, GH_WRITE_VERB, WRITE_METHOD, GH_API_WRITE, CURL_GITHUB_WRITE;
|
|
40126
|
-
var init_toolRules = __esm({
|
|
40127
|
-
"../../src/core/harness/pi/toolRules.ts"() {
|
|
40128
|
-
"use strict";
|
|
40129
|
-
PI_TOOL_BUNDLES = {
|
|
40130
|
-
bash: "shell",
|
|
40131
|
-
read: "files",
|
|
40132
|
-
grep: "files",
|
|
40133
|
-
find: "files",
|
|
40134
|
-
ls: "files",
|
|
40135
|
-
write: "write-files",
|
|
40136
|
-
edit: "write-files",
|
|
40137
|
-
submit_pr_description: "pr",
|
|
40138
|
-
submit_verdict: "verdict"
|
|
40139
|
-
};
|
|
40140
|
-
CODING_REACH = /* @__PURE__ */ new Set([
|
|
40141
|
-
"shell",
|
|
40142
|
-
"files",
|
|
40143
|
-
"write-files",
|
|
40144
|
-
"web",
|
|
40145
|
-
"github-read",
|
|
40146
|
-
"github-write",
|
|
40147
|
-
"pr"
|
|
40148
|
-
]);
|
|
40149
|
-
READ_REACH = /* @__PURE__ */ new Set(["shell", "files", "web", "search", "github-read", "verdict"]);
|
|
40150
|
-
NONE_REACH = /* @__PURE__ */ new Set();
|
|
40151
|
-
allowed = { verdict: "allowed" };
|
|
40152
|
-
refused4 = (reason2) => ({ verdict: "refused", reason: reason2 });
|
|
40153
|
-
outside = (reason2) => ({ verdict: "outside-profile", reason: reason2 });
|
|
40154
|
-
isRecord6 = (v) => typeof v === "object" && v !== null;
|
|
40155
|
-
CREDENTIAL_FILE = /(^|[\s/'"`])(\.git\/)?(github-credentials|\.git-credentials)($|[\s'"`|;&])/;
|
|
40156
|
-
ENV_DUMP = /(^|[;&|(]\s*)((printenv|env)(\s+-[A-Za-z0-9-]+)*|export\s+-p|declare\s+-[a-zA-Z]*[px][a-zA-Z]*|set)\s*($|[|;&>)])|\/proc\/(self|\d+)\/environ|process\.env(?![.[\w])/;
|
|
40157
|
-
CREDENTIAL_VAR = /\$\{?[A-Z][A-Z0-9_]*(_API_KEY|_TOKEN|_SECRET|_BEARER|_PASSWORD)\b|printenv\s+[A-Z][A-Z0-9_]*(_API_KEY|_TOKEN|_SECRET|_BEARER|_PASSWORD)\b/;
|
|
40158
|
-
MERGE_OR_APPROVE = /\bgh\s+pr\s+(merge|review)\b|\/pulls\/\d+\/(merge|reviews)\b/;
|
|
40159
|
-
GIT_THEN_PUSH = String.raw`\bgit(?:\s+(?:-C\s+\S+|--git-dir=\S+|--work-tree=\S+|-c\s+\S+|--no-pager))*\s+push\b`;
|
|
40160
|
-
GIT_PUSH = new RegExp(`${GIT_THEN_PUSH}([^;&|]*)`, "g");
|
|
40161
|
-
ANY_PUSH = new RegExp(GIT_THEN_PUSH);
|
|
40162
|
-
GH_WRITE_VERB = /\bgh\s+(pr|issue)\s+(comment|create|edit|close|reopen|ready|lock|unlock|review|merge|delete|transfer|pin|unpin)\b/;
|
|
40163
|
-
WRITE_METHOD = "(POST|post|PATCH|patch|PUT|put|DELETE|delete)";
|
|
40164
|
-
GH_API_WRITE = new RegExp(
|
|
40165
|
-
`\\bgh\\s+api\\b(?=[^;&|]*(?:\\s(?:-X|--method)[\\s=]*${WRITE_METHOD}\\b|\\s(?:-f|-F|--field|--raw-field|--input)[\\s=]))`
|
|
40166
|
-
);
|
|
40167
|
-
CURL_GITHUB_WRITE = new RegExp(
|
|
40168
|
-
`\\bcurl\\b(?=[^;&|]*api\\.github\\.com)(?=[^;&|]*(?:\\s(?:-X|--request)[\\s=]*${WRITE_METHOD}\\b|\\s(?:-d|--data(?:-\\w+)?|--json|-T|--upload-file)[\\s=]))`
|
|
40169
|
-
);
|
|
40170
|
-
}
|
|
40171
|
-
});
|
|
40172
|
-
|
|
40173
|
-
// ../../src/core/harness/pi/relay.ts
|
|
40174
|
-
async function relayToolCall(harness, calls, ask, opts = {}) {
|
|
40175
|
-
const answer = calls.join(ask.toolCallId, () => runRelayedTool(harness, ask, { signal: calls.signal }));
|
|
40176
|
-
const window = new AbortController();
|
|
40177
|
-
const sleep2 = opts.sleep ?? sleepUnlessAborted;
|
|
40178
|
-
const progress = await Promise.race([
|
|
40179
|
-
answer.then((a) => ({ done: true, answer: a })),
|
|
40180
|
-
sleep2(opts.windowMs ?? RELAY_POLL_WINDOW_MS, window.signal).then(() => ({ done: false }))
|
|
40181
|
-
]);
|
|
40182
|
-
window.abort();
|
|
40183
|
-
return progress;
|
|
40184
|
-
}
|
|
40185
|
-
function relayedToolDefinitions(harness) {
|
|
40186
|
-
return harness.tools.map(({ name, description, inputSchema }) => ({ name, description, inputSchema }));
|
|
40187
|
-
}
|
|
40188
|
-
function authorizeToolCall(harness, ask) {
|
|
40189
|
-
harness.gateSaw(ask.toolCallId);
|
|
40190
|
-
const blocked = harness.toolsBlocked();
|
|
40191
|
-
const refuse3 = (reason2) => {
|
|
40192
|
-
harness.emit({
|
|
40193
|
-
type: "run_note",
|
|
40194
|
-
kind: "tool_refused",
|
|
40195
|
-
summary: redactAndCap(`${ask.tool} refused: ${reason2}`, 300)
|
|
40196
|
-
});
|
|
40197
|
-
return { allow: false, reason: reason2 };
|
|
40198
|
-
};
|
|
40199
|
-
if (blocked !== void 0) return refuse3(blocked);
|
|
40200
|
-
if (harness.tools.some((t) => t.name === ask.tool)) return { allow: true };
|
|
40201
|
-
const verdict = judgeToolCall(ask.tool, ask.input, harness.rules);
|
|
40202
|
-
if (verdict.verdict === "allowed") return { allow: true };
|
|
40203
|
-
return refuse3(verdict.reason);
|
|
40204
|
-
}
|
|
40205
|
-
async function runRelayedTool(harness, ask, opts = {}) {
|
|
40206
|
-
const tool = harness.tools.find((t) => t.name === ask.tool);
|
|
40207
|
-
if (!tool) return { content: [{ type: "text", text: `Unknown tool: ${ask.tool}` }], isError: true };
|
|
40208
|
-
const span = harness.toolSpan(ask.toolCallId);
|
|
40209
|
-
const base = harness.toolContext;
|
|
40210
|
-
const readConversation = base.conversation;
|
|
40211
|
-
const ctx = {
|
|
40212
|
-
...span ? {
|
|
40213
|
-
...base,
|
|
40214
|
-
span,
|
|
40215
|
-
executor: new TracingExecutor(base.executor, span, harness.backend),
|
|
40216
|
-
publish: (e) => harness.emit(withSpanId(e, span.id)),
|
|
40217
|
-
...base.github?.api.withSpan ? { github: { ...base.github, api: base.github.api.withSpan(span) } } : {}
|
|
40218
|
-
} : { ...base, publish: (e) => harness.emit(e) },
|
|
40219
|
-
...opts.signal && !base.signal ? { signal: opts.signal } : {},
|
|
40220
|
-
...readConversation && harness.callSeen ? { conversation: () => harness.callSeen(ask.toolCallId).then(() => readConversation()) } : {},
|
|
40221
|
-
// After the spreads, so the call's own id can never be shadowed by one on the base context.
|
|
40222
|
-
callId: ask.toolCallId
|
|
40223
|
-
};
|
|
40224
|
-
try {
|
|
40225
|
-
const input = typeof ask.input === "object" && ask.input !== null ? ask.input : {};
|
|
40226
|
-
const output = await tool.run(input, ctx);
|
|
40227
|
-
return { content: piContentOf(capToolResultContent(output)), isError: false };
|
|
40228
|
-
} catch (err2) {
|
|
40229
|
-
return errorAnswerOf(err2);
|
|
40230
|
-
}
|
|
40231
|
-
}
|
|
40232
|
-
function errorAnswerOf(err2) {
|
|
40233
|
-
const message3 = err2 instanceof Error ? err2.message : String(err2);
|
|
40234
|
-
return { content: piContentOf(capToolResultContent(`Error: ${message3}`)), isError: true };
|
|
40235
|
-
}
|
|
40236
|
-
function piContentOf(content) {
|
|
40237
|
-
if (typeof content === "string") return [{ type: "text", text: content }];
|
|
40238
|
-
return content.map(
|
|
40239
|
-
(p) => p.type === "text" ? { type: "text", text: p.text } : p.type === "image" ? { type: "image", data: p.data, mimeType: p.mediaType } : { type: "text", text: `[document ${p.name ?? "document"} (${p.mediaType})]` }
|
|
40240
|
-
);
|
|
40241
|
-
}
|
|
40242
|
-
function withSpanId(e, spanId) {
|
|
40243
|
-
switch (e.type) {
|
|
40244
|
-
case "tool_call":
|
|
40245
|
-
case "tool_result":
|
|
40246
|
-
case "run_note":
|
|
40247
|
-
case "assistant":
|
|
40248
|
-
case "skill_use":
|
|
40249
|
-
return { ...e, spanId };
|
|
40250
|
-
default:
|
|
40251
|
-
return e;
|
|
40252
|
-
}
|
|
40253
|
-
}
|
|
40254
|
-
var HarnessRegistry, RELAY_POLL_WINDOW_MS, RelayedCalls;
|
|
40255
|
-
var init_relay = __esm({
|
|
40256
|
-
"../../src/core/harness/pi/relay.ts"() {
|
|
40257
|
-
"use strict";
|
|
40258
|
-
init_tracingExecutor();
|
|
40259
|
-
init_chatMessage();
|
|
40260
|
-
init_awaitChildren();
|
|
40261
|
-
init_runEvents();
|
|
40262
|
-
init_toolRules();
|
|
40263
|
-
HarnessRegistry = class {
|
|
40264
|
-
live = /* @__PURE__ */ new Map();
|
|
40265
|
-
/** The run drives a pi from here, its relayed calls kept beside it; the
|
|
40266
|
-
* returned function forgets it and ends whatever call still runs. */
|
|
40267
|
-
register(harness) {
|
|
40268
|
-
return this.hold({ harness, calls: new RelayedCalls() });
|
|
40269
|
-
}
|
|
40270
|
-
/** A process relaunched under the same run — in a replacement container,
|
|
40271
|
-
* with a rotated bearer (harness-pi item 8) — takes the run's registration
|
|
40272
|
-
* over, and the run's relayed calls stay: a call in flight keeps running
|
|
40273
|
-
* under the same end signal, and an ask by its id through the new
|
|
40274
|
-
* registration joins it and reads its answer. The earlier registration's
|
|
40275
|
-
* forget is a no-op from here; the returned one forgets the run and ends
|
|
40276
|
-
* the calls. Refuses by name a run that is not registered. */
|
|
40277
|
-
replace(harness) {
|
|
40278
|
-
const previous = this.live.get(harness.runId);
|
|
40279
|
-
if (!previous) throw new Error(`run ${harness.runId} is not registered on the relay: nothing to replace`);
|
|
40280
|
-
return this.hold({ harness, calls: previous.calls });
|
|
40281
|
-
}
|
|
40282
|
-
hold(entry) {
|
|
40283
|
-
this.live.set(entry.harness.runId, entry);
|
|
40284
|
-
return () => {
|
|
40285
|
-
if (this.live.get(entry.harness.runId) !== entry) return;
|
|
40286
|
-
this.live.delete(entry.harness.runId);
|
|
40287
|
-
entry.calls.end();
|
|
40288
|
-
};
|
|
40289
|
-
}
|
|
40290
|
-
get(runId3) {
|
|
40291
|
-
return this.live.get(runId3)?.harness;
|
|
40292
|
-
}
|
|
40293
|
-
/** The run's relayed calls, for as long as it is registered. */
|
|
40294
|
-
calls(runId3) {
|
|
40295
|
-
return this.live.get(runId3)?.calls;
|
|
40296
|
-
}
|
|
40297
|
-
size() {
|
|
40298
|
-
return this.live.size;
|
|
40299
|
-
}
|
|
40300
|
-
};
|
|
40301
|
-
RELAY_POLL_WINDOW_MS = 3e4;
|
|
40302
|
-
RelayedCalls = class {
|
|
40303
|
-
calls = /* @__PURE__ */ new Map();
|
|
40304
|
-
/** The ids whose call is still running: joined by `join`, forgotten as each answers. */
|
|
40305
|
-
running = /* @__PURE__ */ new Set();
|
|
40306
|
-
ending = new AbortController();
|
|
40307
|
-
get size() {
|
|
40308
|
-
return this.calls.size;
|
|
40309
|
-
}
|
|
40310
|
-
/** Aborted when the run ends: the signal every call here runs under. */
|
|
40311
|
-
get signal() {
|
|
40312
|
-
return this.ending.signal;
|
|
40313
|
-
}
|
|
40314
|
-
/** The call's answer as a promise: started by `start` on the first ask, the same promise after. */
|
|
40315
|
-
join(callId, start3) {
|
|
40316
|
-
let answer = this.calls.get(callId);
|
|
40317
|
-
if (!answer) {
|
|
40318
|
-
answer = start3();
|
|
40319
|
-
this.calls.set(callId, answer);
|
|
40320
|
-
this.running.add(callId);
|
|
40321
|
-
const done = () => void this.running.delete(callId);
|
|
40322
|
-
answer.then(done, done);
|
|
40323
|
-
}
|
|
40324
|
-
return answer;
|
|
40325
|
-
}
|
|
40326
|
-
/** The ids of the calls still running, in the order they were started. */
|
|
40327
|
-
inFlight() {
|
|
40328
|
-
return [...this.running];
|
|
40329
|
-
}
|
|
40330
|
-
/** Every call still running, awaited together up to one window — the relay
|
|
40331
|
-
* window a request waits, by default — for a relaunch that rebuilds the
|
|
40332
|
-
* process's session (harness-pi item 8): a call that answers inside it is
|
|
40333
|
-
* `done` with its answer, the result the rebuilt session carries; one still
|
|
40334
|
-
* running after it is not, keeps running here for an ask by the same id,
|
|
40335
|
-
* and the rebuilt session carries the still-running note in its place
|
|
40336
|
-
* (`stillRunningNote`). A call whose `start` rejected is `done` with an
|
|
40337
|
-
* error answer, as `runRelayedTool` answers its own throws, so one
|
|
40338
|
-
* straggler's exception never loses the others' answers. Nothing is ended
|
|
40339
|
-
* or re-run. Answers at once with nothing in flight. */
|
|
40340
|
-
async awaitInFlight(opts = {}) {
|
|
40341
|
-
const ids = this.inFlight();
|
|
40342
|
-
if (ids.length === 0) return [];
|
|
40343
|
-
const window = new AbortController();
|
|
40344
|
-
const sleep2 = opts.sleep ?? sleepUnlessAborted;
|
|
40345
|
-
const closed = sleep2(opts.windowMs ?? RELAY_POLL_WINDOW_MS, window.signal).then(() => ({
|
|
40346
|
-
done: false
|
|
40347
|
-
}));
|
|
40348
|
-
const progress = await Promise.all(
|
|
40349
|
-
ids.map(async (callId) => {
|
|
40350
|
-
const answered = this.calls.get(callId).then(
|
|
40351
|
-
(answer) => ({ done: true, answer }),
|
|
40352
|
-
(err2) => ({ done: true, answer: errorAnswerOf(err2) })
|
|
40353
|
-
);
|
|
40354
|
-
return { callId, ...await Promise.race([answered, closed]) };
|
|
40355
|
-
})
|
|
40356
|
-
);
|
|
40357
|
-
window.abort();
|
|
40358
|
-
return progress;
|
|
40359
|
-
}
|
|
40360
|
-
/** The call's answer is known before it is asked (harness-pi item 8): the
|
|
40361
|
-
* record's settlement for a call in flight when the previous generation
|
|
40362
|
-
* died, whose extension asks again with the same id. Every ask reads it and
|
|
40363
|
-
* the tool never runs; a call already joined keeps the answer it has. */
|
|
40364
|
-
settle(callId, answer) {
|
|
40365
|
-
if (!this.calls.has(callId)) this.calls.set(callId, Promise.resolve(answer));
|
|
40366
|
-
}
|
|
40367
|
-
end() {
|
|
40368
|
-
this.ending.abort();
|
|
40369
|
-
this.calls.clear();
|
|
40370
|
-
this.running.clear();
|
|
40371
|
-
}
|
|
40372
|
-
};
|
|
40373
|
-
}
|
|
40374
|
-
});
|
|
40375
|
-
|
|
40376
40872
|
// ../../src/core/runLedger/takeover.ts
|
|
40377
40873
|
var LedgerTakeover;
|
|
40378
40874
|
var init_takeover = __esm({
|
|
@@ -41479,7 +41975,8 @@ function buildMcp(config, secrets, opts = {}) {
|
|
|
41479
41975
|
fetch: webFetch,
|
|
41480
41976
|
publicBaseUrl: opts.publicBaseUrl,
|
|
41481
41977
|
bearers: secrets,
|
|
41482
|
-
resolveEmail: opts.resolveEmail
|
|
41978
|
+
resolveEmail: opts.resolveEmail,
|
|
41979
|
+
resolveName: opts.resolveName
|
|
41483
41980
|
});
|
|
41484
41981
|
return { source: service.source, service };
|
|
41485
41982
|
}
|
|
@@ -42285,6 +42782,45 @@ var init_dedupe = __esm({
|
|
|
42285
42782
|
});
|
|
42286
42783
|
|
|
42287
42784
|
// ../../src/channels/slack/lookups.ts
|
|
42785
|
+
function resolvePersonByEmail(client, email, opts = {}) {
|
|
42786
|
+
const key = email.trim().toLowerCase();
|
|
42787
|
+
if (!key.includes("@") || typeof client.users.lookupByEmail !== "function") return Promise.resolve(void 0);
|
|
42788
|
+
const now = opts.now ?? Date.now;
|
|
42789
|
+
const cached = personByEmailCache.get(key);
|
|
42790
|
+
if (cached && cached.expiresAt > now()) return Promise.resolve(cached.person);
|
|
42791
|
+
const inFlight = personByEmailInFlight.get(key);
|
|
42792
|
+
if (inFlight) return inFlight;
|
|
42793
|
+
const lookup2 = (async () => {
|
|
42794
|
+
let timer;
|
|
42795
|
+
try {
|
|
42796
|
+
const call2 = client.users.lookupByEmail({ email: key }).then(
|
|
42797
|
+
(r) => r.user,
|
|
42798
|
+
() => void 0
|
|
42799
|
+
);
|
|
42800
|
+
const timedOut = /* @__PURE__ */ Symbol("person lookup timed out");
|
|
42801
|
+
const answer = await Promise.race([
|
|
42802
|
+
call2,
|
|
42803
|
+
new Promise((resolve12) => {
|
|
42804
|
+
timer = setTimeout(() => resolve12(timedOut), opts.timeoutMs ?? PERSON_LINK_TIMEOUT_MS);
|
|
42805
|
+
})
|
|
42806
|
+
]);
|
|
42807
|
+
if (answer === timedOut) return void 0;
|
|
42808
|
+
const u = answer;
|
|
42809
|
+
const person = u?.id && !u.deleted && !u.is_bot && !u.is_app_user ? { id: `slack:${u.id}`, ...displayNameOf2(u) ? { name: displayNameOf2(u) } : {} } : void 0;
|
|
42810
|
+
personByEmailCache.set(key, { person, expiresAt: now() + PERSON_LINK_TTL_MS });
|
|
42811
|
+
if (personByEmailCache.size > NAME_CACHE_MAX) {
|
|
42812
|
+
const oldest = personByEmailCache.keys().next().value;
|
|
42813
|
+
if (oldest !== void 0) personByEmailCache.delete(oldest);
|
|
42814
|
+
}
|
|
42815
|
+
return person;
|
|
42816
|
+
} finally {
|
|
42817
|
+
if (timer) clearTimeout(timer);
|
|
42818
|
+
personByEmailInFlight.delete(key);
|
|
42819
|
+
}
|
|
42820
|
+
})();
|
|
42821
|
+
personByEmailInFlight.set(key, lookup2);
|
|
42822
|
+
return lookup2;
|
|
42823
|
+
}
|
|
42288
42824
|
function cachePut(cache2, key, value) {
|
|
42289
42825
|
cache2.set(key, value);
|
|
42290
42826
|
if (cache2.size > NAME_CACHE_MAX) {
|
|
@@ -42338,10 +42874,15 @@ async function resolveTeamUrl(client) {
|
|
|
42338
42874
|
}
|
|
42339
42875
|
return teamUrl;
|
|
42340
42876
|
}
|
|
42341
|
-
var NAME_CACHE_MAX, channelNameCache, userNameCache, teamUrl;
|
|
42877
|
+
var PERSON_LINK_TTL_MS, PERSON_LINK_TIMEOUT_MS, personByEmailCache, personByEmailInFlight, displayNameOf2, NAME_CACHE_MAX, channelNameCache, userNameCache, teamUrl;
|
|
42342
42878
|
var init_lookups = __esm({
|
|
42343
42879
|
"../../src/channels/slack/lookups.ts"() {
|
|
42344
42880
|
"use strict";
|
|
42881
|
+
PERSON_LINK_TTL_MS = 10 * 6e4;
|
|
42882
|
+
PERSON_LINK_TIMEOUT_MS = 1500;
|
|
42883
|
+
personByEmailCache = /* @__PURE__ */ new Map();
|
|
42884
|
+
personByEmailInFlight = /* @__PURE__ */ new Map();
|
|
42885
|
+
displayNameOf2 = (u) => u?.profile?.display_name || u?.profile?.real_name || u?.real_name || u?.name || void 0;
|
|
42345
42886
|
NAME_CACHE_MAX = 1e3;
|
|
42346
42887
|
channelNameCache = /* @__PURE__ */ new Map();
|
|
42347
42888
|
userNameCache = /* @__PURE__ */ new Map();
|
|
@@ -44000,8 +44541,8 @@ function parseIndexCursor(params) {
|
|
|
44000
44541
|
if (!params.has("before") || !Number.isFinite(before) || before <= 0 || beforeId === "") return void 0;
|
|
44001
44542
|
return { before, beforeId };
|
|
44002
44543
|
}
|
|
44003
|
-
function olderRunsHref(cursor) {
|
|
44004
|
-
return `/runs?all=1&before=${cursor.finishedAt}&beforeId=${encodeURIComponent(cursor.id)}`;
|
|
44544
|
+
function olderRunsHref(cursor, opts = {}) {
|
|
44545
|
+
return `/runs?all=1${opts.mine ? "&mine=1" : ""}&before=${cursor.finishedAt}&beforeId=${encodeURIComponent(cursor.id)}`;
|
|
44005
44546
|
}
|
|
44006
44547
|
function readDecision(actor, view) {
|
|
44007
44548
|
const admitted = authorize(actor, "runs:read", RUNS_GET);
|
|
@@ -44099,7 +44640,7 @@ function createLiveViewHandler(deps) {
|
|
|
44099
44640
|
})
|
|
44100
44641
|
);
|
|
44101
44642
|
};
|
|
44102
|
-
const mergedRows = async (live, visibleTo, cursor) => {
|
|
44643
|
+
const mergedRows = async (live, visibleTo, cursor, view = {}) => {
|
|
44103
44644
|
const tokens = new Map(live.map((s) => [s.id, s.token]));
|
|
44104
44645
|
const { runs, nextBefore, storeUnavailable } = await service.listRuns({
|
|
44105
44646
|
status: "all",
|
|
@@ -44111,7 +44652,7 @@ function createLiveViewHandler(deps) {
|
|
|
44111
44652
|
return {
|
|
44112
44653
|
rows,
|
|
44113
44654
|
...storeUnavailable ? { storeUnavailable: true } : {},
|
|
44114
|
-
...nextBefore ? { olderHref: olderRunsHref(nextBefore) } : {},
|
|
44655
|
+
...nextBefore ? { olderHref: olderRunsHref(nextBefore, view) } : {},
|
|
44115
44656
|
...cursor ? { olderThan: cursor.before } : {}
|
|
44116
44657
|
};
|
|
44117
44658
|
};
|
|
@@ -44128,7 +44669,9 @@ function createLiveViewHandler(deps) {
|
|
|
44128
44669
|
}
|
|
44129
44670
|
if (route3.kind === "index") {
|
|
44130
44671
|
const all = url.searchParams.get("all") === "1";
|
|
44131
|
-
const
|
|
44672
|
+
const mine = url.searchParams.get("mine") === "1";
|
|
44673
|
+
const readable2 = readableRuns(ctx.actor);
|
|
44674
|
+
const visibleTo = mine ? allOf([readable2, ownedBy(ctx.actor)]) : readable2;
|
|
44132
44675
|
if (url.searchParams.get("stream") === "1") {
|
|
44133
44676
|
serveIndexEvents(
|
|
44134
44677
|
visibleIndexFeed((onEvent) => index.subscribeIndex(onEvent), visibleTo),
|
|
@@ -44144,6 +44687,8 @@ function createLiveViewHandler(deps) {
|
|
|
44144
44687
|
const seed = {
|
|
44145
44688
|
page: "runs",
|
|
44146
44689
|
all,
|
|
44690
|
+
mine,
|
|
44691
|
+
...ctx.actor.asUser ? { asUser: ctx.actor.asUser } : {},
|
|
44147
44692
|
retentionDays: deps.retention ? deps.retention.retentionDays : null,
|
|
44148
44693
|
now: now(),
|
|
44149
44694
|
rows: [...page2.rows],
|
|
@@ -44161,7 +44706,7 @@ function createLiveViewHandler(deps) {
|
|
|
44161
44706
|
);
|
|
44162
44707
|
return true;
|
|
44163
44708
|
}
|
|
44164
|
-
run2(res, async () => render3(await mergedRows(live, visibleTo, parseIndexCursor(url.searchParams))));
|
|
44709
|
+
run2(res, async () => render3(await mergedRows(live, visibleTo, parseIndexCursor(url.searchParams), { mine })));
|
|
44165
44710
|
return true;
|
|
44166
44711
|
}
|
|
44167
44712
|
if (route3.kind === "scheduled") {
|
|
@@ -44945,15 +45490,28 @@ function createSettingsViewHandler(deps, shell) {
|
|
|
44945
45490
|
action,
|
|
44946
45491
|
kind === "org" ? { type: "config-scope", kind: "org" } : { type: "config-scope", kind: "channel", id: id ?? "" }
|
|
44947
45492
|
).allow;
|
|
45493
|
+
const serversOf = (answer) => answer.ok ? answer.value.servers ?? [] : [];
|
|
44948
45494
|
async function mcpsSeed(caller, channel) {
|
|
44949
|
-
const listed = await deps.commands.invoke("mcp.list", channel ? { options: { channel } } : {}, caller);
|
|
44950
45495
|
const write = {
|
|
44951
45496
|
org: canWrite(caller, "mcp:write", "org"),
|
|
44952
45497
|
channel: channel !== void 0 && canWrite(caller, "mcp:write", "channel", channel)
|
|
44953
45498
|
};
|
|
45499
|
+
const listed = await deps.commands.invoke(
|
|
45500
|
+
"mcp.list",
|
|
45501
|
+
write.org ? { options: { all: true } } : channel ? { options: { channel } } : {},
|
|
45502
|
+
caller
|
|
45503
|
+
);
|
|
44954
45504
|
if (!listed.ok)
|
|
44955
45505
|
return { ...channel ? { channel } : {}, servers: [], unavailable: failureText(listed), canWrite: write };
|
|
44956
|
-
|
|
45506
|
+
if (write.org)
|
|
45507
|
+
return { ...channel ? { channel } : {}, allTiers: true, servers: serversOf(listed), canWrite: write };
|
|
45508
|
+
const servers = serversOf(listed);
|
|
45509
|
+
const readable = await deps.commands.invoke("config.overrides", {}, caller);
|
|
45510
|
+
const others = (readable.ok ? readable.value.channels ?? [] : []).filter((r) => r.channelId !== channel && r.settings.includes("mcpServers")).map((r) => r.channelId);
|
|
45511
|
+
const tiers = await Promise.all(
|
|
45512
|
+
others.map((id) => deps.commands.invoke("mcp.list", { options: { channel: id } }, caller))
|
|
45513
|
+
);
|
|
45514
|
+
for (const answer of tiers) for (const s of serversOf(answer)) if (s.scope === "channel") servers.push(s);
|
|
44957
45515
|
return { ...channel ? { channel } : {}, servers, canWrite: write };
|
|
44958
45516
|
}
|
|
44959
45517
|
async function channelsSeed(caller, channel) {
|
|
@@ -44976,9 +45534,7 @@ function createSettingsViewHandler(deps, shell) {
|
|
|
44976
45534
|
plain4(res, 405, "method not allowed", { allow: "GET" });
|
|
44977
45535
|
return true;
|
|
44978
45536
|
}
|
|
44979
|
-
const caller = deps.callerFor(ctx.identity);
|
|
44980
45537
|
const tab = route3.tab === "home" ? homeTab(deps.capabilities) : route3.tab;
|
|
44981
|
-
const base = { page: "settings", tab, viewer: caller.id, vocabulary: deps.vocabulary };
|
|
44982
45538
|
const failed = (err2) => {
|
|
44983
45539
|
const reason2 = (err2 instanceof Error ? err2.message : String(err2)).slice(0, UPSTREAM_REASON_MAX4);
|
|
44984
45540
|
if (res.headersSent) {
|
|
@@ -44993,20 +45549,27 @@ function createSettingsViewHandler(deps, shell) {
|
|
|
44993
45549
|
res.end(shell("Settings", seed));
|
|
44994
45550
|
};
|
|
44995
45551
|
const channel = "channel" in route3 ? route3.channel : void 0;
|
|
44996
|
-
|
|
44997
|
-
|
|
44998
|
-
|
|
44999
|
-
|
|
45552
|
+
deps.callerFor(ctx.identity).then(async (caller) => {
|
|
45553
|
+
const base = {
|
|
45554
|
+
page: "settings",
|
|
45555
|
+
tab,
|
|
45556
|
+
viewer: caller.id,
|
|
45557
|
+
...caller.actor.asUser ? { asUser: caller.actor.asUser } : {},
|
|
45558
|
+
vocabulary: deps.vocabulary
|
|
45559
|
+
};
|
|
45560
|
+
if (tab === "installation") {
|
|
45561
|
+
if (!authorize(caller.actor, "config:read", { type: "command", id: "config.show" }).allow) {
|
|
45562
|
+
plain4(res, 403, "forbidden");
|
|
45563
|
+
return;
|
|
45564
|
+
}
|
|
45565
|
+
render3({ ...base, installation: deps.installation() });
|
|
45566
|
+
return;
|
|
45000
45567
|
}
|
|
45001
|
-
|
|
45002
|
-
|
|
45003
|
-
}
|
|
45004
|
-
const build = tab === "mcps" ? mcpsSeed(caller, channel) : channelsSeed(caller, channel);
|
|
45005
|
-
build.then(
|
|
45006
|
-
(part) => render3(
|
|
45568
|
+
const part = tab === "mcps" ? await mcpsSeed(caller, channel) : await channelsSeed(caller, channel);
|
|
45569
|
+
render3(
|
|
45007
45570
|
tab === "mcps" ? { ...base, mcps: part } : { ...base, channels: part }
|
|
45008
|
-
)
|
|
45009
|
-
).catch(failed);
|
|
45571
|
+
);
|
|
45572
|
+
}).catch(failed);
|
|
45010
45573
|
return true;
|
|
45011
45574
|
};
|
|
45012
45575
|
}
|
|
@@ -47459,7 +48022,18 @@ async function merge(body, deps, subject2) {
|
|
|
47459
48022
|
return json(502, { ok: false, error: "github_unavailable", message: `${where} could not be read`, at });
|
|
47460
48023
|
if (isReleasePullRequest(facts2))
|
|
47461
48024
|
return refused6(`${where} is the release pull request \u2014 always a person's merge, never the runner's`);
|
|
47462
|
-
if (facts2.state !== "open")
|
|
48025
|
+
if (facts2.state !== "open") {
|
|
48026
|
+
if (facts2.mergedAt !== void 0 && facts2.mergeCommitSha !== void 0)
|
|
48027
|
+
return json(200, {
|
|
48028
|
+
ok: true,
|
|
48029
|
+
outcome: "merged",
|
|
48030
|
+
by: "other",
|
|
48031
|
+
sha: facts2.mergeCommitSha,
|
|
48032
|
+
mergedAt: facts2.mergedAt,
|
|
48033
|
+
at
|
|
48034
|
+
});
|
|
48035
|
+
return refused6(`${where} is ${facts2.state}`);
|
|
48036
|
+
}
|
|
47463
48037
|
if (facts2.headRef !== row2.branch)
|
|
47464
48038
|
return refused6(`${where} heads \`${facts2.headRef ?? "?"}\`, not the unit's branch \`${row2.branch}\``);
|
|
47465
48039
|
if (facts2.headSha === void 0 || !sameCommit(facts2.headSha, headSha))
|
|
@@ -47857,8 +48431,24 @@ function accessActor(identity2, grantsFor) {
|
|
|
47857
48431
|
function serviceTokenAllowed(pathname, identity2) {
|
|
47858
48432
|
return !isServiceToken(identity2) || isCommandPath(pathname);
|
|
47859
48433
|
}
|
|
47860
|
-
function
|
|
47861
|
-
|
|
48434
|
+
async function resolveAccessActor(identity2, opts) {
|
|
48435
|
+
const actor = accessActor(identity2, opts.grantsFor);
|
|
48436
|
+
if (isServiceToken(identity2) || !identity2.email || !opts.personByEmail) return actor;
|
|
48437
|
+
const person = await opts.personByEmail(identity2.email).catch(() => void 0);
|
|
48438
|
+
if (!person || !person.id.startsWith("slack:")) return actor;
|
|
48439
|
+
return {
|
|
48440
|
+
...actor,
|
|
48441
|
+
self: [actor.id, person.id],
|
|
48442
|
+
asUser: { id: person.id, ...person.name ? { name: person.name } : {} }
|
|
48443
|
+
};
|
|
48444
|
+
}
|
|
48445
|
+
async function callerFor(identity2, opts) {
|
|
48446
|
+
return {
|
|
48447
|
+
kind: "access",
|
|
48448
|
+
id: callerIdFor(identity2),
|
|
48449
|
+
actor: await resolveAccessActor(identity2, opts),
|
|
48450
|
+
...identity2.email && !isServiceToken(identity2) ? { email: identity2.email } : {}
|
|
48451
|
+
};
|
|
47862
48452
|
}
|
|
47863
48453
|
function hostOf2(url) {
|
|
47864
48454
|
if (!url) return void 0;
|
|
@@ -47925,7 +48515,7 @@ function createCommandHttpHandler(commands, opts) {
|
|
|
47925
48515
|
refuse3(res, 403, "forbidden_origin", "cross-origin writes are refused");
|
|
47926
48516
|
return;
|
|
47927
48517
|
}
|
|
47928
|
-
const caller = callerFor(identity2, opts);
|
|
48518
|
+
const caller = await callerFor(identity2, opts);
|
|
47929
48519
|
if (CommandRegistry.refuses(cmd, caller)) {
|
|
47930
48520
|
refuse3(res, ERROR_STATUS.unauthorized, "unauthorized", `${caller.id} is not allowed to run ${cmd.id}`);
|
|
47931
48521
|
return;
|
|
@@ -48013,9 +48603,14 @@ async function runBot() {
|
|
|
48013
48603
|
const completions = new PiAiProviders(config.config.providers);
|
|
48014
48604
|
const skills = new BundledSkillStore(DEFAULT_SKILLS_DIR);
|
|
48015
48605
|
let slackEmailLookup;
|
|
48606
|
+
let slackNameLookup;
|
|
48607
|
+
let slackPersonByEmail;
|
|
48608
|
+
const personByEmail = (email) => slackPersonByEmail ? slackPersonByEmail(email) : Promise.resolve(void 0);
|
|
48016
48609
|
const mcpWiring = buildMcp(config, processSecrets, {
|
|
48017
48610
|
publicBaseUrl: process.env.PUBLIC_BASE_URL,
|
|
48018
48611
|
resolveEmail: (userId) => slackEmailLookup ? slackEmailLookup(userId) : Promise.resolve(void 0),
|
|
48612
|
+
// `addedBy` as a name on the MCP lists (record 0042): the cached lookup the runs index uses.
|
|
48613
|
+
resolveName: (userId) => slackNameLookup ? slackNameLookup(userId) : Promise.resolve(void 0),
|
|
48019
48614
|
warn: (m) => console.warn(`[mcp] ${m}`)
|
|
48020
48615
|
});
|
|
48021
48616
|
const mcp = mcpWiring.source ?? new NullMcpToolSource();
|
|
@@ -48211,6 +48806,8 @@ async function runBot() {
|
|
|
48211
48806
|
const conversationReader = new SlackConversationReader(app.client);
|
|
48212
48807
|
deps.conversationReaders = [conversationReader];
|
|
48213
48808
|
slackEmailLookup = (userId) => userId.startsWith("slack:") ? resolveUserEmail(app.client, userId.slice("slack:".length)) : Promise.resolve(void 0);
|
|
48809
|
+
slackNameLookup = (userId) => userId.startsWith("slack:") ? resolveUserName(app.client, userId.slice("slack:".length)) : Promise.resolve(void 0);
|
|
48810
|
+
slackPersonByEmail = (email) => resolvePersonByEmail(app.client, email);
|
|
48214
48811
|
const pendingHistoryWrites = () => runHistoryWriter.pending();
|
|
48215
48812
|
const inFlight = () => activeRunCount() + pendingReflectionCount() + pendingHistoryWrites();
|
|
48216
48813
|
let draining = false;
|
|
@@ -48287,7 +48884,7 @@ async function runBot() {
|
|
|
48287
48884
|
const settingsView = createSettingsViewHandler(
|
|
48288
48885
|
{
|
|
48289
48886
|
commands,
|
|
48290
|
-
callerFor: (identity2) => callerFor(identity2, { grantsFor: (id) => config.grantsFor(id) }),
|
|
48887
|
+
callerFor: (identity2) => callerFor(identity2, { grantsFor: (id) => config.grantsFor(id), personByEmail }),
|
|
48291
48888
|
installation: () => installationSettings(config.config, capabilities),
|
|
48292
48889
|
vocabulary: {
|
|
48293
48890
|
agents: Object.keys(AGENTS),
|
|
@@ -48338,6 +48935,7 @@ async function runBot() {
|
|
|
48338
48935
|
const accessState = `dashboard auth: ${dashboardAuth.describe()}`;
|
|
48339
48936
|
const commandHttp = createCommandHttpHandler(commands, {
|
|
48340
48937
|
grantsFor: (id) => config.grantsFor(id),
|
|
48938
|
+
personByEmail,
|
|
48341
48939
|
publicBaseUrl
|
|
48342
48940
|
});
|
|
48343
48941
|
const commandHttpState = `GET|POST /api/<group>.<verb> (${commands.list().length} commands)`;
|
|
@@ -48401,7 +48999,7 @@ async function runBot() {
|
|
|
48401
48999
|
}
|
|
48402
49000
|
if (webAssets.serve(req, res)) return;
|
|
48403
49001
|
if (isCommandPath(path) || isConnectPath(path) || path === "/runs" || path.startsWith("/runs/") || path === "/residents" || path.startsWith("/residents/") || path === "/costs" || path === "/costs.json" || path.startsWith("/costs/") || path === "/delivery" || path === "/delivery.json" || path.startsWith("/delivery/") || path === "/settings" || path.startsWith("/settings/")) {
|
|
48404
|
-
dashboardAuth.verify(req).then((gate) => {
|
|
49002
|
+
dashboardAuth.verify(req).then(async (gate) => {
|
|
48405
49003
|
if (!gate.ok) {
|
|
48406
49004
|
res.writeHead(gate.status, { "content-type": "text/plain; charset=utf-8" });
|
|
48407
49005
|
res.end(gate.body);
|
|
@@ -48417,7 +49015,10 @@ async function runBot() {
|
|
|
48417
49015
|
return;
|
|
48418
49016
|
}
|
|
48419
49017
|
if (isCommandPath(path)) return commandHttp(req, res, gate.identity);
|
|
48420
|
-
const actor =
|
|
49018
|
+
const actor = await resolveAccessActor(gate.identity, {
|
|
49019
|
+
grantsFor: (id) => config.grantsFor(id),
|
|
49020
|
+
personByEmail
|
|
49021
|
+
});
|
|
48421
49022
|
if (liveView2(req, res, { actor })) return;
|
|
48422
49023
|
if (mcpConnectView(req, res, gate.identity)) return;
|
|
48423
49024
|
if (residentsView(req, res, { actor })) return;
|