@coreplane/switchboard 1.238.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 +5 -0
- 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/cli.js +268 -28
- package/package.json +1 -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",
|
|
@@ -6383,7 +6446,8 @@ function templateView(profile, kind, published) {
|
|
|
6383
6446
|
urls: { publicBaseUrl: urls.publicBaseUrl, stateWorkerUrl: urls.stateWorkerUrl },
|
|
6384
6447
|
access: profile.access,
|
|
6385
6448
|
artifacts: profile.artifacts,
|
|
6386
|
-
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
|
|
6387
6451
|
};
|
|
6388
6452
|
}
|
|
6389
6453
|
function siteView(profile, facts2) {
|
|
@@ -9531,6 +9595,50 @@ ${parts}`);
|
|
|
9531
9595
|
}
|
|
9532
9596
|
});
|
|
9533
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
|
+
|
|
9534
9642
|
// ../../src/core/trace/internalHosts.ts
|
|
9535
9643
|
function internalHostsOf(urls) {
|
|
9536
9644
|
const set = /* @__PURE__ */ new Set();
|
|
@@ -9670,6 +9778,7 @@ var init_cloudflareSandbox = __esm({
|
|
|
9670
9778
|
init_bashTimeout();
|
|
9671
9779
|
init_executor();
|
|
9672
9780
|
init_sandboxErrors();
|
|
9781
|
+
init_seedPlan();
|
|
9673
9782
|
init_tracedFetch();
|
|
9674
9783
|
CloudflareSandboxExecutor = class {
|
|
9675
9784
|
constructor(opts) {
|
|
@@ -9780,6 +9889,22 @@ var init_cloudflareSandbox = __esm({
|
|
|
9780
9889
|
${parts}`);
|
|
9781
9890
|
return truncate(parts || "(no output)");
|
|
9782
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
|
+
}
|
|
9783
9908
|
async readFile(path, opts) {
|
|
9784
9909
|
const r = await this.call("/read", { path }, void 0, void 0, opts?.span);
|
|
9785
9910
|
return truncate(String(r.content ?? ""));
|
|
@@ -10110,6 +10235,18 @@ async function parseResidentBody(res) {
|
|
|
10110
10235
|
}
|
|
10111
10236
|
return sanitizeResidentBody(data);
|
|
10112
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
|
+
}
|
|
10113
10250
|
var DETACH_TIMEOUT_MS, ResidentNeedsRefError, ResidentReuseRefusedError, ResidentOperations, ResidentExecutor;
|
|
10114
10251
|
var init_resident = __esm({
|
|
10115
10252
|
"../../src/execution/resident.ts"() {
|
|
@@ -10249,7 +10386,13 @@ var init_resident = __esm({
|
|
|
10249
10386
|
if (!res.ok) {
|
|
10250
10387
|
return { kind: "unreachable", error: `probe HTTP ${res.status}: ${String(data.error ?? "")}`, transport: false };
|
|
10251
10388
|
}
|
|
10252
|
-
|
|
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
|
+
};
|
|
10253
10396
|
}
|
|
10254
10397
|
/** POST one route; resource + threadKey ride in every body. Reads the FULL
|
|
10255
10398
|
* body as text before parsing: /exec streams heartbeat whitespace and then
|
|
@@ -10756,6 +10899,7 @@ async function makeExecutor(opts, ctx, span) {
|
|
|
10756
10899
|
return { executor: await makePerThreadExecutor(opts, perThreadCheckout(ctx)), backend: perThreadBackend(opts) };
|
|
10757
10900
|
}
|
|
10758
10901
|
let note;
|
|
10902
|
+
let reason2;
|
|
10759
10903
|
let failedAttach;
|
|
10760
10904
|
if (ctx.repo && opts.execution?.resident) {
|
|
10761
10905
|
const resident = opts.execution.resident;
|
|
@@ -10785,17 +10929,35 @@ async function makeExecutor(opts, ctx, span) {
|
|
|
10785
10929
|
} catch (err2) {
|
|
10786
10930
|
if (err2 instanceof ResidentNeedsRefError) throw err2;
|
|
10787
10931
|
failedAttach = residentTraceOf(err2);
|
|
10788
|
-
|
|
10789
|
-
`resident attach failed (${err2 instanceof Error ? err2.message : String(err2)}) \u2014 using fresh sandbox`
|
|
10790
|
-
);
|
|
10932
|
+
reason2 = oneLine(`resident attach failed (${err2 instanceof Error ? err2.message : String(err2)})`);
|
|
10791
10933
|
}
|
|
10792
10934
|
} else if (probe.kind === "unreachable") {
|
|
10793
|
-
|
|
10935
|
+
reason2 = oneLine(`resident unreachable (${probe.error})`);
|
|
10794
10936
|
} else if (probe.state !== "not-onboarded") {
|
|
10795
|
-
|
|
10937
|
+
reason2 = oneLine(`resident ${probe.state}${probe.reason ? ` (${probe.reason})` : ""}`);
|
|
10796
10938
|
} else {
|
|
10797
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`;
|
|
10798
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
|
+
}
|
|
10799
10961
|
}
|
|
10800
10962
|
return {
|
|
10801
10963
|
executor: await makePerThreadExecutor(opts, perThreadCheckout(ctx)),
|
|
@@ -10804,6 +10966,42 @@ async function makeExecutor(opts, ctx, span) {
|
|
|
10804
10966
|
...failedAttach ? { trace: failedAttach.steps } : {}
|
|
10805
10967
|
};
|
|
10806
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
|
+
}
|
|
10807
11005
|
async function reattachWorkspace(opts, ctx, recorded, span) {
|
|
10808
11006
|
const refuse3 = (why) => new WorkspaceReattachRefusedError(recorded, oneLine(why));
|
|
10809
11007
|
if (recorded.backend !== "resident") {
|
|
@@ -11028,6 +11226,7 @@ var init_factory = __esm({
|
|
|
11028
11226
|
init_executor();
|
|
11029
11227
|
init_e2b();
|
|
11030
11228
|
init_cloudflareSandbox();
|
|
11229
|
+
init_seedPlan();
|
|
11031
11230
|
init_resident();
|
|
11032
11231
|
init_residentAdmin();
|
|
11033
11232
|
init_githubApp();
|
|
@@ -25056,6 +25255,10 @@ function activityLine(e) {
|
|
|
25056
25255
|
return "";
|
|
25057
25256
|
}
|
|
25058
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
|
+
}
|
|
25059
25262
|
function attachmentSuffix(images, documents) {
|
|
25060
25263
|
const parts = [];
|
|
25061
25264
|
if (images && images.length > 0) parts.push(`${images.length} image${images.length === 1 ? "" : "s"}`);
|
|
@@ -30436,6 +30639,11 @@ var init_toolsets = __esm({
|
|
|
30436
30639
|
});
|
|
30437
30640
|
|
|
30438
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
|
+
}
|
|
30439
30647
|
function createCardShell(opts) {
|
|
30440
30648
|
let label = opts.label;
|
|
30441
30649
|
let link = opts.link;
|
|
@@ -30472,6 +30680,7 @@ function createCardShell(opts) {
|
|
|
30472
30680
|
return {
|
|
30473
30681
|
title: headline(glyph) + (setupLabel ? ` \u2014 ${setupLabel}` : "") + (parts.suffix ?? "") + (parts.notice ? ` \xB7 ${parts.notice}` : ""),
|
|
30474
30682
|
detail: detail || void 0,
|
|
30683
|
+
...parts.activity ? { activity: parts.activity } : {},
|
|
30475
30684
|
link
|
|
30476
30685
|
};
|
|
30477
30686
|
},
|
|
@@ -31200,6 +31409,12 @@ function reviewTargetBlock(t) {
|
|
|
31200
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."),
|
|
31201
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.`
|
|
31202
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
|
+
);
|
|
31203
31418
|
} else {
|
|
31204
31419
|
lines.push(
|
|
31205
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."),
|
|
@@ -31326,10 +31541,13 @@ async function guardAttachedHead(input) {
|
|
|
31326
31541
|
return { outcome: "unverified" };
|
|
31327
31542
|
}
|
|
31328
31543
|
function makeSystemComposer(input) {
|
|
31329
|
-
const { agent, resident, workspace, prTarget, blocks } = input;
|
|
31544
|
+
const { agent, resident, workspace, prTarget, blocks, seeded } = input;
|
|
31330
31545
|
const residentSystem = resident && agent.residentSystem ? `${agent.residentSystem}
|
|
31331
31546
|
|
|
31332
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;
|
|
31333
31551
|
const targetBlock = (head) => prTarget ? reviewTargetBlock({
|
|
31334
31552
|
repo: prTarget.repo,
|
|
31335
31553
|
pr: prTarget.pr,
|
|
@@ -31339,10 +31557,17 @@ Target repository: ${input.repo}. ` + (workspace ? `Your shell starts in the wor
|
|
|
31339
31557
|
...prTarget.size ? { size: prTarget.size } : {},
|
|
31340
31558
|
resident,
|
|
31341
31559
|
...workspace ? { workspace } : {},
|
|
31560
|
+
...!resident && seeded ? { seeded: { workspace: seeded.workspace } } : {},
|
|
31342
31561
|
...head.verified ? { verifiedAtAttach: true } : {}
|
|
31343
31562
|
}) : void 0;
|
|
31344
31563
|
const agentSystem = (head) => {
|
|
31345
|
-
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");
|
|
31346
31571
|
};
|
|
31347
31572
|
return (head) => [blocks.memory, blocks.notes, blocks.config, blocks.about, blocks.instructions, agentSystem(head)].filter((part) => Boolean(part)).join("\n\n");
|
|
31348
31573
|
}
|
|
@@ -32384,6 +32609,7 @@ function coalesceStatus(inner, minIntervalMs, now = Date.now, schedule = setTime
|
|
|
32384
32609
|
let closed = false;
|
|
32385
32610
|
const key = (f) => `${f.title}
|
|
32386
32611
|
${f.detail ?? ""}
|
|
32612
|
+
${activityKey(f.activity)}
|
|
32387
32613
|
${f.link?.url ?? ""}
|
|
32388
32614
|
${f.link?.label ?? ""}`;
|
|
32389
32615
|
const flush = () => {
|
|
@@ -32418,9 +32644,11 @@ ${f.link?.label ?? ""}`;
|
|
|
32418
32644
|
}
|
|
32419
32645
|
};
|
|
32420
32646
|
}
|
|
32647
|
+
var activityKey;
|
|
32421
32648
|
var init_statusCoalescer = __esm({
|
|
32422
32649
|
"../../src/core/statusCoalescer.ts"() {
|
|
32423
32650
|
"use strict";
|
|
32651
|
+
activityKey = (a) => a === void 0 ? "" : a.kind === "command" ? `command:${a.tool}:${a.command}` : `line:${a.text}`;
|
|
32424
32652
|
}
|
|
32425
32653
|
});
|
|
32426
32654
|
|
|
@@ -32796,7 +33024,7 @@ async function composePrompt(deps, ctx) {
|
|
|
32796
33024
|
resume,
|
|
32797
33025
|
root
|
|
32798
33026
|
} = ctx;
|
|
32799
|
-
const { resident, binding } = selection;
|
|
33027
|
+
const { resident, binding, seeded } = selection;
|
|
32800
33028
|
const skillsBlock = deps.skills ? skillGuidanceBlock(deps.skills, agent.name) : void 0;
|
|
32801
33029
|
const mcpForRun = await root.span(
|
|
32802
33030
|
"dispatch.mcp_discovery",
|
|
@@ -32848,6 +33076,7 @@ async function composePrompt(deps, ctx) {
|
|
|
32848
33076
|
resident: resident === true,
|
|
32849
33077
|
repo: repoCtx.repo,
|
|
32850
33078
|
workspace: binding?.workspace,
|
|
33079
|
+
...seeded ? { seeded: { workspace: seeded.workspace } } : {},
|
|
32851
33080
|
prTarget: isPrReview && repoCtx.repo && repoCtx.pr !== void 0 ? {
|
|
32852
33081
|
repo: repoCtx.repo,
|
|
32853
33082
|
pr: repoCtx.pr,
|
|
@@ -37079,14 +37308,15 @@ async function runLoop(deps, ctx) {
|
|
|
37079
37308
|
const currentFrame = () => shell.live({
|
|
37080
37309
|
suffix: quietSuffix(clock4() - lastActivityAt, inFlightTool),
|
|
37081
37310
|
notice: shutdownNotice(),
|
|
37082
|
-
detail: [checklist,
|
|
37311
|
+
detail: [checklist],
|
|
37312
|
+
activity: lastActivity
|
|
37083
37313
|
});
|
|
37084
37314
|
const checklistAsLeft = () => checklist;
|
|
37085
37315
|
const checklistCheckedOff = () => checklist?.replace(/^(\s*)[○✱](?=\s)/gm, "$1\u2713");
|
|
37086
37316
|
const onProgress = (note) => {
|
|
37087
37317
|
console.log(`[note] ${msg.threadKey} ${note}`);
|
|
37088
37318
|
lastActivityAt = clock4();
|
|
37089
|
-
lastActivity = note;
|
|
37319
|
+
lastActivity = { kind: "line", text: note };
|
|
37090
37320
|
card.update(currentFrame());
|
|
37091
37321
|
};
|
|
37092
37322
|
let toolCalls = 0;
|
|
@@ -37106,8 +37336,8 @@ async function runLoop(deps, ctx) {
|
|
|
37106
37336
|
}
|
|
37107
37337
|
inFlightTool = inFlightToolAfter(inFlightTool, e);
|
|
37108
37338
|
lastActivityAt = clock4();
|
|
37109
|
-
lastActivity =
|
|
37110
|
-
console.log(`[tool] ${msg.threadKey} ${lastActivity}`);
|
|
37339
|
+
lastActivity = cardActivity(e);
|
|
37340
|
+
console.log(`[tool] ${msg.threadKey} ${activityText(lastActivity)}`);
|
|
37111
37341
|
card.update(currentFrame());
|
|
37112
37342
|
};
|
|
37113
37343
|
for (const s of mcpForRun?.servers ?? []) {
|
|
@@ -37756,6 +37986,7 @@ var init_runLoop = __esm({
|
|
|
37756
37986
|
init_llmOutput();
|
|
37757
37987
|
init_runRecord();
|
|
37758
37988
|
init_statusCardLabel();
|
|
37989
|
+
init_statusCardFrame();
|
|
37759
37990
|
init_secrets2();
|
|
37760
37991
|
init_record();
|
|
37761
37992
|
init_reply();
|
|
@@ -43059,21 +43290,27 @@ function render2(frame2) {
|
|
|
43059
43290
|
const title = escapeMrkdwn(frame2.title);
|
|
43060
43291
|
const blocks = [{ type: "context", elements: [{ type: "mrkdwn", text: title }] }];
|
|
43061
43292
|
const elements = [];
|
|
43293
|
+
const line3 = (text) => elements.push({ type: "text", text: elements.length > 0 ? `
|
|
43294
|
+
${text}` : text });
|
|
43062
43295
|
if (frame2.link) elements.push({ type: "link", url: frame2.link.url, text: frame2.link.label });
|
|
43063
|
-
if (frame2.detail)
|
|
43064
|
-
|
|
43065
|
-
|
|
43066
|
-
${
|
|
43067
|
-
|
|
43068
|
-
if (elements.length > 0) {
|
|
43069
|
-
|
|
43070
|
-
|
|
43071
|
-
|
|
43072
|
-
});
|
|
43073
|
-
}
|
|
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 });
|
|
43074
43305
|
return { text: title, blocks };
|
|
43075
43306
|
}
|
|
43076
|
-
|
|
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;
|
|
43077
43314
|
var init_statusCard = __esm({
|
|
43078
43315
|
"../../src/channels/slack/statusCard.ts"() {
|
|
43079
43316
|
"use strict";
|
|
@@ -43083,6 +43320,8 @@ var init_statusCard = __esm({
|
|
|
43083
43320
|
liveCards = /* @__PURE__ */ new Set();
|
|
43084
43321
|
liveCardKey = (channel, ts) => `${channel}:${ts}`;
|
|
43085
43322
|
foreignLiveCards = /* @__PURE__ */ new Set();
|
|
43323
|
+
COMMAND_LINES_MAX = 6;
|
|
43324
|
+
COMMAND_CHARS_MAX = 600;
|
|
43086
43325
|
}
|
|
43087
43326
|
});
|
|
43088
43327
|
|
|
@@ -49360,6 +49599,7 @@ init_threadsElsewhere();
|
|
|
49360
49599
|
init_memory();
|
|
49361
49600
|
init_residentAdmin();
|
|
49362
49601
|
init_residentFleet();
|
|
49602
|
+
init_statusCardFrame();
|
|
49363
49603
|
init_piAi();
|
|
49364
49604
|
init_piHarness();
|
|
49365
49605
|
init_relay();
|
|
@@ -49563,7 +49803,7 @@ var ConsoleIO = class _ConsoleIO {
|
|
|
49563
49803
|
console.error(this.prefix + initial.title);
|
|
49564
49804
|
return {
|
|
49565
49805
|
update: (f) => console.error(
|
|
49566
|
-
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(" | ")
|
|
49567
49807
|
),
|
|
49568
49808
|
done: async (f) => console.error(this.prefix + f.title)
|
|
49569
49809
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coreplane/switchboard",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.239.0",
|
|
4
4
|
"description": "Mention it in Slack and an agent reviews the PR, ships the fix, or answers the question — on the model you choose, with its tools running where you decide.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://openswitchboard.dev",
|