@coreplane/switchboard 1.237.0 → 1.239.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 +40 -12
- package/dist/assets/deploy/cloudflare-sandbox/Dockerfile +8 -1
- package/dist/assets/deploy/cloudflare-sandbox/worker.ts +337 -44
- package/dist/assets/deploy/cloudflare-sandbox/wrangler.template.jsonc +19 -0
- package/dist/assets/deploy/secrets.manifest.json +3 -3
- 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/agents/registry.ts +82 -0
- package/dist/assets/src/execution/seedPlan.ts +267 -0
- package/dist/assets/src/execution/snapshotRetention.ts +62 -0
- package/dist/assets/src/mcp/registry.ts +9 -1
- package/dist/assets/web/dist/.vite/manifest.json +27 -27
- package/dist/assets/web/dist/assets/{ResidentDetailPage-Dc23v_8v.js → ResidentDetailPage-CrfCd8fx.js} +1 -1
- package/dist/assets/web/dist/assets/{ResidentsIndexPage-TR5D1TRR.js → ResidentsIndexPage-BdW0k2h3.js} +1 -1
- package/dist/assets/web/dist/assets/{RunFoldRow-mkUj7WEN.js → RunFoldRow-CbTd9SiS.js} +1 -1
- package/dist/assets/web/dist/assets/{RunRoutePage-CQI17__g.js → RunRoutePage-br0H-8Pz.js} +3 -3
- package/dist/assets/web/dist/assets/{RunsIndexPage-yIP4PlKP.js → RunsIndexPage-Bnzb15zH.js} +1 -1
- package/dist/assets/web/dist/assets/{ScheduledPage-CdCVyNbh.js → ScheduledPage-NLhjzHAz.js} +1 -1
- package/dist/assets/web/dist/assets/SettingsPage-D-i0uVw-.js +1 -0
- package/dist/assets/web/dist/assets/{StatusDot-DmNHX6am.js → StatusDot-DkXALvKf.js} +1 -1
- package/dist/assets/web/dist/assets/{Tooltip-qVnJTgSt.js → Tooltip-DtLIyi5P.js} +1 -1
- package/dist/assets/web/dist/assets/{UnitRoutePage-BcTeH7m2.js → UnitRoutePage-DGXukEAH.js} +1 -1
- package/dist/assets/web/dist/assets/{dist-TAGawIfM.js → dist-DtA7JiH5.js} +1 -1
- package/dist/assets/web/dist/assets/main-CFHF2RvQ.css +1 -0
- package/dist/assets/web/dist/assets/{main-mUd_TJjH.js → main-Dht0in4v.js} +2 -2
- package/dist/cli.js +452 -51
- package/package.json +1 -1
- package/dist/assets/web/dist/assets/SettingsPage-DuA8JpLU.js +0 -1
- package/dist/assets/web/dist/assets/main-csihqEj5.css +0 -1
package/dist/cli.js
CHANGED
|
@@ -1871,7 +1871,7 @@ function getAgent(name) {
|
|
|
1871
1871
|
}
|
|
1872
1872
|
return a;
|
|
1873
1873
|
}
|
|
1874
|
-
var MACHINE_CLASSES, IDENTITIES, RUNAWAY_TURNS_PER_MINUTE, PR_DESCRIPTION_TEMPLATE, NEVER_MERGE, CONTRACT_HEADINGS_LIST, UNIT_CONTRACT, UNIT_HANDOFF, IMAGE_TOOLCHAIN, SANDBOX_TOOLCHAIN, RESIDENT_TOOLCHAIN, SHOW_FILES, FENCED_CONTENT_RULE, NOTEPAD, CODING_SYSTEM, CODING_SYSTEM_RESIDENT, REVIEW_VERDICT_INSTRUCTION, REVIEW_SPEC_CHECK, REVIEW_UNIT_CONTRACT, REVIEW_WHOLE_CHANGE, REVIEW_SYSTEM, REVIEW_SYSTEM_RESIDENT, RESEARCH_SYSTEM, GENERAL_SYSTEM, EXPLORE_SYSTEM, COMPOUND_PRESET, WORK_PRESETS, AGENTS;
|
|
1874
|
+
var MACHINE_CLASSES, IDENTITIES, RUNAWAY_TURNS_PER_MINUTE, PR_DESCRIPTION_TEMPLATE, NEVER_MERGE, CONTRACT_HEADINGS_LIST, UNIT_CONTRACT, UNIT_HANDOFF, IMAGE_TOOLCHAIN, SANDBOX_TOOLCHAIN, RESIDENT_TOOLCHAIN, SHOW_FILES, FENCED_CONTENT_RULE, NOTEPAD, CODING_SYSTEM, CODING_SYSTEM_RESIDENT, CODING_SYSTEM_SEEDED, REVIEW_VERDICT_INSTRUCTION, REVIEW_SPEC_CHECK, REVIEW_UNIT_CONTRACT, REVIEW_WHOLE_CHANGE, REVIEW_SYSTEM, REVIEW_SYSTEM_RESIDENT, REVIEW_SYSTEM_SEEDED, RESEARCH_SYSTEM, GENERAL_SYSTEM, EXPLORE_SYSTEM, COMPOUND_PRESET, WORK_PRESETS, AGENTS;
|
|
1875
1875
|
var init_registry = __esm({
|
|
1876
1876
|
"../../src/agents/registry.ts"() {
|
|
1877
1877
|
"use strict";
|
|
@@ -1974,6 +1974,38 @@ ${NOTEPAD}
|
|
|
1974
1974
|
|
|
1975
1975
|
Maintain the user-facing status card with the update_status tool: right after you decide your plan, post it as a checklist (\u25CB pending items), then update it whenever an item starts (\u2731) or finishes (\u2713). Items are short outcomes ("Implement the fix", "Run the test suite"), never commands. Mark an item \u2713 only after it has actually happened \u2014 never pre-mark reporting/posting steps. This is the only progress the user sees while you work.
|
|
1976
1976
|
|
|
1977
|
+
Report outcomes faithfully: if tests fail or a step was skipped, say so plainly.
|
|
1978
|
+
${FENCED_CONTENT_RULE}
|
|
1979
|
+
Your final message is posted to Slack \u2014 keep it readable, lead with the outcome.`;
|
|
1980
|
+
CODING_SYSTEM_SEEDED = `You are Switchboard's coding agent, operating from a Slack request.
|
|
1981
|
+
|
|
1982
|
+
You work inside a dedicated sandbox with bash, read_file, and write_file tools. ${SANDBOX_TOOLCHAIN}
|
|
1983
|
+
|
|
1984
|
+
THE REPOSITORY IS ALREADY CLONED at \`/workspace/checkout\` \u2014 seeded from the resident's snapshot: a ready git checkout of the target repository on this thread's branch, dependencies installed. Work there \u2014 do not clone it again, do not install dependencies, do not discover or survey other repos. Orient with a few BATCHED commands (e.g. \`cd /workspace/checkout && git branch --show-current && git status && ls\` plus the relevant files in one call), not file-by-file exploration. \`gh\` and git are both authenticated on this host.
|
|
1985
|
+
|
|
1986
|
+
Workflow for shipping a change:
|
|
1987
|
+
1. Create a branch with a descriptive name off the current branch.
|
|
1988
|
+
2. Implement the change. Match the surrounding code's style and conventions.
|
|
1989
|
+
3. Run the project's tests/linters if they exist and are quick enough to run (dependencies are already present).
|
|
1990
|
+
4. Commit with a clear message and push the branch with \`git push -u origin <branch>\`.
|
|
1991
|
+
5. Call the \`diff_digest\` tool to get a distilled summary of your change \u2014 per-file churn, totals, and risky-file flags. It is a distilled summary, not the raw diff: use it to shape the description you submit next \u2014 which files the Tour must walk, what belongs in risks.
|
|
1992
|
+
6. Call the submit_pr_description tool with the typed description object (content contract below) \u2014 every time. Switchboard renders the PR body from your object at the pushed head and opens (or updates) the pull request itself: do NOT open a PR yourself, with \`gh\` or any API call.
|
|
1993
|
+
7. Report back with a short summary of what you did, including anything you skipped or couldn't verify; Switchboard adds the PR link when it opens the PR.
|
|
1994
|
+
|
|
1995
|
+
${NEVER_MERGE}
|
|
1996
|
+
|
|
1997
|
+
${UNIT_CONTRACT}
|
|
1998
|
+
|
|
1999
|
+
${UNIT_HANDOFF}
|
|
2000
|
+
|
|
2001
|
+
${PR_DESCRIPTION_TEMPLATE}
|
|
2002
|
+
|
|
2003
|
+
${SHOW_FILES}
|
|
2004
|
+
|
|
2005
|
+
${NOTEPAD}
|
|
2006
|
+
|
|
2007
|
+
Maintain the user-facing status card with the update_status tool: right after you decide your plan, post it as a checklist (\u25CB pending items), then update it whenever an item starts (\u2731) or finishes (\u2713). Items are short outcomes ("Implement the fix", "Run the test suite"), never commands. Mark an item \u2713 only after it has actually happened \u2014 never pre-mark reporting/posting steps. This is the only progress the user sees while you work.
|
|
2008
|
+
|
|
1977
2009
|
Report outcomes faithfully: if tests fail or a step was skipped, say so plainly.
|
|
1978
2010
|
${FENCED_CONTENT_RULE}
|
|
1979
2011
|
Your final message is posted to Slack \u2014 keep it readable, lead with the outcome.`;
|
|
@@ -2038,6 +2070,35 @@ ${NOTEPAD}
|
|
|
2038
2070
|
|
|
2039
2071
|
Maintain the user-facing status card with the update_status tool: post your plan as a checklist (\u25CB pending), update as items start (\u2731) and finish (\u2713 \u2014 only after they actually happened; never pre-mark reporting steps). Items are short outcomes, never commands.
|
|
2040
2072
|
|
|
2073
|
+
${FENCED_CONTENT_RULE}
|
|
2074
|
+
Your final message is posted to Slack. Lead with a one-line verdict, then the findings.`;
|
|
2075
|
+
REVIEW_SYSTEM_SEEDED = `You are Switchboard's code review agent, operating from a Slack request.
|
|
2076
|
+
|
|
2077
|
+
You have bash and read_file tools in a dedicated sandbox. ${SANDBOX_TOOLCHAIN} Do not modify code, commit, or push \u2014 you are read-only by convention. Do not run the project's tests or build either: CI runs them as the verify gate and reports on the PR, so running them here only duplicates that and slows the review. Your job is to read the code. THE REPOSITORY IS ALREADY CLONED at \`/workspace/checkout\` \u2014 seeded from the resident's snapshot and checked out at the PR head named in the REVIEW TARGET block below, dependencies installed: do not clone it again, do not install anything, do not survey other repos. \`gh\` is available for the pull request's metadata.
|
|
2078
|
+
|
|
2079
|
+
Strategy \u2014 GATHER ONCE, THEN ANALYZE ONCE. Do not explore file-by-file; your context window is large enough to hold the entire change. Speed matters: a review should take minutes, not an hour.
|
|
2080
|
+
|
|
2081
|
+
1. GATHER, in 2-4 batched tool calls total:
|
|
2082
|
+
- from \`/workspace/checkout\`: \`gh pr view <ref> --json title,body,url,baseRefName\` and \`git diff origin/<base>...HEAD\` (the complete diff; \`origin/<base>\` \u2014 the PR's base branch, named in the REVIEW TARGET block \u2014 is already present) in one command
|
|
2083
|
+
- call the \`diff_digest\` tool to orient: it gives per-file churn, totals, and risky-file flags (migrations/schema, auth/permission, whole-file deletions, lockfiles, very large files) so you know where to look hardest before you read a line
|
|
2084
|
+
${REVIEW_WHOLE_CHANGE}
|
|
2085
|
+
- in ONE command, print the full current contents of every changed source file, e.g.: \`git diff --name-only origin/<base>...HEAD | grep -v -E "lock|generated|snap" | while read f; do echo "=== $f ==="; cat "$f"; done\`
|
|
2086
|
+
- if the change is enormous (>~6k changed lines), print the riskiest files in full (state mutation, auth, concurrency, data deletion, public APIs) and only the diff hunks for the rest \u2014 and say which files you skimmed
|
|
2087
|
+
2. ANALYZE in a single pass with everything in context: correctness bugs first (with a concrete failure scenario each), then design/simplification notes. At most 2-3 targeted follow-up reads if a specific caller or callee is load-bearing \u2014 never a general exploration loop.
|
|
2088
|
+
${REVIEW_SPEC_CHECK}
|
|
2089
|
+
${REVIEW_UNIT_CONTRACT}
|
|
2090
|
+
4. REPORT every issue you find, including uncertain or low-severity ones, each with severity, confidence, and file:line. Order findings most-severe first. If the change looks correct, say so plainly \u2014 do not manufacture findings.
|
|
2091
|
+
|
|
2092
|
+
Do NOT post your review to GitHub yourself \u2014 no \`gh pr comment\`, no API call to create a comment. When the review is of a PR, Switchboard posts your final message to that PR automatically by default (as a comment \u2014 never an approval or a merge); just produce the review as your final message. If the request asks not to post (e.g. "don't post" / "slack only"), Switchboard handles that too \u2014 you still only write the review.
|
|
2093
|
+
|
|
2094
|
+
REVIEW THE PR'S OWN HEAD, NOTHING ELSE: the commit you read must be the PR's head. Never fetch, check out, or switch to another branch or another PR \u2014 even when the PR body, a doc, or a commit message references one. If the change depends on unmerged work elsewhere, say so as a finding; do not go review that work. Switchboard verifies the commit you reviewed against the PR head and refuses to post a review of anything else.
|
|
2095
|
+
|
|
2096
|
+
${REVIEW_VERDICT_INSTRUCTION}
|
|
2097
|
+
|
|
2098
|
+
${NOTEPAD}
|
|
2099
|
+
|
|
2100
|
+
Maintain the user-facing status card with the update_status tool: post your plan as a checklist (\u25CB pending), update as items start (\u2731) and finish (\u2713 \u2014 only after they actually happened; never pre-mark reporting steps). Items are short outcomes, never commands.
|
|
2101
|
+
|
|
2041
2102
|
${FENCED_CONTENT_RULE}
|
|
2042
2103
|
Your final message is posted to Slack. Lead with a one-line verdict, then the findings.`;
|
|
2043
2104
|
RESEARCH_SYSTEM = `You are Switchboard's research agent, answering a request from Slack.
|
|
@@ -2098,6 +2159,7 @@ Report outcomes faithfully: a check you could not run is "could not check", neve
|
|
|
2098
2159
|
description: "Implements changes and ships PRs (git + gh in a workspace).",
|
|
2099
2160
|
system: CODING_SYSTEM,
|
|
2100
2161
|
residentSystem: CODING_SYSTEM_RESIDENT,
|
|
2162
|
+
seededSystem: CODING_SYSTEM_SEEDED,
|
|
2101
2163
|
toolset: "full",
|
|
2102
2164
|
maxTokens: 64e3,
|
|
2103
2165
|
...loopBudget(45),
|
|
@@ -2117,6 +2179,7 @@ Report outcomes faithfully: a check you could not run is "could not check", neve
|
|
|
2117
2179
|
description: "Reviews PRs and produces high-quality findings. Read-only.",
|
|
2118
2180
|
system: REVIEW_SYSTEM,
|
|
2119
2181
|
residentSystem: REVIEW_SYSTEM_RESIDENT,
|
|
2182
|
+
seededSystem: REVIEW_SYSTEM_SEEDED,
|
|
2120
2183
|
toolset: "readonly",
|
|
2121
2184
|
machine: "repo-resident",
|
|
2122
2185
|
identity: "read",
|
|
@@ -3269,7 +3332,7 @@ function isOutcome(v) {
|
|
|
3269
3332
|
function isMcpServerEntry(v) {
|
|
3270
3333
|
if (!v || typeof v !== "object" || Array.isArray(v)) return false;
|
|
3271
3334
|
const e = v;
|
|
3272
|
-
return isStr(e.url, MCP_URL_MAX) && (e.agents === void 0 || Array.isArray(e.agents) && e.agents.length > 0 && e.agents.length <= MCP_AGENTS_MAX && e.agents.every((a) => isStr(a, 32))) && (e.auth === "none" || e.auth === "bearer" || e.auth === "oauth") && (e.tokenEnv === void 0 || isStr(e.tokenEnv, 128)) && (e.headersEnv === void 0 || isHeadersEnv(e.headersEnv)) && (e.addedBy === void 0 || isStr(e.addedBy, 260)) && (e.addedAt === void 0 || isNum(e.addedAt));
|
|
3335
|
+
return isStr(e.url, MCP_URL_MAX) && (e.agents === void 0 || Array.isArray(e.agents) && e.agents.length > 0 && e.agents.length <= MCP_AGENTS_MAX && e.agents.every((a) => isStr(a, 32))) && (e.auth === "none" || e.auth === "bearer" || e.auth === "oauth") && (e.tokenEnv === void 0 || isStr(e.tokenEnv, 128)) && (e.headersEnv === void 0 || isHeadersEnv(e.headersEnv)) && (e.addedBy === void 0 || isStr(e.addedBy, 260)) && (e.addedAt === void 0 || isNum(e.addedAt)) && (e.promotedFrom === void 0 || isStr(e.promotedFrom, 260));
|
|
3273
3336
|
}
|
|
3274
3337
|
function isSealedCredential(v) {
|
|
3275
3338
|
if (!v || typeof v !== "object") return false;
|
|
@@ -3295,7 +3358,8 @@ function serverView(scopeKey, name, entry, opts) {
|
|
|
3295
3358
|
state,
|
|
3296
3359
|
source: opts.source,
|
|
3297
3360
|
...entry.addedBy ? { addedBy: entry.addedBy } : {},
|
|
3298
|
-
...entry.addedAt !== void 0 ? { addedAt: entry.addedAt } : {}
|
|
3361
|
+
...entry.addedAt !== void 0 ? { addedAt: entry.addedAt } : {},
|
|
3362
|
+
...entry.promotedFrom ? { promotedFrom: entry.promotedFrom } : {}
|
|
3299
3363
|
};
|
|
3300
3364
|
}
|
|
3301
3365
|
function safeUrl(raw) {
|
|
@@ -4156,6 +4220,15 @@ var init_config2 = __esm({
|
|
|
4156
4220
|
* caller's right to read each channel is decided; `config show --channel`
|
|
4157
4221
|
* is the read that carries the values.
|
|
4158
4222
|
*/
|
|
4223
|
+
/** Every channel and every user with a tier of its own — a static block or a
|
|
4224
|
+
* runtime override — sorted: what `mcp list --all` walks (record 0042). */
|
|
4225
|
+
mcpTierIds() {
|
|
4226
|
+
const ids = (stat, runtime) => [.../* @__PURE__ */ new Set([...Object.keys(stat ?? {}), ...Object.keys(runtime)])].sort();
|
|
4227
|
+
return {
|
|
4228
|
+
channels: ids(this.config.channels, this.overrides.channels),
|
|
4229
|
+
users: ids(this.config.users, this.overrides.users)
|
|
4230
|
+
};
|
|
4231
|
+
}
|
|
4159
4232
|
channelsWithScope() {
|
|
4160
4233
|
const ids = /* @__PURE__ */ new Set([...Object.keys(this.config.channels ?? {}), ...Object.keys(this.overrides.channels)]);
|
|
4161
4234
|
const rows = [];
|
|
@@ -6373,7 +6446,8 @@ function templateView(profile, kind, published) {
|
|
|
6373
6446
|
urls: { publicBaseUrl: urls.publicBaseUrl, stateWorkerUrl: urls.stateWorkerUrl },
|
|
6374
6447
|
access: profile.access,
|
|
6375
6448
|
artifacts: profile.artifacts,
|
|
6376
|
-
bot: { script: profile.workers.bot.script }
|
|
6449
|
+
bot: { script: profile.workers.bot.script },
|
|
6450
|
+
resident: profile.workers.resident ? { script: profile.workers.resident.script } : void 0
|
|
6377
6451
|
};
|
|
6378
6452
|
}
|
|
6379
6453
|
function siteView(profile, facts2) {
|
|
@@ -9521,6 +9595,50 @@ ${parts}`);
|
|
|
9521
9595
|
}
|
|
9522
9596
|
});
|
|
9523
9597
|
|
|
9598
|
+
// ../../src/execution/seedPlan.ts
|
|
9599
|
+
function seedForThread(handle2, thread) {
|
|
9600
|
+
return {
|
|
9601
|
+
slug: thread.slug,
|
|
9602
|
+
checkoutBackupId: handle2.checkoutBackupId,
|
|
9603
|
+
...handle2.depsBackupId ? { depsBackupId: handle2.depsBackupId } : {},
|
|
9604
|
+
ref: handle2.ref,
|
|
9605
|
+
sha: handle2.sha,
|
|
9606
|
+
...thread.ref ? { fetchRef: thread.ref } : {},
|
|
9607
|
+
...thread.ref && thread.headSha ? { fetchSha: thread.headSha } : {}
|
|
9608
|
+
};
|
|
9609
|
+
}
|
|
9610
|
+
function seedRetryDecision(input) {
|
|
9611
|
+
const { answer, attempted, fresh } = input;
|
|
9612
|
+
if (answer.reason === "seed-missing" && !input.alreadyRetried) {
|
|
9613
|
+
if (fresh && fresh.checkoutBackupId !== attempted.checkoutBackupId) {
|
|
9614
|
+
return {
|
|
9615
|
+
action: "retry",
|
|
9616
|
+
seed: seedForThread(fresh, {
|
|
9617
|
+
slug: attempted.slug,
|
|
9618
|
+
...attempted.fetchRef ? { ref: attempted.fetchRef } : {},
|
|
9619
|
+
...attempted.fetchSha ? { headSha: attempted.fetchSha } : {}
|
|
9620
|
+
})
|
|
9621
|
+
};
|
|
9622
|
+
}
|
|
9623
|
+
return { action: "cold", why: `seed missing (${answer.detail}) and the resident published no newer handle` };
|
|
9624
|
+
}
|
|
9625
|
+
return { action: "cold", why: `${answer.reason.replace("seed-", "seed ")} (${answer.detail})` };
|
|
9626
|
+
}
|
|
9627
|
+
function seededSandboxNote(reason2, seeded) {
|
|
9628
|
+
return `${reason2} \u2014 seeded sandbox \xB7 from resident snapshot${seeded.cached ? " (already seeded)" : ""} \xB7 ${seeded.slug} \xB7 ${seeded.ref}@${seeded.sha.slice(0, 7)}`;
|
|
9629
|
+
}
|
|
9630
|
+
var SEED_CHECKOUT_DIR, SEED_RESTORE_MAX_MS, SEED_FIXUP_TIMEOUT_MS, SEED_BUDGET_MS;
|
|
9631
|
+
var init_seedPlan = __esm({
|
|
9632
|
+
"../../src/execution/seedPlan.ts"() {
|
|
9633
|
+
"use strict";
|
|
9634
|
+
init_shellQuote();
|
|
9635
|
+
SEED_CHECKOUT_DIR = "/workspace/checkout";
|
|
9636
|
+
SEED_RESTORE_MAX_MS = 8 * 6e4;
|
|
9637
|
+
SEED_FIXUP_TIMEOUT_MS = 3 * 6e4;
|
|
9638
|
+
SEED_BUDGET_MS = SEED_RESTORE_MAX_MS + SEED_FIXUP_TIMEOUT_MS + 6e4;
|
|
9639
|
+
}
|
|
9640
|
+
});
|
|
9641
|
+
|
|
9524
9642
|
// ../../src/core/trace/internalHosts.ts
|
|
9525
9643
|
function internalHostsOf(urls) {
|
|
9526
9644
|
const set = /* @__PURE__ */ new Set();
|
|
@@ -9660,6 +9778,7 @@ var init_cloudflareSandbox = __esm({
|
|
|
9660
9778
|
init_bashTimeout();
|
|
9661
9779
|
init_executor();
|
|
9662
9780
|
init_sandboxErrors();
|
|
9781
|
+
init_seedPlan();
|
|
9663
9782
|
init_tracedFetch();
|
|
9664
9783
|
CloudflareSandboxExecutor = class {
|
|
9665
9784
|
constructor(opts) {
|
|
@@ -9770,6 +9889,22 @@ var init_cloudflareSandbox = __esm({
|
|
|
9770
9889
|
${parts}`);
|
|
9771
9890
|
return truncate(parts || "(no output)");
|
|
9772
9891
|
}
|
|
9892
|
+
/** `POST /seed` (docs/reference/specs/execution.md item 25): the resident's
|
|
9893
|
+
* snapshot handle, restored into this thread's sandbox before the run's
|
|
9894
|
+
* first command. The Worker streams the answer under SEED_BUDGET_MS — a
|
|
9895
|
+
* restore takes minutes — and names every outcome in the body: `seeded`,
|
|
9896
|
+
* or a reason the caller decides on (`seed-missing` → re-read `/status`
|
|
9897
|
+
* once and retry; anything else → the run goes cold, with the note). The
|
|
9898
|
+
* env rides along as on every route: the fix-up's fetch authenticates with
|
|
9899
|
+
* it. A container still starting or a full fleet is waited out here like
|
|
9900
|
+
* any other route. */
|
|
9901
|
+
async seed(seed, opts) {
|
|
9902
|
+
const r = await this.call("/seed", { seed }, opts?.signal, SEED_BUDGET_MS, opts?.span);
|
|
9903
|
+
if (typeof r.seeded !== "boolean") {
|
|
9904
|
+
throw new ExecInfraError(`sandbox worker /seed answered without a verdict: ${JSON.stringify(r).slice(0, 200)}`);
|
|
9905
|
+
}
|
|
9906
|
+
return r;
|
|
9907
|
+
}
|
|
9773
9908
|
async readFile(path, opts) {
|
|
9774
9909
|
const r = await this.call("/read", { path }, void 0, void 0, opts?.span);
|
|
9775
9910
|
return truncate(String(r.content ?? ""));
|
|
@@ -10100,6 +10235,18 @@ async function parseResidentBody(res) {
|
|
|
10100
10235
|
}
|
|
10101
10236
|
return sanitizeResidentBody(data);
|
|
10102
10237
|
}
|
|
10238
|
+
function seedHandleOf(snapshot) {
|
|
10239
|
+
if (typeof snapshot !== "object" || snapshot === null) return void 0;
|
|
10240
|
+
const s = snapshot;
|
|
10241
|
+
if (typeof s.checkoutBackupId !== "string" || typeof s.ref !== "string" || typeof s.sha !== "string")
|
|
10242
|
+
return void 0;
|
|
10243
|
+
return {
|
|
10244
|
+
checkoutBackupId: s.checkoutBackupId,
|
|
10245
|
+
...typeof s.depsBackupId === "string" ? { depsBackupId: s.depsBackupId } : {},
|
|
10246
|
+
ref: s.ref,
|
|
10247
|
+
sha: s.sha
|
|
10248
|
+
};
|
|
10249
|
+
}
|
|
10103
10250
|
var DETACH_TIMEOUT_MS, ResidentNeedsRefError, ResidentReuseRefusedError, ResidentOperations, ResidentExecutor;
|
|
10104
10251
|
var init_resident = __esm({
|
|
10105
10252
|
"../../src/execution/resident.ts"() {
|
|
@@ -10239,7 +10386,13 @@ var init_resident = __esm({
|
|
|
10239
10386
|
if (!res.ok) {
|
|
10240
10387
|
return { kind: "unreachable", error: `probe HTTP ${res.status}: ${String(data.error ?? "")}`, transport: false };
|
|
10241
10388
|
}
|
|
10242
|
-
|
|
10389
|
+
const seed = seedHandleOf(data.snapshot);
|
|
10390
|
+
return {
|
|
10391
|
+
kind: "status",
|
|
10392
|
+
state: residentState(data.state),
|
|
10393
|
+
reason: String(data.reason ?? ""),
|
|
10394
|
+
...seed ? { seed } : {}
|
|
10395
|
+
};
|
|
10243
10396
|
}
|
|
10244
10397
|
/** POST one route; resource + threadKey ride in every body. Reads the FULL
|
|
10245
10398
|
* body as text before parsing: /exec streams heartbeat whitespace and then
|
|
@@ -10746,6 +10899,7 @@ async function makeExecutor(opts, ctx, span) {
|
|
|
10746
10899
|
return { executor: await makePerThreadExecutor(opts, perThreadCheckout(ctx)), backend: perThreadBackend(opts) };
|
|
10747
10900
|
}
|
|
10748
10901
|
let note;
|
|
10902
|
+
let reason2;
|
|
10749
10903
|
let failedAttach;
|
|
10750
10904
|
if (ctx.repo && opts.execution?.resident) {
|
|
10751
10905
|
const resident = opts.execution.resident;
|
|
@@ -10775,17 +10929,35 @@ async function makeExecutor(opts, ctx, span) {
|
|
|
10775
10929
|
} catch (err2) {
|
|
10776
10930
|
if (err2 instanceof ResidentNeedsRefError) throw err2;
|
|
10777
10931
|
failedAttach = residentTraceOf(err2);
|
|
10778
|
-
|
|
10779
|
-
`resident attach failed (${err2 instanceof Error ? err2.message : String(err2)}) \u2014 using fresh sandbox`
|
|
10780
|
-
);
|
|
10932
|
+
reason2 = oneLine(`resident attach failed (${err2 instanceof Error ? err2.message : String(err2)})`);
|
|
10781
10933
|
}
|
|
10782
10934
|
} else if (probe.kind === "unreachable") {
|
|
10783
|
-
|
|
10935
|
+
reason2 = oneLine(`resident unreachable (${probe.error})`);
|
|
10784
10936
|
} else if (probe.state !== "not-onboarded") {
|
|
10785
|
-
|
|
10937
|
+
reason2 = oneLine(`resident ${probe.state}${probe.reason ? ` (${probe.reason})` : ""}`);
|
|
10786
10938
|
} else {
|
|
10787
10939
|
note = `repo not onboarded as a resident \u2014 running in a cold per-thread sandbox; onboard it (\`repo onboard ${ctx.repo}\`) for a warm, deps-ready environment`;
|
|
10788
10940
|
}
|
|
10941
|
+
if (reason2 !== void 0) {
|
|
10942
|
+
const executor = await makePerThreadExecutor(opts, perThreadCheckout(ctx));
|
|
10943
|
+
const handle2 = probe.kind === "status" ? probe.seed : void 0;
|
|
10944
|
+
const outcome = handle2 && executor instanceof CloudflareSandboxExecutor ? await seedSandbox(executor, handle2, ctx, () => probeResident(resident, token2, resource, span), span) : void 0;
|
|
10945
|
+
if (outcome && "seeded" in outcome) {
|
|
10946
|
+
return {
|
|
10947
|
+
executor,
|
|
10948
|
+
note: seededSandboxNote(reason2, outcome.seeded),
|
|
10949
|
+
backend: perThreadBackend(opts),
|
|
10950
|
+
seeded: outcome.seeded,
|
|
10951
|
+
...failedAttach ? { trace: failedAttach.steps } : {}
|
|
10952
|
+
};
|
|
10953
|
+
}
|
|
10954
|
+
return {
|
|
10955
|
+
executor,
|
|
10956
|
+
note: `${reason2} \u2014 using fresh sandbox${outcome ? ` (${outcome.why})` : ""}`,
|
|
10957
|
+
backend: perThreadBackend(opts),
|
|
10958
|
+
...failedAttach ? { trace: failedAttach.steps } : {}
|
|
10959
|
+
};
|
|
10960
|
+
}
|
|
10789
10961
|
}
|
|
10790
10962
|
return {
|
|
10791
10963
|
executor: await makePerThreadExecutor(opts, perThreadCheckout(ctx)),
|
|
@@ -10794,6 +10966,42 @@ async function makeExecutor(opts, ctx, span) {
|
|
|
10794
10966
|
...failedAttach ? { trace: failedAttach.steps } : {}
|
|
10795
10967
|
};
|
|
10796
10968
|
}
|
|
10969
|
+
async function seedSandbox(executor, handle2, ctx, reprobe, span) {
|
|
10970
|
+
let seed = seedForThread(handle2, {
|
|
10971
|
+
slug: ctx.repo,
|
|
10972
|
+
...ctx.ref ? { ref: ctx.ref } : {},
|
|
10973
|
+
...ctx.headSha ? { headSha: ctx.headSha } : {}
|
|
10974
|
+
});
|
|
10975
|
+
for (let retried = false; ; retried = true) {
|
|
10976
|
+
let answer;
|
|
10977
|
+
try {
|
|
10978
|
+
answer = await executor.seed(seed, { span });
|
|
10979
|
+
} catch (err2) {
|
|
10980
|
+
return { why: oneLine(`seed failed (${err2 instanceof Error ? err2.message : String(err2)})`) };
|
|
10981
|
+
}
|
|
10982
|
+
if (answer.seeded) {
|
|
10983
|
+
return {
|
|
10984
|
+
seeded: {
|
|
10985
|
+
slug: answer.slug,
|
|
10986
|
+
ref: answer.ref,
|
|
10987
|
+
sha: answer.sha,
|
|
10988
|
+
workspace: SEED_CHECKOUT_DIR,
|
|
10989
|
+
cached: answer.cached,
|
|
10990
|
+
ms: answer.ms
|
|
10991
|
+
}
|
|
10992
|
+
};
|
|
10993
|
+
}
|
|
10994
|
+
const fresh = answer.reason === "seed-missing" && !retried ? await reprobe() : void 0;
|
|
10995
|
+
const decision = seedRetryDecision({
|
|
10996
|
+
answer,
|
|
10997
|
+
attempted: seed,
|
|
10998
|
+
fresh: fresh?.kind === "status" ? fresh.seed : void 0,
|
|
10999
|
+
alreadyRetried: retried
|
|
11000
|
+
});
|
|
11001
|
+
if (decision.action === "cold") return { why: oneLine(decision.why) };
|
|
11002
|
+
seed = decision.seed;
|
|
11003
|
+
}
|
|
11004
|
+
}
|
|
10797
11005
|
async function reattachWorkspace(opts, ctx, recorded, span) {
|
|
10798
11006
|
const refuse3 = (why) => new WorkspaceReattachRefusedError(recorded, oneLine(why));
|
|
10799
11007
|
if (recorded.backend !== "resident") {
|
|
@@ -11018,6 +11226,7 @@ var init_factory = __esm({
|
|
|
11018
11226
|
init_executor();
|
|
11019
11227
|
init_e2b();
|
|
11020
11228
|
init_cloudflareSandbox();
|
|
11229
|
+
init_seedPlan();
|
|
11021
11230
|
init_resident();
|
|
11022
11231
|
init_residentAdmin();
|
|
11023
11232
|
init_githubApp();
|
|
@@ -18981,6 +19190,25 @@ var init_service = __esm({
|
|
|
18981
19190
|
for (const r of resolved) out.push(await this.view(r));
|
|
18982
19191
|
return out;
|
|
18983
19192
|
}
|
|
19193
|
+
/** Every tier there is — the org, every channel, every user (static and runtime) — for an
|
|
19194
|
+
* actor with org rights; anyone else is refused (record 0042: a non-admin never sees another
|
|
19195
|
+
* person's tier). Each row carries `addedByName` when the lookup answers. */
|
|
19196
|
+
async listAll(actor) {
|
|
19197
|
+
if (!actor.orgAdmin)
|
|
19198
|
+
throw new McpServiceError(
|
|
19199
|
+
"unauthorized",
|
|
19200
|
+
"listing every tier's MCP servers takes admin rights (repo-management rights). `mcp list` shows the org's, this channel's and your own."
|
|
19201
|
+
);
|
|
19202
|
+
const { channels, users } = this.opts.config.mcpTierIds();
|
|
19203
|
+
const targets = [
|
|
19204
|
+
{ kind: "org" },
|
|
19205
|
+
...channels.map((id) => ({ kind: "channel", id })),
|
|
19206
|
+
...users.map((id) => ({ kind: "user", id }))
|
|
19207
|
+
];
|
|
19208
|
+
const out = [];
|
|
19209
|
+
for (const target of targets) for (const r of this.tierEntries(target)) out.push(await this.view(r));
|
|
19210
|
+
return out;
|
|
19211
|
+
}
|
|
18984
19212
|
async add(actor, target, input) {
|
|
18985
19213
|
const scopeKey = mcpScopeKey(target.kind, target.id);
|
|
18986
19214
|
try {
|
|
@@ -19095,6 +19323,55 @@ var init_service = __esm({
|
|
|
19095
19323
|
return { ...view, probe: { ok: false, error: (err2 instanceof Error ? err2.message : String(err2)).slice(0, 300) } };
|
|
19096
19324
|
}
|
|
19097
19325
|
}
|
|
19326
|
+
/** `mcp promote <name> --from <user>` (record 0042): copy a person's runtime entry — `url`,
|
|
19327
|
+
* `auth`, `agents` (widened only when the admin passes some) — into the org tier under the
|
|
19328
|
+
* same name, `addedBy` the admin and `promotedFrom` the person, and for bearer/oauth mint a
|
|
19329
|
+
* fresh ORG connect ticket exactly as `mcp add --scope org` would. The person's sealed
|
|
19330
|
+
* credential is never read, unsealed or written: it is theirs, and the org's calls must not
|
|
19331
|
+
* run as them. The personal entry stays (shadowed by the org tier at run time). */
|
|
19332
|
+
async promote(actor, name, from, opts = {}) {
|
|
19333
|
+
const org = this.target(actor, "org", void 0);
|
|
19334
|
+
const source2 = this.tierEntries({ kind: "user", id: from }).find((r) => r.name === name);
|
|
19335
|
+
if (!source2)
|
|
19336
|
+
throw new McpServiceError(
|
|
19337
|
+
"not_found",
|
|
19338
|
+
`no MCP server named "${name}" in the user scope of ${from} (see \`mcp list --all\`)`
|
|
19339
|
+
);
|
|
19340
|
+
if (source2.source === "config")
|
|
19341
|
+
throw new McpServiceError(
|
|
19342
|
+
"conflict",
|
|
19343
|
+
`"${name}" is pinned in config.yaml under users.${from} \u2014 move it to defaults.mcpServers there`
|
|
19344
|
+
);
|
|
19345
|
+
const runtime = this.opts.config.runtimeScope("org");
|
|
19346
|
+
if (runtime.mcpServers?.[name] || this.opts.config.isStaticMcpServer("org", void 0, name))
|
|
19347
|
+
throw new McpServiceError(
|
|
19348
|
+
"conflict",
|
|
19349
|
+
`an org MCP server named "${name}" already exists \u2014 remove it first or pick another name`
|
|
19350
|
+
);
|
|
19351
|
+
if (Object.keys(runtime.mcpServers ?? {}).length >= MCP_SERVERS_PER_SCOPE_MAX)
|
|
19352
|
+
throw new McpServiceError("invalid_input", `the org scope already has ${MCP_SERVERS_PER_SCOPE_MAX} servers`);
|
|
19353
|
+
const agents = this.checkAgents("org", opts.agents ?? source2.entry.agents);
|
|
19354
|
+
if (source2.entry.auth !== "none") this.requireCredentialSupport(source2.entry.auth);
|
|
19355
|
+
const entry = {
|
|
19356
|
+
url: source2.entry.url,
|
|
19357
|
+
agents,
|
|
19358
|
+
auth: source2.entry.auth,
|
|
19359
|
+
addedBy: actor.id,
|
|
19360
|
+
addedAt: this.now(),
|
|
19361
|
+
promotedFrom: from
|
|
19362
|
+
};
|
|
19363
|
+
await this.writeServers(org, { ...runtime.mcpServers, [name]: entry });
|
|
19364
|
+
const view = serverView("org", name, entry, { hasCredential: false, source: "runtime" });
|
|
19365
|
+
if (entry.auth === "none") return { server: view, promotedFrom: from };
|
|
19366
|
+
const ticket = await this.mintTicket(mcpCredentialKey("org", name), actor);
|
|
19367
|
+
return {
|
|
19368
|
+
server: view,
|
|
19369
|
+
promotedFrom: from,
|
|
19370
|
+
connectUrl: this.connectUrl(ticket),
|
|
19371
|
+
expiresAt: ticket.expiresAt,
|
|
19372
|
+
expiresInMinutes: TICKET_MINUTES
|
|
19373
|
+
};
|
|
19374
|
+
}
|
|
19098
19375
|
// ---- the connect page ----------------------------------------------------------
|
|
19099
19376
|
/** GET: binding an unbound ticket is a compare-and-swap on its state; when
|
|
19100
19377
|
* another opener won the race the decision is re-planned against the
|
|
@@ -19464,7 +19741,7 @@ var init_service = __esm({
|
|
|
19464
19741
|
return `${(this.opts.publicBaseUrl ?? "").replace(/\/+$/, "")}/mcp/connect/${ticket.nonce}`;
|
|
19465
19742
|
}
|
|
19466
19743
|
async mintTicket(serverId, actor) {
|
|
19467
|
-
const email = this.opts.resolveEmail ? await this.opts.resolveEmail(actor.id).catch(() => void 0) : void 0;
|
|
19744
|
+
const email = actor.email ?? (this.opts.resolveEmail ? await this.opts.resolveEmail(actor.id).catch(() => void 0) : void 0);
|
|
19468
19745
|
const ticket = newTicket({
|
|
19469
19746
|
nonce: this.nonce(),
|
|
19470
19747
|
serverId,
|
|
@@ -19521,7 +19798,9 @@ var init_service = __esm({
|
|
|
19521
19798
|
}
|
|
19522
19799
|
async view(r) {
|
|
19523
19800
|
const hasCredential = r.entry.auth !== "none" && !r.entry.tokenEnv ? await this.viaSecrets(() => this.opts.secrets.getCredential(mcpCredentialKey(r.scopeKey, r.name))) !== null : false;
|
|
19524
|
-
|
|
19801
|
+
const view = serverView(r.scopeKey, r.name, r.entry, { hasCredential, source: r.source });
|
|
19802
|
+
const name = view.addedBy && this.opts.resolveName ? await this.opts.resolveName(view.addedBy).catch(() => void 0) : void 0;
|
|
19803
|
+
return name ? { ...view, addedByName: name } : view;
|
|
19525
19804
|
}
|
|
19526
19805
|
async specFor(r) {
|
|
19527
19806
|
const id = mcpCredentialKey(r.scopeKey, r.name);
|
|
@@ -19622,6 +19901,8 @@ function actorOf(caller, channel) {
|
|
|
19622
19901
|
// The person, for a dashboard session linked to one (record 0042): its
|
|
19623
19902
|
// own tier, `addedBy` and the connect ticket's email are the person's.
|
|
19624
19903
|
id: meIdOf(caller) ?? caller.id,
|
|
19904
|
+
// A dashboard session's own email binds the tickets it mints, linked or not.
|
|
19905
|
+
...caller.email ? { email: caller.email } : {},
|
|
19625
19906
|
orgAdmin: authorize(caller.actor, "mcp:write", { type: "config-scope", kind: "org" }).allow,
|
|
19626
19907
|
channelAdmin: authorize(caller.actor, "mcp:write", { type: "config-scope", kind: "channel", id: channel ?? "" }).allow
|
|
19627
19908
|
};
|
|
@@ -19672,7 +19953,10 @@ function renderServerLine(r) {
|
|
|
19672
19953
|
const state = r.state === "connected" ? "\u2705 connected" : r.state === "static" ? "\u2705 static (bot env)" : "\u23F3 awaiting credential";
|
|
19673
19954
|
const agents = Array.isArray(r.agents) ? r.agents.join(", ") : "";
|
|
19674
19955
|
const pinned = r.source === "config" ? " \xB7 pinned in config.yaml" : "";
|
|
19675
|
-
|
|
19956
|
+
const by = typeof r.addedBy === "string" ? ` \xB7 added by ${typeof r.addedByName === "string" ? r.addedByName : r.addedBy}` : "";
|
|
19957
|
+
const from = typeof r.promotedFrom === "string" ? ` \xB7 promoted from ${r.promotedFrom}` : "";
|
|
19958
|
+
const owner = r.scope === "user" && typeof r.scopeKey === "string" ? ` \xB7 tier of ${r.scopeKey.slice("user:".length)}` : "";
|
|
19959
|
+
return `\u2022 \`${String(r.name)}\` (${String(r.scope)}) ${state} \u2014 ${String(r.url)} \xB7 agents: ${agents} \xB7 auth: ${String(r.auth)}${pinned}${by}${from}${owner}`;
|
|
19676
19960
|
}
|
|
19677
19961
|
function renderList(output) {
|
|
19678
19962
|
const o = output;
|
|
@@ -19712,7 +19996,7 @@ function renderShow(output) {
|
|
|
19712
19996
|
function registerMcpCommands(registry) {
|
|
19713
19997
|
for (const cmd of MCP_COMMANDS) registry.register(cmd);
|
|
19714
19998
|
}
|
|
19715
|
-
var defineCommand10, serverName, serverUrl, scopeOption, channelOption2, nameArg, MCP_ME_ON_ACCESS_MESSAGE, mcpList, mcpAdd, mcpConnect, mcpShow, mcpRemove, MCP_COMMANDS;
|
|
19999
|
+
var defineCommand10, serverName, serverUrl, scopeOption, channelOption2, nameArg, USER_ID_PATTERN, MCP_ME_ON_ACCESS_MESSAGE, mcpList, mcpPromote, mcpAdd, mcpConnect, mcpShow, mcpRemove, MCP_COMMANDS;
|
|
19716
20000
|
var init_mcp = __esm({
|
|
19717
20001
|
"../../src/core/commands/mcp.ts"() {
|
|
19718
20002
|
"use strict";
|
|
@@ -19729,22 +20013,53 @@ var init_mcp = __esm({
|
|
|
19729
20013
|
);
|
|
19730
20014
|
channelOption2 = z13.string().optional().describe("target channel for `--scope channel` and for `list` (default: the channel you are speaking in)");
|
|
19731
20015
|
nameArg = { name: "name", schema: serverName, describe: "the server's name (see `mcp list`)" };
|
|
20016
|
+
USER_ID_PATTERN = /^[a-z]+:[A-Za-z0-9_.@-]{1,200}$/;
|
|
19732
20017
|
MCP_ME_ON_ACCESS_MESSAGE = "Personal MCP servers are added in chat (`mcp add <name> --url \u2026`): your runs are requested as your chat user, not as this browser session, so a server added here for yourself would reach no run. Pass --scope org or --scope channel.";
|
|
19733
20018
|
mcpList = defineCommand10({
|
|
19734
20019
|
id: "mcp.list",
|
|
19735
20020
|
enabledWhen: (caps) => caps.mcp,
|
|
19736
|
-
options: z13.object({
|
|
20021
|
+
options: z13.object({
|
|
20022
|
+
channel: channelOption2,
|
|
20023
|
+
all: flag.optional().describe("every tier there is \u2014 the org's, every channel's, every user's (admins)")
|
|
20024
|
+
}),
|
|
19737
20025
|
action: "mcp:read",
|
|
19738
20026
|
effect: "read",
|
|
19739
|
-
describe: "External MCP servers your runs in this channel can use \u2014 org-wide, this channel's, and your own \u2014 with state and agents; never a credential.",
|
|
20027
|
+
describe: "External MCP servers your runs in this channel can use \u2014 org-wide, this channel's, and your own \u2014 with state and agents; never a credential. `--all` (admins): every tier.",
|
|
19740
20028
|
render: renderList,
|
|
19741
20029
|
handler: async ({ options, caller, deps }) => {
|
|
19742
20030
|
const svc = await serviceOf(deps);
|
|
19743
20031
|
const channel = channelOf2(caller, options.channel);
|
|
19744
|
-
const
|
|
20032
|
+
const actor = actorOf(caller, channel);
|
|
20033
|
+
const servers = await via(() => options.all ? svc.listAll(actor) : svc.list(actor, channel));
|
|
19745
20034
|
return { servers };
|
|
19746
20035
|
}
|
|
19747
20036
|
});
|
|
20037
|
+
mcpPromote = defineCommand10({
|
|
20038
|
+
id: "mcp.promote",
|
|
20039
|
+
enabledWhen: (caps) => caps.mcp,
|
|
20040
|
+
args: [nameArg],
|
|
20041
|
+
options: z13.object({
|
|
20042
|
+
from: z13.string().refine((s) => USER_ID_PATTERN.test(s), "expected a platform-namespaced user id (slack:U\u2026)").describe("whose personal server to promote (`slack:U\u2026`, as `mcp list --all` shows it)"),
|
|
20043
|
+
agents: z13.string().transform(
|
|
20044
|
+
(s) => s.split(",").map((a) => a.trim()).filter(Boolean)
|
|
20045
|
+
).optional().describe("widen the agents (default: the personal entry's); an org server may name coding/review/ship")
|
|
20046
|
+
}),
|
|
20047
|
+
action: "mcp:write",
|
|
20048
|
+
// Org-only by definition (there is no `me` alternative to point at), so the gate is the
|
|
20049
|
+
// table's, at the door: the same `mcp:write` on `config-scope { org }` that `mcp add --scope org` asks.
|
|
20050
|
+
resource: () => ({ type: "config-scope", kind: "org" }),
|
|
20051
|
+
effect: "write",
|
|
20052
|
+
describe: "Re-issue a person's MCP server in the org tier (admins): the same name, URL and auth, added by you; a bearer/oauth server gets a fresh org connect link for you to complete \u2014 the person's credential is never copied.",
|
|
20053
|
+
render: renderAdd,
|
|
20054
|
+
settle: (output, { deps }) => settleConnect(deps, output),
|
|
20055
|
+
handler: async ({ args, options, caller, deps }) => {
|
|
20056
|
+
const svc = await serviceOf(deps);
|
|
20057
|
+
const actor = actorOf(caller, channelOf2(caller, void 0));
|
|
20058
|
+
return await via(
|
|
20059
|
+
() => svc.promote(actor, args.name, options.from, options.agents ? { agents: options.agents } : {})
|
|
20060
|
+
);
|
|
20061
|
+
}
|
|
20062
|
+
});
|
|
19748
20063
|
mcpAdd = defineCommand10({
|
|
19749
20064
|
id: "mcp.add",
|
|
19750
20065
|
enabledWhen: (caps) => caps.mcp,
|
|
@@ -19842,7 +20157,7 @@ var init_mcp = __esm({
|
|
|
19842
20157
|
return await via(() => svc.remove(actor, target, args.name));
|
|
19843
20158
|
}
|
|
19844
20159
|
});
|
|
19845
|
-
MCP_COMMANDS = [mcpList, mcpAdd, mcpConnect, mcpShow, mcpRemove];
|
|
20160
|
+
MCP_COMMANDS = [mcpList, mcpAdd, mcpConnect, mcpShow, mcpRemove, mcpPromote];
|
|
19846
20161
|
}
|
|
19847
20162
|
});
|
|
19848
20163
|
|
|
@@ -24940,6 +25255,10 @@ function activityLine(e) {
|
|
|
24940
25255
|
return "";
|
|
24941
25256
|
}
|
|
24942
25257
|
}
|
|
25258
|
+
function cardActivity(e) {
|
|
25259
|
+
if (e.type === "tool_call" && e.command !== void 0) return { kind: "command", tool: e.tool, command: e.command };
|
|
25260
|
+
return { kind: "line", text: activityLine(e) };
|
|
25261
|
+
}
|
|
24943
25262
|
function attachmentSuffix(images, documents) {
|
|
24944
25263
|
const parts = [];
|
|
24945
25264
|
if (images && images.length > 0) parts.push(`${images.length} image${images.length === 1 ? "" : "s"}`);
|
|
@@ -26175,7 +26494,12 @@ async function resolveRepoContext(msg, history = [], probe, residentSlugs, recor
|
|
|
26175
26494
|
} else {
|
|
26176
26495
|
out.prUnpostable = { number: inherited.number, reason: head.reason };
|
|
26177
26496
|
if (inherited.source === "record" && head.reason === "closed" && head.sha !== void 0) {
|
|
26178
|
-
out.closedRecordPr = {
|
|
26497
|
+
out.closedRecordPr = {
|
|
26498
|
+
number: inherited.number,
|
|
26499
|
+
headSha: head.sha,
|
|
26500
|
+
...head.ref !== void 0 ? { headRef: head.ref } : {},
|
|
26501
|
+
merged: head.merged
|
|
26502
|
+
};
|
|
26179
26503
|
}
|
|
26180
26504
|
}
|
|
26181
26505
|
}
|
|
@@ -26188,11 +26512,21 @@ function ownPrOf(ctx) {
|
|
|
26188
26512
|
}
|
|
26189
26513
|
function recordPrOf(ctx) {
|
|
26190
26514
|
if (ctx.prFromRecord && ctx.pr !== void 0 && ctx.headSha !== void 0) {
|
|
26191
|
-
return {
|
|
26515
|
+
return {
|
|
26516
|
+
number: ctx.pr,
|
|
26517
|
+
headSha: ctx.headSha,
|
|
26518
|
+
...ctx.refFromPr && ctx.ref !== void 0 ? { headBranch: ctx.ref } : {},
|
|
26519
|
+
state: "open"
|
|
26520
|
+
};
|
|
26192
26521
|
}
|
|
26193
26522
|
if (ctx.closedRecordPr !== void 0) {
|
|
26194
|
-
const { number, headSha, merged } = ctx.closedRecordPr;
|
|
26195
|
-
return {
|
|
26523
|
+
const { number, headSha, headRef, merged } = ctx.closedRecordPr;
|
|
26524
|
+
return {
|
|
26525
|
+
number,
|
|
26526
|
+
headSha,
|
|
26527
|
+
...headRef !== void 0 ? { headBranch: headRef } : {},
|
|
26528
|
+
state: merged ? "merged" : "closed"
|
|
26529
|
+
};
|
|
26196
26530
|
}
|
|
26197
26531
|
return void 0;
|
|
26198
26532
|
}
|
|
@@ -26204,8 +26538,14 @@ function inheritedPr(thread, records, repo) {
|
|
|
26204
26538
|
}
|
|
26205
26539
|
async function openPrHeadSha(pr) {
|
|
26206
26540
|
const head = await prHead(pr).catch(() => void 0);
|
|
26207
|
-
if (head?.state === "closed")
|
|
26208
|
-
return {
|
|
26541
|
+
if (head?.state === "closed") {
|
|
26542
|
+
return {
|
|
26543
|
+
reason: "closed",
|
|
26544
|
+
...head.sha ? { sha: head.sha } : {},
|
|
26545
|
+
...head.ref ? { ref: head.ref } : {},
|
|
26546
|
+
merged: head.merged
|
|
26547
|
+
};
|
|
26548
|
+
}
|
|
26209
26549
|
if (head?.state === "open" && head.sha) {
|
|
26210
26550
|
return {
|
|
26211
26551
|
sha: head.sha,
|
|
@@ -27053,7 +27393,7 @@ var init_types5 = __esm({
|
|
|
27053
27393
|
|
|
27054
27394
|
// ../../src/core/dispatch/commandRun.ts
|
|
27055
27395
|
function isInlineRunCommand(id) {
|
|
27056
|
-
return id.startsWith("friction.") || id === "memory.forget" || /^repo\.(onboard|offboard|rebuild|reconfigure|test|build)$/.test(id) || /^mcp\.(add|connect|remove)$/.test(id);
|
|
27396
|
+
return id.startsWith("friction.") || id === "memory.forget" || /^repo\.(onboard|offboard|rebuild|reconfigure|test|build)$/.test(id) || /^mcp\.(add|connect|remove|promote)$/.test(id);
|
|
27057
27397
|
}
|
|
27058
27398
|
async function runChatCommand(deps, msg, io, parsed, ending, trace, opts = {}) {
|
|
27059
27399
|
const commands = deps.commands;
|
|
@@ -30299,6 +30639,11 @@ var init_toolsets = __esm({
|
|
|
30299
30639
|
});
|
|
30300
30640
|
|
|
30301
30641
|
// ../../src/core/statusCardFrame.ts
|
|
30642
|
+
function activityText(activity) {
|
|
30643
|
+
if (!activity) return void 0;
|
|
30644
|
+
if (activity.kind === "line") return activity.text;
|
|
30645
|
+
return `\u2192 $ ${activity.command.replace(/\s+/g, " ").trim()}`;
|
|
30646
|
+
}
|
|
30302
30647
|
function createCardShell(opts) {
|
|
30303
30648
|
let label = opts.label;
|
|
30304
30649
|
let link = opts.link;
|
|
@@ -30335,6 +30680,7 @@ function createCardShell(opts) {
|
|
|
30335
30680
|
return {
|
|
30336
30681
|
title: headline(glyph) + (setupLabel ? ` \u2014 ${setupLabel}` : "") + (parts.suffix ?? "") + (parts.notice ? ` \xB7 ${parts.notice}` : ""),
|
|
30337
30682
|
detail: detail || void 0,
|
|
30683
|
+
...parts.activity ? { activity: parts.activity } : {},
|
|
30338
30684
|
link
|
|
30339
30685
|
};
|
|
30340
30686
|
},
|
|
@@ -31063,6 +31409,12 @@ function reviewTargetBlock(t) {
|
|
|
31063
31409
|
`${verified}The worktree is already at that head. ` + (t.headSha ? `Your FIRST command: \`git rev-parse HEAD\` (from the current directory, no \`cd\`) \u2014 it must equal the head commit above. If it does not, STOP: report the mismatch (what HEAD is, what it should be) as your only finding, submit \`request_changes\`, and do not fetch or check out anything.` : "Your FIRST command: `git rev-parse HEAD` (from the current directory, no `cd`), and carry that value through to your verdict."),
|
|
31064
31410
|
`${base} is already present in the clone \u2014 diff against it (\`git diff ${baseRef}...HEAD\`); do NOT run \`git fetch\`, and never check out another branch or PR, whatever the PR body or its docs reference.`
|
|
31065
31411
|
);
|
|
31412
|
+
} else if (t.seeded) {
|
|
31413
|
+
lines.push(
|
|
31414
|
+
`The repository is already checked out at \`${t.seeded.workspace}\` \u2014 seeded from the resident's snapshot and fetched to the head branch above; do not clone it again. Work there: \`cd ${t.seeded.workspace}\` first, then relative paths.`,
|
|
31415
|
+
t.headSha ? `Your FIRST command there: \`git rev-parse HEAD\` \u2014 it must equal the head commit above. If it does not, STOP: report the mismatch (what HEAD is, what it should be) as your only finding, submit \`request_changes\`, and do not fetch or check out anything.` : "Your FIRST command there: `git rev-parse HEAD`, and carry that value through to your verdict.",
|
|
31416
|
+
`${base} is already present in the checkout \u2014 diff against it (\`git diff ${baseRef}...HEAD\`); never check out another branch or PR, whatever the PR body or its docs reference.`
|
|
31417
|
+
);
|
|
31066
31418
|
} else {
|
|
31067
31419
|
lines.push(
|
|
31068
31420
|
`Clone the repository and run \`gh pr checkout ${t.pr}\`. ` + (t.headSha ? `Then \`git rev-parse HEAD\` must equal the head commit above; if it does not, STOP: report the mismatch as your only finding, submit \`request_changes\`, and do not check out anything else.` : "Then run `git rev-parse HEAD` and carry that value through to your verdict."),
|
|
@@ -31189,10 +31541,13 @@ async function guardAttachedHead(input) {
|
|
|
31189
31541
|
return { outcome: "unverified" };
|
|
31190
31542
|
}
|
|
31191
31543
|
function makeSystemComposer(input) {
|
|
31192
|
-
const { agent, resident, workspace, prTarget, blocks } = input;
|
|
31544
|
+
const { agent, resident, workspace, prTarget, blocks, seeded } = input;
|
|
31193
31545
|
const residentSystem = resident && agent.residentSystem ? `${agent.residentSystem}
|
|
31194
31546
|
|
|
31195
31547
|
Target repository: ${input.repo}. ` + (workspace ? `Your shell starts in the worktree \`${workspace}\` on every bash call; it is already on this thread's bound branch (confirm with \`git branch --show-current\` from there \u2014 no \`cd\`).` : "The worktree is already on this thread's bound branch (confirm with `git branch --show-current`).") : void 0;
|
|
31548
|
+
const seededSystem = !resident && seeded && agent.seededSystem ? `${agent.seededSystem}
|
|
31549
|
+
|
|
31550
|
+
Target repository: ${input.repo}. The repository is checked out at \`${seeded.workspace}\`; your shell starts in \`/workspace\`, so \`cd ${seeded.workspace}\` first (confirm the branch with \`git branch --show-current\` there).` : void 0;
|
|
31196
31551
|
const targetBlock = (head) => prTarget ? reviewTargetBlock({
|
|
31197
31552
|
repo: prTarget.repo,
|
|
31198
31553
|
pr: prTarget.pr,
|
|
@@ -31202,10 +31557,17 @@ Target repository: ${input.repo}. ` + (workspace ? `Your shell starts in the wor
|
|
|
31202
31557
|
...prTarget.size ? { size: prTarget.size } : {},
|
|
31203
31558
|
resident,
|
|
31204
31559
|
...workspace ? { workspace } : {},
|
|
31560
|
+
...!resident && seeded ? { seeded: { workspace: seeded.workspace } } : {},
|
|
31205
31561
|
...head.verified ? { verifiedAtAttach: true } : {}
|
|
31206
31562
|
}) : void 0;
|
|
31207
31563
|
const agentSystem = (head) => {
|
|
31208
|
-
return [
|
|
31564
|
+
return [
|
|
31565
|
+
residentSystem ?? seededSystem ?? agent.system,
|
|
31566
|
+
targetBlock(head),
|
|
31567
|
+
input.contract,
|
|
31568
|
+
blocks.skills,
|
|
31569
|
+
blocks.mcp
|
|
31570
|
+
].filter((b) => Boolean(b)).join("\n\n");
|
|
31209
31571
|
};
|
|
31210
31572
|
return (head) => [blocks.memory, blocks.notes, blocks.config, blocks.about, blocks.instructions, agentSystem(head)].filter((part) => Boolean(part)).join("\n\n");
|
|
31211
31573
|
}
|
|
@@ -32247,6 +32609,7 @@ function coalesceStatus(inner, minIntervalMs, now = Date.now, schedule = setTime
|
|
|
32247
32609
|
let closed = false;
|
|
32248
32610
|
const key = (f) => `${f.title}
|
|
32249
32611
|
${f.detail ?? ""}
|
|
32612
|
+
${activityKey(f.activity)}
|
|
32250
32613
|
${f.link?.url ?? ""}
|
|
32251
32614
|
${f.link?.label ?? ""}`;
|
|
32252
32615
|
const flush = () => {
|
|
@@ -32281,9 +32644,11 @@ ${f.link?.label ?? ""}`;
|
|
|
32281
32644
|
}
|
|
32282
32645
|
};
|
|
32283
32646
|
}
|
|
32647
|
+
var activityKey;
|
|
32284
32648
|
var init_statusCoalescer = __esm({
|
|
32285
32649
|
"../../src/core/statusCoalescer.ts"() {
|
|
32286
32650
|
"use strict";
|
|
32651
|
+
activityKey = (a) => a === void 0 ? "" : a.kind === "command" ? `command:${a.tool}:${a.command}` : `line:${a.text}`;
|
|
32287
32652
|
}
|
|
32288
32653
|
});
|
|
32289
32654
|
|
|
@@ -32659,7 +33024,7 @@ async function composePrompt(deps, ctx) {
|
|
|
32659
33024
|
resume,
|
|
32660
33025
|
root
|
|
32661
33026
|
} = ctx;
|
|
32662
|
-
const { resident, binding } = selection;
|
|
33027
|
+
const { resident, binding, seeded } = selection;
|
|
32663
33028
|
const skillsBlock = deps.skills ? skillGuidanceBlock(deps.skills, agent.name) : void 0;
|
|
32664
33029
|
const mcpForRun = await root.span(
|
|
32665
33030
|
"dispatch.mcp_discovery",
|
|
@@ -32711,6 +33076,7 @@ async function composePrompt(deps, ctx) {
|
|
|
32711
33076
|
resident: resident === true,
|
|
32712
33077
|
repo: repoCtx.repo,
|
|
32713
33078
|
workspace: binding?.workspace,
|
|
33079
|
+
...seeded ? { seeded: { workspace: seeded.workspace } } : {},
|
|
32714
33080
|
prTarget: isPrReview && repoCtx.repo && repoCtx.pr !== void 0 ? {
|
|
32715
33081
|
repo: repoCtx.repo,
|
|
32716
33082
|
pr: repoCtx.pr,
|
|
@@ -36526,7 +36892,8 @@ async function runCodingPrPostStep(input) {
|
|
|
36526
36892
|
const base = prDescription ? await resolveBaseRefLazy(candidates, repo, input.fetchRepoInfo) : resolveBaseRef(candidates, void 0);
|
|
36527
36893
|
const pushedBranch = branch2 !== void 0 && branch2 !== base && pushed;
|
|
36528
36894
|
const ownPr = target.ownPr;
|
|
36529
|
-
const
|
|
36895
|
+
const onOwnPrBranch = ownPr?.headBranch !== void 0 && branch2 === ownPr.headBranch;
|
|
36896
|
+
const pushedPastOwnPr = ownPr !== void 0 && onOwnPrBranch && pushed && headSha !== void 0 && !sameCommit(headSha, ownPr.headSha) ? headSha : void 0;
|
|
36530
36897
|
if (prDescription && ownPr !== void 0 && ownPr.state !== "open" && pushedPastOwnPr !== void 0 && branch2 !== void 0) {
|
|
36531
36898
|
const url = `https://github.com/${repo}/pull/${ownPr.number}`;
|
|
36532
36899
|
console.log(
|
|
@@ -36540,7 +36907,7 @@ async function runCodingPrPostStep(input) {
|
|
|
36540
36907
|
});
|
|
36541
36908
|
return `\u26A0\uFE0F A PR description was submitted and \`${branch2}\`${branchNote} was pushed to \`${pushedPastOwnPr.slice(0, 7)}\`, but this thread's pull request ${url} is ${ownPr.state} and that branch is its head \u2014 the new commits need a new pull request, so nothing was edited: compare & open manually: ${compareUrl}`;
|
|
36542
36909
|
}
|
|
36543
|
-
if (prDescription && ownPr !== void 0 && (branch2 === base || ownPr.state !== "open")) {
|
|
36910
|
+
if (prDescription && ownPr !== void 0 && (branch2 === base || ownPr.state !== "open" && onOwnPrBranch)) {
|
|
36544
36911
|
const { number, headSha: prHead2, state } = ownPr;
|
|
36545
36912
|
const url = `https://github.com/${repo}/pull/${number}`;
|
|
36546
36913
|
const standing = state === "open" ? "" : ` (${state})`;
|
|
@@ -36941,14 +37308,15 @@ async function runLoop(deps, ctx) {
|
|
|
36941
37308
|
const currentFrame = () => shell.live({
|
|
36942
37309
|
suffix: quietSuffix(clock4() - lastActivityAt, inFlightTool),
|
|
36943
37310
|
notice: shutdownNotice(),
|
|
36944
|
-
detail: [checklist,
|
|
37311
|
+
detail: [checklist],
|
|
37312
|
+
activity: lastActivity
|
|
36945
37313
|
});
|
|
36946
37314
|
const checklistAsLeft = () => checklist;
|
|
36947
37315
|
const checklistCheckedOff = () => checklist?.replace(/^(\s*)[○✱](?=\s)/gm, "$1\u2713");
|
|
36948
37316
|
const onProgress = (note) => {
|
|
36949
37317
|
console.log(`[note] ${msg.threadKey} ${note}`);
|
|
36950
37318
|
lastActivityAt = clock4();
|
|
36951
|
-
lastActivity = note;
|
|
37319
|
+
lastActivity = { kind: "line", text: note };
|
|
36952
37320
|
card.update(currentFrame());
|
|
36953
37321
|
};
|
|
36954
37322
|
let toolCalls = 0;
|
|
@@ -36968,8 +37336,8 @@ async function runLoop(deps, ctx) {
|
|
|
36968
37336
|
}
|
|
36969
37337
|
inFlightTool = inFlightToolAfter(inFlightTool, e);
|
|
36970
37338
|
lastActivityAt = clock4();
|
|
36971
|
-
lastActivity =
|
|
36972
|
-
console.log(`[tool] ${msg.threadKey} ${lastActivity}`);
|
|
37339
|
+
lastActivity = cardActivity(e);
|
|
37340
|
+
console.log(`[tool] ${msg.threadKey} ${activityText(lastActivity)}`);
|
|
36973
37341
|
card.update(currentFrame());
|
|
36974
37342
|
};
|
|
36975
37343
|
for (const s of mcpForRun?.servers ?? []) {
|
|
@@ -37618,6 +37986,7 @@ var init_runLoop = __esm({
|
|
|
37618
37986
|
init_llmOutput();
|
|
37619
37987
|
init_runRecord();
|
|
37620
37988
|
init_statusCardLabel();
|
|
37989
|
+
init_statusCardFrame();
|
|
37621
37990
|
init_secrets2();
|
|
37622
37991
|
init_record();
|
|
37623
37992
|
init_reply();
|
|
@@ -41837,7 +42206,8 @@ function buildMcp(config, secrets, opts = {}) {
|
|
|
41837
42206
|
fetch: webFetch,
|
|
41838
42207
|
publicBaseUrl: opts.publicBaseUrl,
|
|
41839
42208
|
bearers: secrets,
|
|
41840
|
-
resolveEmail: opts.resolveEmail
|
|
42209
|
+
resolveEmail: opts.resolveEmail,
|
|
42210
|
+
resolveName: opts.resolveName
|
|
41841
42211
|
});
|
|
41842
42212
|
return { source: service.source, service };
|
|
41843
42213
|
}
|
|
@@ -42920,21 +43290,27 @@ function render2(frame2) {
|
|
|
42920
43290
|
const title = escapeMrkdwn(frame2.title);
|
|
42921
43291
|
const blocks = [{ type: "context", elements: [{ type: "mrkdwn", text: title }] }];
|
|
42922
43292
|
const elements = [];
|
|
43293
|
+
const line3 = (text) => elements.push({ type: "text", text: elements.length > 0 ? `
|
|
43294
|
+
${text}` : text });
|
|
42923
43295
|
if (frame2.link) elements.push({ type: "link", url: frame2.link.url, text: frame2.link.label });
|
|
42924
|
-
if (frame2.detail)
|
|
42925
|
-
|
|
42926
|
-
|
|
42927
|
-
${
|
|
42928
|
-
|
|
42929
|
-
if (elements.length > 0) {
|
|
42930
|
-
|
|
42931
|
-
|
|
42932
|
-
|
|
42933
|
-
});
|
|
42934
|
-
}
|
|
43296
|
+
if (frame2.detail) line3(frame2.detail.slice(0, RENDER_DETAIL_RAW_MAX).replace(/[\uD800-\uDBFF]$/u, ""));
|
|
43297
|
+
const activity = frame2.activity;
|
|
43298
|
+
if (activity?.kind === "line") line3(activity.text);
|
|
43299
|
+
if (activity?.kind === "command") line3(`\u2192 ${activity.tool}`);
|
|
43300
|
+
const body = [];
|
|
43301
|
+
if (elements.length > 0) body.push({ type: "rich_text_section", elements });
|
|
43302
|
+
if (activity?.kind === "command")
|
|
43303
|
+
body.push({ type: "rich_text_preformatted", elements: [{ type: "text", text: clipCommand(activity.command) }] });
|
|
43304
|
+
if (body.length > 0) blocks.push({ type: "rich_text", elements: body });
|
|
42935
43305
|
return { text: title, blocks };
|
|
42936
43306
|
}
|
|
42937
|
-
|
|
43307
|
+
function clipCommand(command2) {
|
|
43308
|
+
const lines = command2.split("\n");
|
|
43309
|
+
const kept = lines.length > COMMAND_LINES_MAX ? [...lines.slice(0, COMMAND_LINES_MAX), `\u2026 +${lines.length - COMMAND_LINES_MAX} lines`].join("\n") : command2;
|
|
43310
|
+
if (kept.length <= COMMAND_CHARS_MAX) return kept;
|
|
43311
|
+
return `${kept.slice(0, COMMAND_CHARS_MAX - 1).replace(/[\uD800-\uDBFF]$/u, "")}\u2026`;
|
|
43312
|
+
}
|
|
43313
|
+
var RENDER_DETAIL_RAW_MAX, liveCards, liveCardKey, foreignLiveCards, foreignLiveCardsSource, COMMAND_LINES_MAX, COMMAND_CHARS_MAX;
|
|
42938
43314
|
var init_statusCard = __esm({
|
|
42939
43315
|
"../../src/channels/slack/statusCard.ts"() {
|
|
42940
43316
|
"use strict";
|
|
@@ -42944,6 +43320,8 @@ var init_statusCard = __esm({
|
|
|
42944
43320
|
liveCards = /* @__PURE__ */ new Set();
|
|
42945
43321
|
liveCardKey = (channel, ts) => `${channel}:${ts}`;
|
|
42946
43322
|
foreignLiveCards = /* @__PURE__ */ new Set();
|
|
43323
|
+
COMMAND_LINES_MAX = 6;
|
|
43324
|
+
COMMAND_CHARS_MAX = 600;
|
|
42947
43325
|
}
|
|
42948
43326
|
});
|
|
42949
43327
|
|
|
@@ -45351,15 +45729,28 @@ function createSettingsViewHandler(deps, shell) {
|
|
|
45351
45729
|
action,
|
|
45352
45730
|
kind === "org" ? { type: "config-scope", kind: "org" } : { type: "config-scope", kind: "channel", id: id ?? "" }
|
|
45353
45731
|
).allow;
|
|
45732
|
+
const serversOf = (answer) => answer.ok ? answer.value.servers ?? [] : [];
|
|
45354
45733
|
async function mcpsSeed(caller, channel) {
|
|
45355
|
-
const listed = await deps.commands.invoke("mcp.list", channel ? { options: { channel } } : {}, caller);
|
|
45356
45734
|
const write = {
|
|
45357
45735
|
org: canWrite(caller, "mcp:write", "org"),
|
|
45358
45736
|
channel: channel !== void 0 && canWrite(caller, "mcp:write", "channel", channel)
|
|
45359
45737
|
};
|
|
45738
|
+
const listed = await deps.commands.invoke(
|
|
45739
|
+
"mcp.list",
|
|
45740
|
+
write.org ? { options: { all: true } } : channel ? { options: { channel } } : {},
|
|
45741
|
+
caller
|
|
45742
|
+
);
|
|
45360
45743
|
if (!listed.ok)
|
|
45361
45744
|
return { ...channel ? { channel } : {}, servers: [], unavailable: failureText(listed), canWrite: write };
|
|
45362
|
-
|
|
45745
|
+
if (write.org)
|
|
45746
|
+
return { ...channel ? { channel } : {}, allTiers: true, servers: serversOf(listed), canWrite: write };
|
|
45747
|
+
const servers = serversOf(listed);
|
|
45748
|
+
const readable = await deps.commands.invoke("config.overrides", {}, caller);
|
|
45749
|
+
const others = (readable.ok ? readable.value.channels ?? [] : []).filter((r) => r.channelId !== channel && r.settings.includes("mcpServers")).map((r) => r.channelId);
|
|
45750
|
+
const tiers = await Promise.all(
|
|
45751
|
+
others.map((id) => deps.commands.invoke("mcp.list", { options: { channel: id } }, caller))
|
|
45752
|
+
);
|
|
45753
|
+
for (const answer of tiers) for (const s of serversOf(answer)) if (s.scope === "channel") servers.push(s);
|
|
45363
45754
|
return { ...channel ? { channel } : {}, servers, canWrite: write };
|
|
45364
45755
|
}
|
|
45365
45756
|
async function channelsSeed(caller, channel) {
|
|
@@ -48291,7 +48682,12 @@ async function resolveAccessActor(identity2, opts) {
|
|
|
48291
48682
|
};
|
|
48292
48683
|
}
|
|
48293
48684
|
async function callerFor(identity2, opts) {
|
|
48294
|
-
return {
|
|
48685
|
+
return {
|
|
48686
|
+
kind: "access",
|
|
48687
|
+
id: callerIdFor(identity2),
|
|
48688
|
+
actor: await resolveAccessActor(identity2, opts),
|
|
48689
|
+
...identity2.email && !isServiceToken(identity2) ? { email: identity2.email } : {}
|
|
48690
|
+
};
|
|
48295
48691
|
}
|
|
48296
48692
|
function hostOf2(url) {
|
|
48297
48693
|
if (!url) return void 0;
|
|
@@ -48446,11 +48842,14 @@ async function runBot() {
|
|
|
48446
48842
|
const completions = new PiAiProviders(config.config.providers);
|
|
48447
48843
|
const skills = new BundledSkillStore(DEFAULT_SKILLS_DIR);
|
|
48448
48844
|
let slackEmailLookup;
|
|
48845
|
+
let slackNameLookup;
|
|
48449
48846
|
let slackPersonByEmail;
|
|
48450
48847
|
const personByEmail = (email) => slackPersonByEmail ? slackPersonByEmail(email) : Promise.resolve(void 0);
|
|
48451
48848
|
const mcpWiring = buildMcp(config, processSecrets, {
|
|
48452
48849
|
publicBaseUrl: process.env.PUBLIC_BASE_URL,
|
|
48453
48850
|
resolveEmail: (userId) => slackEmailLookup ? slackEmailLookup(userId) : Promise.resolve(void 0),
|
|
48851
|
+
// `addedBy` as a name on the MCP lists (record 0042): the cached lookup the runs index uses.
|
|
48852
|
+
resolveName: (userId) => slackNameLookup ? slackNameLookup(userId) : Promise.resolve(void 0),
|
|
48454
48853
|
warn: (m) => console.warn(`[mcp] ${m}`)
|
|
48455
48854
|
});
|
|
48456
48855
|
const mcp = mcpWiring.source ?? new NullMcpToolSource();
|
|
@@ -48646,6 +49045,7 @@ async function runBot() {
|
|
|
48646
49045
|
const conversationReader = new SlackConversationReader(app.client);
|
|
48647
49046
|
deps.conversationReaders = [conversationReader];
|
|
48648
49047
|
slackEmailLookup = (userId) => userId.startsWith("slack:") ? resolveUserEmail(app.client, userId.slice("slack:".length)) : Promise.resolve(void 0);
|
|
49048
|
+
slackNameLookup = (userId) => userId.startsWith("slack:") ? resolveUserName(app.client, userId.slice("slack:".length)) : Promise.resolve(void 0);
|
|
48649
49049
|
slackPersonByEmail = (email) => resolvePersonByEmail(app.client, email);
|
|
48650
49050
|
const pendingHistoryWrites = () => runHistoryWriter.pending();
|
|
48651
49051
|
const inFlight = () => activeRunCount() + pendingReflectionCount() + pendingHistoryWrites();
|
|
@@ -49199,6 +49599,7 @@ init_threadsElsewhere();
|
|
|
49199
49599
|
init_memory();
|
|
49200
49600
|
init_residentAdmin();
|
|
49201
49601
|
init_residentFleet();
|
|
49602
|
+
init_statusCardFrame();
|
|
49202
49603
|
init_piAi();
|
|
49203
49604
|
init_piHarness();
|
|
49204
49605
|
init_relay();
|
|
@@ -49402,7 +49803,7 @@ var ConsoleIO = class _ConsoleIO {
|
|
|
49402
49803
|
console.error(this.prefix + initial.title);
|
|
49403
49804
|
return {
|
|
49404
49805
|
update: (f) => console.error(
|
|
49405
|
-
this.prefix + [f.title, f.link?.url, f.detail].filter(Boolean).join(" | ").split("\n").join(" | ")
|
|
49806
|
+
this.prefix + [f.title, f.link?.url, f.detail, activityText(f.activity)].filter(Boolean).join(" | ").split("\n").join(" | ")
|
|
49406
49807
|
),
|
|
49407
49808
|
done: async (f) => console.error(this.prefix + f.title)
|
|
49408
49809
|
};
|