@coreplane/switchboard 1.236.0 → 1.237.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 +175 -62
- 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/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-Dc23v_8v.js} +1 -1
- package/dist/assets/web/dist/assets/ResidentsIndexPage-TR5D1TRR.js +1 -0
- package/dist/assets/web/dist/assets/{RunFoldRow-Bngn4GjF.js → RunFoldRow-mkUj7WEN.js} +1 -1
- package/dist/assets/web/dist/assets/{RunRoutePage-a-tMrirB.js → RunRoutePage-CQI17__g.js} +3 -3
- package/dist/assets/web/dist/assets/RunsIndexPage-yIP4PlKP.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-CdCVyNbh.js} +1 -1
- package/dist/assets/web/dist/assets/SettingsPage-DuA8JpLU.js +1 -0
- package/dist/assets/web/dist/assets/{StatusDot-D-BPSPpw.js → StatusDot-DmNHX6am.js} +1 -1
- package/dist/assets/web/dist/assets/{Tooltip-DPDx5tXT.js → Tooltip-qVnJTgSt.js} +1 -1
- package/dist/assets/web/dist/assets/{UnitRoutePage-0eiekTHQ.js → UnitRoutePage-BcTeH7m2.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-TAGawIfM.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-csihqEj5.css +1 -0
- package/dist/assets/web/dist/assets/{main-DR8iKpNt.js → main-mUd_TJjH.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 +960 -520
- 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;
|
|
@@ -10044,10 +10066,15 @@ function wakePause(ms, signal2) {
|
|
|
10044
10066
|
});
|
|
10045
10067
|
}
|
|
10046
10068
|
function reboundOf(value) {
|
|
10069
|
+
const move = returnedOf(value);
|
|
10070
|
+
return move !== void 0 && move.pr !== void 0 ? { from: move.from, to: move.to, pr: move.pr } : void 0;
|
|
10071
|
+
}
|
|
10072
|
+
function returnedOf(value) {
|
|
10047
10073
|
if (typeof value !== "object" || value === null) return void 0;
|
|
10048
10074
|
const v = value;
|
|
10049
|
-
if (typeof v.from !== "string" || typeof v.to !== "string" ||
|
|
10050
|
-
if (
|
|
10075
|
+
if (typeof v.from !== "string" || typeof v.to !== "string" || !v.from || !v.to) return void 0;
|
|
10076
|
+
if (v.pr === void 0) return { from: v.from, to: v.to };
|
|
10077
|
+
if (typeof v.pr !== "number" || !Number.isSafeInteger(v.pr) || v.pr <= 0) return void 0;
|
|
10051
10078
|
return { from: v.from, to: v.to, pr: v.pr };
|
|
10052
10079
|
}
|
|
10053
10080
|
function rebindRefusedOf(value) {
|
|
@@ -10288,7 +10315,7 @@ var init_resident = __esm({
|
|
|
10288
10315
|
const trace = sanitizeGraftedSteps(data.trace);
|
|
10289
10316
|
const rebound = reboundOf(data.rebound);
|
|
10290
10317
|
const rebindRefused = rebindRefusedOf(data.rebindRefused);
|
|
10291
|
-
const returned =
|
|
10318
|
+
const returned = returnedOf(data.returned);
|
|
10292
10319
|
this.lastBinding = {
|
|
10293
10320
|
ref: data.ref,
|
|
10294
10321
|
sha: data.sha,
|
|
@@ -10858,7 +10885,7 @@ function rebindLabel(binding) {
|
|
|
10858
10885
|
if (binding.rebound) return ` \xB7 rebound to ${binding.rebound.to} (this thread's PR #${binding.rebound.pr})`;
|
|
10859
10886
|
if (binding.returned) {
|
|
10860
10887
|
const r = binding.returned;
|
|
10861
|
-
return ` \xB7 returned to ${r.to} (the branch of this thread's PR #${r.pr} is gone)`;
|
|
10888
|
+
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
10889
|
}
|
|
10863
10890
|
if (binding.rebindRefused) {
|
|
10864
10891
|
const r = binding.rebindRefused;
|
|
@@ -15232,6 +15259,7 @@ var init_commandRegistry = __esm({
|
|
|
15232
15259
|
commandId: cmd.id,
|
|
15233
15260
|
callerKind: caller.kind,
|
|
15234
15261
|
callerId: caller.id,
|
|
15262
|
+
...caller.actor.asUser ? { asUser: caller.actor.asUser.id } : {},
|
|
15235
15263
|
effect: cmd.effect,
|
|
15236
15264
|
outcome: res.ok ? "ok" : res.error,
|
|
15237
15265
|
...reason2 === void 0 ? {} : { reason: reason2 },
|
|
@@ -15416,8 +15444,14 @@ function assertMayEditChannel(caller, channel) {
|
|
|
15416
15444
|
if (!mayEditChannel(caller, channel))
|
|
15417
15445
|
throw new CommandError("unauthorized", "Channel config changes are restricted.");
|
|
15418
15446
|
}
|
|
15419
|
-
function
|
|
15420
|
-
if (caller.kind
|
|
15447
|
+
function meIdOf(caller) {
|
|
15448
|
+
if (caller.kind !== "access") return caller.id;
|
|
15449
|
+
return caller.actor.self?.find((id) => id.startsWith("slack:"));
|
|
15450
|
+
}
|
|
15451
|
+
function meIdOrRefuse(caller) {
|
|
15452
|
+
const id = meIdOf(caller);
|
|
15453
|
+
if (id === void 0) throw new CommandError("unauthorized", ME_ON_ACCESS_MESSAGE);
|
|
15454
|
+
return id;
|
|
15421
15455
|
}
|
|
15422
15456
|
async function mayReadChannel(caller, channel, deps) {
|
|
15423
15457
|
const origin = caller.origin?.channelId;
|
|
@@ -15477,7 +15511,7 @@ var init_config4 = __esm({
|
|
|
15477
15511
|
const channel = targetChannel(caller, options.channel);
|
|
15478
15512
|
await assertMayReadChannel(caller, channel, deps);
|
|
15479
15513
|
const description = {
|
|
15480
|
-
...await deps.config.describeConfig(channel, caller.id),
|
|
15514
|
+
...await deps.config.describeConfig(channel, meIdOf(caller) ?? caller.id),
|
|
15481
15515
|
channelConfigRestricted: !mayEditChannel(caller, channel)
|
|
15482
15516
|
};
|
|
15483
15517
|
return description;
|
|
@@ -15570,8 +15604,7 @@ var init_config4 = __esm({
|
|
|
15570
15604
|
assertMayEditChannel(caller, channel);
|
|
15571
15605
|
effective = await deps.config.setChannelOverride(channel, patch);
|
|
15572
15606
|
} else {
|
|
15573
|
-
|
|
15574
|
-
effective = await deps.config.setUserOverride(caller.id, patch);
|
|
15607
|
+
effective = await deps.config.setUserOverride(meIdOrRefuse(caller), patch);
|
|
15575
15608
|
}
|
|
15576
15609
|
return { scope: args.scope, effective: summarizeScope(effective) };
|
|
15577
15610
|
}
|
|
@@ -15590,8 +15623,7 @@ var init_config4 = __esm({
|
|
|
15590
15623
|
assertMayEditChannel(caller, channel);
|
|
15591
15624
|
await deps.config.clearChannelOverride(channel);
|
|
15592
15625
|
} else {
|
|
15593
|
-
|
|
15594
|
-
await deps.config.clearUserOverride(caller.id);
|
|
15626
|
+
await deps.config.clearUserOverride(meIdOrRefuse(caller));
|
|
15595
15627
|
}
|
|
15596
15628
|
return { scope: args.scope, cleared: true };
|
|
15597
15629
|
}
|
|
@@ -15633,7 +15665,7 @@ ${quote(current ?? "")}`;
|
|
|
15633
15665
|
handler: async ({ args, options, caller, deps }) => {
|
|
15634
15666
|
const channel = args.scope === "channel" ? targetChannel(caller, options.channel) : void 0;
|
|
15635
15667
|
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);
|
|
15668
|
+
const scopes = await deps.config.scopes(channel ?? caller.origin?.channelId ?? "", meIdOf(caller) ?? caller.id);
|
|
15637
15669
|
const current = (args.scope === "channel" ? scopes.channel : scopes.user).instructions?.trim();
|
|
15638
15670
|
if (args.text === void 0)
|
|
15639
15671
|
return { scope: args.scope, action: "show", ...current ? { instructions: current } : {} };
|
|
@@ -15650,8 +15682,7 @@ ${quote(current ?? "")}`;
|
|
|
15650
15682
|
assertMayEditChannel(caller, channel);
|
|
15651
15683
|
effective = await deps.config.setChannelOverride(channel, patch);
|
|
15652
15684
|
} else {
|
|
15653
|
-
|
|
15654
|
-
effective = await deps.config.setUserOverride(caller.id, patch);
|
|
15685
|
+
effective = await deps.config.setUserOverride(meIdOrRefuse(caller), patch);
|
|
15655
15686
|
}
|
|
15656
15687
|
if (text.length === 0) {
|
|
15657
15688
|
const fromStatic = effective.instructions?.trim();
|
|
@@ -15782,7 +15813,25 @@ function anyOf(alternatives) {
|
|
|
15782
15813
|
if (flat.length === 1) return flat[0];
|
|
15783
15814
|
return { kind: "or", of: flat };
|
|
15784
15815
|
}
|
|
15785
|
-
function
|
|
15816
|
+
function allOf(parts) {
|
|
15817
|
+
const flat = [];
|
|
15818
|
+
for (const p of parts) {
|
|
15819
|
+
if (p.kind === "none") return NONE;
|
|
15820
|
+
if (p.kind === "all") continue;
|
|
15821
|
+
if (p.kind === "and") flat.push(...p.of);
|
|
15822
|
+
else flat.push(p);
|
|
15823
|
+
}
|
|
15824
|
+
if (flat.length === 0) return ALL;
|
|
15825
|
+
if (flat.length === 1) return flat[0];
|
|
15826
|
+
return { kind: "and", of: flat };
|
|
15827
|
+
}
|
|
15828
|
+
function selfPredicate(selfIds) {
|
|
15829
|
+
return anyOf(selfIds.map((userId) => ({ kind: "user-is", userId })));
|
|
15830
|
+
}
|
|
15831
|
+
function ownedBy(actor) {
|
|
15832
|
+
return selfPredicate(selfIdsOf(actor));
|
|
15833
|
+
}
|
|
15834
|
+
function compileCondition(condition, grants, selfIds) {
|
|
15786
15835
|
switch (condition.kind) {
|
|
15787
15836
|
case "has-grant": {
|
|
15788
15837
|
if (grants.actions === "all") return ALL;
|
|
@@ -15793,7 +15842,9 @@ function compileCondition(condition, grants, selfId) {
|
|
|
15793
15842
|
if (grants.channels === "all") return ALL;
|
|
15794
15843
|
return anyOf([grants.channels.size === 0 ? NONE : { kind: "channels-in", channelIds: grants.channels }, PUBLIC]);
|
|
15795
15844
|
case "is-self":
|
|
15796
|
-
return
|
|
15845
|
+
return selfPredicate(selfIds);
|
|
15846
|
+
case "acts-as-person":
|
|
15847
|
+
return actsAsPerson(selfIds) ? ALL : NONE;
|
|
15797
15848
|
case "owner-of":
|
|
15798
15849
|
if (grants.repos === "all") return ALL;
|
|
15799
15850
|
return grants.repos.size === 0 ? NONE : { kind: "repos-in", repos: grants.repos };
|
|
@@ -15802,17 +15853,9 @@ function compileCondition(condition, grants, selfId) {
|
|
|
15802
15853
|
}
|
|
15803
15854
|
return NONE;
|
|
15804
15855
|
}
|
|
15805
|
-
function compileRule(rule, grants,
|
|
15856
|
+
function compileRule(rule, grants, selfIds) {
|
|
15806
15857
|
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 };
|
|
15858
|
+
return allOf(rule.when.map((condition) => compileCondition(condition, grants, selfIds)));
|
|
15816
15859
|
}
|
|
15817
15860
|
function predicateWith(rules, actor, action, resourceType, kind) {
|
|
15818
15861
|
if (RESOURCE_KINDS[resourceType] && kind === void 0) {
|
|
@@ -15822,12 +15865,12 @@ function predicateWith(rules, actor, action, resourceType, kind) {
|
|
|
15822
15865
|
if (!target) throw new TypeError(`authz predicate: no target for ${resourceType}${kind ? `/${kind}` : ""}`);
|
|
15823
15866
|
if (!isKnownActorKind(actor.kind)) return NONE;
|
|
15824
15867
|
const grants = effectiveGrants(actor);
|
|
15825
|
-
const
|
|
15868
|
+
const selfIds = selfIdsOf(actor);
|
|
15826
15869
|
const alternatives = [];
|
|
15827
15870
|
for (const rule of rules) {
|
|
15828
15871
|
if (rule.action !== action || ruleTarget(rule) !== target) continue;
|
|
15829
15872
|
if (rule.actorKinds && !rule.actorKinds.includes(actor.kind)) continue;
|
|
15830
|
-
alternatives.push(compileRule(rule, grants,
|
|
15873
|
+
alternatives.push(compileRule(rule, grants, selfIds));
|
|
15831
15874
|
}
|
|
15832
15875
|
return anyOf(alternatives);
|
|
15833
15876
|
}
|
|
@@ -19576,7 +19619,9 @@ async function serviceOf(deps) {
|
|
|
19576
19619
|
}
|
|
19577
19620
|
function actorOf(caller, channel) {
|
|
19578
19621
|
return {
|
|
19579
|
-
|
|
19622
|
+
// The person, for a dashboard session linked to one (record 0042): its
|
|
19623
|
+
// own tier, `addedBy` and the connect ticket's email are the person's.
|
|
19624
|
+
id: meIdOf(caller) ?? caller.id,
|
|
19580
19625
|
orgAdmin: authorize(caller.actor, "mcp:write", { type: "config-scope", kind: "org" }).allow,
|
|
19581
19626
|
channelAdmin: authorize(caller.actor, "mcp:write", { type: "config-scope", kind: "channel", id: channel ?? "" }).allow
|
|
19582
19627
|
};
|
|
@@ -19585,7 +19630,7 @@ function channelOf2(caller, option) {
|
|
|
19585
19630
|
return option ?? caller.origin?.channelId;
|
|
19586
19631
|
}
|
|
19587
19632
|
async function writeTarget(svc, caller, actor, word, channel) {
|
|
19588
|
-
if ((word ?? "me") === "me" && caller
|
|
19633
|
+
if ((word ?? "me") === "me" && meIdOf(caller) === void 0)
|
|
19589
19634
|
throw new CommandError("unauthorized", MCP_ME_ON_ACCESS_MESSAGE);
|
|
19590
19635
|
return via(() => svc.target(actor, word ?? "me", channel));
|
|
19591
19636
|
}
|
|
@@ -19674,6 +19719,7 @@ var init_mcp = __esm({
|
|
|
19674
19719
|
init_config3();
|
|
19675
19720
|
init_service();
|
|
19676
19721
|
init_authorize();
|
|
19722
|
+
init_config4();
|
|
19677
19723
|
init_commandRegistry();
|
|
19678
19724
|
defineCommand10 = commandDefiner();
|
|
19679
19725
|
serverName = z13.string().refine((s) => MCP_SERVER_NAME_PATTERN.test(s), "expected a slug: lowercase letters, digits, dashes (\u2264 32 chars)");
|
|
@@ -22669,14 +22715,16 @@ var init_runs = __esm({
|
|
|
22669
22715
|
sinceMs: z18.coerce.number().int().nonnegative().optional().describe("only runs started at or after this epoch ms"),
|
|
22670
22716
|
limit: positiveInt3.max(RUN_LIST_MAX_LIMIT).optional().describe(`page size (max ${RUN_LIST_MAX_LIMIT})`),
|
|
22671
22717
|
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")
|
|
22718
|
+
beforeId: runId2.optional().describe("page cursor tie-breaker: the last id of the previous page"),
|
|
22719
|
+
mine: flag.optional().describe("only the runs you requested \u2014 for a dashboard session, the Slack user its email names (record 0042)")
|
|
22673
22720
|
}),
|
|
22674
22721
|
action: "runs:read",
|
|
22675
22722
|
effect: "read",
|
|
22676
22723
|
describe: "List runs (live and persisted, newest first) \u2014 metadata only, never message text.",
|
|
22677
22724
|
handler: async ({ options, caller, deps }) => {
|
|
22678
|
-
const
|
|
22679
|
-
const
|
|
22725
|
+
const { thread, parent, mine, ...rest } = options;
|
|
22726
|
+
const readable = predicateFor(caller.actor, "runs:read", "run");
|
|
22727
|
+
const visibleTo = mine ? allOf([readable, ownedBy(caller.actor)]) : readable;
|
|
22680
22728
|
return asJson3(
|
|
22681
22729
|
await (await deps.runs()).listRuns({
|
|
22682
22730
|
...rest,
|
|
@@ -25768,7 +25816,13 @@ async function fetchPullRequestFacts(pr) {
|
|
|
25768
25816
|
...typeof data.title === "string" && data.title ? { title: data.title } : {},
|
|
25769
25817
|
..."auto_merge" in data ? { autoMergeEnabled: data.auto_merge !== null } : {},
|
|
25770
25818
|
..."mergeable" in data ? { mergeable: data.mergeable === null ? null : data.mergeable === true } : {},
|
|
25771
|
-
...typeof data.mergeable_state === "string" && data.mergeable_state ? { mergeableState: data.mergeable_state } : {}
|
|
25819
|
+
...typeof data.mergeable_state === "string" && data.mergeable_state ? { mergeableState: data.mergeable_state } : {},
|
|
25820
|
+
// The merge commit rides only a merged row: on an open pull request GitHub
|
|
25821
|
+
// reports a test-merge sha under `merge_commit_sha`.
|
|
25822
|
+
...typeof data.merged_at === "string" && data.merged_at ? {
|
|
25823
|
+
mergedAt: data.merged_at,
|
|
25824
|
+
...typeof data.merge_commit_sha === "string" && /^[0-9a-f]{40}$/.test(data.merge_commit_sha) ? { mergeCommitSha: data.merge_commit_sha } : {}
|
|
25825
|
+
} : {}
|
|
25772
25826
|
};
|
|
25773
25827
|
}
|
|
25774
25828
|
async function mergePullRequest(pr, opts) {
|
|
@@ -32512,6 +32566,14 @@ async function attachRound(deps, ctx) {
|
|
|
32512
32566
|
return attached;
|
|
32513
32567
|
});
|
|
32514
32568
|
}
|
|
32569
|
+
async function reattachWorkspace2(deps, ctx) {
|
|
32570
|
+
try {
|
|
32571
|
+
return { kind: "attached", round: await attachRound(deps, ctx) };
|
|
32572
|
+
} catch (err2) {
|
|
32573
|
+
if (err2 instanceof WorkspaceReattachRefusedError) return { kind: "reattach_refused", why: err2.why };
|
|
32574
|
+
throw err2;
|
|
32575
|
+
}
|
|
32576
|
+
}
|
|
32515
32577
|
async function attachWorkspace(deps, ctx) {
|
|
32516
32578
|
const { msg, io, refuse: refuse3, card, shell, closeLines, clock: clock4, agent, profile, repoCtx, root, reattach } = ctx;
|
|
32517
32579
|
let round2;
|
|
@@ -32908,7 +32970,7 @@ function openThroughSeam(harness, deps, run2) {
|
|
|
32908
32970
|
}
|
|
32909
32971
|
return harness.open(deps, run2);
|
|
32910
32972
|
}
|
|
32911
|
-
var isRecord3, relaunchesOf, isPiFacts, HarnessInterruptedError, HarnessMismatchError;
|
|
32973
|
+
var isRecord3, relaunchesOf, isPiFacts, HarnessInterruptedError, HarnessMismatchError, RELAUNCH_CEILING, HarnessContainerReplacedError;
|
|
32912
32974
|
var init_contract4 = __esm({
|
|
32913
32975
|
"../../src/core/harness/contract.ts"() {
|
|
32914
32976
|
"use strict";
|
|
@@ -32939,6 +33001,114 @@ var init_contract4 = __esm({
|
|
|
32939
33001
|
expected;
|
|
32940
33002
|
found;
|
|
32941
33003
|
};
|
|
33004
|
+
RELAUNCH_CEILING = 2;
|
|
33005
|
+
HarnessContainerReplacedError = class extends HarnessInterruptedError {
|
|
33006
|
+
constructor(message3, said2, was, now, record2) {
|
|
33007
|
+
super(message3, "container replaced under the run", "container_replaced");
|
|
33008
|
+
this.said = said2;
|
|
33009
|
+
this.was = was;
|
|
33010
|
+
this.now = now;
|
|
33011
|
+
this.record = record2;
|
|
33012
|
+
this.name = "HarnessContainerReplacedError";
|
|
33013
|
+
}
|
|
33014
|
+
said;
|
|
33015
|
+
was;
|
|
33016
|
+
now;
|
|
33017
|
+
record;
|
|
33018
|
+
};
|
|
33019
|
+
}
|
|
33020
|
+
});
|
|
33021
|
+
|
|
33022
|
+
// ../../src/core/dispatch/relaunch.ts
|
|
33023
|
+
async function prepareRelaunch(deps, ctx) {
|
|
33024
|
+
const { replaced, facts: facts2, harness } = ctx;
|
|
33025
|
+
const refuse3 = (why, reason2, refusal = "container_replaced") => ({
|
|
33026
|
+
kind: "refused",
|
|
33027
|
+
interruption: new RelaunchRefusedError(why, reason2, refusal)
|
|
33028
|
+
});
|
|
33029
|
+
if (harness.history !== "authored-session")
|
|
33030
|
+
return refuse3(
|
|
33031
|
+
`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`,
|
|
33032
|
+
"container replaced under the run; the harness keeps its own store; restarting from the request"
|
|
33033
|
+
);
|
|
33034
|
+
if (facts2 === void 0)
|
|
33035
|
+
return refuse3(
|
|
33036
|
+
"the row carries no harness facts to count a relaunch on; the run restarts from its request",
|
|
33037
|
+
"container replaced under the run before its process left facts; restarting from the request"
|
|
33038
|
+
);
|
|
33039
|
+
const relaunches = facts2.relaunches;
|
|
33040
|
+
if (relaunches >= RELAUNCH_CEILING)
|
|
33041
|
+
return refuse3(
|
|
33042
|
+
`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`,
|
|
33043
|
+
`relaunch ceiling: ${relaunches} relaunches already; restarting from the request`
|
|
33044
|
+
);
|
|
33045
|
+
let round2;
|
|
33046
|
+
if (ctx.binding !== void 0) {
|
|
33047
|
+
const reattached = await reattachWorkspace2(deps, {
|
|
33048
|
+
threadKey: ctx.threadKey,
|
|
33049
|
+
agent: ctx.agent,
|
|
33050
|
+
profile: ctx.profile,
|
|
33051
|
+
repoCtx: ctx.repoCtx,
|
|
33052
|
+
root: ctx.root,
|
|
33053
|
+
clock: ctx.clock,
|
|
33054
|
+
reattach: ctx.binding
|
|
33055
|
+
});
|
|
33056
|
+
if (reattached.kind === "reattach_refused")
|
|
33057
|
+
return refuse3(
|
|
33058
|
+
`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`,
|
|
33059
|
+
"workspace lost with the replaced container; restarting from the request",
|
|
33060
|
+
"workspace_lost"
|
|
33061
|
+
);
|
|
33062
|
+
round2 = reattached.round;
|
|
33063
|
+
}
|
|
33064
|
+
const counted = { ...facts2, relaunches: relaunches + 1 };
|
|
33065
|
+
let written = counted;
|
|
33066
|
+
let bearer;
|
|
33067
|
+
if (deps.runBearers) {
|
|
33068
|
+
const rotated = deps.runBearers.rotate(ctx.runId, (secretHash) => {
|
|
33069
|
+
written = { ...counted, bearerHash: secretHash };
|
|
33070
|
+
ctx.saveFacts(written);
|
|
33071
|
+
});
|
|
33072
|
+
if (!rotated.ok)
|
|
33073
|
+
return refuse3(
|
|
33074
|
+
`the run's bearer could not be rotated for the relaunch (${rotated.reason}); the run restarts from its request`,
|
|
33075
|
+
`the run's bearer could not be rotated (${rotated.reason}); restarting from the request`
|
|
33076
|
+
);
|
|
33077
|
+
bearer = rotated.token;
|
|
33078
|
+
} else {
|
|
33079
|
+
ctx.saveFacts(written);
|
|
33080
|
+
}
|
|
33081
|
+
const { deadline, ...record2 } = replaced.record;
|
|
33082
|
+
return {
|
|
33083
|
+
kind: "relaunch",
|
|
33084
|
+
resume: {
|
|
33085
|
+
...record2,
|
|
33086
|
+
remainingMs: Math.max(0, deadline - ctx.clock()),
|
|
33087
|
+
facts: written,
|
|
33088
|
+
relaunch: {
|
|
33089
|
+
...replaced.was !== void 0 ? { from: replaced.was } : {},
|
|
33090
|
+
...replaced.now !== void 0 ? { to: replaced.now } : {}
|
|
33091
|
+
}
|
|
33092
|
+
},
|
|
33093
|
+
...bearer !== void 0 ? { bearer } : {},
|
|
33094
|
+
...round2 !== void 0 ? { round: round2 } : {}
|
|
33095
|
+
};
|
|
33096
|
+
}
|
|
33097
|
+
function ordinal(n2) {
|
|
33098
|
+
return n2 === 2 ? "second" : n2 === 3 ? "third" : n2 === 4 ? "fourth" : `${n2}th`;
|
|
33099
|
+
}
|
|
33100
|
+
var RelaunchRefusedError;
|
|
33101
|
+
var init_relaunch = __esm({
|
|
33102
|
+
"../../src/core/dispatch/relaunch.ts"() {
|
|
33103
|
+
"use strict";
|
|
33104
|
+
init_contract4();
|
|
33105
|
+
init_provision();
|
|
33106
|
+
RelaunchRefusedError = class extends HarnessInterruptedError {
|
|
33107
|
+
constructor(message3, reason2, refusal) {
|
|
33108
|
+
super(message3, reason2, refusal);
|
|
33109
|
+
this.name = "RelaunchRefusedError";
|
|
33110
|
+
}
|
|
33111
|
+
};
|
|
32942
33112
|
}
|
|
32943
33113
|
});
|
|
32944
33114
|
|
|
@@ -32980,7 +33150,7 @@ function startScript(start3) {
|
|
|
32980
33150
|
`mkfifo -m 600 ${shellQuote(paths.fifo)}`,
|
|
32981
33151
|
keepLog ? `: >> ${shellQuote(paths.log)}` : `: > ${shellQuote(paths.log)}`,
|
|
32982
33152
|
keepLog ? `: >> ${shellQuote(paths.errLog)}` : `: > ${shellQuote(paths.errLog)}`,
|
|
32983
|
-
`setsid -f sh -c ${shellQuote(inner)}`,
|
|
33153
|
+
`setsid -f sh -c ${shellQuote(inner)} ${DETACHED_STDIO}`,
|
|
32984
33154
|
`sleep 0.3`,
|
|
32985
33155
|
`cat ${shellQuote(paths.pidFile)}`
|
|
32986
33156
|
].join(" && ");
|
|
@@ -33087,7 +33257,7 @@ function stdoutOf(operation, out) {
|
|
|
33087
33257
|
const stdout = cut >= 0 ? out.slice(0, cut) : out;
|
|
33088
33258
|
return stdout === "(no output)" ? "" : stdout;
|
|
33089
33259
|
}
|
|
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;
|
|
33260
|
+
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
33261
|
var init_container = __esm({
|
|
33092
33262
|
"../../src/core/harness/container.ts"() {
|
|
33093
33263
|
"use strict";
|
|
@@ -33109,6 +33279,7 @@ var init_container = __esm({
|
|
|
33109
33279
|
LOG_READ_BYTES = 48 * 1024;
|
|
33110
33280
|
COMMAND_WORD = /^[A-Za-z0-9_./-]+$/;
|
|
33111
33281
|
FREE_PORT_JS = 'const s=require("net").createServer();s.listen(0,"127.0.0.1",()=>{console.log(s.address().port);s.close()})';
|
|
33282
|
+
DETACHED_STDIO = "</dev/null >/dev/null 2>&1";
|
|
33112
33283
|
IDENTITY_WORD = /^[A-Za-z0-9-]{1,64}$/;
|
|
33113
33284
|
METHOD_WORD = /^[A-Z]+$/;
|
|
33114
33285
|
PATH_WORD = /^\/\S*$/;
|
|
@@ -34251,12 +34422,32 @@ var init_mirror = __esm({
|
|
|
34251
34422
|
seedEchoPending = false;
|
|
34252
34423
|
mirroredTail;
|
|
34253
34424
|
iteration = 0;
|
|
34425
|
+
/** The rows written since the mirror started, and the compaction entries
|
|
34426
|
+
* among them by position — kept whether or not the ledger is wired, so
|
|
34427
|
+
* the harness holds this generation's copy of the record it wrote
|
|
34428
|
+
* (`written`), what a relaunch under a living bot rebuilds from. Bounded
|
|
34429
|
+
* by the run as the ledger's transcript and pi's own session file are — one
|
|
34430
|
+
* row per turn under the run's turn guard, the same bytes both already
|
|
34431
|
+
* hold — and never trimmed: a rebuilt session renders every row, with each
|
|
34432
|
+
* compaction where it sat, so a row dropped here would be a row the record
|
|
34433
|
+
* clause lost. */
|
|
34434
|
+
rows = [];
|
|
34435
|
+
compactionRows = [];
|
|
34254
34436
|
/** The highest ledger inbox seq folded in so far (run-history item 40). */
|
|
34255
34437
|
inboxConsumedSeq = 0;
|
|
34256
34438
|
/** Whether a report is owed: the mirror is wired and something happened. */
|
|
34257
34439
|
get wired() {
|
|
34258
34440
|
return this.deps.onStep !== void 0;
|
|
34259
34441
|
}
|
|
34442
|
+
/** The transcript rows this mirror has written since it started (the rows
|
|
34443
|
+
* after the seed or the resumed transcript it was constructed on), and the
|
|
34444
|
+
* compaction entries among them, each `before` counted from the first of
|
|
34445
|
+
* these rows: what a relaunch under a living bot rebuilds from
|
|
34446
|
+
* (harness-pi item 8), this generation's copy of the ledger's rows. A row
|
|
34447
|
+
* met again on a re-attach (`alreadyHeld`) was the caller's and is not here. */
|
|
34448
|
+
get written() {
|
|
34449
|
+
return { messages: [...this.rows], compactions: [...this.compactionRows] };
|
|
34450
|
+
}
|
|
34260
34451
|
/** The row (the run's own index) the calls under way ride in — the last
|
|
34261
34452
|
* assistant turn the ledger holds — for the `tool_call` stamp (run-history
|
|
34262
34453
|
* item 53); undefined before any assistant turn, or when the transcript's
|
|
@@ -34277,6 +34468,19 @@ var init_mirror = __esm({
|
|
|
34277
34468
|
expectSeedEcho() {
|
|
34278
34469
|
this.seedEchoPending = true;
|
|
34279
34470
|
}
|
|
34471
|
+
/** The settlement turn a rebuilt process's session starts on (harness-pi
|
|
34472
|
+
* item 8) — one tool result per call in flight at the death: the relay's
|
|
34473
|
+
* answer, the still-running note or the restart note — primed as the user
|
|
34474
|
+
* content pending for the next step, the write-through's own convention
|
|
34475
|
+
* (the results since the last assistant turn are the one user turn the next
|
|
34476
|
+
* step carries). The ledger's next rows are then the session's,
|
|
34477
|
+
* `[user(settlements + the continue echo), assistant]` from the seed index,
|
|
34478
|
+
* so a rebuild from the ledger at the next death hands the model a
|
|
34479
|
+
* `tool_result` for every `tool_use` and the two records agree on what the
|
|
34480
|
+
* model saw. Never on a re-attach, where pi's own log carries the results. */
|
|
34481
|
+
prime(parts) {
|
|
34482
|
+
this.pendingUser.push(...parts);
|
|
34483
|
+
}
|
|
34280
34484
|
/** The row about to be written, against the transcript's tail — compared
|
|
34281
34485
|
* once, with the first row the mirror meets: equal means the ledger holds
|
|
34282
34486
|
* it already (read again, harness-pi item 8), together with the results
|
|
@@ -34295,7 +34499,6 @@ var init_mirror = __esm({
|
|
|
34295
34499
|
* there — so the harness can move the row's offset past it. A result or a
|
|
34296
34500
|
* steer's text waits in memory for the next step, and the answer is no. */
|
|
34297
34501
|
async onMessage(message3, turn) {
|
|
34298
|
-
if (!this.deps.onStep) return false;
|
|
34299
34502
|
const chat = chatMessageOf(message3);
|
|
34300
34503
|
if (!chat) return false;
|
|
34301
34504
|
if (chat.content.length === 0) return false;
|
|
@@ -34307,7 +34510,7 @@ var init_mirror = __esm({
|
|
|
34307
34510
|
this.pendingUser.push(...chat.content);
|
|
34308
34511
|
return false;
|
|
34309
34512
|
}
|
|
34310
|
-
if (this.alreadyHeld({ turn: chat })) return
|
|
34513
|
+
if (this.alreadyHeld({ turn: chat })) return this.wired;
|
|
34311
34514
|
const turns = [];
|
|
34312
34515
|
if (this.pendingUser.length > 0) turns.push({ role: "user", content: this.pendingUser });
|
|
34313
34516
|
this.pendingUser = [];
|
|
@@ -34324,6 +34527,8 @@ var init_mirror = __esm({
|
|
|
34324
34527
|
};
|
|
34325
34528
|
this.idx += turns.length;
|
|
34326
34529
|
this.lastAssistantIdx = this.idx - 1;
|
|
34530
|
+
this.rows.push(...turns);
|
|
34531
|
+
if (!this.deps.onStep) return false;
|
|
34327
34532
|
await this.deps.onStep(report2);
|
|
34328
34533
|
return true;
|
|
34329
34534
|
}
|
|
@@ -34333,8 +34538,7 @@ var init_mirror = __esm({
|
|
|
34333
34538
|
* the summary where pi wrote it and the index moves past it. Answers as
|
|
34334
34539
|
* `onMessage` does: the ledger holds everything up to the entry. */
|
|
34335
34540
|
async onCompaction(entry, turn) {
|
|
34336
|
-
if (
|
|
34337
|
-
if (this.alreadyHeld({ compaction: entry })) return true;
|
|
34541
|
+
if (this.alreadyHeld({ compaction: entry })) return this.wired;
|
|
34338
34542
|
const turns = [];
|
|
34339
34543
|
if (this.pendingUser.length > 0) turns.push({ role: "user", content: this.pendingUser });
|
|
34340
34544
|
this.pendingUser = [];
|
|
@@ -34349,6 +34553,9 @@ var init_mirror = __esm({
|
|
|
34349
34553
|
inboxConsumedSeq: this.inboxConsumedSeq
|
|
34350
34554
|
};
|
|
34351
34555
|
this.idx += turns.length + 1;
|
|
34556
|
+
this.rows.push(...turns);
|
|
34557
|
+
this.compactionRows.push({ before: this.rows.length, entry });
|
|
34558
|
+
if (!this.deps.onStep) return false;
|
|
34352
34559
|
await this.deps.onStep(report2);
|
|
34353
34560
|
return true;
|
|
34354
34561
|
}
|
|
@@ -34507,6 +34714,426 @@ var init_process = __esm({
|
|
|
34507
34714
|
}
|
|
34508
34715
|
});
|
|
34509
34716
|
|
|
34717
|
+
// ../../src/execution/tracingExecutor.ts
|
|
34718
|
+
var TracingExecutor;
|
|
34719
|
+
var init_tracingExecutor = __esm({
|
|
34720
|
+
"../../src/execution/tracingExecutor.ts"() {
|
|
34721
|
+
"use strict";
|
|
34722
|
+
TracingExecutor = class {
|
|
34723
|
+
constructor(inner, span, backend) {
|
|
34724
|
+
this.inner = inner;
|
|
34725
|
+
this.span = span;
|
|
34726
|
+
this.backend = backend;
|
|
34727
|
+
const innerRelease = inner.release?.bind(inner);
|
|
34728
|
+
if (innerRelease) this.release = (mode) => this.timed("exec.release", (s) => innerRelease(mode, { span: s }));
|
|
34729
|
+
const innerMoveTo = inner.moveTo?.bind(inner);
|
|
34730
|
+
if (innerMoveTo) this.moveTo = (sha) => this.timed("exec.move_to", (s) => innerMoveTo(sha, { span: s }));
|
|
34731
|
+
const innerReadBytes = inner.readBytes?.bind(inner);
|
|
34732
|
+
if (innerReadBytes)
|
|
34733
|
+
this.readBytes = (path) => this.timed("exec.read_bytes", (s) => innerReadBytes(path, { span: s }));
|
|
34734
|
+
}
|
|
34735
|
+
inner;
|
|
34736
|
+
span;
|
|
34737
|
+
backend;
|
|
34738
|
+
release;
|
|
34739
|
+
moveTo;
|
|
34740
|
+
readBytes;
|
|
34741
|
+
/** Each op under its own `exec.*` span, handed to the inner executor as
|
|
34742
|
+
* `opts.span` so its HTTP calls become `http.client` children (item 21). */
|
|
34743
|
+
timed(name, fn, extra = {}) {
|
|
34744
|
+
return this.span.span(name, (s) => fn(s), {
|
|
34745
|
+
attrs: {
|
|
34746
|
+
...this.backend !== void 0 ? { backend: this.backend } : {},
|
|
34747
|
+
...extra.timeoutMs !== void 0 ? { timeoutMs: extra.timeoutMs } : {}
|
|
34748
|
+
}
|
|
34749
|
+
});
|
|
34750
|
+
}
|
|
34751
|
+
exec(command2, opts) {
|
|
34752
|
+
return this.timed("exec.exec", (s) => this.inner.exec(command2, { ...opts, span: s }), {
|
|
34753
|
+
timeoutMs: opts?.timeoutMs
|
|
34754
|
+
});
|
|
34755
|
+
}
|
|
34756
|
+
readFile(path) {
|
|
34757
|
+
return this.timed("exec.read_file", (s) => this.inner.readFile(path, { span: s }));
|
|
34758
|
+
}
|
|
34759
|
+
writeFile(path, content) {
|
|
34760
|
+
return this.timed("exec.write_file", (s) => this.inner.writeFile(path, content, { span: s }));
|
|
34761
|
+
}
|
|
34762
|
+
};
|
|
34763
|
+
}
|
|
34764
|
+
});
|
|
34765
|
+
|
|
34766
|
+
// ../../src/core/chatMessage.ts
|
|
34767
|
+
function capToolResultContent(content, cap = MAX_TOOL_RESULT_CHARS) {
|
|
34768
|
+
const capText = (text) => text.length > cap ? `${text.slice(0, cap)}
|
|
34769
|
+
\u2026[tool result truncated: ${text.length - cap} of ${text.length} characters cut \u2014 ask for a narrower slice]` : text;
|
|
34770
|
+
if (typeof content === "string") return capText(content);
|
|
34771
|
+
return content.map((p) => p.type === "text" ? { ...p, text: capText(p.text) } : p);
|
|
34772
|
+
}
|
|
34773
|
+
var MAX_TOOL_RESULT_CHARS;
|
|
34774
|
+
var init_chatMessage = __esm({
|
|
34775
|
+
"../../src/core/chatMessage.ts"() {
|
|
34776
|
+
"use strict";
|
|
34777
|
+
MAX_TOOL_RESULT_CHARS = 12e4;
|
|
34778
|
+
}
|
|
34779
|
+
});
|
|
34780
|
+
|
|
34781
|
+
// ../../src/core/harness/pi/toolRules.ts
|
|
34782
|
+
import { isAbsolute as isAbsolute5, relative, resolve as resolve10 } from "node:path";
|
|
34783
|
+
function reachFor(identity2) {
|
|
34784
|
+
if (identity2 === "write") return CODING_REACH;
|
|
34785
|
+
if (identity2 === "read") return READ_REACH;
|
|
34786
|
+
return NONE_REACH;
|
|
34787
|
+
}
|
|
34788
|
+
function judgeToolCall(tool, input, ctx) {
|
|
34789
|
+
const bundle = PI_TOOL_BUNDLES[tool];
|
|
34790
|
+
if (bundle === void 0) return outside(`${tool} is not in any bundle the ${ctx.identity} identity reaches`);
|
|
34791
|
+
if (!reachFor(ctx.identity).has(bundle)) {
|
|
34792
|
+
return outside(
|
|
34793
|
+
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`
|
|
34794
|
+
);
|
|
34795
|
+
}
|
|
34796
|
+
const args = isRecord6(input) ? input : {};
|
|
34797
|
+
if (tool === "bash") return judgeBash(args.command, ctx);
|
|
34798
|
+
if (bundle === "files" || bundle === "write-files") return judgePath(args.path, ctx);
|
|
34799
|
+
return allowed;
|
|
34800
|
+
}
|
|
34801
|
+
function judgeBash(command2, ctx) {
|
|
34802
|
+
if (typeof command2 !== "string") return refused3("malformed \u2014 bash without a string command");
|
|
34803
|
+
if (CREDENTIAL_FILE.test(command2)) return refused3("credential \u2014 reads the executor's credential store");
|
|
34804
|
+
if (ENV_DUMP.test(command2)) return refused3("credential \u2014 dumps the process environment");
|
|
34805
|
+
if (CREDENTIAL_VAR.test(command2)) return refused3("credential \u2014 expands a credential variable");
|
|
34806
|
+
if (MERGE_OR_APPROVE.test(command2)) {
|
|
34807
|
+
return refused3("merge/approve \u2014 a coding run never merges or approves a pull request");
|
|
34808
|
+
}
|
|
34809
|
+
if (ctx.identity !== "write") {
|
|
34810
|
+
if (ANY_PUSH.test(command2)) return refused3("read-only \u2014 a read-identity run never pushes");
|
|
34811
|
+
if (GH_WRITE_VERB.test(command2) || GH_API_WRITE.test(command2) || CURL_GITHUB_WRITE.test(command2)) {
|
|
34812
|
+
return refused3("read-only \u2014 a read-identity run never writes to GitHub");
|
|
34813
|
+
}
|
|
34814
|
+
return allowed;
|
|
34815
|
+
}
|
|
34816
|
+
for (const match of command2.matchAll(GIT_PUSH)) {
|
|
34817
|
+
const verdict = judgePush(match[1], ctx);
|
|
34818
|
+
if (verdict.verdict !== "allowed") return verdict;
|
|
34819
|
+
}
|
|
34820
|
+
return allowed;
|
|
34821
|
+
}
|
|
34822
|
+
function judgePush(tail, ctx) {
|
|
34823
|
+
const words = tail.split(/\s+/).filter((w) => w.length > 0 && !w.startsWith("-"));
|
|
34824
|
+
const [remote, refspec] = words;
|
|
34825
|
+
if (remote !== void 0 && remote !== "origin") {
|
|
34826
|
+
return refused3(`repo:use \u2014 push to remote \`${remote}\`, not the run's repository (origin)`);
|
|
34827
|
+
}
|
|
34828
|
+
if (refspec === void 0) return allowed;
|
|
34829
|
+
const destination = refspec.replace(/^\+/, "").split(":").pop() ?? refspec;
|
|
34830
|
+
if (ctx.branch !== void 0) {
|
|
34831
|
+
const branch3 = destination === "HEAD" ? ctx.branch : destination.replace(/^refs\/heads\//, "");
|
|
34832
|
+
if (branch3 !== ctx.branch) return refused3(`repo:use \u2014 push to \`${branch3}\`, not the run's branch ${ctx.branch}`);
|
|
34833
|
+
return allowed;
|
|
34834
|
+
}
|
|
34835
|
+
const branch2 = destination.replace(/^refs\/heads\//, "");
|
|
34836
|
+
if (branch2 !== "HEAD" && (ctx.protectedBranches ?? []).includes(branch2)) {
|
|
34837
|
+
return refused3(
|
|
34838
|
+
`repo:use \u2014 push to \`${branch2}\`, the branch this run's pull request targets; push your own branch`
|
|
34839
|
+
);
|
|
34840
|
+
}
|
|
34841
|
+
return allowed;
|
|
34842
|
+
}
|
|
34843
|
+
function judgePath(path, ctx) {
|
|
34844
|
+
if (path === void 0) return allowed;
|
|
34845
|
+
if (typeof path !== "string") return refused3("malformed \u2014 a path tool without a string path");
|
|
34846
|
+
if (CREDENTIAL_FILE.test(path)) return refused3("credential \u2014 reads the executor's credential store");
|
|
34847
|
+
const rel = relative(ctx.checkout, resolve10(ctx.checkout, path));
|
|
34848
|
+
if (rel.startsWith("..") || isAbsolute5(rel)) return refused3(`path \u2014 \`${path}\` resolves outside the checkout`);
|
|
34849
|
+
return allowed;
|
|
34850
|
+
}
|
|
34851
|
+
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;
|
|
34852
|
+
var init_toolRules = __esm({
|
|
34853
|
+
"../../src/core/harness/pi/toolRules.ts"() {
|
|
34854
|
+
"use strict";
|
|
34855
|
+
PI_TOOL_BUNDLES = {
|
|
34856
|
+
bash: "shell",
|
|
34857
|
+
read: "files",
|
|
34858
|
+
grep: "files",
|
|
34859
|
+
find: "files",
|
|
34860
|
+
ls: "files",
|
|
34861
|
+
write: "write-files",
|
|
34862
|
+
edit: "write-files",
|
|
34863
|
+
submit_pr_description: "pr",
|
|
34864
|
+
submit_verdict: "verdict"
|
|
34865
|
+
};
|
|
34866
|
+
CODING_REACH = /* @__PURE__ */ new Set([
|
|
34867
|
+
"shell",
|
|
34868
|
+
"files",
|
|
34869
|
+
"write-files",
|
|
34870
|
+
"web",
|
|
34871
|
+
"github-read",
|
|
34872
|
+
"github-write",
|
|
34873
|
+
"pr"
|
|
34874
|
+
]);
|
|
34875
|
+
READ_REACH = /* @__PURE__ */ new Set(["shell", "files", "web", "search", "github-read", "verdict"]);
|
|
34876
|
+
NONE_REACH = /* @__PURE__ */ new Set();
|
|
34877
|
+
allowed = { verdict: "allowed" };
|
|
34878
|
+
refused3 = (reason2) => ({ verdict: "refused", reason: reason2 });
|
|
34879
|
+
outside = (reason2) => ({ verdict: "outside-profile", reason: reason2 });
|
|
34880
|
+
isRecord6 = (v) => typeof v === "object" && v !== null;
|
|
34881
|
+
CREDENTIAL_FILE = /(^|[\s/'"`])(\.git\/)?(github-credentials|\.git-credentials)($|[\s'"`|;&])/;
|
|
34882
|
+
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])/;
|
|
34883
|
+
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/;
|
|
34884
|
+
MERGE_OR_APPROVE = /\bgh\s+pr\s+(merge|review)\b|\/pulls\/\d+\/(merge|reviews)\b/;
|
|
34885
|
+
GIT_THEN_PUSH = String.raw`\bgit(?:\s+(?:-C\s+\S+|--git-dir=\S+|--work-tree=\S+|-c\s+\S+|--no-pager))*\s+push\b`;
|
|
34886
|
+
GIT_PUSH = new RegExp(`${GIT_THEN_PUSH}([^;&|]*)`, "g");
|
|
34887
|
+
ANY_PUSH = new RegExp(GIT_THEN_PUSH);
|
|
34888
|
+
GH_WRITE_VERB = /\bgh\s+(pr|issue)\s+(comment|create|edit|close|reopen|ready|lock|unlock|review|merge|delete|transfer|pin|unpin)\b/;
|
|
34889
|
+
WRITE_METHOD = "(POST|post|PATCH|patch|PUT|put|DELETE|delete)";
|
|
34890
|
+
GH_API_WRITE = new RegExp(
|
|
34891
|
+
`\\bgh\\s+api\\b(?=[^;&|]*(?:\\s(?:-X|--method)[\\s=]*${WRITE_METHOD}\\b|\\s(?:-f|-F|--field|--raw-field|--input)[\\s=]))`
|
|
34892
|
+
);
|
|
34893
|
+
CURL_GITHUB_WRITE = new RegExp(
|
|
34894
|
+
`\\bcurl\\b(?=[^;&|]*api\\.github\\.com)(?=[^;&|]*(?:\\s(?:-X|--request)[\\s=]*${WRITE_METHOD}\\b|\\s(?:-d|--data(?:-\\w+)?|--json|-T|--upload-file)[\\s=]))`
|
|
34895
|
+
);
|
|
34896
|
+
}
|
|
34897
|
+
});
|
|
34898
|
+
|
|
34899
|
+
// ../../src/core/harness/pi/relay.ts
|
|
34900
|
+
function stillRunningNote(tool) {
|
|
34901
|
+
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.`;
|
|
34902
|
+
}
|
|
34903
|
+
async function relayToolCall(harness, calls, ask, opts = {}) {
|
|
34904
|
+
const answer = calls.join(ask.toolCallId, () => runRelayedTool(harness, ask, { signal: calls.signal }));
|
|
34905
|
+
const window = new AbortController();
|
|
34906
|
+
const sleep2 = opts.sleep ?? sleepUnlessAborted;
|
|
34907
|
+
const progress = await Promise.race([
|
|
34908
|
+
answer.then((a) => ({ done: true, answer: a })),
|
|
34909
|
+
sleep2(opts.windowMs ?? RELAY_POLL_WINDOW_MS, window.signal).then(() => ({ done: false }))
|
|
34910
|
+
]);
|
|
34911
|
+
window.abort();
|
|
34912
|
+
return progress;
|
|
34913
|
+
}
|
|
34914
|
+
function relayedToolDefinitions(harness) {
|
|
34915
|
+
return harness.tools.map(({ name, description, inputSchema }) => ({ name, description, inputSchema }));
|
|
34916
|
+
}
|
|
34917
|
+
function authorizeToolCall(harness, ask) {
|
|
34918
|
+
harness.gateSaw(ask.toolCallId);
|
|
34919
|
+
const blocked = harness.toolsBlocked();
|
|
34920
|
+
const refuse3 = (reason2) => {
|
|
34921
|
+
harness.emit({
|
|
34922
|
+
type: "run_note",
|
|
34923
|
+
kind: "tool_refused",
|
|
34924
|
+
summary: redactAndCap(`${ask.tool} refused: ${reason2}`, 300)
|
|
34925
|
+
});
|
|
34926
|
+
return { allow: false, reason: reason2 };
|
|
34927
|
+
};
|
|
34928
|
+
if (blocked !== void 0) return refuse3(blocked);
|
|
34929
|
+
if (harness.tools.some((t) => t.name === ask.tool)) return { allow: true };
|
|
34930
|
+
const verdict = judgeToolCall(ask.tool, ask.input, harness.rules);
|
|
34931
|
+
if (verdict.verdict === "allowed") return { allow: true };
|
|
34932
|
+
return refuse3(verdict.reason);
|
|
34933
|
+
}
|
|
34934
|
+
async function runRelayedTool(harness, ask, opts = {}) {
|
|
34935
|
+
const tool = harness.tools.find((t) => t.name === ask.tool);
|
|
34936
|
+
if (!tool) return { content: [{ type: "text", text: `Unknown tool: ${ask.tool}` }], isError: true };
|
|
34937
|
+
const span = harness.toolSpan(ask.toolCallId);
|
|
34938
|
+
const base = harness.toolContext;
|
|
34939
|
+
const readConversation = base.conversation;
|
|
34940
|
+
const ctx = {
|
|
34941
|
+
...span ? {
|
|
34942
|
+
...base,
|
|
34943
|
+
span,
|
|
34944
|
+
executor: new TracingExecutor(base.executor, span, harness.backend),
|
|
34945
|
+
publish: (e) => harness.emit(withSpanId(e, span.id)),
|
|
34946
|
+
...base.github?.api.withSpan ? { github: { ...base.github, api: base.github.api.withSpan(span) } } : {}
|
|
34947
|
+
} : { ...base, publish: (e) => harness.emit(e) },
|
|
34948
|
+
...opts.signal && !base.signal ? { signal: opts.signal } : {},
|
|
34949
|
+
...readConversation && harness.callSeen ? { conversation: () => harness.callSeen(ask.toolCallId).then(() => readConversation()) } : {},
|
|
34950
|
+
// After the spreads, so the call's own id can never be shadowed by one on the base context.
|
|
34951
|
+
callId: ask.toolCallId
|
|
34952
|
+
};
|
|
34953
|
+
try {
|
|
34954
|
+
const input = typeof ask.input === "object" && ask.input !== null ? ask.input : {};
|
|
34955
|
+
const output = await tool.run(input, ctx);
|
|
34956
|
+
return { content: piContentOf(capToolResultContent(output)), isError: false };
|
|
34957
|
+
} catch (err2) {
|
|
34958
|
+
return errorAnswerOf(err2);
|
|
34959
|
+
}
|
|
34960
|
+
}
|
|
34961
|
+
function errorAnswerOf(err2) {
|
|
34962
|
+
const message3 = err2 instanceof Error ? err2.message : String(err2);
|
|
34963
|
+
return { content: piContentOf(capToolResultContent(`Error: ${message3}`)), isError: true };
|
|
34964
|
+
}
|
|
34965
|
+
function piContentOf(content) {
|
|
34966
|
+
if (typeof content === "string") return [{ type: "text", text: content }];
|
|
34967
|
+
return content.map(
|
|
34968
|
+
(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})]` }
|
|
34969
|
+
);
|
|
34970
|
+
}
|
|
34971
|
+
function withSpanId(e, spanId) {
|
|
34972
|
+
switch (e.type) {
|
|
34973
|
+
case "tool_call":
|
|
34974
|
+
case "tool_result":
|
|
34975
|
+
case "run_note":
|
|
34976
|
+
case "assistant":
|
|
34977
|
+
case "skill_use":
|
|
34978
|
+
return { ...e, spanId };
|
|
34979
|
+
default:
|
|
34980
|
+
return e;
|
|
34981
|
+
}
|
|
34982
|
+
}
|
|
34983
|
+
var HarnessRegistry, RELAY_POLL_WINDOW_MS, RelayedCalls;
|
|
34984
|
+
var init_relay = __esm({
|
|
34985
|
+
"../../src/core/harness/pi/relay.ts"() {
|
|
34986
|
+
"use strict";
|
|
34987
|
+
init_tracingExecutor();
|
|
34988
|
+
init_chatMessage();
|
|
34989
|
+
init_awaitChildren();
|
|
34990
|
+
init_runEvents();
|
|
34991
|
+
init_toolRules();
|
|
34992
|
+
HarnessRegistry = class {
|
|
34993
|
+
live = /* @__PURE__ */ new Map();
|
|
34994
|
+
/** The run drives a pi from here, its relayed calls kept beside it; the
|
|
34995
|
+
* returned function forgets it and ends whatever call still runs. */
|
|
34996
|
+
register(harness) {
|
|
34997
|
+
return this.hold({ harness, calls: new RelayedCalls() });
|
|
34998
|
+
}
|
|
34999
|
+
/** A process relaunched under the same run — in a replacement container,
|
|
35000
|
+
* with a rotated bearer (harness-pi item 8) — takes the run's registration
|
|
35001
|
+
* over, and the run's relayed calls stay: a call in flight keeps running
|
|
35002
|
+
* under the same end signal, and an ask by its id through the new
|
|
35003
|
+
* registration joins it and reads its answer. The earlier registration's
|
|
35004
|
+
* forget is a no-op from here; the returned one forgets the run and ends
|
|
35005
|
+
* the calls. Refuses by name a run that is not registered. */
|
|
35006
|
+
replace(harness) {
|
|
35007
|
+
const previous = this.live.get(harness.runId);
|
|
35008
|
+
if (!previous) throw new Error(`run ${harness.runId} is not registered on the relay: nothing to replace`);
|
|
35009
|
+
return this.hold({ harness, calls: previous.calls });
|
|
35010
|
+
}
|
|
35011
|
+
/** The run forgotten whichever registration holds it, its calls ended: the
|
|
35012
|
+
* run loop's, for a run whose process was found gone with its container
|
|
35013
|
+
* and is not relaunched — the harness left the registration standing so a
|
|
35014
|
+
* relaunch could take it over (`replace`) with the calls kept, and a
|
|
35015
|
+
* refusal has to end them. A run not registered is nothing to forget. */
|
|
35016
|
+
forget(runId3) {
|
|
35017
|
+
const entry = this.live.get(runId3);
|
|
35018
|
+
if (!entry) return;
|
|
35019
|
+
this.live.delete(runId3);
|
|
35020
|
+
entry.calls.end();
|
|
35021
|
+
}
|
|
35022
|
+
hold(entry) {
|
|
35023
|
+
this.live.set(entry.harness.runId, entry);
|
|
35024
|
+
return () => {
|
|
35025
|
+
if (this.live.get(entry.harness.runId) !== entry) return;
|
|
35026
|
+
this.live.delete(entry.harness.runId);
|
|
35027
|
+
entry.calls.end();
|
|
35028
|
+
};
|
|
35029
|
+
}
|
|
35030
|
+
get(runId3) {
|
|
35031
|
+
return this.live.get(runId3)?.harness;
|
|
35032
|
+
}
|
|
35033
|
+
/** The run's relayed calls, for as long as it is registered. */
|
|
35034
|
+
calls(runId3) {
|
|
35035
|
+
return this.live.get(runId3)?.calls;
|
|
35036
|
+
}
|
|
35037
|
+
size() {
|
|
35038
|
+
return this.live.size;
|
|
35039
|
+
}
|
|
35040
|
+
};
|
|
35041
|
+
RELAY_POLL_WINDOW_MS = 3e4;
|
|
35042
|
+
RelayedCalls = class {
|
|
35043
|
+
calls = /* @__PURE__ */ new Map();
|
|
35044
|
+
/** The ids whose call is still running: joined by `join`, forgotten as each answers. */
|
|
35045
|
+
running = /* @__PURE__ */ new Set();
|
|
35046
|
+
/** The ids answered from the record (`settle`), never run here. */
|
|
35047
|
+
settledIds = /* @__PURE__ */ new Set();
|
|
35048
|
+
ending = new AbortController();
|
|
35049
|
+
get size() {
|
|
35050
|
+
return this.calls.size;
|
|
35051
|
+
}
|
|
35052
|
+
/** Aborted when the run ends: the signal every call here runs under. */
|
|
35053
|
+
get signal() {
|
|
35054
|
+
return this.ending.signal;
|
|
35055
|
+
}
|
|
35056
|
+
/** The call's answer as a promise: started by `start` on the first ask, the same promise after. */
|
|
35057
|
+
join(callId, start3) {
|
|
35058
|
+
let answer = this.calls.get(callId);
|
|
35059
|
+
if (!answer) {
|
|
35060
|
+
answer = start3();
|
|
35061
|
+
this.calls.set(callId, answer);
|
|
35062
|
+
this.running.add(callId);
|
|
35063
|
+
const done = () => void this.running.delete(callId);
|
|
35064
|
+
answer.then(done, done);
|
|
35065
|
+
}
|
|
35066
|
+
return answer;
|
|
35067
|
+
}
|
|
35068
|
+
/** The ids of the calls still running, in the order they were started. */
|
|
35069
|
+
inFlight() {
|
|
35070
|
+
return [...this.running];
|
|
35071
|
+
}
|
|
35072
|
+
/** A call asked here whose answer already landed: the answer, or nothing
|
|
35073
|
+
* for a call never asked, one still running (`awaitInFlight` reads those)
|
|
35074
|
+
* or one settled from the record (`settle`: the record's answer, not the
|
|
35075
|
+
* bot's). What a relaunch reads for a call the record names beside the
|
|
35076
|
+
* calls it awaits (harness-pi item 8): an answer that landed after the
|
|
35077
|
+
* process died and before it could ask again is carried into the rebuilt
|
|
35078
|
+
* session too, never lost to a restart note. A `start` that rejected is
|
|
35079
|
+
* the error answer `runRelayedTool` would have given. */
|
|
35080
|
+
async answered(callId) {
|
|
35081
|
+
const answer = this.calls.get(callId);
|
|
35082
|
+
if (!answer || this.running.has(callId) || this.settledIds.has(callId)) return void 0;
|
|
35083
|
+
return answer.then(
|
|
35084
|
+
(a) => a,
|
|
35085
|
+
(err2) => errorAnswerOf(err2)
|
|
35086
|
+
);
|
|
35087
|
+
}
|
|
35088
|
+
/** Every call still running, awaited together up to one window — the relay
|
|
35089
|
+
* window a request waits, by default — for a relaunch that rebuilds the
|
|
35090
|
+
* process's session (harness-pi item 8): a call that answers inside it is
|
|
35091
|
+
* `done` with its answer, the result the rebuilt session carries; one still
|
|
35092
|
+
* running after it is not, keeps running here for an ask by the same id,
|
|
35093
|
+
* and the rebuilt session carries the still-running note in its place
|
|
35094
|
+
* (`stillRunningNote`). A call whose `start` rejected is `done` with an
|
|
35095
|
+
* error answer, as `runRelayedTool` answers its own throws, so one
|
|
35096
|
+
* straggler's exception never loses the others' answers. Nothing is ended
|
|
35097
|
+
* or re-run. Answers at once with nothing in flight. */
|
|
35098
|
+
async awaitInFlight(opts = {}) {
|
|
35099
|
+
const ids = this.inFlight();
|
|
35100
|
+
if (ids.length === 0) return [];
|
|
35101
|
+
const window = new AbortController();
|
|
35102
|
+
const sleep2 = opts.sleep ?? sleepUnlessAborted;
|
|
35103
|
+
const closed = sleep2(opts.windowMs ?? RELAY_POLL_WINDOW_MS, window.signal).then(() => ({
|
|
35104
|
+
done: false
|
|
35105
|
+
}));
|
|
35106
|
+
const progress = await Promise.all(
|
|
35107
|
+
ids.map(async (callId) => {
|
|
35108
|
+
const answered = this.calls.get(callId).then(
|
|
35109
|
+
(answer) => ({ done: true, answer }),
|
|
35110
|
+
(err2) => ({ done: true, answer: errorAnswerOf(err2) })
|
|
35111
|
+
);
|
|
35112
|
+
return { callId, ...await Promise.race([answered, closed]) };
|
|
35113
|
+
})
|
|
35114
|
+
);
|
|
35115
|
+
window.abort();
|
|
35116
|
+
return progress;
|
|
35117
|
+
}
|
|
35118
|
+
/** The call's answer is known before it is asked (harness-pi item 8): the
|
|
35119
|
+
* record's settlement for a call in flight when the previous generation
|
|
35120
|
+
* died, whose extension asks again with the same id. Every ask reads it and
|
|
35121
|
+
* the tool never runs; a call already joined keeps the answer it has. */
|
|
35122
|
+
settle(callId, answer) {
|
|
35123
|
+
if (this.calls.has(callId)) return;
|
|
35124
|
+
this.calls.set(callId, Promise.resolve(answer));
|
|
35125
|
+
this.settledIds.add(callId);
|
|
35126
|
+
}
|
|
35127
|
+
end() {
|
|
35128
|
+
this.ending.abort();
|
|
35129
|
+
this.calls.clear();
|
|
35130
|
+
this.running.clear();
|
|
35131
|
+
this.settledIds.clear();
|
|
35132
|
+
}
|
|
35133
|
+
};
|
|
35134
|
+
}
|
|
35135
|
+
});
|
|
35136
|
+
|
|
34510
35137
|
// ../../src/core/harness/pi/transport.ts
|
|
34511
35138
|
var NEWLINE, PiRpcTransport;
|
|
34512
35139
|
var init_transport = __esm({
|
|
@@ -34642,16 +35269,25 @@ function promptOf(seed) {
|
|
|
34642
35269
|
function settlementText(s) {
|
|
34643
35270
|
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
35271
|
}
|
|
34645
|
-
function settlementResults(settlements) {
|
|
35272
|
+
function settlementResults(settlements, settled = /* @__PURE__ */ new Map()) {
|
|
34646
35273
|
if (settlements.length === 0) return void 0;
|
|
34647
35274
|
return {
|
|
34648
35275
|
role: "user",
|
|
34649
|
-
content: settlements.map((s) =>
|
|
34650
|
-
|
|
34651
|
-
|
|
34652
|
-
|
|
34653
|
-
|
|
34654
|
-
|
|
35276
|
+
content: settlements.map((s) => {
|
|
35277
|
+
const relay = settled.get(s.toolUse.id);
|
|
35278
|
+
return {
|
|
35279
|
+
type: "tool_result",
|
|
35280
|
+
toolUseId: s.toolUse.id,
|
|
35281
|
+
content: relay?.text ?? settlementText(s),
|
|
35282
|
+
...relay?.isError ?? true ? { isError: true } : {}
|
|
35283
|
+
};
|
|
35284
|
+
})
|
|
35285
|
+
};
|
|
35286
|
+
}
|
|
35287
|
+
function relaySettlementOf(answer) {
|
|
35288
|
+
return {
|
|
35289
|
+
text: answer.content.map((c) => c.type === "text" ? c.text : "[image]").join("\n"),
|
|
35290
|
+
isError: answer.isError
|
|
34655
35291
|
};
|
|
34656
35292
|
}
|
|
34657
35293
|
function ledgerTailOf(resume) {
|
|
@@ -34782,12 +35418,36 @@ async function runPiHarnessOpen(deps, run2) {
|
|
|
34782
35418
|
await deps.sleep(seenTick);
|
|
34783
35419
|
}
|
|
34784
35420
|
};
|
|
34785
|
-
const
|
|
35421
|
+
const relaunch = run2.resume?.relaunch;
|
|
35422
|
+
const forget = relaunch !== void 0 && deps.registry.get(run2.runId) !== void 0 ? deps.registry.replace(live) : deps.registry.register(live);
|
|
34786
35423
|
const calls = deps.registry.calls(run2.runId);
|
|
34787
35424
|
for (const s of run2.resume?.settlements ?? []) calls?.settle(s.toolUse.id, settlementAnswer(s));
|
|
34788
35425
|
let pid;
|
|
34789
35426
|
let transport;
|
|
34790
35427
|
let facts2;
|
|
35428
|
+
const recordBase = {
|
|
35429
|
+
messages: run2.resume?.messages ?? run2.messages,
|
|
35430
|
+
compactions: run2.resume?.compactions ?? []
|
|
35431
|
+
};
|
|
35432
|
+
const relaySettled = /* @__PURE__ */ new Map();
|
|
35433
|
+
let stillRunning = 0;
|
|
35434
|
+
const recordNow = () => {
|
|
35435
|
+
const written = mirror.written;
|
|
35436
|
+
const messages = [...recordBase.messages, ...written.messages];
|
|
35437
|
+
const last = messages.at(-1);
|
|
35438
|
+
const calls2 = last?.role === "assistant" ? last.content.filter((p) => p.type === "tool_use") : [];
|
|
35439
|
+
return {
|
|
35440
|
+
messages,
|
|
35441
|
+
compactions: [
|
|
35442
|
+
...recordBase.compactions,
|
|
35443
|
+
...written.compactions.map((c) => ({ ...c, before: recordBase.messages.length + c.before }))
|
|
35444
|
+
],
|
|
35445
|
+
settlements: calls2.map((toolUse) => ({ toolUse, action: "synthetic", text: replacedCallNote(toolUse.name) })),
|
|
35446
|
+
turn: bridge.turns,
|
|
35447
|
+
inboxConsumedSeq: mirror.inboxConsumedSeq,
|
|
35448
|
+
deadline
|
|
35449
|
+
};
|
|
35450
|
+
};
|
|
34791
35451
|
let mirrored = 0;
|
|
34792
35452
|
const save = () => {
|
|
34793
35453
|
if (facts2) {
|
|
@@ -34814,8 +35474,8 @@ async function runPiHarnessOpen(deps, run2) {
|
|
|
34814
35474
|
hardStopped ? "the run was hard-stopped" : bypass ? "the run was stopped: a tool call bypassed the gate" : "the run ended"
|
|
34815
35475
|
);
|
|
34816
35476
|
save();
|
|
34817
|
-
forget();
|
|
34818
35477
|
if (replaced !== void 0) return;
|
|
35478
|
+
forget();
|
|
34819
35479
|
if (pid !== void 0) await container.kill(pid).catch(() => {
|
|
34820
35480
|
});
|
|
34821
35481
|
if (paths !== void 0) await container.remove(paths).catch(() => {
|
|
@@ -34827,7 +35487,7 @@ async function runPiHarnessOpen(deps, run2) {
|
|
|
34827
35487
|
let elsewhere;
|
|
34828
35488
|
const here = await container.identity();
|
|
34829
35489
|
const honoured = (hash) => hash !== void 0 && (deps.bearers === void 0 || deps.bearers.adopt(run2.runId, hash));
|
|
34830
|
-
if (recorded !== void 0) {
|
|
35490
|
+
if (recorded !== void 0 && relaunch === void 0) {
|
|
34831
35491
|
const located2 = await locatePi(recorded, container, here);
|
|
34832
35492
|
if (located2 === "another-container") {
|
|
34833
35493
|
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 +35522,7 @@ async function runPiHarnessOpen(deps, run2) {
|
|
|
34862
35522
|
);
|
|
34863
35523
|
} else {
|
|
34864
35524
|
paths = piRunPathsAt(await container.makeRoot(piRunPaths(run2.runId).dir));
|
|
34865
|
-
if (recorded?.root !== void 0 && recorded.root !== paths.dir && elsewhere === void 0)
|
|
35525
|
+
if (recorded?.root !== void 0 && recorded.root !== paths.dir && elsewhere === void 0 && relaunch === void 0)
|
|
34866
35526
|
await container.remove(piRunPathsAt(recorded.root)).catch(() => {
|
|
34867
35527
|
});
|
|
34868
35528
|
const spec = {
|
|
@@ -34878,12 +35538,33 @@ async function runPiHarnessOpen(deps, run2) {
|
|
|
34878
35538
|
};
|
|
34879
35539
|
let session;
|
|
34880
35540
|
if (run2.resume) {
|
|
34881
|
-
const
|
|
35541
|
+
const wanted = new Map(run2.resume.settlements.map((s) => [s.toolUse.id, s.toolUse.name]));
|
|
35542
|
+
const running = calls?.inFlight().filter((id) => wanted.has(id)) ?? [];
|
|
35543
|
+
if (running.length > 0)
|
|
35544
|
+
run2.onProgress?.(
|
|
35545
|
+
`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`
|
|
35546
|
+
);
|
|
35547
|
+
for (const p of await calls?.awaitInFlight({ sleep: (ms) => deps.sleep(ms) }) ?? []) {
|
|
35548
|
+
const tool = wanted.get(p.callId);
|
|
35549
|
+
if (tool === void 0) continue;
|
|
35550
|
+
relaySettled.set(
|
|
35551
|
+
p.callId,
|
|
35552
|
+
p.done ? relaySettlementOf(p.answer) : { text: stillRunningNote(tool), isError: false }
|
|
35553
|
+
);
|
|
35554
|
+
if (!p.done) stillRunning++;
|
|
35555
|
+
}
|
|
35556
|
+
for (const [callId] of wanted) {
|
|
35557
|
+
if (relaySettled.has(callId)) continue;
|
|
35558
|
+
const answer2 = await calls?.answered(callId);
|
|
35559
|
+
if (answer2 !== void 0) relaySettled.set(callId, relaySettlementOf(answer2));
|
|
35560
|
+
}
|
|
35561
|
+
const settled2 = settlementResults(run2.resume.settlements, relaySettled);
|
|
34882
35562
|
session = {
|
|
34883
35563
|
stem: "resumed",
|
|
34884
35564
|
messages: settled2 ? [...run2.resume.messages, settled2] : run2.resume.messages,
|
|
34885
35565
|
compactions: run2.resume.compactions ?? []
|
|
34886
35566
|
};
|
|
35567
|
+
if (settled2) mirror.prime(settled2.content);
|
|
34887
35568
|
} else {
|
|
34888
35569
|
const earlier2 = splitSeed(run2.messages).session;
|
|
34889
35570
|
if (earlier2.length > 0) session = { stem: "seed", messages: earlier2, compactions: [] };
|
|
@@ -34908,7 +35589,20 @@ async function runPiHarnessOpen(deps, run2) {
|
|
|
34908
35589
|
)
|
|
34909
35590
|
);
|
|
34910
35591
|
}
|
|
34911
|
-
if (run2.resume) {
|
|
35592
|
+
if (run2.resume && relaunch !== void 0) {
|
|
35593
|
+
const inFlight = run2.resume.settlements.length;
|
|
35594
|
+
const answered = relaySettled.size - stillRunning;
|
|
35595
|
+
const lost = inFlight - relaySettled.size;
|
|
35596
|
+
const settledHow = inFlight === 0 ? "nothing was in flight" : `${inFlight} call(s) were in flight: ` + [
|
|
35597
|
+
...answered > 0 ? [`${answered} answered on the relay`] : [],
|
|
35598
|
+
...stillRunning > 0 ? [`${stillRunning} still running there`] : [],
|
|
35599
|
+
...lost > 0 ? [`${lost} lost with the container, each answered with a restart note`] : []
|
|
35600
|
+
].join(", ");
|
|
35601
|
+
note(
|
|
35602
|
+
"resumed",
|
|
35603
|
+
`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`
|
|
35604
|
+
);
|
|
35605
|
+
} else if (run2.resume) {
|
|
34912
35606
|
const lost = run2.resume.settlements.length;
|
|
34913
35607
|
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
35608
|
note(
|
|
@@ -35066,7 +35760,8 @@ ${stagedLine}` : prompt;
|
|
|
35066
35760
|
return new PiContainerReplacedError(
|
|
35067
35761
|
said2.message,
|
|
35068
35762
|
facts2?.container,
|
|
35069
|
-
await container.identity().catch(() => void 0)
|
|
35763
|
+
await container.identity().catch(() => void 0),
|
|
35764
|
+
recordNow()
|
|
35070
35765
|
);
|
|
35071
35766
|
};
|
|
35072
35767
|
check();
|
|
@@ -35336,6 +36031,7 @@ var init_harness = __esm({
|
|
|
35336
36031
|
init_mirror();
|
|
35337
36032
|
init_process();
|
|
35338
36033
|
init_protocol();
|
|
36034
|
+
init_relay();
|
|
35339
36035
|
init_transport();
|
|
35340
36036
|
FINALE_TIMEOUT_MS = 3 * 6e4;
|
|
35341
36037
|
CALL_SEEN_WAIT_MS = 3e3;
|
|
@@ -35355,21 +36051,17 @@ var init_harness = __esm({
|
|
|
35355
36051
|
this.name = "GateBypassed";
|
|
35356
36052
|
}
|
|
35357
36053
|
};
|
|
35358
|
-
PiContainerReplacedError = class extends
|
|
35359
|
-
constructor(said2, was, now) {
|
|
36054
|
+
PiContainerReplacedError = class extends HarnessContainerReplacedError {
|
|
36055
|
+
constructor(said2, was, now, record2) {
|
|
35360
36056
|
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
|
-
|
|
36057
|
+
`the container running pi was replaced (${was ?? "unknown"} \u2192 ${now ?? "unknown"}; the executor said: ${redactAndCap(said2.replace(/\s+/g, " ").trim(), 240)})`,
|
|
36058
|
+
said2,
|
|
36059
|
+
was,
|
|
36060
|
+
now,
|
|
36061
|
+
record2
|
|
35364
36062
|
);
|
|
35365
|
-
this.said = said2;
|
|
35366
|
-
this.was = was;
|
|
35367
|
-
this.now = now;
|
|
35368
36063
|
this.name = "PiContainerReplacedError";
|
|
35369
36064
|
}
|
|
35370
|
-
said;
|
|
35371
|
-
was;
|
|
35372
|
-
now;
|
|
35373
36065
|
};
|
|
35374
36066
|
POLICY_REFUSAL_REPLY = "the model refused this request under its usage policy \u2014 rephrase it and the thread continues";
|
|
35375
36067
|
ModelPolicyRefusedError = class extends Error {
|
|
@@ -36199,7 +36891,6 @@ async function runLoop(deps, ctx) {
|
|
|
36199
36891
|
mcpForRun,
|
|
36200
36892
|
run: run2,
|
|
36201
36893
|
registry,
|
|
36202
|
-
round: round2,
|
|
36203
36894
|
admitted,
|
|
36204
36895
|
ledgerRun,
|
|
36205
36896
|
resume,
|
|
@@ -36226,7 +36917,9 @@ async function runLoop(deps, ctx) {
|
|
|
36226
36917
|
seed
|
|
36227
36918
|
} = ctx;
|
|
36228
36919
|
const agent = budgetedAgent(ctx.agent, profile);
|
|
36229
|
-
const {
|
|
36920
|
+
const { round: round2 } = ctx;
|
|
36921
|
+
let { executor } = round2.selection;
|
|
36922
|
+
const { binding } = round2.selection;
|
|
36230
36923
|
const coordinatorBase = (async () => {
|
|
36231
36924
|
if (coordinator === void 0) return void 0;
|
|
36232
36925
|
if (coordinator.base !== void 0 || repoCtx.baseRef !== void 0) return coordinator.base;
|
|
@@ -36420,6 +37113,12 @@ async function runLoop(deps, ctx) {
|
|
|
36420
37113
|
const postedBefore = resume ? reviewPostedBefore(resume.events) : void 0;
|
|
36421
37114
|
if (postedBefore) reviewHead = postedBefore.head;
|
|
36422
37115
|
const facts2 = resume ? harnessFactsOf(resume.row.state.harness) : void 0;
|
|
37116
|
+
let lastFacts = facts2;
|
|
37117
|
+
const saveFacts = (h) => {
|
|
37118
|
+
lastFacts = h;
|
|
37119
|
+
ledgerRun?.setState({ harness: h });
|
|
37120
|
+
};
|
|
37121
|
+
let workspaceBinding = workspaceBindingFor(round2.selection, profile.machine);
|
|
36423
37122
|
try {
|
|
36424
37123
|
if (finish2) {
|
|
36425
37124
|
onEvent({
|
|
@@ -36490,11 +37189,21 @@ async function runLoop(deps, ctx) {
|
|
|
36490
37189
|
)
|
|
36491
37190
|
)
|
|
36492
37191
|
];
|
|
36493
|
-
|
|
37192
|
+
let bearer = ctx.bearer;
|
|
37193
|
+
let harnessResume = reentry ? {
|
|
37194
|
+
messages: reentry.messages,
|
|
37195
|
+
compactions: reentry.compactions,
|
|
37196
|
+
settlements: reentry.settlements,
|
|
37197
|
+
remainingMs: reentry.remainingMs,
|
|
37198
|
+
turn: reentry.turn,
|
|
37199
|
+
inboxConsumedSeq: reentry.inboxConsumedSeq,
|
|
37200
|
+
...facts2 ? { facts: facts2 } : {}
|
|
37201
|
+
} : void 0;
|
|
37202
|
+
const openRun = () => openThroughSeam(
|
|
36494
37203
|
harnessDeps.harness,
|
|
36495
37204
|
{
|
|
36496
37205
|
container: harnessDeps.containerFor?.(executor, profile.machine) ?? harnessContainerFor(executor, profile.machine),
|
|
36497
|
-
bearer
|
|
37206
|
+
bearer,
|
|
36498
37207
|
harnessUrl,
|
|
36499
37208
|
registry: harnessDeps.registry,
|
|
36500
37209
|
...deps.runBearers ? { bearers: deps.runBearers } : {},
|
|
@@ -36525,24 +37234,55 @@ async function runLoop(deps, ctx) {
|
|
|
36525
37234
|
...stageFollowUps ? { stageFollowUps } : {},
|
|
36526
37235
|
onEvent,
|
|
36527
37236
|
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
|
-
} : {}
|
|
37237
|
+
...ledgerRun ? { onStep: ledgerRun.step.bind(ledgerRun), logIndexOf: ledgerRun.logIndexOf.bind(ledgerRun) } : {},
|
|
37238
|
+
saveFacts,
|
|
37239
|
+
...harnessResume ? { resume: harnessResume } : {}
|
|
36544
37240
|
}
|
|
36545
37241
|
);
|
|
37242
|
+
for (; ; ) {
|
|
37243
|
+
try {
|
|
37244
|
+
harnessSession = await openRun();
|
|
37245
|
+
break;
|
|
37246
|
+
} catch (err2) {
|
|
37247
|
+
if (!(err2 instanceof HarnessContainerReplacedError)) throw err2;
|
|
37248
|
+
let decision;
|
|
37249
|
+
try {
|
|
37250
|
+
decision = await prepareRelaunch(deps, {
|
|
37251
|
+
runId: run2.id,
|
|
37252
|
+
threadKey: msg.threadKey,
|
|
37253
|
+
agent: ctx.agent,
|
|
37254
|
+
profile,
|
|
37255
|
+
repoCtx,
|
|
37256
|
+
root,
|
|
37257
|
+
clock: clock4,
|
|
37258
|
+
harness: harnessDeps.harness,
|
|
37259
|
+
replaced: err2,
|
|
37260
|
+
facts: lastFacts,
|
|
37261
|
+
binding: workspaceBinding,
|
|
37262
|
+
saveFacts
|
|
37263
|
+
});
|
|
37264
|
+
} catch (failed) {
|
|
37265
|
+
harnessDeps.registry.forget(run2.id);
|
|
37266
|
+
throw failed;
|
|
37267
|
+
}
|
|
37268
|
+
if (decision.kind === "refused") {
|
|
37269
|
+
harnessDeps.registry.forget(run2.id);
|
|
37270
|
+
onEvent({ type: "run_note", kind: "sandbox_restarted", summary: decision.interruption.message });
|
|
37271
|
+
throw decision.interruption;
|
|
37272
|
+
}
|
|
37273
|
+
if (decision.round !== void 0) {
|
|
37274
|
+
executor = decision.round.selection.executor;
|
|
37275
|
+
toolContext.executor = executor;
|
|
37276
|
+
const rebound = workspaceBindingFor(decision.round.selection, profile.machine);
|
|
37277
|
+
if (rebound !== void 0) {
|
|
37278
|
+
workspaceBinding = rebound;
|
|
37279
|
+
ledgerRun?.setState({ binding: rebound });
|
|
37280
|
+
}
|
|
37281
|
+
}
|
|
37282
|
+
if (decision.bearer !== void 0) bearer = decision.bearer;
|
|
37283
|
+
harnessResume = decision.resume;
|
|
37284
|
+
}
|
|
37285
|
+
}
|
|
36546
37286
|
answer = harnessSession.answer;
|
|
36547
37287
|
}
|
|
36548
37288
|
if (isPrReview && repoCtx.repo && repoCtx.pr !== void 0 && run2.control.requested !== "hard" && postedBefore === void 0) {
|
|
@@ -36851,7 +37591,9 @@ var init_runLoop = __esm({
|
|
|
36851
37591
|
init_provider();
|
|
36852
37592
|
init_toolsets();
|
|
36853
37593
|
init_contract4();
|
|
37594
|
+
init_relaunch();
|
|
36854
37595
|
init_botHostContainer();
|
|
37596
|
+
init_factory();
|
|
36855
37597
|
init_container();
|
|
36856
37598
|
init_harness();
|
|
36857
37599
|
init_windDown();
|
|
@@ -37004,7 +37746,7 @@ async function shipPreflight(input) {
|
|
|
37004
37746
|
`\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
37747
|
);
|
|
37006
37748
|
}
|
|
37007
|
-
const ref = repoCtx.ref && !repoCtx.refFromPr && repoCtx.ref.toLowerCase() !== repo.toLowerCase() ? repoCtx.ref : void 0;
|
|
37749
|
+
const ref = repoCtx.ref && !repoCtx.refFromPr && !isUnitBranch(repoCtx.ref) && repoCtx.ref.toLowerCase() !== repo.toLowerCase() ? repoCtx.ref : void 0;
|
|
37008
37750
|
return {
|
|
37009
37751
|
ok: true,
|
|
37010
37752
|
entry: { repo, base: resolveBaseRef([ref], info?.defaultBranch) }
|
|
@@ -37137,7 +37879,7 @@ function planWhere(p, units, mergedBefore) {
|
|
|
37137
37879
|
async function handOffToCoordinator(deps, input) {
|
|
37138
37880
|
const log = deps.log ?? console.log;
|
|
37139
37881
|
const planned = await plan(deps, input);
|
|
37140
|
-
if (!planned.ok) return
|
|
37882
|
+
if (!planned.ok) return refused4(planned.reply);
|
|
37141
37883
|
const p = planned.planned;
|
|
37142
37884
|
const planOf = () => ({
|
|
37143
37885
|
id: p.planId,
|
|
@@ -37167,15 +37909,15 @@ async function handOffToCoordinator(deps, input) {
|
|
|
37167
37909
|
const status3 = await deps.status(latest.id);
|
|
37168
37910
|
const where = `plan \`${p.planId}\``;
|
|
37169
37911
|
if (status3.kind === "unanswered")
|
|
37170
|
-
return
|
|
37912
|
+
return refused4(
|
|
37171
37913
|
`\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
37914
|
);
|
|
37173
37915
|
if (status3.kind === "status" && RUNNING.has(status3.status))
|
|
37174
|
-
return
|
|
37916
|
+
return refused4(
|
|
37175
37917
|
`\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
37918
|
);
|
|
37177
37919
|
if (status3.kind === "status" && !ENDED.has(status3.status))
|
|
37178
|
-
return
|
|
37920
|
+
return refused4(
|
|
37179
37921
|
`\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
37922
|
);
|
|
37181
37923
|
const merged = /* @__PURE__ */ new Set();
|
|
@@ -37187,7 +37929,7 @@ async function handOffToCoordinator(deps, input) {
|
|
|
37187
37929
|
}
|
|
37188
37930
|
const remaining = p.selected.filter((u) => !merged.has(u));
|
|
37189
37931
|
if (remaining.length === 0)
|
|
37190
|
-
return
|
|
37932
|
+
return refused4(
|
|
37191
37933
|
`\u{1F6AB} Every unit of ${where} this request names is merged already (${p.selected.join(", ")}) \u2014 nothing left to run.`
|
|
37192
37934
|
);
|
|
37193
37935
|
const mergedBefore = p.selected.filter((u) => merged.has(u));
|
|
@@ -37222,16 +37964,16 @@ async function start(deps, input, instance, units, where, write) {
|
|
|
37222
37964
|
const put = write === "replace" ? await deps.instances.replace(instance) : await deps.instances.put(instance);
|
|
37223
37965
|
if (!put.ok) {
|
|
37224
37966
|
if (put.reason === "unavailable")
|
|
37225
|
-
return
|
|
37967
|
+
return refused4(
|
|
37226
37968
|
"\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
37969
|
);
|
|
37228
|
-
return
|
|
37970
|
+
return refused4(
|
|
37229
37971
|
`\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
37972
|
);
|
|
37231
37973
|
}
|
|
37232
37974
|
const rows = await deps.instances.putUnits(units);
|
|
37233
37975
|
if (!rows.ok)
|
|
37234
|
-
return
|
|
37976
|
+
return refused4(
|
|
37235
37977
|
"\u26A0\uFE0F The plan runner needs run history on the state Worker: the unit rows could not be written, so nothing ran."
|
|
37236
37978
|
);
|
|
37237
37979
|
let answer;
|
|
@@ -37248,26 +37990,26 @@ async function start(deps, input, instance, units, where, write) {
|
|
|
37248
37990
|
}
|
|
37249
37991
|
case "duplicate": {
|
|
37250
37992
|
const status3 = answer.status !== void 0 ? `, status: ${answer.status}` : "";
|
|
37251
|
-
return
|
|
37993
|
+
return refused4(
|
|
37252
37994
|
`\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
37995
|
);
|
|
37254
37996
|
}
|
|
37255
37997
|
case "failed":
|
|
37256
37998
|
case "unanswered":
|
|
37257
37999
|
log(`[ship] ${input.msg.threadKey}: the plan runner ${instance.id} could not be started \u2014 ${answer.reason}`);
|
|
37258
|
-
return
|
|
38000
|
+
return refused4(
|
|
37259
38001
|
`\u26A0\uFE0F The plan runner could not be started: ${answer.reason}. Nothing ran; re-issue the request to try again.`
|
|
37260
38002
|
);
|
|
37261
38003
|
}
|
|
37262
38004
|
}
|
|
37263
|
-
var
|
|
38005
|
+
var refused4, describe6, RUNNING, ENDED;
|
|
37264
38006
|
var init_handOff = __esm({
|
|
37265
38007
|
"../../src/core/coordinator/handOff.ts"() {
|
|
37266
38008
|
"use strict";
|
|
37267
38009
|
init_contract();
|
|
37268
38010
|
init_preflight();
|
|
37269
38011
|
init_coordinator();
|
|
37270
|
-
|
|
38012
|
+
refused4 = (reply) => ({ status: "aborted", reply });
|
|
37271
38013
|
describe6 = (err2) => err2 instanceof Error ? err2.message : String(err2);
|
|
37272
38014
|
RUNNING = /* @__PURE__ */ new Set(["queued", "running", "paused", "waiting", "waitingForPause"]);
|
|
37273
38015
|
ENDED = /* @__PURE__ */ new Set(["complete", "errored", "terminated"]);
|
|
@@ -37867,12 +38609,12 @@ function continuable(run2) {
|
|
|
37867
38609
|
return run2.finished && run2.session !== void 0 && run2.agent !== void 0;
|
|
37868
38610
|
}
|
|
37869
38611
|
function stickyAgentOf(runs) {
|
|
37870
|
-
const newest = runs
|
|
38612
|
+
const newest = runs.find(addressed);
|
|
37871
38613
|
if (newest === void 0 || !continuable(newest)) return void 0;
|
|
37872
38614
|
return newest.agent;
|
|
37873
38615
|
}
|
|
37874
38616
|
function threadRouteOf(runs, agent) {
|
|
37875
|
-
const newest = runs
|
|
38617
|
+
const newest = runs.find(addressed);
|
|
37876
38618
|
if (newest === void 0 || !continuable(newest) || newest.agent !== agent || newest.route === void 0)
|
|
37877
38619
|
return void 0;
|
|
37878
38620
|
const { preset, reason: reason2, model } = newest.route;
|
|
@@ -37896,11 +38638,12 @@ function threadPrOf(runs) {
|
|
|
37896
38638
|
if (run2 === void 0) return void 0;
|
|
37897
38639
|
return { repo: run2.repo, number: run2.pr.number, at: run2.finishedAt ?? run2.startedAt };
|
|
37898
38640
|
}
|
|
37899
|
-
var THREAD_READ_LIMIT;
|
|
38641
|
+
var THREAD_READ_LIMIT, addressed;
|
|
37900
38642
|
var init_thread = __esm({
|
|
37901
38643
|
"../../src/core/dispatch/thread.ts"() {
|
|
37902
38644
|
"use strict";
|
|
37903
38645
|
THREAD_READ_LIMIT = 8;
|
|
38646
|
+
addressed = (run2) => run2.parentInstanceId === void 0;
|
|
37904
38647
|
}
|
|
37905
38648
|
});
|
|
37906
38649
|
|
|
@@ -39988,391 +40731,6 @@ var init_piHarness = __esm({
|
|
|
39988
40731
|
}
|
|
39989
40732
|
});
|
|
39990
40733
|
|
|
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
40734
|
// ../../src/core/runLedger/takeover.ts
|
|
40377
40735
|
var LedgerTakeover;
|
|
40378
40736
|
var init_takeover = __esm({
|
|
@@ -42285,6 +42643,45 @@ var init_dedupe = __esm({
|
|
|
42285
42643
|
});
|
|
42286
42644
|
|
|
42287
42645
|
// ../../src/channels/slack/lookups.ts
|
|
42646
|
+
function resolvePersonByEmail(client, email, opts = {}) {
|
|
42647
|
+
const key = email.trim().toLowerCase();
|
|
42648
|
+
if (!key.includes("@") || typeof client.users.lookupByEmail !== "function") return Promise.resolve(void 0);
|
|
42649
|
+
const now = opts.now ?? Date.now;
|
|
42650
|
+
const cached = personByEmailCache.get(key);
|
|
42651
|
+
if (cached && cached.expiresAt > now()) return Promise.resolve(cached.person);
|
|
42652
|
+
const inFlight = personByEmailInFlight.get(key);
|
|
42653
|
+
if (inFlight) return inFlight;
|
|
42654
|
+
const lookup2 = (async () => {
|
|
42655
|
+
let timer;
|
|
42656
|
+
try {
|
|
42657
|
+
const call2 = client.users.lookupByEmail({ email: key }).then(
|
|
42658
|
+
(r) => r.user,
|
|
42659
|
+
() => void 0
|
|
42660
|
+
);
|
|
42661
|
+
const timedOut = /* @__PURE__ */ Symbol("person lookup timed out");
|
|
42662
|
+
const answer = await Promise.race([
|
|
42663
|
+
call2,
|
|
42664
|
+
new Promise((resolve12) => {
|
|
42665
|
+
timer = setTimeout(() => resolve12(timedOut), opts.timeoutMs ?? PERSON_LINK_TIMEOUT_MS);
|
|
42666
|
+
})
|
|
42667
|
+
]);
|
|
42668
|
+
if (answer === timedOut) return void 0;
|
|
42669
|
+
const u = answer;
|
|
42670
|
+
const person = u?.id && !u.deleted && !u.is_bot && !u.is_app_user ? { id: `slack:${u.id}`, ...displayNameOf2(u) ? { name: displayNameOf2(u) } : {} } : void 0;
|
|
42671
|
+
personByEmailCache.set(key, { person, expiresAt: now() + PERSON_LINK_TTL_MS });
|
|
42672
|
+
if (personByEmailCache.size > NAME_CACHE_MAX) {
|
|
42673
|
+
const oldest = personByEmailCache.keys().next().value;
|
|
42674
|
+
if (oldest !== void 0) personByEmailCache.delete(oldest);
|
|
42675
|
+
}
|
|
42676
|
+
return person;
|
|
42677
|
+
} finally {
|
|
42678
|
+
if (timer) clearTimeout(timer);
|
|
42679
|
+
personByEmailInFlight.delete(key);
|
|
42680
|
+
}
|
|
42681
|
+
})();
|
|
42682
|
+
personByEmailInFlight.set(key, lookup2);
|
|
42683
|
+
return lookup2;
|
|
42684
|
+
}
|
|
42288
42685
|
function cachePut(cache2, key, value) {
|
|
42289
42686
|
cache2.set(key, value);
|
|
42290
42687
|
if (cache2.size > NAME_CACHE_MAX) {
|
|
@@ -42338,10 +42735,15 @@ async function resolveTeamUrl(client) {
|
|
|
42338
42735
|
}
|
|
42339
42736
|
return teamUrl;
|
|
42340
42737
|
}
|
|
42341
|
-
var NAME_CACHE_MAX, channelNameCache, userNameCache, teamUrl;
|
|
42738
|
+
var PERSON_LINK_TTL_MS, PERSON_LINK_TIMEOUT_MS, personByEmailCache, personByEmailInFlight, displayNameOf2, NAME_CACHE_MAX, channelNameCache, userNameCache, teamUrl;
|
|
42342
42739
|
var init_lookups = __esm({
|
|
42343
42740
|
"../../src/channels/slack/lookups.ts"() {
|
|
42344
42741
|
"use strict";
|
|
42742
|
+
PERSON_LINK_TTL_MS = 10 * 6e4;
|
|
42743
|
+
PERSON_LINK_TIMEOUT_MS = 1500;
|
|
42744
|
+
personByEmailCache = /* @__PURE__ */ new Map();
|
|
42745
|
+
personByEmailInFlight = /* @__PURE__ */ new Map();
|
|
42746
|
+
displayNameOf2 = (u) => u?.profile?.display_name || u?.profile?.real_name || u?.real_name || u?.name || void 0;
|
|
42345
42747
|
NAME_CACHE_MAX = 1e3;
|
|
42346
42748
|
channelNameCache = /* @__PURE__ */ new Map();
|
|
42347
42749
|
userNameCache = /* @__PURE__ */ new Map();
|
|
@@ -44000,8 +44402,8 @@ function parseIndexCursor(params) {
|
|
|
44000
44402
|
if (!params.has("before") || !Number.isFinite(before) || before <= 0 || beforeId === "") return void 0;
|
|
44001
44403
|
return { before, beforeId };
|
|
44002
44404
|
}
|
|
44003
|
-
function olderRunsHref(cursor) {
|
|
44004
|
-
return `/runs?all=1&before=${cursor.finishedAt}&beforeId=${encodeURIComponent(cursor.id)}`;
|
|
44405
|
+
function olderRunsHref(cursor, opts = {}) {
|
|
44406
|
+
return `/runs?all=1${opts.mine ? "&mine=1" : ""}&before=${cursor.finishedAt}&beforeId=${encodeURIComponent(cursor.id)}`;
|
|
44005
44407
|
}
|
|
44006
44408
|
function readDecision(actor, view) {
|
|
44007
44409
|
const admitted = authorize(actor, "runs:read", RUNS_GET);
|
|
@@ -44099,7 +44501,7 @@ function createLiveViewHandler(deps) {
|
|
|
44099
44501
|
})
|
|
44100
44502
|
);
|
|
44101
44503
|
};
|
|
44102
|
-
const mergedRows = async (live, visibleTo, cursor) => {
|
|
44504
|
+
const mergedRows = async (live, visibleTo, cursor, view = {}) => {
|
|
44103
44505
|
const tokens = new Map(live.map((s) => [s.id, s.token]));
|
|
44104
44506
|
const { runs, nextBefore, storeUnavailable } = await service.listRuns({
|
|
44105
44507
|
status: "all",
|
|
@@ -44111,7 +44513,7 @@ function createLiveViewHandler(deps) {
|
|
|
44111
44513
|
return {
|
|
44112
44514
|
rows,
|
|
44113
44515
|
...storeUnavailable ? { storeUnavailable: true } : {},
|
|
44114
|
-
...nextBefore ? { olderHref: olderRunsHref(nextBefore) } : {},
|
|
44516
|
+
...nextBefore ? { olderHref: olderRunsHref(nextBefore, view) } : {},
|
|
44115
44517
|
...cursor ? { olderThan: cursor.before } : {}
|
|
44116
44518
|
};
|
|
44117
44519
|
};
|
|
@@ -44128,7 +44530,9 @@ function createLiveViewHandler(deps) {
|
|
|
44128
44530
|
}
|
|
44129
44531
|
if (route3.kind === "index") {
|
|
44130
44532
|
const all = url.searchParams.get("all") === "1";
|
|
44131
|
-
const
|
|
44533
|
+
const mine = url.searchParams.get("mine") === "1";
|
|
44534
|
+
const readable2 = readableRuns(ctx.actor);
|
|
44535
|
+
const visibleTo = mine ? allOf([readable2, ownedBy(ctx.actor)]) : readable2;
|
|
44132
44536
|
if (url.searchParams.get("stream") === "1") {
|
|
44133
44537
|
serveIndexEvents(
|
|
44134
44538
|
visibleIndexFeed((onEvent) => index.subscribeIndex(onEvent), visibleTo),
|
|
@@ -44144,6 +44548,8 @@ function createLiveViewHandler(deps) {
|
|
|
44144
44548
|
const seed = {
|
|
44145
44549
|
page: "runs",
|
|
44146
44550
|
all,
|
|
44551
|
+
mine,
|
|
44552
|
+
...ctx.actor.asUser ? { asUser: ctx.actor.asUser } : {},
|
|
44147
44553
|
retentionDays: deps.retention ? deps.retention.retentionDays : null,
|
|
44148
44554
|
now: now(),
|
|
44149
44555
|
rows: [...page2.rows],
|
|
@@ -44161,7 +44567,7 @@ function createLiveViewHandler(deps) {
|
|
|
44161
44567
|
);
|
|
44162
44568
|
return true;
|
|
44163
44569
|
}
|
|
44164
|
-
run2(res, async () => render3(await mergedRows(live, visibleTo, parseIndexCursor(url.searchParams))));
|
|
44570
|
+
run2(res, async () => render3(await mergedRows(live, visibleTo, parseIndexCursor(url.searchParams), { mine })));
|
|
44165
44571
|
return true;
|
|
44166
44572
|
}
|
|
44167
44573
|
if (route3.kind === "scheduled") {
|
|
@@ -44976,9 +45382,7 @@ function createSettingsViewHandler(deps, shell) {
|
|
|
44976
45382
|
plain4(res, 405, "method not allowed", { allow: "GET" });
|
|
44977
45383
|
return true;
|
|
44978
45384
|
}
|
|
44979
|
-
const caller = deps.callerFor(ctx.identity);
|
|
44980
45385
|
const tab = route3.tab === "home" ? homeTab(deps.capabilities) : route3.tab;
|
|
44981
|
-
const base = { page: "settings", tab, viewer: caller.id, vocabulary: deps.vocabulary };
|
|
44982
45386
|
const failed = (err2) => {
|
|
44983
45387
|
const reason2 = (err2 instanceof Error ? err2.message : String(err2)).slice(0, UPSTREAM_REASON_MAX4);
|
|
44984
45388
|
if (res.headersSent) {
|
|
@@ -44993,20 +45397,27 @@ function createSettingsViewHandler(deps, shell) {
|
|
|
44993
45397
|
res.end(shell("Settings", seed));
|
|
44994
45398
|
};
|
|
44995
45399
|
const channel = "channel" in route3 ? route3.channel : void 0;
|
|
44996
|
-
|
|
44997
|
-
|
|
44998
|
-
|
|
44999
|
-
|
|
45400
|
+
deps.callerFor(ctx.identity).then(async (caller) => {
|
|
45401
|
+
const base = {
|
|
45402
|
+
page: "settings",
|
|
45403
|
+
tab,
|
|
45404
|
+
viewer: caller.id,
|
|
45405
|
+
...caller.actor.asUser ? { asUser: caller.actor.asUser } : {},
|
|
45406
|
+
vocabulary: deps.vocabulary
|
|
45407
|
+
};
|
|
45408
|
+
if (tab === "installation") {
|
|
45409
|
+
if (!authorize(caller.actor, "config:read", { type: "command", id: "config.show" }).allow) {
|
|
45410
|
+
plain4(res, 403, "forbidden");
|
|
45411
|
+
return;
|
|
45412
|
+
}
|
|
45413
|
+
render3({ ...base, installation: deps.installation() });
|
|
45414
|
+
return;
|
|
45000
45415
|
}
|
|
45001
|
-
|
|
45002
|
-
|
|
45003
|
-
}
|
|
45004
|
-
const build = tab === "mcps" ? mcpsSeed(caller, channel) : channelsSeed(caller, channel);
|
|
45005
|
-
build.then(
|
|
45006
|
-
(part) => render3(
|
|
45416
|
+
const part = tab === "mcps" ? await mcpsSeed(caller, channel) : await channelsSeed(caller, channel);
|
|
45417
|
+
render3(
|
|
45007
45418
|
tab === "mcps" ? { ...base, mcps: part } : { ...base, channels: part }
|
|
45008
|
-
)
|
|
45009
|
-
).catch(failed);
|
|
45419
|
+
);
|
|
45420
|
+
}).catch(failed);
|
|
45010
45421
|
return true;
|
|
45011
45422
|
};
|
|
45012
45423
|
}
|
|
@@ -47459,7 +47870,18 @@ async function merge(body, deps, subject2) {
|
|
|
47459
47870
|
return json(502, { ok: false, error: "github_unavailable", message: `${where} could not be read`, at });
|
|
47460
47871
|
if (isReleasePullRequest(facts2))
|
|
47461
47872
|
return refused6(`${where} is the release pull request \u2014 always a person's merge, never the runner's`);
|
|
47462
|
-
if (facts2.state !== "open")
|
|
47873
|
+
if (facts2.state !== "open") {
|
|
47874
|
+
if (facts2.mergedAt !== void 0 && facts2.mergeCommitSha !== void 0)
|
|
47875
|
+
return json(200, {
|
|
47876
|
+
ok: true,
|
|
47877
|
+
outcome: "merged",
|
|
47878
|
+
by: "other",
|
|
47879
|
+
sha: facts2.mergeCommitSha,
|
|
47880
|
+
mergedAt: facts2.mergedAt,
|
|
47881
|
+
at
|
|
47882
|
+
});
|
|
47883
|
+
return refused6(`${where} is ${facts2.state}`);
|
|
47884
|
+
}
|
|
47463
47885
|
if (facts2.headRef !== row2.branch)
|
|
47464
47886
|
return refused6(`${where} heads \`${facts2.headRef ?? "?"}\`, not the unit's branch \`${row2.branch}\``);
|
|
47465
47887
|
if (facts2.headSha === void 0 || !sameCommit(facts2.headSha, headSha))
|
|
@@ -47857,8 +48279,19 @@ function accessActor(identity2, grantsFor) {
|
|
|
47857
48279
|
function serviceTokenAllowed(pathname, identity2) {
|
|
47858
48280
|
return !isServiceToken(identity2) || isCommandPath(pathname);
|
|
47859
48281
|
}
|
|
47860
|
-
function
|
|
47861
|
-
|
|
48282
|
+
async function resolveAccessActor(identity2, opts) {
|
|
48283
|
+
const actor = accessActor(identity2, opts.grantsFor);
|
|
48284
|
+
if (isServiceToken(identity2) || !identity2.email || !opts.personByEmail) return actor;
|
|
48285
|
+
const person = await opts.personByEmail(identity2.email).catch(() => void 0);
|
|
48286
|
+
if (!person || !person.id.startsWith("slack:")) return actor;
|
|
48287
|
+
return {
|
|
48288
|
+
...actor,
|
|
48289
|
+
self: [actor.id, person.id],
|
|
48290
|
+
asUser: { id: person.id, ...person.name ? { name: person.name } : {} }
|
|
48291
|
+
};
|
|
48292
|
+
}
|
|
48293
|
+
async function callerFor(identity2, opts) {
|
|
48294
|
+
return { kind: "access", id: callerIdFor(identity2), actor: await resolveAccessActor(identity2, opts) };
|
|
47862
48295
|
}
|
|
47863
48296
|
function hostOf2(url) {
|
|
47864
48297
|
if (!url) return void 0;
|
|
@@ -47925,7 +48358,7 @@ function createCommandHttpHandler(commands, opts) {
|
|
|
47925
48358
|
refuse3(res, 403, "forbidden_origin", "cross-origin writes are refused");
|
|
47926
48359
|
return;
|
|
47927
48360
|
}
|
|
47928
|
-
const caller = callerFor(identity2, opts);
|
|
48361
|
+
const caller = await callerFor(identity2, opts);
|
|
47929
48362
|
if (CommandRegistry.refuses(cmd, caller)) {
|
|
47930
48363
|
refuse3(res, ERROR_STATUS.unauthorized, "unauthorized", `${caller.id} is not allowed to run ${cmd.id}`);
|
|
47931
48364
|
return;
|
|
@@ -48013,6 +48446,8 @@ async function runBot() {
|
|
|
48013
48446
|
const completions = new PiAiProviders(config.config.providers);
|
|
48014
48447
|
const skills = new BundledSkillStore(DEFAULT_SKILLS_DIR);
|
|
48015
48448
|
let slackEmailLookup;
|
|
48449
|
+
let slackPersonByEmail;
|
|
48450
|
+
const personByEmail = (email) => slackPersonByEmail ? slackPersonByEmail(email) : Promise.resolve(void 0);
|
|
48016
48451
|
const mcpWiring = buildMcp(config, processSecrets, {
|
|
48017
48452
|
publicBaseUrl: process.env.PUBLIC_BASE_URL,
|
|
48018
48453
|
resolveEmail: (userId) => slackEmailLookup ? slackEmailLookup(userId) : Promise.resolve(void 0),
|
|
@@ -48211,6 +48646,7 @@ async function runBot() {
|
|
|
48211
48646
|
const conversationReader = new SlackConversationReader(app.client);
|
|
48212
48647
|
deps.conversationReaders = [conversationReader];
|
|
48213
48648
|
slackEmailLookup = (userId) => userId.startsWith("slack:") ? resolveUserEmail(app.client, userId.slice("slack:".length)) : Promise.resolve(void 0);
|
|
48649
|
+
slackPersonByEmail = (email) => resolvePersonByEmail(app.client, email);
|
|
48214
48650
|
const pendingHistoryWrites = () => runHistoryWriter.pending();
|
|
48215
48651
|
const inFlight = () => activeRunCount() + pendingReflectionCount() + pendingHistoryWrites();
|
|
48216
48652
|
let draining = false;
|
|
@@ -48287,7 +48723,7 @@ async function runBot() {
|
|
|
48287
48723
|
const settingsView = createSettingsViewHandler(
|
|
48288
48724
|
{
|
|
48289
48725
|
commands,
|
|
48290
|
-
callerFor: (identity2) => callerFor(identity2, { grantsFor: (id) => config.grantsFor(id) }),
|
|
48726
|
+
callerFor: (identity2) => callerFor(identity2, { grantsFor: (id) => config.grantsFor(id), personByEmail }),
|
|
48291
48727
|
installation: () => installationSettings(config.config, capabilities),
|
|
48292
48728
|
vocabulary: {
|
|
48293
48729
|
agents: Object.keys(AGENTS),
|
|
@@ -48338,6 +48774,7 @@ async function runBot() {
|
|
|
48338
48774
|
const accessState = `dashboard auth: ${dashboardAuth.describe()}`;
|
|
48339
48775
|
const commandHttp = createCommandHttpHandler(commands, {
|
|
48340
48776
|
grantsFor: (id) => config.grantsFor(id),
|
|
48777
|
+
personByEmail,
|
|
48341
48778
|
publicBaseUrl
|
|
48342
48779
|
});
|
|
48343
48780
|
const commandHttpState = `GET|POST /api/<group>.<verb> (${commands.list().length} commands)`;
|
|
@@ -48401,7 +48838,7 @@ async function runBot() {
|
|
|
48401
48838
|
}
|
|
48402
48839
|
if (webAssets.serve(req, res)) return;
|
|
48403
48840
|
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) => {
|
|
48841
|
+
dashboardAuth.verify(req).then(async (gate) => {
|
|
48405
48842
|
if (!gate.ok) {
|
|
48406
48843
|
res.writeHead(gate.status, { "content-type": "text/plain; charset=utf-8" });
|
|
48407
48844
|
res.end(gate.body);
|
|
@@ -48417,7 +48854,10 @@ async function runBot() {
|
|
|
48417
48854
|
return;
|
|
48418
48855
|
}
|
|
48419
48856
|
if (isCommandPath(path)) return commandHttp(req, res, gate.identity);
|
|
48420
|
-
const actor =
|
|
48857
|
+
const actor = await resolveAccessActor(gate.identity, {
|
|
48858
|
+
grantsFor: (id) => config.grantsFor(id),
|
|
48859
|
+
personByEmail
|
|
48860
|
+
});
|
|
48421
48861
|
if (liveView2(req, res, { actor })) return;
|
|
48422
48862
|
if (mcpConnectView(req, res, gate.identity)) return;
|
|
48423
48863
|
if (residentsView(req, res, { actor })) return;
|