@coreplane/switchboard 1.216.2 → 1.218.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/config/config.example.yaml +6 -0
- package/dist/assets/deploy/cloudflare/package.json +1 -1
- package/dist/assets/deploy/cloudflare/worker.ts +4 -0
- package/dist/assets/deploy/cloudflare-docs/package.json +1 -1
- package/dist/assets/deploy/cloudflare-memory/package.json +1 -1
- package/dist/assets/deploy/cloudflare-memory/worker.ts +96 -6
- package/dist/assets/deploy/cloudflare-resident/package.json +1 -1
- package/dist/assets/deploy/cloudflare-sandbox/package.json +1 -1
- package/dist/assets/deploy/secrets.manifest.json +12 -0
- package/dist/assets/package-lock.json +5676 -7966
- package/dist/assets/package.json +2 -2
- package/dist/assets/source.json +3 -3
- package/dist/assets/src/agents/registry.ts +18 -0
- package/dist/assets/src/core/runEvents.ts +13 -0
- package/dist/assets/src/core/runFriction.ts +4 -2
- package/dist/assets/src/core/runLedger/sessionLog.ts +22 -0
- package/dist/assets/src/core/runLedger/types.ts +17 -0
- package/dist/assets/src/core/runRecord.ts +4 -2
- package/dist/assets/src/mcp/registry.ts +33 -5
- package/dist/assets/web/dist/.vite/manifest.json +18 -18
- package/dist/assets/web/dist/assets/{ResidentDetailPage-CvwjhjlG.js → ResidentDetailPage-DvCt96Dk.js} +1 -1
- package/dist/assets/web/dist/assets/{ResidentsIndexPage-DoN6XuNx.js → ResidentsIndexPage-BZaYja1-.js} +1 -1
- package/dist/assets/web/dist/assets/{RunRoutePage-FzLNexjF.js → RunRoutePage-DI4vzCUu.js} +4 -4
- package/dist/assets/web/dist/assets/{RunsIndexPage-DhSUbF9k.js → RunsIndexPage-Ch9uP4p4.js} +1 -1
- package/dist/assets/web/dist/assets/{ScheduledPage-Bq0Yg4nT.js → ScheduledPage-DAkAHMCH.js} +1 -1
- package/dist/assets/web/dist/assets/{StatusDot-D8Wwt1KC.js → StatusDot-CfBonGcV.js} +1 -1
- package/dist/assets/web/dist/assets/{Tooltip-DpDK7jWZ.js → Tooltip-CBD36E3W.js} +1 -1
- package/dist/assets/web/dist/assets/dist-DQ1U73LF.js +1542 -0
- package/dist/assets/web/dist/assets/{main-BFkEOy3K.js → main-CdLnG-yl.js} +2 -2
- package/dist/cli.js +1066 -343
- package/package.json +1 -1
- package/dist/assets/web/dist/assets/dist-B7BVkB7x.js +0 -1547
package/dist/cli.js
CHANGED
|
@@ -206,6 +206,9 @@ async function ensureWorkArea(at, version2, workers, deps) {
|
|
|
206
206
|
}
|
|
207
207
|
return { ok: true, copied: plan3.copy, installed: plan3.install };
|
|
208
208
|
}
|
|
209
|
+
function checkoutInstallHolds(root, dir, exists) {
|
|
210
|
+
return exists(join3(root, dir, "node_modules", "wrangler")) || exists(join3(root, "node_modules", "wrangler"));
|
|
211
|
+
}
|
|
209
212
|
function imageBuiltOutsideDir(renderedConfig) {
|
|
210
213
|
const code = renderedConfig.split("\n").filter((l) => !l.trimStart().startsWith("//")).join("\n");
|
|
211
214
|
for (const m of code.matchAll(/"image"\s*:\s*"([^"]+)"/g)) {
|
|
@@ -1796,7 +1799,7 @@ function getAgent(name) {
|
|
|
1796
1799
|
}
|
|
1797
1800
|
return a;
|
|
1798
1801
|
}
|
|
1799
|
-
var MACHINE_CLASSES, IDENTITIES, HARNESSES, RUNAWAY_TURNS_PER_MINUTE, PR_DESCRIPTION_TEMPLATE, NEVER_MERGE, CONTRACT_HEADINGS_LIST, UNIT_CONTRACT, UNIT_HANDOFF, IMAGE_TOOLCHAIN, SANDBOX_TOOLCHAIN, RESIDENT_TOOLCHAIN, SHOW_FILES, 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;
|
|
1802
|
+
var MACHINE_CLASSES, IDENTITIES, HARNESSES, RUNAWAY_TURNS_PER_MINUTE, PR_DESCRIPTION_TEMPLATE, NEVER_MERGE, CONTRACT_HEADINGS_LIST, UNIT_CONTRACT, UNIT_HANDOFF, IMAGE_TOOLCHAIN, SANDBOX_TOOLCHAIN, RESIDENT_TOOLCHAIN, SHOW_FILES, 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;
|
|
1800
1803
|
var init_registry = __esm({
|
|
1801
1804
|
"../../src/agents/registry.ts"() {
|
|
1802
1805
|
"use strict";
|
|
@@ -1827,6 +1830,8 @@ SCREENSHOTS GO TO BOTH PLACES, ALL OF THEM: when the request asks for screenshot
|
|
|
1827
1830
|
Whole files: up to 1 GiB where the artifact store is configured (a recording, a large PDF), 10 MiB otherwise \u2014 the tool's result says which applies; over the limit, link to the file.
|
|
1828
1831
|
Files the person dropped on the thread that were too large to show you inline are already in ./attachments/ in your workspace when the turn's text names them (a video for ffmpeg, a large PDF, a zip); read them from there \u2014 never ask for a re-upload.
|
|
1829
1832
|
Text stays in your message; do not attach what you can say.`;
|
|
1833
|
+
NOTEPAD = `YOUR NOTES AND YOUR REACH BACK. This thread's conversation outlives your context window and this run: every turn \u2014 yours, the person's, every tool call and its output, from this run and the runs before it in this thread \u2014 is kept in a log you can search with the \`recall\` tool (words \u2192 the matching turns with their numbers; a turn number \u2192 that turn whole). When something you need is no longer in front of you, recall it instead of redoing the work or guessing.
|
|
1834
|
+
Keep notes with the \`notes\` tool: one short document, replaced whole each time, at most 8 KiB \u2014 decisions and their reasons, the names of things you found (files, tests, commits, the head your tests were green at), what is not yet proven. They are the one thing sure to survive a compaction and to reach the next run in this thread: they ride your system prompt at its start and come back to you right after a compaction. Write them when you decide something worth keeping, not only at the end.`;
|
|
1830
1835
|
CODING_SYSTEM = `You are Switchboard's coding agent, operating from a Slack request.
|
|
1831
1836
|
|
|
1832
1837
|
You work inside a dedicated workspace directory with bash, read_file, and write_file tools. ${SANDBOX_TOOLCHAIN}
|
|
@@ -1856,6 +1861,8 @@ ${PR_DESCRIPTION_TEMPLATE}
|
|
|
1856
1861
|
|
|
1857
1862
|
${SHOW_FILES}
|
|
1858
1863
|
|
|
1864
|
+
${NOTEPAD}
|
|
1865
|
+
|
|
1859
1866
|
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 ("Clone repo and read the diff", "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.
|
|
1860
1867
|
|
|
1861
1868
|
If the request doesn't name a repository and you can't infer it, ask for it instead of guessing.
|
|
@@ -1890,6 +1897,8 @@ ${PR_DESCRIPTION_TEMPLATE}
|
|
|
1890
1897
|
|
|
1891
1898
|
${SHOW_FILES}
|
|
1892
1899
|
|
|
1900
|
+
${NOTEPAD}
|
|
1901
|
+
|
|
1893
1902
|
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.
|
|
1894
1903
|
|
|
1895
1904
|
Report outcomes faithfully: if tests fail or a step was skipped, say so plainly.
|
|
@@ -1922,6 +1931,8 @@ REVIEW THE PR'S OWN HEAD, NOTHING ELSE: the commit you read must be the PR's hea
|
|
|
1922
1931
|
|
|
1923
1932
|
${REVIEW_VERDICT_INSTRUCTION}
|
|
1924
1933
|
|
|
1934
|
+
${NOTEPAD}
|
|
1935
|
+
|
|
1925
1936
|
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.
|
|
1926
1937
|
|
|
1927
1938
|
Your final message is posted to Slack. Lead with a one-line verdict, then the findings.`;
|
|
@@ -1948,6 +1959,8 @@ REVIEW THE PR'S OWN HEAD, NOTHING ELSE: the commit you read must be the PR's hea
|
|
|
1948
1959
|
|
|
1949
1960
|
${REVIEW_VERDICT_INSTRUCTION}
|
|
1950
1961
|
|
|
1962
|
+
${NOTEPAD}
|
|
1963
|
+
|
|
1951
1964
|
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.
|
|
1952
1965
|
|
|
1953
1966
|
Your final message is posted to Slack. Lead with a one-line verdict, then the findings.`;
|
|
@@ -1981,6 +1994,8 @@ READ-ONLY: NEVER open a pull request, and never commit or push \u2014 no branch,
|
|
|
1981
1994
|
|
|
1982
1995
|
Maintain the user-facing status card with the update_status tool: post your plan as a checklist (\u25CB pending) once you have it, and update items as they start (\u2731) and finish (\u2713 \u2014 only after they actually happened). Items are short outcomes ("Clone and install", "Time the full suite"), never commands.
|
|
1983
1996
|
|
|
1997
|
+
${NOTEPAD}
|
|
1998
|
+
|
|
1984
1999
|
Report outcomes faithfully: a check you could not run is "could not check", never a guess. Use Slack-friendly formatting (no markdown headers; *bold*, bullets, code blocks \u2014 render the claim table as aligned rows inside a code block). Your final message is posted to Slack: lead with the overall verdict in one line, then the claim table, then what a follow-up should do.`;
|
|
1985
2000
|
COMPOUND_PRESET = "conductor";
|
|
1986
2001
|
WORK_PRESETS = {
|
|
@@ -2167,8 +2182,8 @@ var init_effort = __esm({
|
|
|
2167
2182
|
});
|
|
2168
2183
|
|
|
2169
2184
|
// ../../src/core/dashboardAuthConfig.ts
|
|
2170
|
-
function resolveDashboardAuthMode(
|
|
2171
|
-
if (
|
|
2185
|
+
function resolveDashboardAuthMode(configured3, accessConfigured) {
|
|
2186
|
+
if (configured3 !== void 0) return configured3;
|
|
2172
2187
|
return accessConfigured ? "access" : "none";
|
|
2173
2188
|
}
|
|
2174
2189
|
function tokenSubjectOf(actor) {
|
|
@@ -2603,6 +2618,11 @@ function splitCredentialKey(key) {
|
|
|
2603
2618
|
const name = key.slice(slash + 1);
|
|
2604
2619
|
return parseMcpScopeKey(scopeKey) && MCP_SERVER_NAME_RE.test(name) ? { scopeKey, name } : void 0;
|
|
2605
2620
|
}
|
|
2621
|
+
function isHeadersEnv(v) {
|
|
2622
|
+
if (!v || typeof v !== "object" || Array.isArray(v)) return false;
|
|
2623
|
+
const entries = Object.entries(v);
|
|
2624
|
+
return entries.length > 0 && entries.length <= MCP_HEADERS_MAX && entries.every(([name, envVar]) => isStr(name, 64) && isStr(envVar, 128));
|
|
2625
|
+
}
|
|
2606
2626
|
function isOutcome(v) {
|
|
2607
2627
|
if (!v || typeof v !== "object" || Array.isArray(v)) return false;
|
|
2608
2628
|
const o = v;
|
|
@@ -2611,7 +2631,7 @@ function isOutcome(v) {
|
|
|
2611
2631
|
function isMcpServerEntry(v) {
|
|
2612
2632
|
if (!v || typeof v !== "object" || Array.isArray(v)) return false;
|
|
2613
2633
|
const e = v;
|
|
2614
|
-
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.addedBy === void 0 || isStr(e.addedBy, 260)) && (e.addedAt === void 0 || isNum(e.addedAt));
|
|
2634
|
+
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));
|
|
2615
2635
|
}
|
|
2616
2636
|
function isSealedCredential(v) {
|
|
2617
2637
|
if (!v || typeof v !== "object") return false;
|
|
@@ -2626,7 +2646,7 @@ function isMcpTicket(v) {
|
|
|
2626
2646
|
}
|
|
2627
2647
|
function serverView(scopeKey, name, entry, opts) {
|
|
2628
2648
|
const parsed = parseMcpScopeKey(scopeKey);
|
|
2629
|
-
const state = entry.auth === "none" ? "connected" : entry.tokenEnv ? "static" : opts.hasCredential ? "connected" : "awaiting_credential";
|
|
2649
|
+
const state = entry.auth === "none" ? entry.headersEnv ? "static" : "connected" : entry.tokenEnv ? "static" : opts.hasCredential ? "connected" : "awaiting_credential";
|
|
2630
2650
|
return {
|
|
2631
2651
|
name,
|
|
2632
2652
|
scope: parsed?.kind ?? "org",
|
|
@@ -2648,7 +2668,7 @@ function safeUrl(raw) {
|
|
|
2648
2668
|
return "(invalid url)";
|
|
2649
2669
|
}
|
|
2650
2670
|
}
|
|
2651
|
-
var MCP_TICKET_STATES, MCP_TICKET_TTL_MS, MCP_SERVER_NAME_MAX, MCP_SERVER_NAME_RE, MCP_URL_MAX, MCP_AGENTS_MAX, MCP_SELF_SERVE_AGENTS, MCP_TOKEN_MAX_CHARS, MCP_SERVERS_PER_SCOPE_MAX, isStr, isNum;
|
|
2671
|
+
var MCP_TICKET_STATES, MCP_TICKET_TTL_MS, MCP_SERVER_NAME_MAX, MCP_SERVER_NAME_RE, MCP_URL_MAX, MCP_AGENTS_MAX, MCP_SELF_SERVE_AGENTS, MCP_TOKEN_MAX_CHARS, MCP_SERVERS_PER_SCOPE_MAX, isStr, isNum, MCP_HEADERS_MAX, MCP_HEADER_NAME_RE;
|
|
2652
2672
|
var init_registry2 = __esm({
|
|
2653
2673
|
"../../src/mcp/registry.ts"() {
|
|
2654
2674
|
"use strict";
|
|
@@ -2669,6 +2689,8 @@ var init_registry2 = __esm({
|
|
|
2669
2689
|
MCP_SERVERS_PER_SCOPE_MAX = 32;
|
|
2670
2690
|
isStr = (v, max = 4096) => typeof v === "string" && v.length > 0 && v.length <= max;
|
|
2671
2691
|
isNum = (v) => typeof v === "number" && Number.isFinite(v);
|
|
2692
|
+
MCP_HEADERS_MAX = 8;
|
|
2693
|
+
MCP_HEADER_NAME_RE = /^[!#$%&'*+.^_`|~0-9A-Za-z-]{1,64}$/;
|
|
2672
2694
|
}
|
|
2673
2695
|
});
|
|
2674
2696
|
|
|
@@ -2753,7 +2775,9 @@ function validateMcpServers(layer, source2) {
|
|
|
2753
2775
|
`${source2}: ${path}.${name}: server names are slugs (lowercase letters, digits, dashes; \u2264 ${MCP_SERVER_NAME_MAX} chars)`
|
|
2754
2776
|
);
|
|
2755
2777
|
if (!isMcpServerEntry(raw))
|
|
2756
|
-
throw new Error(
|
|
2778
|
+
throw new Error(
|
|
2779
|
+
`${source2}: ${path}.${name} must be { url, auth: none|bearer|oauth, agents?, tokenEnv?, headersEnv? }`
|
|
2780
|
+
);
|
|
2757
2781
|
try {
|
|
2758
2782
|
assertUrlAllowed(raw.url);
|
|
2759
2783
|
} catch (err2) {
|
|
@@ -2774,6 +2798,14 @@ function validateMcpServers(layer, source2) {
|
|
|
2774
2798
|
}
|
|
2775
2799
|
if (raw.tokenEnv !== void 0 && raw.auth !== "bearer")
|
|
2776
2800
|
throw new Error(`${source2}: ${path}.${name}.tokenEnv only applies to auth: bearer`);
|
|
2801
|
+
for (const header4 of Object.keys(raw.headersEnv ?? {})) {
|
|
2802
|
+
if (!MCP_HEADER_NAME_RE.test(header4))
|
|
2803
|
+
throw new Error(`${source2}: ${path}.${name}.headersEnv: "${header4}" is not an HTTP header name`);
|
|
2804
|
+
if (header4.toLowerCase() === "authorization")
|
|
2805
|
+
throw new Error(
|
|
2806
|
+
`${source2}: ${path}.${name}.headersEnv: the Authorization header is \`auth\`'s \u2014 use auth: bearer with tokenEnv`
|
|
2807
|
+
);
|
|
2808
|
+
}
|
|
2777
2809
|
}
|
|
2778
2810
|
};
|
|
2779
2811
|
check("defaults.mcpServers", "org", layer.defaults?.mcpServers);
|
|
@@ -4989,7 +5021,7 @@ function planDeploy(opts, host, loaded, images) {
|
|
|
4989
5021
|
function formatPlan(plan3) {
|
|
4990
5022
|
const missing = plan3.checks.nodeModulesMissing;
|
|
4991
5023
|
const checkout = plan3.root.mode === "checkout";
|
|
4992
|
-
const nodeModules = missing.length === 0 ? "node_modules present in every dir" : checkout ? `
|
|
5024
|
+
const nodeModules = missing.length === 0 ? "node_modules present in every dir" : checkout ? `install missing for ${missing.join(", ")} \u2014 the runner will \`npm ci\` at the checkout's root first` : `${missing.join(", ")} not installed under ${WORK_AREA_DIR}/ \u2014 the runner will \`npm ci\` them there first`;
|
|
4993
5025
|
const tree = checkout ? `${plan3.checks.cleanTree ? "clean tree" : "tree unchecked"}; ${plan3.checks.atOriginMain ? "HEAD == origin/main" : "any branch (--allow-branch)"}` : `Worker sources are the package's (version ${plan3.root.version ?? "?"}), materialised under ${WORK_AREA_DIR}/ \u2014 no git`;
|
|
4994
5026
|
const lines = [
|
|
4995
5027
|
...plan3.affected ? [formatAffectedText(plan3.affected)] : [],
|
|
@@ -6241,7 +6273,7 @@ var init_sandboxLiveGate = __esm({
|
|
|
6241
6273
|
// ../../src/deploy/run.ts
|
|
6242
6274
|
import { spawn as spawn3 } from "node:child_process";
|
|
6243
6275
|
import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "node:fs";
|
|
6244
|
-
import { dirname as dirname4, isAbsolute as isAbsolute3
|
|
6276
|
+
import { dirname as dirname4, isAbsolute as isAbsolute3 } from "node:path";
|
|
6245
6277
|
function run(cmd, args, opts) {
|
|
6246
6278
|
const env = { ...process.env, ...opts.set ?? {} };
|
|
6247
6279
|
for (const k of opts.unset ?? []) delete env[k];
|
|
@@ -6348,7 +6380,7 @@ function packageModeProblems(plan3) {
|
|
|
6348
6380
|
return problems;
|
|
6349
6381
|
}
|
|
6350
6382
|
function hasNodeModules(dir) {
|
|
6351
|
-
if (OPERATOR_ROOT.mode === "checkout") return
|
|
6383
|
+
if (OPERATOR_ROOT.mode === "checkout") return checkoutInstallHolds(OPERATOR_ROOT.root, dir, existsSync5);
|
|
6352
6384
|
const state = readWorkAreaState(OPERATOR_ROOT.workArea);
|
|
6353
6385
|
return state.kind === "stamped" && state.stamp.installed.includes(dir);
|
|
6354
6386
|
}
|
|
@@ -6514,10 +6546,10 @@ async function pushConfigOnHost(opts) {
|
|
|
6514
6546
|
return pushConfigDocument(read, { stateWorkerUrl: opts.stateWorkerUrl, key: opts.key, env: process.env });
|
|
6515
6547
|
}
|
|
6516
6548
|
async function ensureNodeModules(step, io) {
|
|
6517
|
-
const dir = workerDir(step.dir);
|
|
6518
6549
|
if (hasNodeModules(step.dir)) return true;
|
|
6519
|
-
|
|
6520
|
-
|
|
6550
|
+
const cwd = OPERATOR_ROOT.mode === "checkout" ? OPERATOR_ROOT.root : workerDir(step.dir);
|
|
6551
|
+
io.log(`[deploy:all] ${step.name}: install missing \u2014 npm ci in ${cwd}`);
|
|
6552
|
+
const r = await run("npm", ["ci", "--silent"], { cwd });
|
|
6521
6553
|
if (r.code !== 0) io.warn(r.output);
|
|
6522
6554
|
return r.code === 0;
|
|
6523
6555
|
}
|
|
@@ -7582,6 +7614,159 @@ var init_imagesHost = __esm({
|
|
|
7582
7614
|
}
|
|
7583
7615
|
});
|
|
7584
7616
|
|
|
7617
|
+
// ../../src/deploy/artifactsBucket.ts
|
|
7618
|
+
function lifecycleRulesFor(retentionDays) {
|
|
7619
|
+
if (!Number.isInteger(retentionDays) || retentionDays < 1) {
|
|
7620
|
+
throw new Error(`retentionDays must be an integer >= 1, got ${String(retentionDays)}`);
|
|
7621
|
+
}
|
|
7622
|
+
return [
|
|
7623
|
+
{
|
|
7624
|
+
id: EXPIRE_RULE_ID,
|
|
7625
|
+
enabled: true,
|
|
7626
|
+
conditions: { prefix: "" },
|
|
7627
|
+
deleteObjectsTransition: { condition: { type: "Age", maxAge: retentionDays * DAY_SECONDS } }
|
|
7628
|
+
},
|
|
7629
|
+
{
|
|
7630
|
+
id: ABORT_RULE_ID,
|
|
7631
|
+
enabled: true,
|
|
7632
|
+
conditions: { prefix: "" },
|
|
7633
|
+
abortMultipartUploadsTransition: { condition: { type: "Age", maxAge: ABORT_MULTIPART_AFTER_DAYS * DAY_SECONDS } }
|
|
7634
|
+
}
|
|
7635
|
+
];
|
|
7636
|
+
}
|
|
7637
|
+
function describeRules(rules) {
|
|
7638
|
+
return rules.map((r) => {
|
|
7639
|
+
const days = (s) => `${s / DAY_SECONDS} day${s === DAY_SECONDS ? "" : "s"}`;
|
|
7640
|
+
if (r.deleteObjectsTransition)
|
|
7641
|
+
return `${r.id}: delete every object ${days(r.deleteObjectsTransition.condition.maxAge)} after it was written`;
|
|
7642
|
+
if (r.abortMultipartUploadsTransition)
|
|
7643
|
+
return `${r.id}: abort an incomplete multipart upload after ${days(r.abortMultipartUploadsTransition.condition.maxAge)}`;
|
|
7644
|
+
return `${r.id}: (no transition)`;
|
|
7645
|
+
});
|
|
7646
|
+
}
|
|
7647
|
+
function rulesMatch(readBack, wanted) {
|
|
7648
|
+
const rules = Array.isArray(readBack?.rules) ? readBack.rules : null;
|
|
7649
|
+
if (!rules) return { ok: false, problem: "the read-back carries no `rules` array" };
|
|
7650
|
+
const problems = [];
|
|
7651
|
+
for (const want of wanted) {
|
|
7652
|
+
const got = rules.find((r) => r?.id === want.id);
|
|
7653
|
+
if (!got) {
|
|
7654
|
+
problems.push(`rule ${want.id} is missing`);
|
|
7655
|
+
continue;
|
|
7656
|
+
}
|
|
7657
|
+
if (got.enabled !== true) problems.push(`rule ${want.id} is not enabled`);
|
|
7658
|
+
const wantAge = want.deleteObjectsTransition?.condition.maxAge ?? want.abortMultipartUploadsTransition?.condition.maxAge;
|
|
7659
|
+
const gotAge = got.deleteObjectsTransition?.condition?.maxAge ?? got.abortMultipartUploadsTransition?.condition?.maxAge;
|
|
7660
|
+
if (gotAge !== wantAge) problems.push(`rule ${want.id} has maxAge ${String(gotAge)}, not ${String(wantAge)}`);
|
|
7661
|
+
}
|
|
7662
|
+
return problems.length === 0 ? { ok: true } : { ok: false, problem: problems.join("; ") };
|
|
7663
|
+
}
|
|
7664
|
+
function privacyOf(managed, custom) {
|
|
7665
|
+
const open = [];
|
|
7666
|
+
if (managed.enabled) open.push(`the managed r2.dev domain ${managed.domain} is enabled`);
|
|
7667
|
+
for (const d of custom) if (d.enabled) open.push(`the custom domain ${d.domain} is enabled`);
|
|
7668
|
+
return open.length === 0 ? { private: true, open: [] } : { private: false, open };
|
|
7669
|
+
}
|
|
7670
|
+
function bucketTokenMissingProblem() {
|
|
7671
|
+
return `${API_TOKEN_ENV} is not set \u2014 the bucket's lifecycle rules and domain settings are read and written with a Cloudflare API token holding Workers R2 Storage: Edit (Read suffices for \`artifacts check\`); the bot's ARTIFACTS_R2_* token cannot do this and is never used here`;
|
|
7672
|
+
}
|
|
7673
|
+
function bucketCallProblem(method, url, status3, text) {
|
|
7674
|
+
const words = text.trim().slice(0, 300);
|
|
7675
|
+
const permission = status3 === 401 || status3 === 403 ? ` \u2014 the token lacks the permission (Workers R2 Storage: ${method === "GET" ? "Read" : "Edit"} on this account)` : "";
|
|
7676
|
+
return `${method} ${url} answered HTTP ${status3}${permission}${words ? `: ${words}` : ""}`;
|
|
7677
|
+
}
|
|
7678
|
+
function artifactsBucketHost(options = {}) {
|
|
7679
|
+
const env = options.env ?? process.env;
|
|
7680
|
+
const fetchImpl = options.fetch ?? fetch;
|
|
7681
|
+
const call2 = async (method, path, body) => {
|
|
7682
|
+
const token2 = env[API_TOKEN_ENV]?.trim();
|
|
7683
|
+
if (!token2) return { ok: false, problem: bucketTokenMissingProblem() };
|
|
7684
|
+
const url = `${API}${path}`;
|
|
7685
|
+
let res;
|
|
7686
|
+
try {
|
|
7687
|
+
res = await fetchImpl(url, {
|
|
7688
|
+
method,
|
|
7689
|
+
headers: {
|
|
7690
|
+
authorization: `Bearer ${token2}`,
|
|
7691
|
+
...body !== void 0 ? { "content-type": "application/json" } : {}
|
|
7692
|
+
},
|
|
7693
|
+
...body !== void 0 ? { body: JSON.stringify(body) } : {}
|
|
7694
|
+
});
|
|
7695
|
+
} catch (err2) {
|
|
7696
|
+
return { ok: false, problem: `${method} ${url} failed \u2014 ${err2 instanceof Error ? err2.message : String(err2)}` };
|
|
7697
|
+
}
|
|
7698
|
+
const text = await res.text().catch(() => "");
|
|
7699
|
+
if (!res.ok) return { ok: false, problem: bucketCallProblem(method, url, res.status, text) };
|
|
7700
|
+
let envelope;
|
|
7701
|
+
try {
|
|
7702
|
+
envelope = JSON.parse(text);
|
|
7703
|
+
} catch {
|
|
7704
|
+
return { ok: false, problem: `${method} ${url} answered HTTP ${res.status} with a body that is not JSON` };
|
|
7705
|
+
}
|
|
7706
|
+
if (envelope.success !== true) {
|
|
7707
|
+
const errors = Array.isArray(envelope.errors) ? envelope.errors.map((e) => e?.message ?? JSON.stringify(e)).join("; ") : "no error given";
|
|
7708
|
+
return { ok: false, problem: `${method} ${url} answered success: false \u2014 ${errors}` };
|
|
7709
|
+
}
|
|
7710
|
+
return { ok: true, value: envelope.result };
|
|
7711
|
+
};
|
|
7712
|
+
const bucketPath = (account2, bucket, tail) => `/accounts/${encodeURIComponent(account2)}/r2/buckets/${encodeURIComponent(bucket)}/${tail}`;
|
|
7713
|
+
return {
|
|
7714
|
+
async putLifecycle(account2, bucket, rules) {
|
|
7715
|
+
const r = await call2("PUT", bucketPath(account2, bucket, "lifecycle"), { rules });
|
|
7716
|
+
return r.ok ? { ok: true, value: void 0 } : r;
|
|
7717
|
+
},
|
|
7718
|
+
getLifecycle(account2, bucket) {
|
|
7719
|
+
return call2("GET", bucketPath(account2, bucket, "lifecycle"));
|
|
7720
|
+
},
|
|
7721
|
+
async managedDomain(account2, bucket) {
|
|
7722
|
+
const r = await call2("GET", bucketPath(account2, bucket, "domains/managed"));
|
|
7723
|
+
if (!r.ok) return r;
|
|
7724
|
+
const v = r.value;
|
|
7725
|
+
if (!v || typeof v.domain !== "string" || typeof v.enabled !== "boolean") {
|
|
7726
|
+
return {
|
|
7727
|
+
ok: false,
|
|
7728
|
+
problem: `the managed-domain answer has no domain/enabled: ${JSON.stringify(r.value).slice(0, 200)}`
|
|
7729
|
+
};
|
|
7730
|
+
}
|
|
7731
|
+
return { ok: true, value: { domain: v.domain, enabled: v.enabled } };
|
|
7732
|
+
},
|
|
7733
|
+
async customDomains(account2, bucket) {
|
|
7734
|
+
const r = await call2("GET", bucketPath(account2, bucket, "domains/custom"));
|
|
7735
|
+
if (!r.ok) return r;
|
|
7736
|
+
const list = r.value?.domains;
|
|
7737
|
+
if (!Array.isArray(list)) {
|
|
7738
|
+
return {
|
|
7739
|
+
ok: false,
|
|
7740
|
+
problem: `the custom-domains answer has no domains array: ${JSON.stringify(r.value).slice(0, 200)}`
|
|
7741
|
+
};
|
|
7742
|
+
}
|
|
7743
|
+
const domains = [];
|
|
7744
|
+
for (const d of list) {
|
|
7745
|
+
if (typeof d?.domain !== "string" || typeof d.enabled !== "boolean") {
|
|
7746
|
+
return {
|
|
7747
|
+
ok: false,
|
|
7748
|
+
problem: `a custom domain entry has no domain/enabled: ${JSON.stringify(d).slice(0, 200)}`
|
|
7749
|
+
};
|
|
7750
|
+
}
|
|
7751
|
+
domains.push({ domain: d.domain, enabled: d.enabled });
|
|
7752
|
+
}
|
|
7753
|
+
return { ok: true, value: domains };
|
|
7754
|
+
}
|
|
7755
|
+
};
|
|
7756
|
+
}
|
|
7757
|
+
var ABORT_MULTIPART_AFTER_DAYS, DAY_SECONDS, EXPIRE_RULE_ID, ABORT_RULE_ID, API;
|
|
7758
|
+
var init_artifactsBucket = __esm({
|
|
7759
|
+
"../../src/deploy/artifactsBucket.ts"() {
|
|
7760
|
+
"use strict";
|
|
7761
|
+
init_imagesHost();
|
|
7762
|
+
ABORT_MULTIPART_AFTER_DAYS = 1;
|
|
7763
|
+
DAY_SECONDS = 86400;
|
|
7764
|
+
EXPIRE_RULE_ID = "switchboard-artifacts-expire";
|
|
7765
|
+
ABORT_RULE_ID = "switchboard-artifacts-abort-multipart";
|
|
7766
|
+
API = "https://api.cloudflare.com/client/v4";
|
|
7767
|
+
}
|
|
7768
|
+
});
|
|
7769
|
+
|
|
7585
7770
|
// ../../src/deploy/secrets.ts
|
|
7586
7771
|
import { z as z3 } from "zod";
|
|
7587
7772
|
function isOptionalOn(secret2, worker) {
|
|
@@ -7671,9 +7856,9 @@ var init_secrets = __esm({
|
|
|
7671
7856
|
import { spawn as spawn4 } from "node:child_process";
|
|
7672
7857
|
import { existsSync as existsSync6, readFileSync as readFileSync7 } from "node:fs";
|
|
7673
7858
|
import { homedir as homedir2 } from "node:os";
|
|
7674
|
-
import { isAbsolute as isAbsolute4, join as
|
|
7859
|
+
import { isAbsolute as isAbsolute4, join as join5, resolve as resolve4 } from "node:path";
|
|
7675
7860
|
function expandDir(path, root = OPERATOR_ROOT.root) {
|
|
7676
|
-
if (path === "~" || path.startsWith("~/")) return
|
|
7861
|
+
if (path === "~" || path.startsWith("~/")) return join5(homedir2(), path.slice(1));
|
|
7677
7862
|
if (isAbsolute4(path)) return path;
|
|
7678
7863
|
return resolve4(root, path);
|
|
7679
7864
|
}
|
|
@@ -7717,7 +7902,7 @@ async function opFieldLabels(source2) {
|
|
|
7717
7902
|
}
|
|
7718
7903
|
function wranglerBin(dir, workspaceRoot, exists = existsSync6) {
|
|
7719
7904
|
for (const base of [dir, workspaceRoot]) {
|
|
7720
|
-
const bin =
|
|
7905
|
+
const bin = join5(base, "node_modules", ".bin", "wrangler");
|
|
7721
7906
|
if (exists(bin)) return bin;
|
|
7722
7907
|
}
|
|
7723
7908
|
return "wrangler";
|
|
@@ -7738,7 +7923,7 @@ var init_secretsHost = __esm({
|
|
|
7738
7923
|
if (source2.kind === "dir") {
|
|
7739
7924
|
const dir = expandDir(source2.path);
|
|
7740
7925
|
if (!existsSync6(dir)) return { ok: false, problem: `secretsSource ${source2.path}: no such directory (${dir})` };
|
|
7741
|
-
return { ok: true, present: new Set(names.filter((n2) => existsSync6(
|
|
7926
|
+
return { ok: true, present: new Set(names.filter((n2) => existsSync6(join5(dir, n2)))) };
|
|
7742
7927
|
}
|
|
7743
7928
|
const labels = await opFieldLabels(source2);
|
|
7744
7929
|
if (!labels.ok) return labels;
|
|
@@ -7747,7 +7932,7 @@ var init_secretsHost = __esm({
|
|
|
7747
7932
|
put: async (source2, dir, name) => {
|
|
7748
7933
|
let value;
|
|
7749
7934
|
if (source2.kind === "dir") {
|
|
7750
|
-
value = readFileSync7(
|
|
7935
|
+
value = readFileSync7(join5(expandDir(source2.path), name), "utf8");
|
|
7751
7936
|
} else {
|
|
7752
7937
|
const r2 = await spawnCollect("op", ["read", `op://${source2.vault}/${source2.item}/${name}`]);
|
|
7753
7938
|
if (r2.code !== 0) return { code: r2.code, output: `op read exited ${r2.code} \u2014 ${r2.stderr.trim()}` };
|
|
@@ -7842,6 +8027,7 @@ var init_runEvents = __esm({
|
|
|
7842
8027
|
"mcp_unavailable",
|
|
7843
8028
|
"follow_up",
|
|
7844
8029
|
"resumed",
|
|
8030
|
+
"seed",
|
|
7845
8031
|
"description_turn",
|
|
7846
8032
|
"cold_sandbox",
|
|
7847
8033
|
"pr_not_opened",
|
|
@@ -7960,11 +8146,11 @@ var init_readingDiff = __esm({
|
|
|
7960
8146
|
// ../../src/core/meatProcess.ts
|
|
7961
8147
|
import { spawn as spawn5 } from "node:child_process";
|
|
7962
8148
|
import { accessSync, constants } from "node:fs";
|
|
7963
|
-
import { delimiter, join as
|
|
8149
|
+
import { delimiter, join as join6 } from "node:path";
|
|
7964
8150
|
function meatOnPath(env, binary = MEAT_BINARY) {
|
|
7965
8151
|
return (env.PATH ?? "").split(delimiter).filter((dir) => dir !== "").some((dir) => {
|
|
7966
8152
|
try {
|
|
7967
|
-
accessSync(
|
|
8153
|
+
accessSync(join6(dir, binary), constants.X_OK);
|
|
7968
8154
|
return true;
|
|
7969
8155
|
} catch {
|
|
7970
8156
|
return false;
|
|
@@ -8044,10 +8230,10 @@ import { chmod, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises"
|
|
|
8044
8230
|
import { randomBytes } from "node:crypto";
|
|
8045
8231
|
import { createInterface } from "node:readline/promises";
|
|
8046
8232
|
import { Writable } from "node:stream";
|
|
8047
|
-
import { dirname as dirname5, join as
|
|
8233
|
+
import { dirname as dirname5, join as join7, resolve as resolve5 } from "node:path";
|
|
8048
8234
|
async function readTemplates(root = PACKAGE_ROOT) {
|
|
8049
8235
|
const read = async (key) => {
|
|
8050
|
-
const path =
|
|
8236
|
+
const path = join7(root, TEMPLATE_PATHS[key]);
|
|
8051
8237
|
if (!existsSync7(path)) throw new Error(`${TEMPLATE_PATHS[key]}: no such file under ${root}`);
|
|
8052
8238
|
return readFile(path, "utf8");
|
|
8053
8239
|
};
|
|
@@ -8061,7 +8247,7 @@ function isCheckoutRoot(cwd = process.cwd(), root = PACKAGE_ROOT) {
|
|
|
8061
8247
|
}
|
|
8062
8248
|
}
|
|
8063
8249
|
async function writePlannedFile(file, cwd = process.cwd()) {
|
|
8064
|
-
const path =
|
|
8250
|
+
const path = join7(cwd, file.path);
|
|
8065
8251
|
await mkdir(dirname5(path), { recursive: true });
|
|
8066
8252
|
const temp = `${path}.${randomBytes(6).toString("hex")}.tmp`;
|
|
8067
8253
|
try {
|
|
@@ -8096,7 +8282,7 @@ function ttyPrompter(io = { stdin: process.stdin, stdout: process.stdout }) {
|
|
|
8096
8282
|
};
|
|
8097
8283
|
}
|
|
8098
8284
|
function projectFact(name, root) {
|
|
8099
|
-
const facts2 = JSON.parse(readFileSync8(
|
|
8285
|
+
const facts2 = JSON.parse(readFileSync8(join7(root, "project.json"), "utf8"));
|
|
8100
8286
|
const value = facts2[name];
|
|
8101
8287
|
if (typeof value !== "string") throw new Error(`project.json: no \`${name}\` fact`);
|
|
8102
8288
|
return value;
|
|
@@ -8112,7 +8298,7 @@ function hostSetupIO(cwd = OPERATOR_ROOT.root, startedIn = process.cwd()) {
|
|
|
8112
8298
|
templates: () => readTemplates(),
|
|
8113
8299
|
// Where the files went when that is not where the operator stands — `init` prints it.
|
|
8114
8300
|
root: () => resolve5(cwd) === resolve5(startedIn) ? void 0 : cwd,
|
|
8115
|
-
exists: async (path) => existsSync7(
|
|
8301
|
+
exists: async (path) => existsSync7(join7(cwd, path)),
|
|
8116
8302
|
write: (file) => writePlannedFile(file, cwd),
|
|
8117
8303
|
// A file the operator NAMED (`--github-private-key-file app.pem`) is where the operator stands,
|
|
8118
8304
|
// not in the installation: `init` run from ~/Downloads reads ~/Downloads/app.pem.
|
|
@@ -8168,9 +8354,9 @@ var init_binaryRead = __esm({
|
|
|
8168
8354
|
|
|
8169
8355
|
// ../../src/secrets.ts
|
|
8170
8356
|
import { readFileSync as readFileSync9 } from "node:fs";
|
|
8171
|
-
import { join as
|
|
8357
|
+
import { join as join8 } from "node:path";
|
|
8172
8358
|
function manifestNames() {
|
|
8173
|
-
const raw = JSON.parse(readFileSync9(
|
|
8359
|
+
const raw = JSON.parse(readFileSync9(join8(PACKAGE_ROOT, MANIFEST_PATH), "utf8"));
|
|
8174
8360
|
const parsed = parseManifest2(raw);
|
|
8175
8361
|
if (!parsed.ok) throw new Error(`${MANIFEST_PATH} is invalid \u2014 ${parsed.problems.join("; ")}`);
|
|
8176
8362
|
return parsed.manifest.secrets.map((s) => s.name);
|
|
@@ -11997,8 +12183,110 @@ var init_commandRegistry = __esm({
|
|
|
11997
12183
|
}
|
|
11998
12184
|
});
|
|
11999
12185
|
|
|
12000
|
-
// ../../src/core/commands/
|
|
12186
|
+
// ../../src/core/commands/artifacts.ts
|
|
12001
12187
|
import { z as z5 } from "zod";
|
|
12188
|
+
async function configured2(deps) {
|
|
12189
|
+
const cfg = await deps.artifacts.config();
|
|
12190
|
+
if (!cfg) {
|
|
12191
|
+
throw new CommandError(
|
|
12192
|
+
"unavailable",
|
|
12193
|
+
"artifacts: is not configured \u2014 name the bucket under `artifacts.r2` in config.yaml before managing it (docs/how-to/store-run-artifacts.md)"
|
|
12194
|
+
);
|
|
12195
|
+
}
|
|
12196
|
+
return cfg;
|
|
12197
|
+
}
|
|
12198
|
+
function registerArtifactsCommands(registry) {
|
|
12199
|
+
for (const cmd of artifactsCommands) registry.register(cmd);
|
|
12200
|
+
}
|
|
12201
|
+
var defineCommand2, lifecycleOptions, artifactsLifecycle, artifactsCheck, artifactsCommands;
|
|
12202
|
+
var init_artifacts = __esm({
|
|
12203
|
+
"../../src/core/commands/artifacts.ts"() {
|
|
12204
|
+
"use strict";
|
|
12205
|
+
init_config();
|
|
12206
|
+
init_artifactsBucket();
|
|
12207
|
+
init_commandRegistry();
|
|
12208
|
+
defineCommand2 = commandDefiner();
|
|
12209
|
+
lifecycleOptions = z5.object({
|
|
12210
|
+
dryRun: flag.optional().describe("print the rules that would be applied and touch nothing")
|
|
12211
|
+
});
|
|
12212
|
+
artifactsLifecycle = defineCommand2({
|
|
12213
|
+
id: "artifacts.lifecycle",
|
|
12214
|
+
options: lifecycleOptions,
|
|
12215
|
+
action: "deploy:write",
|
|
12216
|
+
effect: "write",
|
|
12217
|
+
surfaces: { chat: false, mcp: false, http: false },
|
|
12218
|
+
describe: "Apply the artifacts bucket's lifecycle rules from config.yaml \u2014 objects expire after `artifacts.retentionDays` (default 30), incomplete multipart uploads abort after one day \u2014 and read them back; `--dry-run` prints the rules and touches nothing. Operator-side: CLOUDFLARE_API_TOKEN with Workers R2 Storage: Edit, never the bot's token.",
|
|
12219
|
+
render: (output) => {
|
|
12220
|
+
const o = output;
|
|
12221
|
+
const lines = describeRules(o.rules).map((l) => ` - ${l}`);
|
|
12222
|
+
const head = o.applied ? `applied ${o.rules.length} lifecycle rule(s) to ${o.bucket} (account ${o.account}) and read them back equal:` : `dry run \u2014 would apply ${o.rules.length} lifecycle rule(s) to ${o.bucket} (account ${o.account}); nothing touched:`;
|
|
12223
|
+
return [head, ...lines].join("\n");
|
|
12224
|
+
},
|
|
12225
|
+
handler: async ({ options, deps }) => {
|
|
12226
|
+
const cfg = await configured2(deps);
|
|
12227
|
+
const retentionDays = cfg.retentionDays ?? ARTIFACT_DEFAULTS.retentionDays;
|
|
12228
|
+
const rules = lifecycleRulesFor(retentionDays);
|
|
12229
|
+
const { accountId: account2, bucket } = cfg.r2;
|
|
12230
|
+
const out = { account: account2, bucket, retentionDays, rules, applied: false, readBack: false };
|
|
12231
|
+
if (options.dryRun) return out;
|
|
12232
|
+
const put = await deps.artifacts.bucket.putLifecycle(account2, bucket, rules);
|
|
12233
|
+
if (!put.ok)
|
|
12234
|
+
throw new CommandError("unavailable", `applying the lifecycle rules to ${bucket} failed \u2014 ${put.problem}`);
|
|
12235
|
+
const read = await deps.artifacts.bucket.getLifecycle(account2, bucket);
|
|
12236
|
+
if (!read.ok)
|
|
12237
|
+
throw new CommandError(
|
|
12238
|
+
"unavailable",
|
|
12239
|
+
`the rules were applied to ${bucket} but reading them back failed \u2014 ${read.problem}; run \`artifacts lifecycle\` again to confirm`
|
|
12240
|
+
);
|
|
12241
|
+
const match = rulesMatch(read.value, rules);
|
|
12242
|
+
if (!match.ok)
|
|
12243
|
+
throw new CommandError(
|
|
12244
|
+
"unavailable",
|
|
12245
|
+
`the rules were applied to ${bucket} but the read-back differs \u2014 ${match.problem}; the bucket's configuration is not what config.yaml says`
|
|
12246
|
+
);
|
|
12247
|
+
return { ...out, applied: true, readBack: true };
|
|
12248
|
+
}
|
|
12249
|
+
});
|
|
12250
|
+
artifactsCheck = defineCommand2({
|
|
12251
|
+
id: "artifacts.check",
|
|
12252
|
+
action: "deploy:write",
|
|
12253
|
+
effect: "read",
|
|
12254
|
+
surfaces: { chat: false, mcp: false, http: false },
|
|
12255
|
+
describe: "Report whether the artifacts bucket is private: its managed r2.dev domain must be disabled and no custom domain enabled \u2014 the two ways R2 serves a bucket without a signature. Operator-side: CLOUDFLARE_API_TOKEN with Workers R2 Storage: Read, never the bot's token.",
|
|
12256
|
+
render: (output) => {
|
|
12257
|
+
const o = output;
|
|
12258
|
+
if (o.private) {
|
|
12259
|
+
return `${o.bucket} (account ${o.account}) is private: the managed domain ${o.managedDomain.domain} is disabled and ${o.customDomains.length === 0 ? "no custom domain is attached" : `none of its ${o.customDomains.length} custom domain(s) is enabled`}`;
|
|
12260
|
+
}
|
|
12261
|
+
return [`${o.bucket} (account ${o.account}) is NOT private:`, ...o.open.map((l) => ` - ${l}`)].join("\n");
|
|
12262
|
+
},
|
|
12263
|
+
handler: async ({ deps }) => {
|
|
12264
|
+
const cfg = await configured2(deps);
|
|
12265
|
+
const { accountId: account2, bucket } = cfg.r2;
|
|
12266
|
+
const managed = await deps.artifacts.bucket.managedDomain(account2, bucket);
|
|
12267
|
+
if (!managed.ok)
|
|
12268
|
+
throw new CommandError("unavailable", `reading ${bucket}'s managed domain failed \u2014 ${managed.problem}`);
|
|
12269
|
+
const custom = await deps.artifacts.bucket.customDomains(account2, bucket);
|
|
12270
|
+
if (!custom.ok)
|
|
12271
|
+
throw new CommandError("unavailable", `reading ${bucket}'s custom domains failed \u2014 ${custom.problem}`);
|
|
12272
|
+
const verdict = privacyOf(managed.value, custom.value);
|
|
12273
|
+
const out = {
|
|
12274
|
+
account: account2,
|
|
12275
|
+
bucket,
|
|
12276
|
+
private: verdict.private,
|
|
12277
|
+
open: [...verdict.open],
|
|
12278
|
+
managedDomain: managed.value,
|
|
12279
|
+
customDomains: custom.value
|
|
12280
|
+
};
|
|
12281
|
+
return out;
|
|
12282
|
+
}
|
|
12283
|
+
});
|
|
12284
|
+
artifactsCommands = [artifactsLifecycle, artifactsCheck];
|
|
12285
|
+
}
|
|
12286
|
+
});
|
|
12287
|
+
|
|
12288
|
+
// ../../src/core/commands/config.ts
|
|
12289
|
+
import { z as z6 } from "zod";
|
|
12002
12290
|
function targetChannel(caller, channel) {
|
|
12003
12291
|
const target = channel ?? caller.origin?.channelId;
|
|
12004
12292
|
if (!target) throw new CommandError("invalid_input", "channel: required on this surface \u2014 pass --channel <id>");
|
|
@@ -12018,7 +12306,7 @@ function summarizeScope(s) {
|
|
|
12018
12306
|
function registerConfigCommands(registry) {
|
|
12019
12307
|
for (const cmd of configCommands) registry.register(cmd);
|
|
12020
12308
|
}
|
|
12021
|
-
var
|
|
12309
|
+
var defineCommand3, scopeArg, channelOption, effort, effortLevels, modelRef, boundaryOption, who, configShow, configSet, configClear, quote, configInstructions, configCommands;
|
|
12022
12310
|
var init_config4 = __esm({
|
|
12023
12311
|
"../../src/core/commands/config.ts"() {
|
|
12024
12312
|
"use strict";
|
|
@@ -12028,25 +12316,25 @@ var init_config4 = __esm({
|
|
|
12028
12316
|
init_effort();
|
|
12029
12317
|
init_authorize();
|
|
12030
12318
|
init_commandRegistry();
|
|
12031
|
-
|
|
12319
|
+
defineCommand3 = commandDefiner();
|
|
12032
12320
|
scopeArg = {
|
|
12033
12321
|
name: "scope",
|
|
12034
|
-
schema:
|
|
12322
|
+
schema: z6.enum(["channel", "me"]),
|
|
12035
12323
|
describe: "`channel` (everyone here) or `me` (your own runs)"
|
|
12036
12324
|
};
|
|
12037
|
-
channelOption =
|
|
12038
|
-
effort =
|
|
12325
|
+
channelOption = z6.string().optional().describe("target channel (default: the channel you are speaking in)");
|
|
12326
|
+
effort = z6.enum(EFFORT_LEVELS);
|
|
12039
12327
|
effortLevels = `<${EFFORT_LEVELS.join("|")}>`;
|
|
12040
|
-
modelRef =
|
|
12041
|
-
boundaryOption =
|
|
12042
|
-
maxMinutes:
|
|
12043
|
-
maxIdentity:
|
|
12044
|
-
machines:
|
|
12328
|
+
modelRef = z6.string().min(1);
|
|
12329
|
+
boundaryOption = z6.object({
|
|
12330
|
+
maxMinutes: z6.coerce.number().int().min(MIN_BOUNDARY_MINUTES).optional().describe(`cap the wall-clock budget of every run in this scope (minutes, >= ${MIN_BOUNDARY_MINUTES})`),
|
|
12331
|
+
maxIdentity: z6.enum(IDENTITIES).optional().describe(`cap the identity runs may act as <${IDENTITIES.join("|")}> \u2014 a preset above it is refused`),
|
|
12332
|
+
machines: z6.string().optional().describe(`the machine classes runs may execute on, comma-separated from ${MACHINE_CLASSES.join(", ")}`)
|
|
12045
12333
|
}).optional();
|
|
12046
12334
|
who = (scope) => scope === "channel" ? "channel" : "your";
|
|
12047
|
-
configShow =
|
|
12335
|
+
configShow = defineCommand3({
|
|
12048
12336
|
id: "config.show",
|
|
12049
|
-
options:
|
|
12337
|
+
options: z6.object({ channel: channelOption }),
|
|
12050
12338
|
action: "config:read",
|
|
12051
12339
|
effect: "read",
|
|
12052
12340
|
describe: "The effective agent/model/effort for you in this channel, the defaults, both scopes, and what is restricted.",
|
|
@@ -12060,15 +12348,15 @@ var init_config4 = __esm({
|
|
|
12060
12348
|
return description;
|
|
12061
12349
|
}
|
|
12062
12350
|
});
|
|
12063
|
-
configSet =
|
|
12351
|
+
configSet = defineCommand3({
|
|
12064
12352
|
id: "config.set",
|
|
12065
12353
|
args: [scopeArg],
|
|
12066
|
-
options:
|
|
12067
|
-
agent:
|
|
12354
|
+
options: z6.object({
|
|
12355
|
+
agent: z6.string().min(1).optional().describe("force which agent handles requests in this scope"),
|
|
12068
12356
|
model: modelRef.optional().describe("force a model (provider/model) regardless of agent"),
|
|
12069
|
-
models:
|
|
12357
|
+
models: z6.record(z6.string(), modelRef).optional().describe("per-agent model: --models.<agent> provider/model"),
|
|
12070
12358
|
effort: effort.optional().describe(`force a model effort ${effortLevels}`),
|
|
12071
|
-
efforts:
|
|
12359
|
+
efforts: z6.record(z6.string(), effort).optional().describe(`per-agent effort: --efforts.<agent> ${effortLevels}`),
|
|
12072
12360
|
boundary: boundaryOption,
|
|
12073
12361
|
channel: channelOption
|
|
12074
12362
|
}),
|
|
@@ -12135,10 +12423,10 @@ var init_config4 = __esm({
|
|
|
12135
12423
|
return { scope: args.scope, effective: summarizeScope(effective) };
|
|
12136
12424
|
}
|
|
12137
12425
|
});
|
|
12138
|
-
configClear =
|
|
12426
|
+
configClear = defineCommand3({
|
|
12139
12427
|
id: "config.clear",
|
|
12140
12428
|
args: [scopeArg],
|
|
12141
|
-
options:
|
|
12429
|
+
options: z6.object({ channel: channelOption }),
|
|
12142
12430
|
action: "config:write",
|
|
12143
12431
|
effect: "write",
|
|
12144
12432
|
describe: "Drop every runtime override of a channel (gated) or of yourself; static config.yaml values show through again.",
|
|
@@ -12155,18 +12443,18 @@ var init_config4 = __esm({
|
|
|
12155
12443
|
}
|
|
12156
12444
|
});
|
|
12157
12445
|
quote = (text) => `> ${text.replace(/\n/g, "\n> ")}`;
|
|
12158
|
-
configInstructions =
|
|
12446
|
+
configInstructions = defineCommand3({
|
|
12159
12447
|
id: "config.instructions",
|
|
12160
12448
|
args: [
|
|
12161
12449
|
scopeArg,
|
|
12162
12450
|
{
|
|
12163
12451
|
name: "text",
|
|
12164
|
-
schema:
|
|
12452
|
+
schema: z6.string().optional(),
|
|
12165
12453
|
describe: 'the instructions; omit to show the current text, pass "" to clear',
|
|
12166
12454
|
rest: true
|
|
12167
12455
|
}
|
|
12168
12456
|
],
|
|
12169
|
-
options:
|
|
12457
|
+
options: z6.object({ channel: channelOption }),
|
|
12170
12458
|
action: "config:write",
|
|
12171
12459
|
effect: "write",
|
|
12172
12460
|
describe: "Custom instructions for a channel (gated) or for yourself \u2014 advisory prompt content that never changes agent, model, or permissions.",
|
|
@@ -12226,8 +12514,8 @@ ${quote(current ?? "")}`;
|
|
|
12226
12514
|
});
|
|
12227
12515
|
|
|
12228
12516
|
// ../../src/core/commands/contract.ts
|
|
12229
|
-
import { basename, dirname as dirname8, join as
|
|
12230
|
-
import { z as
|
|
12517
|
+
import { basename, dirname as dirname8, join as join9 } from "node:path";
|
|
12518
|
+
import { z as z7 } from "zod";
|
|
12231
12519
|
function repoRootOfPlan(planPath) {
|
|
12232
12520
|
const plans = dirname8(planPath);
|
|
12233
12521
|
const docs = dirname8(plans);
|
|
@@ -12245,24 +12533,24 @@ function renderOutput(output) {
|
|
|
12245
12533
|
function registerContractCommands(registry) {
|
|
12246
12534
|
for (const cmd of contractCommands) registry.register(cmd);
|
|
12247
12535
|
}
|
|
12248
|
-
var
|
|
12536
|
+
var defineCommand4, SPECS_DIR, RULES_FILES, contractRender, contractCommands;
|
|
12249
12537
|
var init_contract2 = __esm({
|
|
12250
12538
|
"../../src/core/commands/contract.ts"() {
|
|
12251
12539
|
"use strict";
|
|
12252
12540
|
init_commandRegistry();
|
|
12253
12541
|
init_contract();
|
|
12254
|
-
|
|
12255
|
-
SPECS_DIR =
|
|
12542
|
+
defineCommand4 = commandDefiner();
|
|
12543
|
+
SPECS_DIR = join9("docs", "reference", "specs");
|
|
12256
12544
|
RULES_FILES = ["AGENTS.md", "CLAUDE.md"];
|
|
12257
|
-
contractRender =
|
|
12545
|
+
contractRender = defineCommand4({
|
|
12258
12546
|
id: "contract.render",
|
|
12259
|
-
options:
|
|
12260
|
-
plan:
|
|
12261
|
-
unit:
|
|
12262
|
-
root:
|
|
12263
|
-
branch:
|
|
12264
|
-
onto:
|
|
12265
|
-
maxChars:
|
|
12547
|
+
options: z7.object({
|
|
12548
|
+
plan: z7.string().min(1).describe("path of the plan record (docs/plans/<file>.md)"),
|
|
12549
|
+
unit: z7.string().regex(/^U\d+$/, "expected a unit id like U<n>").describe("the unit to render, as its heading spells it (U<n>)"),
|
|
12550
|
+
root: z7.string().min(1).optional().describe("the repository root the specs and rules are read under (default: the root the plan path implies)"),
|
|
12551
|
+
branch: z7.string().min(1).optional().describe("the unit's branch, named in the first instruction"),
|
|
12552
|
+
onto: z7.string().min(1).optional().describe("the merged parent the branch is rebased onto"),
|
|
12553
|
+
maxChars: z7.coerce.number().int().positive().optional().describe(`the rendered block's budget in characters (default ${DEFAULT_CONTRACT_MAX_CHARS})`)
|
|
12266
12554
|
}),
|
|
12267
12555
|
action: "contract:read",
|
|
12268
12556
|
effect: "read",
|
|
@@ -12276,10 +12564,10 @@ var init_contract2 = __esm({
|
|
|
12276
12564
|
const unit = parsePlanUnit(planMarkdown, options.unit);
|
|
12277
12565
|
const specs = /* @__PURE__ */ new Map();
|
|
12278
12566
|
for (const spec of new Set(unit ? specItemRefs(unit.section).map((r) => r.spec) : []))
|
|
12279
|
-
specs.set(spec, await deps.contract.readFile(
|
|
12567
|
+
specs.set(spec, await deps.contract.readFile(join9(root, SPECS_DIR, spec)));
|
|
12280
12568
|
let agentRules;
|
|
12281
12569
|
for (const file of RULES_FILES) {
|
|
12282
|
-
const text = await deps.contract.readFile(
|
|
12570
|
+
const text = await deps.contract.readFile(join9(root, file));
|
|
12283
12571
|
if (text !== void 0) {
|
|
12284
12572
|
agentRules = { file, text };
|
|
12285
12573
|
break;
|
|
@@ -13204,7 +13492,7 @@ function isSetupInstallCommand(summary) {
|
|
|
13204
13492
|
return INSTALL_SEGMENT.test(cmd.replace(QUOTED, '""'));
|
|
13205
13493
|
}
|
|
13206
13494
|
function isNarrative(ev) {
|
|
13207
|
-
return ev.type === "input" || ev.type === "context" || ev.type === "assistant" || ev.type === "answer" || ev.type === "run_meta";
|
|
13495
|
+
return ev.type === "input" || ev.type === "context" || ev.type === "assistant" || ev.type === "answer" || ev.type === "notes" || ev.type === "run_meta";
|
|
13208
13496
|
}
|
|
13209
13497
|
function spanSet(events) {
|
|
13210
13498
|
const normalized = normalizeSpans(events);
|
|
@@ -13909,7 +14197,7 @@ var init_runRecord = __esm({
|
|
|
13909
14197
|
RUN_STATUSES = ["completed", "stopped_soft", "stopped_hard", "failed", "interrupted"];
|
|
13910
14198
|
RUN_ID_PATTERN = /^[A-Za-z0-9_-]{1,64}$/;
|
|
13911
14199
|
REVIEW_HEAD_PATTERN = /^[0-9a-f]{7,40}$/;
|
|
13912
|
-
RUN_SEEDS = ["channel", "parent"];
|
|
14200
|
+
RUN_SEEDS = ["channel", "parent", "session"];
|
|
13913
14201
|
SESSION_KEY_PATTERN = /^[A-Za-z0-9_.:@+/-]{1,512}$/;
|
|
13914
14202
|
isIndex = (v) => typeof v === "number" && Number.isInteger(v) && v >= 0;
|
|
13915
14203
|
MACHINE_CLASSES_IN_RECORD = {
|
|
@@ -13954,11 +14242,11 @@ var init_runRecord = __esm({
|
|
|
13954
14242
|
});
|
|
13955
14243
|
|
|
13956
14244
|
// ../../src/core/commands/delivery.ts
|
|
13957
|
-
import { z as
|
|
14245
|
+
import { z as z8 } from "zod";
|
|
13958
14246
|
function registerDeliveryCommands(registry) {
|
|
13959
14247
|
for (const cmd of deliveryCommands) registry.register(cmd);
|
|
13960
14248
|
}
|
|
13961
|
-
var
|
|
14249
|
+
var defineCommand5, REPO_SLUG3, ISO_DAY3, asJson, deliveryReport, deliveryCommands;
|
|
13962
14250
|
var init_delivery2 = __esm({
|
|
13963
14251
|
"../../src/core/commands/delivery.ts"() {
|
|
13964
14252
|
"use strict";
|
|
@@ -13966,16 +14254,16 @@ var init_delivery2 = __esm({
|
|
|
13966
14254
|
init_commandRegistry();
|
|
13967
14255
|
init_delivery();
|
|
13968
14256
|
init_runRecord();
|
|
13969
|
-
|
|
14257
|
+
defineCommand5 = commandDefiner();
|
|
13970
14258
|
REPO_SLUG3 = /^(?!\.+\/)[\w.-]+\/(?!\.+$)[\w.-]+$/;
|
|
13971
14259
|
ISO_DAY3 = /^\d{4}-\d{2}-\d{2}$/;
|
|
13972
14260
|
asJson = (v) => v;
|
|
13973
|
-
deliveryReport =
|
|
14261
|
+
deliveryReport = defineCommand5({
|
|
13974
14262
|
id: "delivery.report",
|
|
13975
|
-
options:
|
|
13976
|
-
repo:
|
|
13977
|
-
since:
|
|
13978
|
-
weeks:
|
|
14263
|
+
options: z8.object({
|
|
14264
|
+
repo: z8.string().regex(REPO_SLUG3).optional().describe("the repository (`owner/name`); default: the first under `delivery.repos`"),
|
|
14265
|
+
since: z8.string().regex(ISO_DAY3).optional().describe("the first day of the range (`YYYY-MM-DD`, UTC); the range ends today"),
|
|
14266
|
+
weeks: z8.coerce.number().int().min(1).max(MAX_WEEKS).optional().describe(
|
|
13979
14267
|
`Monday-start weeks back from this one (default 4, at most ${MAX_WEEKS}); --since, when given, decides instead`
|
|
13980
14268
|
),
|
|
13981
14269
|
fresh: flag.optional().describe("read GitHub now instead of the repository's snapshot, and refresh the snapshot")
|
|
@@ -14020,7 +14308,7 @@ var init_delivery2 = __esm({
|
|
|
14020
14308
|
});
|
|
14021
14309
|
|
|
14022
14310
|
// ../../src/core/commands/deploy.ts
|
|
14023
|
-
import { z as
|
|
14311
|
+
import { z as z9 } from "zod";
|
|
14024
14312
|
function toOptions(o, dryRun, affected) {
|
|
14025
14313
|
return {
|
|
14026
14314
|
only: o.only,
|
|
@@ -14130,7 +14418,7 @@ async function copyImages(copies, account2, deps) {
|
|
|
14130
14418
|
function registerDeployCommands(registry) {
|
|
14131
14419
|
for (const cmd of deployCommands) registry.register(cmd);
|
|
14132
14420
|
}
|
|
14133
|
-
var
|
|
14421
|
+
var defineCommand6, workerList, positiveInt, gitRef, deployOptions, planJson, deployPlan, allOptions, copiedImage, listCopies, deployAll, restartOptions, deployRestart, initOptions, deployInit, configOptions, deployConfig, secretNames, secretsOptions, deploySecrets, imagesOptions, deployImages, deployCommands;
|
|
14134
14422
|
var init_deploy = __esm({
|
|
14135
14423
|
"../../src/core/commands/deploy.ts"() {
|
|
14136
14424
|
"use strict";
|
|
@@ -14143,16 +14431,16 @@ var init_deploy = __esm({
|
|
|
14143
14431
|
init_wranglerTemplate();
|
|
14144
14432
|
init_secrets();
|
|
14145
14433
|
init_commandRegistry();
|
|
14146
|
-
|
|
14147
|
-
workerList =
|
|
14434
|
+
defineCommand6 = commandDefiner();
|
|
14435
|
+
workerList = z9.string().transform(
|
|
14148
14436
|
(s) => s.split(",").map((x) => x.trim()).filter(Boolean)
|
|
14149
14437
|
).refine(
|
|
14150
14438
|
(names) => names.every((n2) => DEPLOY_ORDER.includes(n2)),
|
|
14151
14439
|
`expected a comma list of Workers (${DEPLOY_ORDER.join(", ")})`
|
|
14152
14440
|
).transform((names) => [...new Set(names)]);
|
|
14153
|
-
positiveInt =
|
|
14154
|
-
gitRef =
|
|
14155
|
-
deployOptions =
|
|
14441
|
+
positiveInt = z9.coerce.number().int().positive();
|
|
14442
|
+
gitRef = z9.string().regex(/^[A-Za-z0-9_./^~@{}][A-Za-z0-9_./^~@{}-]{0,199}$/, "expected a git revision (a sha, tag, branch, or HEAD^)");
|
|
14443
|
+
deployOptions = z9.object({
|
|
14156
14444
|
only: workerList.optional().describe(
|
|
14157
14445
|
`deploy only these Workers (comma list of ${DEPLOY_ORDER.join(", ")}); with --affected, only those of them that are affected`
|
|
14158
14446
|
),
|
|
@@ -14171,7 +14459,7 @@ var init_deploy = __esm({
|
|
|
14171
14459
|
poll: positiveInt.optional().describe("seconds between preflight retries (default 60)")
|
|
14172
14460
|
});
|
|
14173
14461
|
planJson = (plan3) => plan3;
|
|
14174
|
-
deployPlan =
|
|
14462
|
+
deployPlan = defineCommand6({
|
|
14175
14463
|
id: "deploy.plan",
|
|
14176
14464
|
options: deployOptions,
|
|
14177
14465
|
action: "deploy:read",
|
|
@@ -14187,7 +14475,7 @@ var init_deploy = __esm({
|
|
|
14187
14475
|
});
|
|
14188
14476
|
copiedImage = (c) => ({ kind: c.kind, source: c.source, target: c.target });
|
|
14189
14477
|
listCopies = (copies) => copies.map((c) => `${c.kind} \u2190 ${c.source}`).join(", ");
|
|
14190
|
-
deployAll =
|
|
14478
|
+
deployAll = defineCommand6({
|
|
14191
14479
|
id: "deploy.all",
|
|
14192
14480
|
options: allOptions,
|
|
14193
14481
|
action: "deploy:write",
|
|
@@ -14238,15 +14526,15 @@ ${formatDeployResults(result.results, result.notAttempted)}`
|
|
|
14238
14526
|
return output(result.results, copies, []);
|
|
14239
14527
|
}
|
|
14240
14528
|
});
|
|
14241
|
-
restartOptions =
|
|
14242
|
-
only:
|
|
14529
|
+
restartOptions = z9.object({
|
|
14530
|
+
only: z9.enum(["bot"]).optional().describe("the Worker to restart \u2014 only `bot` has a long-lived container (default bot)"),
|
|
14243
14531
|
force: flag.optional().describe(
|
|
14244
14532
|
"restart even when the bot cannot be consulted (no JSON on /healthz) \u2014 the fail-closed cases; runs in flight never refuse, they hand off"
|
|
14245
14533
|
),
|
|
14246
14534
|
waitMax: positiveInt.optional().describe("minutes to wait out a refusal (the bot not answering with JSON) before giving up (default 10)"),
|
|
14247
14535
|
poll: positiveInt.optional().describe("seconds between retries while refused (default 60)")
|
|
14248
14536
|
});
|
|
14249
|
-
deployRestart =
|
|
14537
|
+
deployRestart = defineCommand6({
|
|
14250
14538
|
id: "deploy.restart",
|
|
14251
14539
|
options: restartOptions,
|
|
14252
14540
|
action: "deploy:write",
|
|
@@ -14287,12 +14575,12 @@ ${formatDeployResults(result.results, result.notAttempted)}`
|
|
|
14287
14575
|
};
|
|
14288
14576
|
}
|
|
14289
14577
|
});
|
|
14290
|
-
initOptions =
|
|
14578
|
+
initOptions = z9.object({
|
|
14291
14579
|
check: flag.optional().describe(
|
|
14292
14580
|
"compare only: report each rendered file that differs from the one on disk and fail when any does (the `deploy:check` gate); nothing is written"
|
|
14293
14581
|
)
|
|
14294
14582
|
});
|
|
14295
|
-
deployInit =
|
|
14583
|
+
deployInit = defineCommand6({
|
|
14296
14584
|
id: "deploy.init",
|
|
14297
14585
|
options: initOptions,
|
|
14298
14586
|
action: "deploy:write",
|
|
@@ -14339,12 +14627,12 @@ ${formatDeployResults(result.results, result.notAttempted)}`
|
|
|
14339
14627
|
return output;
|
|
14340
14628
|
}
|
|
14341
14629
|
});
|
|
14342
|
-
configOptions =
|
|
14343
|
-
source:
|
|
14630
|
+
configOptions = z9.object({
|
|
14631
|
+
source: z9.string().min(1).optional().describe(
|
|
14344
14632
|
"read the config from this source instead of the profile's configSource (a path, github://owner/repo/path@ref, or op://Vault/Item/field)"
|
|
14345
14633
|
)
|
|
14346
14634
|
});
|
|
14347
|
-
deployConfig =
|
|
14635
|
+
deployConfig = defineCommand6({
|
|
14348
14636
|
id: "deploy.config",
|
|
14349
14637
|
options: configOptions,
|
|
14350
14638
|
action: "deploy:write",
|
|
@@ -14379,21 +14667,21 @@ the bot reads it on its next start: \`deploy restart\``;
|
|
|
14379
14667
|
return output;
|
|
14380
14668
|
}
|
|
14381
14669
|
});
|
|
14382
|
-
secretNames =
|
|
14670
|
+
secretNames = z9.string().transform(
|
|
14383
14671
|
(s) => s.split(",").map((x) => x.trim()).filter(Boolean)
|
|
14384
14672
|
).refine(
|
|
14385
14673
|
(names) => names.length > 0 && names.every((n2) => /^[A-Z][A-Z0-9_]*$/.test(n2)),
|
|
14386
14674
|
"expected a comma list of secret names (SCREAMING_SNAKE)"
|
|
14387
14675
|
).transform((names) => [...new Set(names)]);
|
|
14388
|
-
secretsOptions =
|
|
14676
|
+
secretsOptions = z9.object({
|
|
14389
14677
|
only: secretNames.optional().describe("put only these secrets (comma list of names from deploy/secrets.manifest.json)")
|
|
14390
14678
|
});
|
|
14391
|
-
deploySecrets =
|
|
14679
|
+
deploySecrets = defineCommand6({
|
|
14392
14680
|
id: "deploy.secrets",
|
|
14393
14681
|
args: [
|
|
14394
14682
|
{
|
|
14395
14683
|
name: "worker",
|
|
14396
|
-
schema:
|
|
14684
|
+
schema: z9.enum(DEPLOY_ORDER),
|
|
14397
14685
|
describe: `the Worker to provision (${DEPLOY_ORDER.join(", ")})`
|
|
14398
14686
|
}
|
|
14399
14687
|
],
|
|
@@ -14455,12 +14743,12 @@ the bot reads it on its next start: \`deploy restart\``;
|
|
|
14455
14743
|
return output;
|
|
14456
14744
|
}
|
|
14457
14745
|
});
|
|
14458
|
-
imagesOptions =
|
|
14746
|
+
imagesOptions = z9.object({
|
|
14459
14747
|
dryRun: flag.optional().describe(
|
|
14460
14748
|
"say which images the account registry already holds at the version and which would be copied; nothing is copied"
|
|
14461
14749
|
)
|
|
14462
14750
|
});
|
|
14463
|
-
deployImages =
|
|
14751
|
+
deployImages = defineCommand6({
|
|
14464
14752
|
id: "deploy.images",
|
|
14465
14753
|
options: imagesOptions,
|
|
14466
14754
|
action: "deploy:write",
|
|
@@ -14511,25 +14799,25 @@ the bot reads it on its next start: \`deploy restart\``;
|
|
|
14511
14799
|
});
|
|
14512
14800
|
|
|
14513
14801
|
// ../../src/core/commands/env.ts
|
|
14514
|
-
import { z as
|
|
14802
|
+
import { z as z10 } from "zod";
|
|
14515
14803
|
function registerEnvCommands(registry) {
|
|
14516
14804
|
for (const cmd of envCommands) registry.register(cmd);
|
|
14517
14805
|
}
|
|
14518
|
-
var
|
|
14806
|
+
var defineCommand7, envBootstrap, envCommands;
|
|
14519
14807
|
var init_env = __esm({
|
|
14520
14808
|
"../../src/core/commands/env.ts"() {
|
|
14521
14809
|
"use strict";
|
|
14522
14810
|
init_bootstrap();
|
|
14523
14811
|
init_commandRegistry();
|
|
14524
|
-
|
|
14525
|
-
envBootstrap =
|
|
14812
|
+
defineCommand7 = commandDefiner();
|
|
14813
|
+
envBootstrap = defineCommand7({
|
|
14526
14814
|
id: "env.bootstrap",
|
|
14527
|
-
options:
|
|
14528
|
-
env:
|
|
14529
|
-
service:
|
|
14815
|
+
options: z10.object({
|
|
14816
|
+
env: z10.string().min(1).describe("environment to materialize (UAT only; anything else is refused)"),
|
|
14817
|
+
service: z10.string().min(1).describe("which downstream service's toolchain env to fill"),
|
|
14530
14818
|
apply: flag.optional().describe("resolve the refs and WRITE the chmod-600 env file (default: dry-run)"),
|
|
14531
|
-
out:
|
|
14532
|
-
manifest:
|
|
14819
|
+
out: z10.string().min(1).optional().describe("env-file path (default .agent-env/<service>.<env>.env)"),
|
|
14820
|
+
manifest: z10.string().min(1).optional().describe(`manifest path (default ${DEFAULT_MANIFEST_PATH})`)
|
|
14533
14821
|
}),
|
|
14534
14822
|
action: "env:write",
|
|
14535
14823
|
effect: "write",
|
|
@@ -15127,6 +15415,7 @@ function isRunEvent(v) {
|
|
|
15127
15415
|
case "input":
|
|
15128
15416
|
case "assistant":
|
|
15129
15417
|
case "context":
|
|
15418
|
+
case "notes":
|
|
15130
15419
|
return typeof o.text === "string";
|
|
15131
15420
|
// the narrative events carry text, not a summary
|
|
15132
15421
|
case "run_meta":
|
|
@@ -15263,7 +15552,7 @@ var init_selfImprovement = __esm({
|
|
|
15263
15552
|
});
|
|
15264
15553
|
|
|
15265
15554
|
// ../../src/core/commands/friction.ts
|
|
15266
|
-
import { z as
|
|
15555
|
+
import { z as z11 } from "zod";
|
|
15267
15556
|
async function ledgerOf(deps) {
|
|
15268
15557
|
let ledger;
|
|
15269
15558
|
try {
|
|
@@ -15291,7 +15580,7 @@ function inProgressHint(diagnosis, finished) {
|
|
|
15291
15580
|
function registerFrictionCommands(registry) {
|
|
15292
15581
|
for (const cmd of frictionCommands) registry.register(cmd);
|
|
15293
15582
|
}
|
|
15294
|
-
var
|
|
15583
|
+
var defineCommand8, positiveInt2, repoSlug, NO_LEDGER_MESSAGE, NO_REPO_MESSAGE, asJson2, render, visibleRuns, frictionReport, frictionPropose, frictionAnalyze, frictionCommands;
|
|
15295
15584
|
var init_friction = __esm({
|
|
15296
15585
|
"../../src/core/commands/friction.ts"() {
|
|
15297
15586
|
"use strict";
|
|
@@ -15302,19 +15591,19 @@ var init_friction = __esm({
|
|
|
15302
15591
|
init_runEventLines();
|
|
15303
15592
|
init_runFriction();
|
|
15304
15593
|
init_selfImprovement();
|
|
15305
|
-
|
|
15306
|
-
positiveInt2 =
|
|
15307
|
-
repoSlug =
|
|
15594
|
+
defineCommand8 = commandDefiner();
|
|
15595
|
+
positiveInt2 = z11.coerce.number().int().positive();
|
|
15596
|
+
repoSlug = z11.string().refine((s) => /^[\w.-]+\/[\w.-]+$/.test(s), "expected an owner/name slug");
|
|
15308
15597
|
NO_LEDGER_MESSAGE = "Run history is not configured in this deployment (`runHistory`), so there are no recent runs to analyze.";
|
|
15309
15598
|
NO_REPO_MESSAGE = "Set `selfImprovement.repo` (an `owner/name`) in config.yaml to tell `friction propose` where to file issues.";
|
|
15310
15599
|
asJson2 = (r) => r;
|
|
15311
15600
|
render = (output) => formatSelfImprovementReport(output);
|
|
15312
15601
|
visibleRuns = (caller) => predicateFor(caller.actor, "runs:read", "run");
|
|
15313
|
-
frictionReport =
|
|
15602
|
+
frictionReport = defineCommand8({
|
|
15314
15603
|
id: "friction.report",
|
|
15315
15604
|
enabledWhen: (caps) => caps.runHistory,
|
|
15316
|
-
options:
|
|
15317
|
-
sinceMs:
|
|
15605
|
+
options: z11.object({
|
|
15606
|
+
sinceMs: z11.coerce.number().int().nonnegative().optional().describe("only runs finished at or after this epoch ms"),
|
|
15318
15607
|
limit: positiveInt2.optional().describe("newest n runs (default: the ledger's retained window)"),
|
|
15319
15608
|
minRuns: positiveInt2.optional().describe("distinct runs a pattern must recur in (default selfImprovement.minRuns, else 2)")
|
|
15320
15609
|
}),
|
|
@@ -15340,10 +15629,10 @@ var init_friction = __esm({
|
|
|
15340
15629
|
});
|
|
15341
15630
|
}
|
|
15342
15631
|
});
|
|
15343
|
-
frictionPropose =
|
|
15632
|
+
frictionPropose = defineCommand8({
|
|
15344
15633
|
id: "friction.propose",
|
|
15345
15634
|
enabledWhen: (caps) => caps.runHistory,
|
|
15346
|
-
options:
|
|
15635
|
+
options: z11.object({
|
|
15347
15636
|
dryRun: flag.optional().describe("compute and report everything, file nothing"),
|
|
15348
15637
|
top: positiveInt2.optional().describe("proposals filed per pass (default selfImprovement.top, else 3)"),
|
|
15349
15638
|
minRuns: positiveInt2.optional().describe("distinct runs a pattern must recur in (default selfImprovement.minRuns, else 2)"),
|
|
@@ -15375,17 +15664,17 @@ var init_friction = __esm({
|
|
|
15375
15664
|
}
|
|
15376
15665
|
}
|
|
15377
15666
|
});
|
|
15378
|
-
frictionAnalyze =
|
|
15667
|
+
frictionAnalyze = defineCommand8({
|
|
15379
15668
|
id: "friction.analyze",
|
|
15380
15669
|
args: [
|
|
15381
15670
|
{
|
|
15382
15671
|
name: "source",
|
|
15383
|
-
schema:
|
|
15672
|
+
schema: z11.string().optional(),
|
|
15384
15673
|
describe: "a saved run stream: JSON lines of run events or a curl'ed /runs/:id/events SSE capture (default: stdin)"
|
|
15385
15674
|
}
|
|
15386
15675
|
],
|
|
15387
|
-
options:
|
|
15388
|
-
slowMs:
|
|
15676
|
+
options: z11.object({
|
|
15677
|
+
slowMs: z11.coerce.number().nonnegative().optional().describe("a tool call slower than this many ms is a slow-tool finding"),
|
|
15389
15678
|
inProgress: flag.optional().describe("the capture was taken mid-run: a trailing call without a result is still executing, not a dead run")
|
|
15390
15679
|
}),
|
|
15391
15680
|
action: "friction:read",
|
|
@@ -15438,7 +15727,7 @@ var init_friction = __esm({
|
|
|
15438
15727
|
});
|
|
15439
15728
|
|
|
15440
15729
|
// ../../src/core/commandSurface.ts
|
|
15441
|
-
import { z as
|
|
15730
|
+
import { z as z12 } from "zod";
|
|
15442
15731
|
function camelToKebab(s) {
|
|
15443
15732
|
return s.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`);
|
|
15444
15733
|
}
|
|
@@ -15510,7 +15799,7 @@ function isBooleanSchema(schema) {
|
|
|
15510
15799
|
return false;
|
|
15511
15800
|
}
|
|
15512
15801
|
function jsonSchemaOf(schema) {
|
|
15513
|
-
const out =
|
|
15802
|
+
const out = z12.toJSONSchema(unwrap(schema), { io: "input" });
|
|
15514
15803
|
delete out.$schema;
|
|
15515
15804
|
return out;
|
|
15516
15805
|
}
|
|
@@ -15522,7 +15811,7 @@ function jsonSchemaFor(cmd) {
|
|
|
15522
15811
|
if (!acceptsUndefined(arg.schema)) required.push(arg.name);
|
|
15523
15812
|
}
|
|
15524
15813
|
if (cmd.options) {
|
|
15525
|
-
const opts =
|
|
15814
|
+
const opts = z12.toJSONSchema(cmd.options, { io: "input" });
|
|
15526
15815
|
Object.assign(properties, opts.properties ?? {});
|
|
15527
15816
|
required.push(...opts.required ?? []);
|
|
15528
15817
|
}
|
|
@@ -15762,17 +16051,17 @@ function chatCommandList(commands) {
|
|
|
15762
16051
|
function registerHelpCommands(registry) {
|
|
15763
16052
|
for (const cmd of helpCommands) registry.register(cmd);
|
|
15764
16053
|
}
|
|
15765
|
-
var
|
|
16054
|
+
var defineCommand9, COMMANDS_REFERENCE_URL, DIRECTIVES_HELP, helpShow, helpCommandsList, helpCommands;
|
|
15766
16055
|
var init_help = __esm({
|
|
15767
16056
|
"../../src/core/commands/help.ts"() {
|
|
15768
16057
|
"use strict";
|
|
15769
16058
|
init_commandRegistry();
|
|
15770
16059
|
init_commandSurface();
|
|
15771
16060
|
init_docsLink();
|
|
15772
|
-
|
|
16061
|
+
defineCommand9 = commandDefiner();
|
|
15773
16062
|
COMMANDS_REFERENCE_URL = `${PROJECT_DOCS_URL}/reference/slack-commands`;
|
|
15774
16063
|
DIRECTIVES_HELP = "`agent:review model:anthropic/claude-opus-5 effort:low budget:20 look at PR 42` (effort: low | medium | high \u2014 lower = faster turns; budget: whole minutes, at least 2 \u2014 narrows this run's wall clock, never widens it)";
|
|
15775
|
-
helpShow =
|
|
16064
|
+
helpShow = defineCommand9({
|
|
15776
16065
|
id: "help.show",
|
|
15777
16066
|
action: "help:read",
|
|
15778
16067
|
effect: "read",
|
|
@@ -15783,7 +16072,7 @@ var init_help = __esm({
|
|
|
15783
16072
|
commandsReference: COMMANDS_REFERENCE_URL
|
|
15784
16073
|
})
|
|
15785
16074
|
});
|
|
15786
|
-
helpCommandsList =
|
|
16075
|
+
helpCommandsList = defineCommand9({
|
|
15787
16076
|
id: "help.commands",
|
|
15788
16077
|
action: "help:read",
|
|
15789
16078
|
effect: "read",
|
|
@@ -16975,7 +17264,11 @@ var init_service = __esm({
|
|
|
16975
17264
|
/** A fresh connect link for a runtime bearer/oauth server (first connect, or a re-key). */
|
|
16976
17265
|
async connect(actor, target, name) {
|
|
16977
17266
|
const { scopeKey, entry, source: source2 } = this.owned(target, name);
|
|
16978
|
-
if (entry.auth === "none")
|
|
17267
|
+
if (entry.auth === "none")
|
|
17268
|
+
throw new McpServiceError(
|
|
17269
|
+
"invalid_input",
|
|
17270
|
+
entry.headersEnv ? `"${name}" is pinned in config.yaml with headersEnv \u2014 its headers are environment variables on the bot, not a stored credential` : `"${name}" needs no credential (auth: none)`
|
|
17271
|
+
);
|
|
16979
17272
|
if (entry.tokenEnv)
|
|
16980
17273
|
throw new McpServiceError(
|
|
16981
17274
|
"invalid_input",
|
|
@@ -17452,6 +17745,15 @@ var init_service = __esm({
|
|
|
17452
17745
|
url: r.entry.url,
|
|
17453
17746
|
agents: r.entry.agents ?? [...MCP_SELF_SERVE_AGENTS]
|
|
17454
17747
|
};
|
|
17748
|
+
if (r.entry.headersEnv) {
|
|
17749
|
+
const headers = {};
|
|
17750
|
+
for (const [header4, envVar] of Object.entries(r.entry.headersEnv)) {
|
|
17751
|
+
const value = this.opts.bearers.named(envVar);
|
|
17752
|
+
if (!value) return { name: r.name, unavailable: `${envVar} is not set on the bot` };
|
|
17753
|
+
headers[header4] = value.reveal();
|
|
17754
|
+
}
|
|
17755
|
+
base.headers = headers;
|
|
17756
|
+
}
|
|
17455
17757
|
if (r.entry.auth === "none") return { spec: base };
|
|
17456
17758
|
if (r.entry.tokenEnv) {
|
|
17457
17759
|
const token2 = this.opts.bearers.named(r.entry.tokenEnv);
|
|
@@ -17520,7 +17822,7 @@ var init_service = __esm({
|
|
|
17520
17822
|
});
|
|
17521
17823
|
|
|
17522
17824
|
// ../../src/core/commands/mcp.ts
|
|
17523
|
-
import { z as
|
|
17825
|
+
import { z as z13 } from "zod";
|
|
17524
17826
|
async function serviceOf(deps) {
|
|
17525
17827
|
const svc = await deps.mcp.service();
|
|
17526
17828
|
if ("unavailable" in svc) throw new CommandError("unavailable", svc.unavailable);
|
|
@@ -17571,7 +17873,7 @@ async function settleConnect(deps, output) {
|
|
|
17571
17873
|
}
|
|
17572
17874
|
}
|
|
17573
17875
|
function renderServerLine(r) {
|
|
17574
|
-
const state = r.state === "connected" ? "\u2705 connected" : r.state === "static" ? "\u2705 static (
|
|
17876
|
+
const state = r.state === "connected" ? "\u2705 connected" : r.state === "static" ? "\u2705 static (bot env)" : "\u23F3 awaiting credential";
|
|
17575
17877
|
const agents = Array.isArray(r.agents) ? r.agents.join(", ") : "";
|
|
17576
17878
|
const pinned = r.source === "config" ? " \xB7 pinned in config.yaml" : "";
|
|
17577
17879
|
return `\u2022 \`${String(r.name)}\` (${String(r.scope)}) ${state} \u2014 ${String(r.url)} \xB7 agents: ${agents} \xB7 auth: ${String(r.auth)}${pinned}`;
|
|
@@ -17614,7 +17916,7 @@ function renderShow(output) {
|
|
|
17614
17916
|
function registerMcpCommands(registry) {
|
|
17615
17917
|
for (const cmd of MCP_COMMANDS) registry.register(cmd);
|
|
17616
17918
|
}
|
|
17617
|
-
var
|
|
17919
|
+
var defineCommand10, serverName, serverUrl, scopeOption, channelOption2, nameArg, mcpList, mcpAdd, mcpConnect, mcpShow, mcpRemove, MCP_COMMANDS;
|
|
17618
17920
|
var init_mcp = __esm({
|
|
17619
17921
|
"../../src/core/commands/mcp.ts"() {
|
|
17620
17922
|
"use strict";
|
|
@@ -17622,18 +17924,18 @@ var init_mcp = __esm({
|
|
|
17622
17924
|
init_service();
|
|
17623
17925
|
init_authorize();
|
|
17624
17926
|
init_commandRegistry();
|
|
17625
|
-
|
|
17626
|
-
serverName =
|
|
17627
|
-
serverUrl =
|
|
17628
|
-
scopeOption =
|
|
17927
|
+
defineCommand10 = commandDefiner();
|
|
17928
|
+
serverName = z13.string().refine((s) => MCP_SERVER_NAME_PATTERN.test(s), "expected a slug: lowercase letters, digits, dashes (\u2264 32 chars)");
|
|
17929
|
+
serverUrl = z13.string().refine((s) => /^https?:\/\/\S+$/.test(s), "expected an http(s) URL");
|
|
17930
|
+
scopeOption = z13.enum(["me", "channel", "org"]).optional().describe(
|
|
17629
17931
|
"whose server: yours (`me`, default), this channel's (`channel`, channel-config rights), or org-wide (`org`, admins)"
|
|
17630
17932
|
);
|
|
17631
|
-
channelOption2 =
|
|
17933
|
+
channelOption2 = z13.string().optional().describe("target channel for `--scope channel` and for `list` (default: the channel you are speaking in)");
|
|
17632
17934
|
nameArg = { name: "name", schema: serverName, describe: "the server's name (see `mcp list`)" };
|
|
17633
|
-
mcpList =
|
|
17935
|
+
mcpList = defineCommand10({
|
|
17634
17936
|
id: "mcp.list",
|
|
17635
17937
|
enabledWhen: (caps) => caps.mcp,
|
|
17636
|
-
options:
|
|
17938
|
+
options: z13.object({ channel: channelOption2 }),
|
|
17637
17939
|
action: "mcp:read",
|
|
17638
17940
|
effect: "read",
|
|
17639
17941
|
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.",
|
|
@@ -17645,7 +17947,7 @@ var init_mcp = __esm({
|
|
|
17645
17947
|
return { servers };
|
|
17646
17948
|
}
|
|
17647
17949
|
});
|
|
17648
|
-
mcpAdd =
|
|
17950
|
+
mcpAdd = defineCommand10({
|
|
17649
17951
|
id: "mcp.add",
|
|
17650
17952
|
enabledWhen: (caps) => caps.mcp,
|
|
17651
17953
|
args: [
|
|
@@ -17655,15 +17957,15 @@ var init_mcp = __esm({
|
|
|
17655
17957
|
describe: "a short slug for the server (becomes the tool prefix mcp__<name>__)"
|
|
17656
17958
|
}
|
|
17657
17959
|
],
|
|
17658
|
-
options:
|
|
17960
|
+
options: z13.object({
|
|
17659
17961
|
url: serverUrl.describe("the server's Streamable-HTTP endpoint (https://\u2026/mcp)"),
|
|
17660
17962
|
scope: scopeOption,
|
|
17661
|
-
agents:
|
|
17963
|
+
agents: z13.string().transform(
|
|
17662
17964
|
(s) => s.split(",").map((a) => a.trim()).filter(Boolean)
|
|
17663
17965
|
).optional().describe(
|
|
17664
17966
|
"comma-separated agents that may use it (default general,research; only org servers may name coding/review/ship)"
|
|
17665
17967
|
),
|
|
17666
|
-
auth:
|
|
17968
|
+
auth: z13.enum(["oauth", "bearer", "none"]).optional().describe(
|
|
17667
17969
|
"how the server authenticates: `oauth` (you sign in on a one-time link), `bearer` (you paste a token on a one-time link), or `none`; omit to detect it from the server"
|
|
17668
17970
|
),
|
|
17669
17971
|
channel: channelOption2
|
|
@@ -17688,11 +17990,11 @@ var init_mcp = __esm({
|
|
|
17688
17990
|
);
|
|
17689
17991
|
}
|
|
17690
17992
|
});
|
|
17691
|
-
mcpConnect =
|
|
17993
|
+
mcpConnect = defineCommand10({
|
|
17692
17994
|
id: "mcp.connect",
|
|
17693
17995
|
enabledWhen: (caps) => caps.mcp,
|
|
17694
17996
|
args: [nameArg],
|
|
17695
|
-
options:
|
|
17997
|
+
options: z13.object({ scope: scopeOption, channel: channelOption2 }),
|
|
17696
17998
|
action: "mcp:write",
|
|
17697
17999
|
effect: "write",
|
|
17698
18000
|
describe: "A fresh one-time link to sign in to an OAuth server or enter (or replace) a bearer server's token \u2014 only you can complete it; it expires in 10 minutes.",
|
|
@@ -17706,11 +18008,11 @@ var init_mcp = __esm({
|
|
|
17706
18008
|
return await via(() => svc.connect(actor, target, args.name));
|
|
17707
18009
|
}
|
|
17708
18010
|
});
|
|
17709
|
-
mcpShow =
|
|
18011
|
+
mcpShow = defineCommand10({
|
|
17710
18012
|
id: "mcp.show",
|
|
17711
18013
|
enabledWhen: (caps) => caps.mcp,
|
|
17712
18014
|
args: [nameArg],
|
|
17713
|
-
options:
|
|
18015
|
+
options: z13.object({ scope: scopeOption, channel: channelOption2 }),
|
|
17714
18016
|
action: "mcp:read",
|
|
17715
18017
|
effect: "read",
|
|
17716
18018
|
describe: "One MCP server's entry plus a live probe of the tools it offers (names, read-only flags); never a credential.",
|
|
@@ -17726,11 +18028,11 @@ var init_mcp = __esm({
|
|
|
17726
18028
|
return await via(() => svc.show(actor, target, args.name));
|
|
17727
18029
|
}
|
|
17728
18030
|
});
|
|
17729
|
-
mcpRemove =
|
|
18031
|
+
mcpRemove = defineCommand10({
|
|
17730
18032
|
id: "mcp.remove",
|
|
17731
18033
|
enabledWhen: (caps) => caps.mcp,
|
|
17732
18034
|
args: [nameArg],
|
|
17733
|
-
options:
|
|
18035
|
+
options: z13.object({ scope: scopeOption, channel: channelOption2 }),
|
|
17734
18036
|
action: "mcp:write",
|
|
17735
18037
|
effect: "write",
|
|
17736
18038
|
describe: "Remove an MCP server you added and its stored credential (yours freely; channel ones need channel-config rights, org-wide ones admin rights).",
|
|
@@ -18168,7 +18470,7 @@ var init_llmOutput = __esm({
|
|
|
18168
18470
|
});
|
|
18169
18471
|
|
|
18170
18472
|
// ../../src/core/memory/reflection.ts
|
|
18171
|
-
import { z as
|
|
18473
|
+
import { z as z14 } from "zod";
|
|
18172
18474
|
function shouldReflect(input) {
|
|
18173
18475
|
if (input.agentName !== void 0 && NO_REFLECT_AGENTS.has(input.agentName)) return false;
|
|
18174
18476
|
return input.toolCalls > 0 || input.historyTurns >= REFLECT_MIN_TURNS;
|
|
@@ -18387,7 +18689,7 @@ var init_reflection = __esm({
|
|
|
18387
18689
|
"Output raw JSON with no code fence and no prose."
|
|
18388
18690
|
].join("\n");
|
|
18389
18691
|
SUMMARY_INHERITANCE = ["user", "repo", "channel"];
|
|
18390
|
-
REFLECTION_ENVELOPE = jsonOutput(
|
|
18692
|
+
REFLECTION_ENVELOPE = jsonOutput(z14.object({ facts: z14.array(z14.unknown()), summary: z14.unknown().optional() }), {
|
|
18391
18693
|
name: "reflection-envelope"
|
|
18392
18694
|
});
|
|
18393
18695
|
SCOPE_KINDS = ["org", "user", "repo", "channel"];
|
|
@@ -18507,11 +18809,11 @@ var init_workerStore = __esm({
|
|
|
18507
18809
|
});
|
|
18508
18810
|
|
|
18509
18811
|
// ../../src/core/memory/buildStore.ts
|
|
18510
|
-
function resolveScopeCap(
|
|
18511
|
-
if (
|
|
18512
|
-
if (Number.isInteger(
|
|
18812
|
+
function resolveScopeCap(configured3, warn) {
|
|
18813
|
+
if (configured3 === void 0) return DEFAULT_SCOPE_CAP;
|
|
18814
|
+
if (Number.isInteger(configured3) && configured3 >= 1 && configured3 <= MAX_SCOPE_CAP) return configured3;
|
|
18513
18815
|
warn(
|
|
18514
|
-
`memory.maxRecordsPerScope must be an integer between 1 and ${MAX_SCOPE_CAP} (got ${String(
|
|
18816
|
+
`memory.maxRecordsPerScope must be an integer between 1 and ${MAX_SCOPE_CAP} (got ${String(configured3)}) \u2014 using the default ${DEFAULT_SCOPE_CAP}.`
|
|
18515
18817
|
);
|
|
18516
18818
|
return DEFAULT_SCOPE_CAP;
|
|
18517
18819
|
}
|
|
@@ -18619,7 +18921,7 @@ var init_memory = __esm({
|
|
|
18619
18921
|
});
|
|
18620
18922
|
|
|
18621
18923
|
// ../../src/core/commands/memory.ts
|
|
18622
|
-
import { z as
|
|
18924
|
+
import { z as z15 } from "zod";
|
|
18623
18925
|
function scopeKeyOfMemoryId(id) {
|
|
18624
18926
|
const m = /^mem:(.+):\d+$/.exec(id);
|
|
18625
18927
|
return m ? m[1] : void 0;
|
|
@@ -18673,18 +18975,18 @@ _(limit ${limit} reached \u2014 there may be more; narrow with words or raise \`
|
|
|
18673
18975
|
function registerMemoryCommands(registry) {
|
|
18674
18976
|
for (const cmd of memoryCommands) registry.register(cmd);
|
|
18675
18977
|
}
|
|
18676
|
-
var
|
|
18978
|
+
var defineCommand11, MEMORY_LIST_LIMIT, MEMORY_LIST_MAX_LIMIT, MEMORY_OFF_MESSAGE, memoryId, repoScopeKey, channelScopeKey, isSharedScope, LIST_SCOPES, memoryList, memoryForget, memoryCommands;
|
|
18677
18979
|
var init_memory2 = __esm({
|
|
18678
18980
|
"../../src/core/commands/memory.ts"() {
|
|
18679
18981
|
"use strict";
|
|
18680
18982
|
init_authorize();
|
|
18681
18983
|
init_commandRegistry();
|
|
18682
18984
|
init_memory();
|
|
18683
|
-
|
|
18985
|
+
defineCommand11 = commandDefiner();
|
|
18684
18986
|
MEMORY_LIST_LIMIT = 20;
|
|
18685
18987
|
MEMORY_LIST_MAX_LIMIT = 50;
|
|
18686
18988
|
MEMORY_OFF_MESSAGE = "Memory is off in this deployment (`memory.enabled` is not set), so there is nothing to list or forget.";
|
|
18687
|
-
memoryId =
|
|
18989
|
+
memoryId = z15.string().refine(
|
|
18688
18990
|
(id) => scopeKeyOfMemoryId(id) !== void 0,
|
|
18689
18991
|
"expected a memory id like mem:<scope>:<n> (see `memory list`)"
|
|
18690
18992
|
);
|
|
@@ -18692,21 +18994,21 @@ var init_memory2 = __esm({
|
|
|
18692
18994
|
channelScopeKey = (channelId) => deriveScopeKey("channel", { channelId });
|
|
18693
18995
|
isSharedScope = (key, keys) => key === keys.org || key.startsWith("repo:") || key.startsWith("channel:");
|
|
18694
18996
|
LIST_SCOPES = ["me", "org", "repo", "channel", "all"];
|
|
18695
|
-
memoryList =
|
|
18997
|
+
memoryList = defineCommand11({
|
|
18696
18998
|
id: "memory.list",
|
|
18697
18999
|
enabledWhen: (caps) => caps.memory,
|
|
18698
19000
|
args: [
|
|
18699
19001
|
{
|
|
18700
19002
|
name: "query",
|
|
18701
|
-
schema:
|
|
19003
|
+
schema: z15.string().optional(),
|
|
18702
19004
|
describe: "words a record must mention (whole-token match)",
|
|
18703
19005
|
rest: true
|
|
18704
19006
|
}
|
|
18705
19007
|
],
|
|
18706
|
-
options:
|
|
18707
|
-
scope:
|
|
18708
|
-
limit:
|
|
18709
|
-
repo:
|
|
19008
|
+
options: z15.object({
|
|
19009
|
+
scope: z15.enum(LIST_SCOPES).optional().describe("which scopes to list (default all: yours, this repo's, this channel's, and the shared org scope)"),
|
|
19010
|
+
limit: z15.coerce.number().int().min(1).max(MEMORY_LIST_MAX_LIMIT).optional().describe(`records per scope (default ${MEMORY_LIST_LIMIT}, max ${MEMORY_LIST_MAX_LIMIT})`),
|
|
19011
|
+
repo: z15.string().refine((s) => /^[\w.-]+\/[\w.-]+$/.test(s), "expected an owner/name slug").optional().describe("the repo whose scope to list (default: the repo this thread is bound to)")
|
|
18710
19012
|
}),
|
|
18711
19013
|
action: "memory:read",
|
|
18712
19014
|
effect: "read",
|
|
@@ -18760,7 +19062,7 @@ var init_memory2 = __esm({
|
|
|
18760
19062
|
};
|
|
18761
19063
|
}
|
|
18762
19064
|
});
|
|
18763
|
-
memoryForget =
|
|
19065
|
+
memoryForget = defineCommand11({
|
|
18764
19066
|
id: "memory.forget",
|
|
18765
19067
|
enabledWhen: (caps) => caps.memory,
|
|
18766
19068
|
args: [{ name: "id", schema: memoryId, describe: "the record id (`mem:<scope>:<n>`, from `memory list`)" }],
|
|
@@ -18895,7 +19197,7 @@ var init_residentDiskBudget = __esm({
|
|
|
18895
19197
|
});
|
|
18896
19198
|
|
|
18897
19199
|
// ../../src/core/commands/repo.ts
|
|
18898
|
-
import { z as
|
|
19200
|
+
import { z as z16 } from "zod";
|
|
18899
19201
|
async function adminOf(deps, span) {
|
|
18900
19202
|
const api = await deps.repo.admin();
|
|
18901
19203
|
if ("unavailable" in api) throw new CommandError("unavailable", api.unavailable);
|
|
@@ -19003,7 +19305,7 @@ ${clipOpOutput(text)}
|
|
|
19003
19305
|
\`\`\`` : `${icon} ${str4(o.summary)}`;
|
|
19004
19306
|
}
|
|
19005
19307
|
function defineOp(op) {
|
|
19006
|
-
return
|
|
19308
|
+
return defineCommand12({
|
|
19007
19309
|
id: `repo.${op}`,
|
|
19008
19310
|
// The op needs a backend: the resident service, or the local workspace (`defaultOperations`).
|
|
19009
19311
|
enabledWhen: (caps) => caps.residents || caps.execution === "local",
|
|
@@ -19061,7 +19363,7 @@ function defineOp(op) {
|
|
|
19061
19363
|
function registerRepoCommands(registry) {
|
|
19062
19364
|
for (const cmd of repoCommands) registry.register(cmd);
|
|
19063
19365
|
}
|
|
19064
|
-
var
|
|
19366
|
+
var defineCommand12, DEFAULT_COMMANDS, DEFAULT_REF, SETTLE_POLL_MS, SETTLE_MAX_MS, repoSlug2, gitRef2, command, slugArg, n, obj, str4, repoList, repoOnboard, COMMAND_KEYS, dryRunOptions, repoOffboard, repoRebuild, repoReconfigure, NO_OPS_BACKEND_MESSAGE, repoTest, repoBuild, repoCommands;
|
|
19065
19367
|
var init_repo = __esm({
|
|
19066
19368
|
"../../src/core/commands/repo.ts"() {
|
|
19067
19369
|
"use strict";
|
|
@@ -19069,19 +19371,19 @@ var init_repo = __esm({
|
|
|
19069
19371
|
init_residentAdmin();
|
|
19070
19372
|
init_repoToolchain();
|
|
19071
19373
|
init_residentDiskBudget();
|
|
19072
|
-
|
|
19374
|
+
defineCommand12 = commandDefiner();
|
|
19073
19375
|
DEFAULT_COMMANDS = NPM_FALLBACK_COMMANDS;
|
|
19074
19376
|
DEFAULT_REF = "main";
|
|
19075
19377
|
SETTLE_POLL_MS = 1e4;
|
|
19076
19378
|
SETTLE_MAX_MS = 12 * 6e4;
|
|
19077
|
-
repoSlug2 =
|
|
19078
|
-
gitRef2 =
|
|
19079
|
-
command =
|
|
19379
|
+
repoSlug2 = z16.string().refine((s) => parseSlug(s) !== void 0, "expected a GitHub owner/name slug").transform((s) => parseSlug(s));
|
|
19380
|
+
gitRef2 = z16.string().refine((s) => validRef(s) !== void 0, "expected a plausible git branch ref (e.g. main)");
|
|
19381
|
+
command = z16.string().min(1);
|
|
19080
19382
|
slugArg = { name: "slug", schema: repoSlug2, describe: "GitHub owner/name of the repo" };
|
|
19081
19383
|
n = (v) => String(typeof v === "number" ? v : v ?? "?");
|
|
19082
19384
|
obj = (v) => typeof v === "object" && v !== null && !Array.isArray(v) ? v : {};
|
|
19083
19385
|
str4 = (v) => typeof v === "string" ? v : String(v ?? "?");
|
|
19084
|
-
repoList =
|
|
19386
|
+
repoList = defineCommand12({
|
|
19085
19387
|
id: "repo.list",
|
|
19086
19388
|
enabledWhen: (caps) => caps.residents,
|
|
19087
19389
|
action: "repo:read",
|
|
@@ -19098,11 +19400,11 @@ var init_repo = __esm({
|
|
|
19098
19400
|
return res.data;
|
|
19099
19401
|
}
|
|
19100
19402
|
});
|
|
19101
|
-
repoOnboard =
|
|
19403
|
+
repoOnboard = defineCommand12({
|
|
19102
19404
|
id: "repo.onboard",
|
|
19103
19405
|
enabledWhen: (caps) => caps.residents,
|
|
19104
19406
|
args: [slugArg],
|
|
19105
|
-
options:
|
|
19407
|
+
options: z16.object({
|
|
19106
19408
|
ref: gitRef2.optional().describe(`default branch to keep warm (default ${DEFAULT_REF})`),
|
|
19107
19409
|
test: command.optional().describe(
|
|
19108
19410
|
"the repo's test command (default: detected from the repo root \u2014 `<pm> test`, or a no-op without a test script)"
|
|
@@ -19189,10 +19491,10 @@ var init_repo = __esm({
|
|
|
19189
19491
|
settle: (output, { deps }) => settleProvisioning(deps, str4(output.slug))
|
|
19190
19492
|
});
|
|
19191
19493
|
COMMAND_KEYS = ["install", "build", "test"];
|
|
19192
|
-
dryRunOptions =
|
|
19494
|
+
dryRunOptions = z16.object({
|
|
19193
19495
|
dryRun: flag.optional().describe("compute and return the itemized plan without executing")
|
|
19194
19496
|
});
|
|
19195
|
-
repoOffboard =
|
|
19497
|
+
repoOffboard = defineCommand12({
|
|
19196
19498
|
id: "repo.offboard",
|
|
19197
19499
|
enabledWhen: (caps) => caps.residents,
|
|
19198
19500
|
args: [slugArg],
|
|
@@ -19226,7 +19528,7 @@ var init_repo = __esm({
|
|
|
19226
19528
|
return { ...r.data, slug: args.slug, dryRun };
|
|
19227
19529
|
}
|
|
19228
19530
|
});
|
|
19229
|
-
repoRebuild =
|
|
19531
|
+
repoRebuild = defineCommand12({
|
|
19230
19532
|
id: "repo.rebuild",
|
|
19231
19533
|
enabledWhen: (caps) => caps.residents,
|
|
19232
19534
|
args: [slugArg],
|
|
@@ -19265,11 +19567,11 @@ var init_repo = __esm({
|
|
|
19265
19567
|
return { ...r.data, slug: args.slug, dryRun };
|
|
19266
19568
|
}
|
|
19267
19569
|
});
|
|
19268
|
-
repoReconfigure =
|
|
19570
|
+
repoReconfigure = defineCommand12({
|
|
19269
19571
|
id: "repo.reconfigure",
|
|
19270
19572
|
enabledWhen: (caps) => caps.residents,
|
|
19271
19573
|
args: [slugArg],
|
|
19272
|
-
options:
|
|
19574
|
+
options: z16.object({
|
|
19273
19575
|
ref: gitRef2.optional().describe("new default branch"),
|
|
19274
19576
|
test: command.optional().describe("new test command"),
|
|
19275
19577
|
build: command.optional().describe("new build command"),
|
|
@@ -20239,7 +20541,7 @@ var init_runsService = __esm({
|
|
|
20239
20541
|
});
|
|
20240
20542
|
|
|
20241
20543
|
// ../../src/core/commands/review.ts
|
|
20242
|
-
import { z as
|
|
20544
|
+
import { z as z17 } from "zod";
|
|
20243
20545
|
function artifactJson(a) {
|
|
20244
20546
|
return {
|
|
20245
20547
|
...a.model !== void 0 ? { model: a.model } : {},
|
|
@@ -20308,7 +20610,7 @@ function refused2(err2) {
|
|
|
20308
20610
|
function registerReviewCommands(registry) {
|
|
20309
20611
|
for (const cmd of reviewCommands) registry.register(cmd);
|
|
20310
20612
|
}
|
|
20311
|
-
var
|
|
20613
|
+
var defineCommand13, runId, ABRIDGE_OFF_MESSAGE, reviewAbridge, reviewCommands;
|
|
20312
20614
|
var init_review = __esm({
|
|
20313
20615
|
"../../src/core/commands/review.ts"() {
|
|
20314
20616
|
"use strict";
|
|
@@ -20317,17 +20619,17 @@ var init_review = __esm({
|
|
|
20317
20619
|
init_reviewAbridge();
|
|
20318
20620
|
init_runRecord();
|
|
20319
20621
|
init_runsService();
|
|
20320
|
-
|
|
20321
|
-
runId =
|
|
20622
|
+
defineCommand13 = commandDefiner();
|
|
20623
|
+
runId = z17.string().regex(RUN_ID_PATTERN);
|
|
20322
20624
|
ABRIDGE_OFF_MESSAGE = "The abridged reading diff is off in this deployment: it needs the `meat` binary on the bot host, the Anthropic provider's credential, `review.readingDiff.provider` not `off`, and run history to store it.";
|
|
20323
|
-
reviewAbridge =
|
|
20625
|
+
reviewAbridge = defineCommand13({
|
|
20324
20626
|
id: "review.abridge",
|
|
20325
20627
|
// Hidden unless the abridging can happen here (the binary, the credential,
|
|
20326
20628
|
// the switch) AND there is a record to append to.
|
|
20327
20629
|
enabledWhen: (caps) => caps.runHistory && caps.readingDiffAbridge,
|
|
20328
20630
|
args: [{ name: "id", schema: runId, describe: "run id of a finished PR review" }],
|
|
20329
|
-
options:
|
|
20330
|
-
model:
|
|
20631
|
+
options: z17.object({
|
|
20632
|
+
model: z17.string().min(1).optional().describe("meat's model (default review.readingDiff.meatModel, else claude-opus-5)"),
|
|
20331
20633
|
force: flag.optional().describe("recompute even when an abridged diff is already stored, or retry a failed one"),
|
|
20332
20634
|
wait: flag.optional().describe("block until the abridging finishes and answer the outcome instead of `running`")
|
|
20333
20635
|
}),
|
|
@@ -20359,7 +20661,7 @@ var init_review = __esm({
|
|
|
20359
20661
|
});
|
|
20360
20662
|
|
|
20361
20663
|
// ../../src/core/commands/runs.ts
|
|
20362
|
-
import { z as
|
|
20664
|
+
import { z as z18 } from "zod";
|
|
20363
20665
|
function unwrap2(res) {
|
|
20364
20666
|
if (res.ok) return res.value;
|
|
20365
20667
|
throw new CommandError(res.error, res.error === "not_found" ? "run not found" : "run already finished");
|
|
@@ -20400,7 +20702,7 @@ function wrapEvent(e) {
|
|
|
20400
20702
|
function registerRunsCommands(registry) {
|
|
20401
20703
|
for (const cmd of runsCommands) registry.register(cmd);
|
|
20402
20704
|
}
|
|
20403
|
-
var
|
|
20705
|
+
var defineCommand14, runId2, positiveInt3, idArg, logDenied, asJson3, runsList, runsGet, runsEvents, runsFriction, runsStop, runsCommands;
|
|
20404
20706
|
var init_runs = __esm({
|
|
20405
20707
|
"../../src/core/commands/runs.ts"() {
|
|
20406
20708
|
"use strict";
|
|
@@ -20409,21 +20711,24 @@ var init_runs = __esm({
|
|
|
20409
20711
|
init_commandRegistry();
|
|
20410
20712
|
init_runRecord();
|
|
20411
20713
|
init_runsService();
|
|
20412
|
-
|
|
20413
|
-
runId2 =
|
|
20414
|
-
positiveInt3 =
|
|
20714
|
+
defineCommand14 = commandDefiner();
|
|
20715
|
+
runId2 = z18.string().regex(RUN_ID_PATTERN);
|
|
20716
|
+
positiveInt3 = z18.coerce.number().int().positive();
|
|
20415
20717
|
idArg = { name: "id", schema: runId2, describe: "run id" };
|
|
20416
20718
|
logDenied = (entry) => console.log(JSON.stringify({ audit: "authz", ...entry }));
|
|
20417
20719
|
asJson3 = (v) => v;
|
|
20418
|
-
runsList =
|
|
20720
|
+
runsList = defineCommand14({
|
|
20419
20721
|
id: "runs.list",
|
|
20420
|
-
options:
|
|
20421
|
-
status:
|
|
20422
|
-
agent:
|
|
20423
|
-
channel:
|
|
20424
|
-
|
|
20722
|
+
options: z18.object({
|
|
20723
|
+
status: z18.enum(["active", "finished", "all"]).default("active").describe("which runs: live (default), persisted, or both"),
|
|
20724
|
+
agent: z18.string().min(1).optional().describe("only runs of this agent"),
|
|
20725
|
+
channel: z18.string().min(1).optional().describe("only runs in this platform-namespaced channel (`slack:C0123`, `http:ops`)"),
|
|
20726
|
+
thread: z18.string().min(1).optional().describe(
|
|
20727
|
+
"only runs in this thread, newest first (`slack:C0123:1712.34` \u2014 a thread's story, its sessions' runs)"
|
|
20728
|
+
),
|
|
20729
|
+
sinceMs: z18.coerce.number().int().nonnegative().optional().describe("only runs started at or after this epoch ms"),
|
|
20425
20730
|
limit: positiveInt3.max(RUN_LIST_MAX_LIMIT).optional().describe(`page size (max ${RUN_LIST_MAX_LIMIT})`),
|
|
20426
|
-
before:
|
|
20731
|
+
before: z18.coerce.number().int().nonnegative().optional().describe("page cursor: runs finished before this epoch ms"),
|
|
20427
20732
|
beforeId: runId2.optional().describe("page cursor tie-breaker: the last id of the previous page")
|
|
20428
20733
|
}),
|
|
20429
20734
|
action: "runs:read",
|
|
@@ -20431,14 +20736,17 @@ var init_runs = __esm({
|
|
|
20431
20736
|
describe: "List runs (live and persisted, newest first) \u2014 metadata only, never message text.",
|
|
20432
20737
|
handler: async ({ options, caller, deps }) => {
|
|
20433
20738
|
const visibleTo = predicateFor(caller.actor, "runs:read", "run");
|
|
20434
|
-
|
|
20739
|
+
const { thread, ...rest } = options;
|
|
20740
|
+
return asJson3(
|
|
20741
|
+
await (await deps.runs()).listRuns({ ...rest, ...thread !== void 0 ? { threadKey: thread } : {}, visibleTo })
|
|
20742
|
+
);
|
|
20435
20743
|
}
|
|
20436
20744
|
});
|
|
20437
|
-
runsGet =
|
|
20745
|
+
runsGet = defineCommand14({
|
|
20438
20746
|
id: "runs.get",
|
|
20439
20747
|
args: [idArg],
|
|
20440
|
-
options:
|
|
20441
|
-
include:
|
|
20748
|
+
options: z18.object({
|
|
20749
|
+
include: z18.enum(["messages"]).optional().describe("add the run's events, free text wrapped as untrusted content")
|
|
20442
20750
|
}),
|
|
20443
20751
|
action: "runs:read",
|
|
20444
20752
|
effect: "read",
|
|
@@ -20458,11 +20766,11 @@ var init_runs = __esm({
|
|
|
20458
20766
|
return asJson3(view);
|
|
20459
20767
|
}
|
|
20460
20768
|
});
|
|
20461
|
-
runsEvents =
|
|
20769
|
+
runsEvents = defineCommand14({
|
|
20462
20770
|
id: "runs.events",
|
|
20463
20771
|
args: [idArg],
|
|
20464
|
-
options:
|
|
20465
|
-
afterSeq:
|
|
20772
|
+
options: z18.object({
|
|
20773
|
+
afterSeq: z18.coerce.number().int().nonnegative().optional().describe("events with seq greater than this"),
|
|
20466
20774
|
limit: positiveInt3.max(MAX_EVENTS_PAGE).optional().describe(`page size (max ${MAX_EVENTS_PAGE})`)
|
|
20467
20775
|
}),
|
|
20468
20776
|
action: "runs:read",
|
|
@@ -20476,7 +20784,7 @@ var init_runs = __esm({
|
|
|
20476
20784
|
return asJson3({ ...page2, events: page2.events.map(wrapEvent) });
|
|
20477
20785
|
}
|
|
20478
20786
|
});
|
|
20479
|
-
runsFriction =
|
|
20787
|
+
runsFriction = defineCommand14({
|
|
20480
20788
|
id: "runs.friction",
|
|
20481
20789
|
args: [idArg],
|
|
20482
20790
|
action: "runs:read",
|
|
@@ -20489,10 +20797,10 @@ var init_runs = __esm({
|
|
|
20489
20797
|
return asJson3(unwrap2(await runs.getRunFriction(args.id)));
|
|
20490
20798
|
}
|
|
20491
20799
|
});
|
|
20492
|
-
runsStop =
|
|
20800
|
+
runsStop = defineCommand14({
|
|
20493
20801
|
id: "runs.stop",
|
|
20494
20802
|
args: [idArg],
|
|
20495
|
-
options:
|
|
20803
|
+
options: z18.object({ mode: z18.enum(["soft", "hard"]).describe("soft = finish the current step; hard = abort now") }),
|
|
20496
20804
|
action: "runs:write",
|
|
20497
20805
|
effect: "write",
|
|
20498
20806
|
describe: "Request a live run to stop (`--mode soft` = finish the current step; `hard` = abort now). Records the caller as the actor.",
|
|
@@ -20516,16 +20824,16 @@ var init_runs = __esm({
|
|
|
20516
20824
|
function registerScheduleCommands(registry) {
|
|
20517
20825
|
for (const cmd of scheduleCommands) registry.register(cmd);
|
|
20518
20826
|
}
|
|
20519
|
-
var
|
|
20827
|
+
var defineCommand15, ACTION_LABEL, utc, scheduleList, scheduleCommands;
|
|
20520
20828
|
var init_schedule = __esm({
|
|
20521
20829
|
"../../src/core/commands/schedule.ts"() {
|
|
20522
20830
|
"use strict";
|
|
20523
20831
|
init_commandRegistry();
|
|
20524
20832
|
init_schedules();
|
|
20525
|
-
|
|
20833
|
+
defineCommand15 = commandDefiner();
|
|
20526
20834
|
ACTION_LABEL = { healthz: "keep-alive", watchdog: "resident watchdog" };
|
|
20527
20835
|
utc = (ms) => `${new Date(ms).toISOString().slice(0, 16).replace("T", " ")} UTC`;
|
|
20528
|
-
scheduleList =
|
|
20836
|
+
scheduleList = defineCommand15({
|
|
20529
20837
|
id: "schedule.list",
|
|
20530
20838
|
enabledWhen: (caps) => caps.schedules,
|
|
20531
20839
|
action: "schedule:read",
|
|
@@ -20789,7 +21097,7 @@ var init_plan2 = __esm({
|
|
|
20789
21097
|
});
|
|
20790
21098
|
|
|
20791
21099
|
// ../../src/core/commands/setup.ts
|
|
20792
|
-
import { z as
|
|
21100
|
+
import { z as z19 } from "zod";
|
|
20793
21101
|
async function gatherAnswers(o, setup) {
|
|
20794
21102
|
const ask = setup.prompt;
|
|
20795
21103
|
const given = (v) => v !== void 0;
|
|
@@ -20859,33 +21167,33 @@ function capabilityLine(c) {
|
|
|
20859
21167
|
function registerSetupCommands(registry) {
|
|
20860
21168
|
for (const cmd of setupCommands) registry.register(cmd);
|
|
20861
21169
|
}
|
|
20862
|
-
var
|
|
21170
|
+
var defineCommand16, secret, digits, workerName, hostname2, httpUrl, account, initOptions2, DEFAULT_NAME, setupInit, ASK, setupCommands;
|
|
20863
21171
|
var init_setup = __esm({
|
|
20864
21172
|
"../../src/core/commands/setup.ts"() {
|
|
20865
21173
|
"use strict";
|
|
20866
21174
|
init_plan2();
|
|
20867
21175
|
init_commandRegistry();
|
|
20868
21176
|
init_deploy();
|
|
20869
|
-
|
|
20870
|
-
secret =
|
|
20871
|
-
digits =
|
|
20872
|
-
workerName =
|
|
20873
|
-
hostname2 =
|
|
20874
|
-
httpUrl =
|
|
20875
|
-
account =
|
|
20876
|
-
initOptions2 =
|
|
20877
|
-
organization:
|
|
21177
|
+
defineCommand16 = commandDefiner();
|
|
21178
|
+
secret = z19.string().min(1);
|
|
21179
|
+
digits = z19.string().regex(/^\d+$/, "expected a number");
|
|
21180
|
+
workerName = z19.string().regex(/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/, "expected a Worker name: lowercase letters, digits, hyphens");
|
|
21181
|
+
hostname2 = z19.string().regex(/^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$/, "expected a bare DNS name (example.com)");
|
|
21182
|
+
httpUrl = z19.string().regex(/^https?:\/\/\S+$/, "expected an http(s) URL");
|
|
21183
|
+
account = z19.string().regex(/^[0-9a-f]{32}$/, "expected a Cloudflare account id: 32 hex characters");
|
|
21184
|
+
initOptions2 = z19.object({
|
|
21185
|
+
organization: z19.string().min(1).optional().describe("the GitHub organization (or user) this installation serves \u2014 config.yaml's `organization`"),
|
|
20878
21186
|
anthropicKey: secret.optional().describe("an Anthropic API key \u2192 ANTHROPIC_API_KEY and the `anthropic` provider"),
|
|
20879
21187
|
openaiCompatible: httpUrl.optional().describe(
|
|
20880
21188
|
"an OpenAI-compatible endpoint's base URL (https://api.openai.com/v1, http://localhost:11434/v1) \u2192 the `openai` provider"
|
|
20881
21189
|
),
|
|
20882
|
-
model:
|
|
21190
|
+
model: z19.string().min(1).optional().describe("with --openai-compatible: the model every agent runs on at that endpoint (openai/<model>)"),
|
|
20883
21191
|
modelKey: secret.optional().describe("with --openai-compatible: the endpoint's key \u2192 OPENAI_API_KEY (a local endpoint needs none)"),
|
|
20884
21192
|
slackAppToken: secret.optional().describe("the Slack app-level token (xapp-\u2026) \u2192 SLACK_APP_TOKEN"),
|
|
20885
21193
|
slackBotToken: secret.optional().describe("the Slack bot token (xoxb-\u2026) \u2192 SLACK_BOT_TOKEN"),
|
|
20886
21194
|
githubAppId: digits.optional().describe("the GitHub App's id \u2192 GITHUB_APP_ID"),
|
|
20887
21195
|
githubInstallationId: digits.optional().describe("the GitHub App's installation id \u2192 GITHUB_APP_INSTALLATION_ID"),
|
|
20888
|
-
githubPrivateKeyFile:
|
|
21196
|
+
githubPrivateKeyFile: z19.string().min(1).optional().describe("path to the GitHub App's private key (PEM) \u2192 GITHUB_APP_PRIVATE_KEY, one quoted line"),
|
|
20889
21197
|
cloudflare: account.optional().describe(
|
|
20890
21198
|
"a Cloudflare account id: also write deploy/profile.json and render the Worker configs (needs --zone; from a checkout's root, or anywhere when this CLI is the published package)"
|
|
20891
21199
|
),
|
|
@@ -20895,7 +21203,7 @@ var init_setup = __esm({
|
|
|
20895
21203
|
dryRun: flag.optional().describe("write nothing; print each file as it would be written, secrets masked")
|
|
20896
21204
|
});
|
|
20897
21205
|
DEFAULT_NAME = "switchboard";
|
|
20898
|
-
setupInit =
|
|
21206
|
+
setupInit = defineCommand16({
|
|
20899
21207
|
id: "setup.init",
|
|
20900
21208
|
options: initOptions2,
|
|
20901
21209
|
action: "setup:write",
|
|
@@ -20995,13 +21303,13 @@ function renderStatus(output) {
|
|
|
20995
21303
|
function registerStatusCommands(registry) {
|
|
20996
21304
|
for (const cmd of statusCommands) registry.register(cmd);
|
|
20997
21305
|
}
|
|
20998
|
-
var
|
|
21306
|
+
var defineCommand17, statusShow, statusCommands;
|
|
20999
21307
|
var init_status = __esm({
|
|
21000
21308
|
"../../src/core/commands/status.ts"() {
|
|
21001
21309
|
"use strict";
|
|
21002
21310
|
init_commandRegistry();
|
|
21003
|
-
|
|
21004
|
-
statusShow =
|
|
21311
|
+
defineCommand17 = commandDefiner();
|
|
21312
|
+
statusShow = defineCommand17({
|
|
21005
21313
|
id: "status.show",
|
|
21006
21314
|
action: "status:read",
|
|
21007
21315
|
effect: "read",
|
|
@@ -21040,6 +21348,7 @@ function registerCoreCommands(registry) {
|
|
|
21040
21348
|
registerSetupCommands(registry);
|
|
21041
21349
|
registerContractCommands(registry);
|
|
21042
21350
|
registerDeliveryCommands(registry);
|
|
21351
|
+
registerArtifactsCommands(registry);
|
|
21043
21352
|
}
|
|
21044
21353
|
function coreCommandGroups() {
|
|
21045
21354
|
const registry = new CommandRegistry({ audit: () => {
|
|
@@ -21051,6 +21360,7 @@ var init_all = __esm({
|
|
|
21051
21360
|
"../../src/core/commands/all.ts"() {
|
|
21052
21361
|
"use strict";
|
|
21053
21362
|
init_commandRegistry();
|
|
21363
|
+
init_artifacts();
|
|
21054
21364
|
init_config4();
|
|
21055
21365
|
init_contract2();
|
|
21056
21366
|
init_delivery2();
|
|
@@ -21334,6 +21644,11 @@ function buildCoreCommands(config, store, wiring) {
|
|
|
21334
21644
|
images: imagesHostIO({ log: (l) => console.log(l), now: wiring.now ?? systemClock }),
|
|
21335
21645
|
cliVersion: cliVersionOnHost
|
|
21336
21646
|
},
|
|
21647
|
+
// `artifacts lifecycle` / `artifacts check`: the config's section and the operator's Cloudflare calls.
|
|
21648
|
+
artifacts: {
|
|
21649
|
+
config: async () => (await cfg()).config.artifacts,
|
|
21650
|
+
bucket: artifactsBucketHost()
|
|
21651
|
+
},
|
|
21337
21652
|
env: { bootstrap: bootstrapOnHost },
|
|
21338
21653
|
status: { snapshot: () => (wiring.status ?? unstampedStatus)() },
|
|
21339
21654
|
// `setup init` writes the operator's working directory — the one the CLI runs in.
|
|
@@ -21367,6 +21682,7 @@ var init_commandCatalogue = __esm({
|
|
|
21367
21682
|
init_clock();
|
|
21368
21683
|
init_run();
|
|
21369
21684
|
init_imagesHost();
|
|
21685
|
+
init_artifactsBucket();
|
|
21370
21686
|
init_secretsHost();
|
|
21371
21687
|
init_host3();
|
|
21372
21688
|
init_executor();
|
|
@@ -21585,6 +21901,12 @@ function runPageLink(id) {
|
|
|
21585
21901
|
if (!base) return void 0;
|
|
21586
21902
|
return `${base.replace(/\/+$/, "")}/runs/${encodeURIComponent(id)}`;
|
|
21587
21903
|
}
|
|
21904
|
+
function artifactLink(id, key, token2) {
|
|
21905
|
+
const page2 = runPageLink(id);
|
|
21906
|
+
if (!page2) return void 0;
|
|
21907
|
+
const path = key.split("/").map(encodeURIComponent).join("/");
|
|
21908
|
+
return `${page2}/artifacts/${path}${token2 ? `?t=${encodeURIComponent(token2)}` : ""}`;
|
|
21909
|
+
}
|
|
21588
21910
|
function stripPlatformPrefix(id) {
|
|
21589
21911
|
const i = id.indexOf(":");
|
|
21590
21912
|
return i === -1 ? id : id.slice(i + 1);
|
|
@@ -21653,6 +21975,8 @@ function activityLine(e) {
|
|
|
21653
21975
|
return "request received";
|
|
21654
21976
|
case "context":
|
|
21655
21977
|
return "context recorded";
|
|
21978
|
+
case "notes":
|
|
21979
|
+
return "\u{1F4DD} notes saved";
|
|
21656
21980
|
case "answer":
|
|
21657
21981
|
return "answer ready";
|
|
21658
21982
|
case "run_meta":
|
|
@@ -24257,7 +24581,8 @@ async function readRequest(ctx) {
|
|
|
24257
24581
|
}
|
|
24258
24582
|
function resolveRun(deps, ctx) {
|
|
24259
24583
|
const { msg, directives, history } = ctx;
|
|
24260
|
-
const
|
|
24584
|
+
const fromTurns = lastThreadDirectives(history);
|
|
24585
|
+
const sticky = ctx.stickyAgent !== void 0 ? { ...fromTurns, agent: ctx.stickyAgent } : fromTurns;
|
|
24261
24586
|
const resolved = deps.config.resolve({
|
|
24262
24587
|
channelId: msg.channelId,
|
|
24263
24588
|
userId: msg.userId,
|
|
@@ -25082,7 +25407,7 @@ var init_tracingExecutor = __esm({
|
|
|
25082
25407
|
});
|
|
25083
25408
|
|
|
25084
25409
|
// ../../src/core/prDescription.ts
|
|
25085
|
-
import { z as
|
|
25410
|
+
import { z as z20 } from "zod";
|
|
25086
25411
|
function parsePrDescription(input) {
|
|
25087
25412
|
return PrDescriptionSchema.parse(input);
|
|
25088
25413
|
}
|
|
@@ -25370,34 +25695,34 @@ var init_prDescription = __esm({
|
|
|
25370
25695
|
"../../src/core/prDescription.ts"() {
|
|
25371
25696
|
"use strict";
|
|
25372
25697
|
init_redact();
|
|
25373
|
-
line2 =
|
|
25374
|
-
prose =
|
|
25375
|
-
TourAnchorSchema =
|
|
25698
|
+
line2 = z20.string().trim().min(1).refine((s) => !/[\r\n]/.test(s), "must be a single line (no embedded newlines)");
|
|
25699
|
+
prose = z20.string().trim().min(1);
|
|
25700
|
+
TourAnchorSchema = z20.object({
|
|
25376
25701
|
// Repo-relative: no leading `/`, no `..` SEGMENT (a filename like `a..b.ts` is fine).
|
|
25377
|
-
path:
|
|
25378
|
-
from:
|
|
25379
|
-
to:
|
|
25702
|
+
path: z20.string().trim().min(1).refine((p) => !p.startsWith("/") && !p.split("/").includes(".."), "path must be repo-relative"),
|
|
25703
|
+
from: z20.number().int().positive(),
|
|
25704
|
+
to: z20.number().int().positive()
|
|
25380
25705
|
}).refine((a) => a.to >= a.from, "to must be >= from");
|
|
25381
|
-
TourStepSchema =
|
|
25706
|
+
TourStepSchema = z20.object({
|
|
25382
25707
|
title: line2,
|
|
25383
25708
|
description: prose,
|
|
25384
25709
|
lookFor: prose.optional(),
|
|
25385
25710
|
anchor: TourAnchorSchema
|
|
25386
25711
|
});
|
|
25387
|
-
PrDescriptionSchema =
|
|
25712
|
+
PrDescriptionSchema = z20.object({
|
|
25388
25713
|
/** The PR title's single source. Metadata for the PR's own title field —
|
|
25389
25714
|
* never rendered into the body (GitHub shows the title itself). */
|
|
25390
25715
|
title: line2,
|
|
25391
25716
|
tldr: prose,
|
|
25392
25717
|
whatWhy: prose,
|
|
25393
|
-
tour:
|
|
25718
|
+
tour: z20.array(TourStepSchema).min(1),
|
|
25394
25719
|
/** Every touched file the Tour steps did not cover, one line each. */
|
|
25395
|
-
remaining:
|
|
25396
|
-
decisions:
|
|
25720
|
+
remaining: z20.array(z20.object({ path: line2, note: prose })),
|
|
25721
|
+
decisions: z20.array(z20.object({ title: line2, rationale: prose })).min(1),
|
|
25397
25722
|
risks: prose,
|
|
25398
|
-
validation:
|
|
25723
|
+
validation: z20.object({
|
|
25399
25724
|
summary: prose.optional(),
|
|
25400
|
-
criteria:
|
|
25725
|
+
criteria: z20.array(z20.object({ criterion: prose, proof: prose })).min(1)
|
|
25401
25726
|
})
|
|
25402
25727
|
});
|
|
25403
25728
|
REPO_RE = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/;
|
|
@@ -26169,10 +26494,11 @@ async function attachThroughStore(path, name, lead, artifacts, ctx) {
|
|
|
26169
26494
|
}
|
|
26170
26495
|
ctx.publish?.({ type: "artifact", direction: "out", key, name, size, contentType });
|
|
26171
26496
|
if (!ctx.uploadTicket) {
|
|
26172
|
-
const
|
|
26497
|
+
const link = artifacts.artifactUrl?.(key);
|
|
26498
|
+
const where = link ? ` \u2014 ${link}` : ` is on the run page as ${key}`;
|
|
26173
26499
|
await artifacts.reply(`${lead}
|
|
26174
|
-
\u{1F4CE} ${name} (${size} bytes)
|
|
26175
|
-
return `attached ${name} (${size} bytes) to the run page; this conversation's channel takes no file uploads, so the lead and the
|
|
26500
|
+
\u{1F4CE} ${name} (${size} bytes)${where}`);
|
|
26501
|
+
return `attached ${name} (${size} bytes) to the run page as ${key}; this conversation's channel takes no file uploads, so the lead and the file's link were posted instead`;
|
|
26176
26502
|
}
|
|
26177
26503
|
const kept = "the file is kept on the run page";
|
|
26178
26504
|
let ticket;
|
|
@@ -26638,8 +26964,136 @@ var init_runs2 = __esm({
|
|
|
26638
26964
|
}
|
|
26639
26965
|
});
|
|
26640
26966
|
|
|
26967
|
+
// ../../src/core/runLedger/sessionLog.ts
|
|
26968
|
+
function sessionKey(threadKey, agent) {
|
|
26969
|
+
return `${threadKey}:${agent ?? "-"}`;
|
|
26970
|
+
}
|
|
26971
|
+
function requestIndex(seed) {
|
|
26972
|
+
for (let i = seed.length - 1; i >= 0; i--) if (seed[i].role === "user") return i;
|
|
26973
|
+
return 0;
|
|
26974
|
+
}
|
|
26975
|
+
var DEFAULT_SESSION_LOG_MAX_BYTES, NOTEPAD_MAX_BYTES, GAP_MARKER;
|
|
26976
|
+
var init_sessionLog = __esm({
|
|
26977
|
+
"../../src/core/runLedger/sessionLog.ts"() {
|
|
26978
|
+
"use strict";
|
|
26979
|
+
init_runRecord();
|
|
26980
|
+
init_runRecord();
|
|
26981
|
+
DEFAULT_SESSION_LOG_MAX_BYTES = DEFAULT_RETENTION_POLICY.sessionLogMaxBytes;
|
|
26982
|
+
NOTEPAD_MAX_BYTES = 8192;
|
|
26983
|
+
GAP_MARKER = "[The log of this conversation ends short of what the previous run saw: its connection to the ledger broke, so its later turns and its final reply are not here.]";
|
|
26984
|
+
}
|
|
26985
|
+
});
|
|
26986
|
+
|
|
26987
|
+
// ../../src/tools/session.ts
|
|
26988
|
+
function sessionCapabilityFor(run2, ledger) {
|
|
26989
|
+
const session = run2?.session;
|
|
26990
|
+
if (!session) return void 0;
|
|
26991
|
+
return {
|
|
26992
|
+
session,
|
|
26993
|
+
search: (query, limit) => ledger.searchSession(session.key, query, limit),
|
|
26994
|
+
readTurn: async (idx) => (await ledger.readSession(session.key, idx, idx)).messages[0],
|
|
26995
|
+
readNotepad: () => ledger.readNotepad(session.key),
|
|
26996
|
+
writeNotepad: (text) => ledger.writeNotepad(session.key, text)
|
|
26997
|
+
};
|
|
26998
|
+
}
|
|
26999
|
+
async function mayRead(ctx) {
|
|
27000
|
+
if (!ctx.runs?.runId) return false;
|
|
27001
|
+
const res = await ctx.runs.service.getRun(ctx.runs.runId);
|
|
27002
|
+
return res.ok && authorize(ctx.runs.actor, "runs:read", runResource(res.value)).allow;
|
|
27003
|
+
}
|
|
27004
|
+
var UNAVAILABLE3, DEFAULT_HITS, MAX_HITS, SNIPPET_CHARS, snippetOf, recallTool, notesTool, SESSION_TOOLS;
|
|
27005
|
+
var init_session = __esm({
|
|
27006
|
+
"../../src/tools/session.ts"() {
|
|
27007
|
+
"use strict";
|
|
27008
|
+
init_authorize();
|
|
27009
|
+
init_runEvents();
|
|
27010
|
+
init_sessionLog();
|
|
27011
|
+
init_runRecord();
|
|
27012
|
+
init_runsService();
|
|
27013
|
+
UNAVAILABLE3 = "the session tools are not available in this context: this run has no session log.";
|
|
27014
|
+
DEFAULT_HITS = 5;
|
|
27015
|
+
MAX_HITS = 50;
|
|
27016
|
+
SNIPPET_CHARS = 300;
|
|
27017
|
+
snippetOf = (text) => {
|
|
27018
|
+
const line3 = text.replace(/\s+/g, " ").trim();
|
|
27019
|
+
return line3.length > SNIPPET_CHARS ? `${line3.slice(0, SNIPPET_CHARS - 1)}\u2026` : line3;
|
|
27020
|
+
};
|
|
27021
|
+
recallTool = {
|
|
27022
|
+
name: "recall",
|
|
27023
|
+
description: "Search this conversation's whole log \u2014 every turn of this thread on this agent, earlier runs' included and everything a compaction removed from your window \u2014 for words, and get the matching turns in relevance order with their turn numbers and a snippet; or pass `turn` to read one turn whole (a tool result, a command, a message). Use it when you need something said or seen earlier that is not in front of you: a failing test's name, a command's output, a decision. `limit` defaults to 5 (at most 50).",
|
|
27024
|
+
inputSchema: {
|
|
27025
|
+
type: "object",
|
|
27026
|
+
properties: {
|
|
27027
|
+
query: { type: "string", description: "Words to search for; matching is by word, in relevance order" },
|
|
27028
|
+
limit: { type: "integer", description: "How many turns to return (default 5, at most 50)" },
|
|
27029
|
+
turn: {
|
|
27030
|
+
type: "integer",
|
|
27031
|
+
description: "A turn number from an earlier recall: returns that turn whole instead of searching"
|
|
27032
|
+
}
|
|
27033
|
+
}
|
|
27034
|
+
},
|
|
27035
|
+
sideEffectFree: true,
|
|
27036
|
+
async run(input, ctx) {
|
|
27037
|
+
if (!ctx.session) return UNAVAILABLE3;
|
|
27038
|
+
const turn = typeof input.turn === "number" ? Math.trunc(input.turn) : void 0;
|
|
27039
|
+
const query = typeof input.query === "string" ? input.query.trim() : "";
|
|
27040
|
+
if (turn === void 0 && query.length === 0) return "recall needs a query or a turn number.";
|
|
27041
|
+
if (!await mayRead(ctx)) return JSON.stringify(turn !== void 0 ? { turn, content: null } : { hits: [] });
|
|
27042
|
+
if (turn !== void 0) {
|
|
27043
|
+
const message2 = turn >= 0 ? await ctx.session.readTurn(turn) : void 0;
|
|
27044
|
+
if (!message2) return `no turn ${turn} in this session's log.`;
|
|
27045
|
+
return JSON.stringify({ turn, role: message2.role, content: message2.content });
|
|
27046
|
+
}
|
|
27047
|
+
const limit = Math.min(
|
|
27048
|
+
MAX_HITS,
|
|
27049
|
+
Math.max(1, typeof input.limit === "number" ? Math.trunc(input.limit) : DEFAULT_HITS)
|
|
27050
|
+
);
|
|
27051
|
+
const { hits, gaps } = await ctx.session.search(query, limit);
|
|
27052
|
+
const notes = [];
|
|
27053
|
+
if (hits.length === 0)
|
|
27054
|
+
notes.push("no turn of this session's log matches; your notes (notes {}) may hold what you look for");
|
|
27055
|
+
if (ctx.session.session.seedFrom === 0) notes.push("this is the first run of its session: the log begins with it");
|
|
27056
|
+
return JSON.stringify({
|
|
27057
|
+
hits: hits.map((h) => ({
|
|
27058
|
+
turn: h.idx,
|
|
27059
|
+
...h.role !== void 0 ? { role: h.role } : {},
|
|
27060
|
+
snippet: snippetOf(h.text)
|
|
27061
|
+
})),
|
|
27062
|
+
...gaps.length ? {
|
|
27063
|
+
gaps: `the log has a gap at ${gaps.map((g) => `turn ${g}`).join(", ")}: a run before it detached from the ledger, so turns it saw after that point are not here`
|
|
27064
|
+
} : {},
|
|
27065
|
+
...notes.length ? { note: notes.join("; ") } : {}
|
|
27066
|
+
});
|
|
27067
|
+
}
|
|
27068
|
+
};
|
|
27069
|
+
notesTool = {
|
|
27070
|
+
name: "notes",
|
|
27071
|
+
description: "Your notes for this thread \u2014 one document, replaced whole on every write, at most 8 KiB. Keep in it what must survive a compaction and reach the next run in this thread: decisions and their reasons, the names of things you found (files, tests, commits), what is not yet proven. They ride your system prompt at the start of every later run and reach you again right after a compaction. Call with `text` to write; with nothing to read.",
|
|
27072
|
+
inputSchema: {
|
|
27073
|
+
type: "object",
|
|
27074
|
+
properties: { text: { type: "string", description: "The whole notepad as it should read from now on" } }
|
|
27075
|
+
},
|
|
27076
|
+
async run(input, ctx) {
|
|
27077
|
+
if (!ctx.session) return UNAVAILABLE3;
|
|
27078
|
+
if (typeof input.text !== "string") {
|
|
27079
|
+
const current = await ctx.session.readNotepad();
|
|
27080
|
+
return current && current.text.length > 0 ? current.text : "(your notes for this thread are empty)";
|
|
27081
|
+
}
|
|
27082
|
+
const bytes = utf8ByteLength(input.text);
|
|
27083
|
+
if (bytes > NOTEPAD_MAX_BYTES)
|
|
27084
|
+
return `your notes are ${bytes} bytes; the notepad holds at most ${NOTEPAD_MAX_BYTES} \u2014 shorten them and write again.`;
|
|
27085
|
+
const written = await ctx.session.writeNotepad(input.text);
|
|
27086
|
+
if (!written.ok) return `notes not saved (${written.reason}): another generation drives this run now.`;
|
|
27087
|
+
ctx.publish?.({ type: "notes", text: redactSecrets(input.text) });
|
|
27088
|
+
return `notes saved (${bytes} bytes); they ride your system prompt at the next run in this thread and reach you again after a compaction.`;
|
|
27089
|
+
}
|
|
27090
|
+
};
|
|
27091
|
+
SESSION_TOOLS = [recallTool, notesTool];
|
|
27092
|
+
}
|
|
27093
|
+
});
|
|
27094
|
+
|
|
26641
27095
|
// ../../src/tools/workspace.ts
|
|
26642
|
-
import { z as
|
|
27096
|
+
import { z as z21 } from "zod";
|
|
26643
27097
|
var bashTool, readFileTool, writeFileTool, NAME_STATUS_MARK, NAME_STATUS_MARK_LINE, OUTPUT_CUT_RE, diffDigestTool, submitVerdictTool, submitDispositionsTool, submitPrDescriptionTool, handoffEntry, submitHandoffTool, updateStatusTool, TOOLSETS;
|
|
26644
27098
|
var init_workspace = __esm({
|
|
26645
27099
|
"../../src/tools/workspace.ts"() {
|
|
@@ -26656,6 +27110,7 @@ var init_workspace = __esm({
|
|
|
26656
27110
|
init_skills();
|
|
26657
27111
|
init_attach();
|
|
26658
27112
|
init_runs2();
|
|
27113
|
+
init_session();
|
|
26659
27114
|
bashTool = {
|
|
26660
27115
|
name: "bash",
|
|
26661
27116
|
description: "Run a bash command in the workspace directory. Use for git, gh, tests, builds, and inspecting files. Commands time out after 5 minutes (300000 ms) by default; pass timeoutMs when a command legitimately needs longer \u2014 a full test suite, a large build \u2014 up to the 1200000 ms (20 minute) maximum; a command is also clipped to the run's own remaining time, so it can never outlive the run. Output is truncated at 30k characters.",
|
|
@@ -26938,7 +27393,7 @@ ${raw.trim()}`;
|
|
|
26938
27393
|
try {
|
|
26939
27394
|
desc = parsePrDescription(input);
|
|
26940
27395
|
} catch (err2) {
|
|
26941
|
-
const detail = err2 instanceof
|
|
27396
|
+
const detail = err2 instanceof z21.ZodError ? err2.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ") : String(err2);
|
|
26942
27397
|
return `error: invalid PR description \u2014 ${detail}`;
|
|
26943
27398
|
}
|
|
26944
27399
|
ctx.onPrDescription?.(desc);
|
|
@@ -27025,7 +27480,8 @@ ${raw.trim()}`;
|
|
|
27025
27480
|
listSkillsTool,
|
|
27026
27481
|
useSkillTool,
|
|
27027
27482
|
...GITHUB_READ_TOOLS,
|
|
27028
|
-
...GITHUB_ISSUE_WRITE_TOOLS
|
|
27483
|
+
...GITHUB_ISSUE_WRITE_TOOLS,
|
|
27484
|
+
...SESSION_TOOLS
|
|
27029
27485
|
],
|
|
27030
27486
|
readonly: [
|
|
27031
27487
|
bashTool,
|
|
@@ -27036,7 +27492,8 @@ ${raw.trim()}`;
|
|
|
27036
27492
|
diffDigestTool,
|
|
27037
27493
|
listSkillsTool,
|
|
27038
27494
|
useSkillTool,
|
|
27039
|
-
...GITHUB_READ_TOOLS
|
|
27495
|
+
...GITHUB_READ_TOOLS,
|
|
27496
|
+
...SESSION_TOOLS
|
|
27040
27497
|
],
|
|
27041
27498
|
web: [webFetchTool, webSearchTool, updateStatusTool, ...GITHUB_READ_TOOLS],
|
|
27042
27499
|
/** The general agent: no workspace, no shell — GitHub reads + issue writes
|
|
@@ -27056,7 +27513,8 @@ ${raw.trim()}`;
|
|
|
27056
27513
|
webSearchTool,
|
|
27057
27514
|
listSkillsTool,
|
|
27058
27515
|
useSkillTool,
|
|
27059
|
-
...GITHUB_READ_TOOLS
|
|
27516
|
+
...GITHUB_READ_TOOLS,
|
|
27517
|
+
...SESSION_TOOLS
|
|
27060
27518
|
],
|
|
27061
27519
|
/** The conductor (docs/reference/specs/agent-conductor.md): the five run
|
|
27062
27520
|
* tools — the only toolset that holds them, so no other preset can start,
|
|
@@ -27934,7 +28392,7 @@ Target repository: ${input.repo}. ` + (workspace ? `Your shell starts in the wor
|
|
|
27934
28392
|
const agentSystem = (head) => {
|
|
27935
28393
|
return [residentSystem ?? agent.system, targetBlock(head), input.contract, blocks.skills, blocks.mcp].filter((b) => Boolean(b)).join("\n\n");
|
|
27936
28394
|
};
|
|
27937
|
-
return (head) => [blocks.memory, blocks.config, blocks.about, blocks.instructions, agentSystem(head)].filter((part) => Boolean(part)).join("\n\n");
|
|
28395
|
+
return (head) => [blocks.memory, blocks.notes, blocks.config, blocks.about, blocks.instructions, agentSystem(head)].filter((part) => Boolean(part)).join("\n\n");
|
|
27938
28396
|
}
|
|
27939
28397
|
async function settleReviewedHead(input) {
|
|
27940
28398
|
return input.span ? input.span.span("run.settle_reviewed_head", (span) => settle(input, span)) : settle(input, void 0);
|
|
@@ -28777,13 +29235,13 @@ var init_frontmatter = __esm({
|
|
|
28777
29235
|
|
|
28778
29236
|
// ../../src/skills/stores.ts
|
|
28779
29237
|
import { existsSync as existsSync11, readFileSync as readFileSync13, readdirSync as readdirSync2 } from "node:fs";
|
|
28780
|
-
import { join as
|
|
29238
|
+
import { join as join10 } from "node:path";
|
|
28781
29239
|
function loadBundledSkills(dir) {
|
|
28782
29240
|
if (!existsSync11(dir)) return [];
|
|
28783
29241
|
const skills = [];
|
|
28784
29242
|
for (const entry of readdirSync2(dir, { withFileTypes: true })) {
|
|
28785
29243
|
if (!entry.isDirectory()) continue;
|
|
28786
|
-
const file =
|
|
29244
|
+
const file = join10(dir, entry.name, "SKILL.md");
|
|
28787
29245
|
if (!existsSync11(file)) continue;
|
|
28788
29246
|
try {
|
|
28789
29247
|
skills.push(parseSkillMarkdown(readFileSync13(file, "utf8")));
|
|
@@ -29340,6 +29798,20 @@ function mintRunBearer(deps, ctx) {
|
|
|
29340
29798
|
publish: (event) => registry.publish(runId3, event)
|
|
29341
29799
|
});
|
|
29342
29800
|
}
|
|
29801
|
+
function sessionNotesBlock(session) {
|
|
29802
|
+
const parts = [];
|
|
29803
|
+
const notepad = session.notepad?.trim();
|
|
29804
|
+
const summary = session.summary?.trim();
|
|
29805
|
+
if (notepad)
|
|
29806
|
+
parts.push(
|
|
29807
|
+
"YOUR NOTES FOR THIS THREAD (kept with the `notes` tool \u2014 the one thing sure to survive a compaction and reach the next run here; replace them whole when they change):\n" + notepad
|
|
29808
|
+
);
|
|
29809
|
+
if (summary)
|
|
29810
|
+
parts.push(
|
|
29811
|
+
"SUMMARY OF THE EARLIER CONVERSATION (written when the context was compacted; every earlier turn is still reachable with `recall`):\n" + summary
|
|
29812
|
+
);
|
|
29813
|
+
return parts.length ? parts.join("\n\n") : void 0;
|
|
29814
|
+
}
|
|
29343
29815
|
async function composePrompt(deps, ctx) {
|
|
29344
29816
|
const {
|
|
29345
29817
|
msg,
|
|
@@ -29402,6 +29874,7 @@ async function composePrompt(deps, ctx) {
|
|
|
29402
29874
|
);
|
|
29403
29875
|
const instructionsBlock = customInstructionsBlock(scopes);
|
|
29404
29876
|
const memoryBlock = await root.span("dispatch.compose", () => memoryBlockP);
|
|
29877
|
+
const notesBlock = ctx.session ? sessionNotesBlock(ctx.session) : void 0;
|
|
29405
29878
|
const composeSystem = makeSystemComposer({
|
|
29406
29879
|
agent,
|
|
29407
29880
|
resident: resident === true,
|
|
@@ -29417,6 +29890,7 @@ async function composePrompt(deps, ctx) {
|
|
|
29417
29890
|
...ctx.contract !== void 0 ? { contract: ctx.contract } : {},
|
|
29418
29891
|
blocks: {
|
|
29419
29892
|
memory: memoryBlock,
|
|
29893
|
+
notes: notesBlock,
|
|
29420
29894
|
config: configBlock,
|
|
29421
29895
|
about: aboutBlock,
|
|
29422
29896
|
instructions: instructionsBlock,
|
|
@@ -29483,7 +29957,8 @@ async function claimRun(deps, ctx) {
|
|
|
29483
29957
|
parentRunId,
|
|
29484
29958
|
coordinator,
|
|
29485
29959
|
seed,
|
|
29486
|
-
route: route3
|
|
29960
|
+
route: route3,
|
|
29961
|
+
seedLog
|
|
29487
29962
|
} = ctx;
|
|
29488
29963
|
const { resident, binding } = selection;
|
|
29489
29964
|
let ledgerRun = ctx.ledgerRun;
|
|
@@ -29528,8 +30003,9 @@ async function claimRun(deps, ctx) {
|
|
|
29528
30003
|
({ name, description, inputSchema }) => ({ name, description, inputSchema })
|
|
29529
30004
|
),
|
|
29530
30005
|
// The seed carries the EFFECTIVE budget, so a resume runs on what
|
|
29531
|
-
// this run was admitted with, not on the preset's own number
|
|
29532
|
-
seed
|
|
30006
|
+
// this run was admitted with, not on the preset's own number — and,
|
|
30007
|
+
// for a seed read from the log, the rows it reuses (session-log item 9).
|
|
30008
|
+
seed: { messages, budgetMs: profile.minutes * 6e4, ...seedLog ? { log: seedLog } : {} },
|
|
29533
30009
|
// A stop asked of another container (`/runs/stop` there) reaches this
|
|
29534
30010
|
// run through its heartbeat and is honored like a local one; a fence
|
|
29535
30011
|
// (another generation took the run) is a hard stop — nothing more may
|
|
@@ -29599,7 +30075,7 @@ function writeFileScripts(path, content) {
|
|
|
29599
30075
|
chunks.push(content.slice(i, i + WRITE_CHUNK_CHARS));
|
|
29600
30076
|
const dir = path.slice(0, path.lastIndexOf("/")) || ".";
|
|
29601
30077
|
return chunks.map(
|
|
29602
|
-
(chunk, i) => i === 0 ? `mkdir -p ${shellQuote(dir)} &&
|
|
30078
|
+
(chunk, i) => i === 0 ? `umask 077 && mkdir -p ${shellQuote(dir)} && printf '%s' ${shellQuote(chunk)} > ${shellQuote(path)}` : `printf '%s' ${shellQuote(chunk)} >> ${shellQuote(path)}`
|
|
29603
30079
|
);
|
|
29604
30080
|
}
|
|
29605
30081
|
function startScript(start3) {
|
|
@@ -29610,7 +30086,7 @@ function startScript(start3) {
|
|
|
29610
30086
|
`pi ${args.map(shellQuote).join(" ")} <&3 2>>${shellQuote(paths.errLog)} | ${LOG_FILTER} >> ${shellQuote(paths.log)}`
|
|
29611
30087
|
].join("; ");
|
|
29612
30088
|
return [
|
|
29613
|
-
`mkdir -p ${shellQuote(paths.dir)} ${shellQuote(paths.sessionDir)} ${shellQuote(paths.commandDir)}`,
|
|
30089
|
+
`(umask 077 && mkdir -p ${shellQuote(paths.dir)} ${shellQuote(paths.sessionDir)} ${shellQuote(paths.commandDir)})`,
|
|
29614
30090
|
`rm -f ${shellQuote(paths.fifo)}`,
|
|
29615
30091
|
`mkfifo -m 600 ${shellQuote(paths.fifo)}`,
|
|
29616
30092
|
`: > ${shellQuote(paths.log)}`,
|
|
@@ -29638,6 +30114,9 @@ function killScript(pid) {
|
|
|
29638
30114
|
function tailScript(path, bytes) {
|
|
29639
30115
|
return `tail -c ${bytes} ${shellQuote(path)} 2>/dev/null || true`;
|
|
29640
30116
|
}
|
|
30117
|
+
function removeScript(dir) {
|
|
30118
|
+
return `rm -rf ${shellQuote(dir)}`;
|
|
30119
|
+
}
|
|
29641
30120
|
function stdoutOf(operation, out) {
|
|
29642
30121
|
const exit = parseExitPrefix(out);
|
|
29643
30122
|
if (exit.failed) throw new PiContainerError(operation, out);
|
|
@@ -29706,6 +30185,9 @@ var init_container = __esm({
|
|
|
29706
30185
|
return "";
|
|
29707
30186
|
}
|
|
29708
30187
|
}
|
|
30188
|
+
async remove(paths) {
|
|
30189
|
+
stdoutOf("remove", await this.exec(removeScript(paths.dir)));
|
|
30190
|
+
}
|
|
29709
30191
|
exec(script, env) {
|
|
29710
30192
|
return this.executor.exec(script, { timeoutMs: OP_TIMEOUT_MS, ...env ? { env } : {} });
|
|
29711
30193
|
}
|
|
@@ -30382,9 +30864,8 @@ function piBuiltinToolsFor(identity2) {
|
|
|
30382
30864
|
function piThinkingLevel(effort2) {
|
|
30383
30865
|
return effort2;
|
|
30384
30866
|
}
|
|
30385
|
-
function piRunPaths(runId3
|
|
30386
|
-
const
|
|
30387
|
-
const dir = `${root}/${runId3}`;
|
|
30867
|
+
function piRunPaths(runId3) {
|
|
30868
|
+
const dir = `/tmp/switchboard-pi-${runId3}`;
|
|
30388
30869
|
return {
|
|
30389
30870
|
dir,
|
|
30390
30871
|
agentDir: `${dir}/agent`,
|
|
@@ -30587,6 +31068,14 @@ var init_transport = __esm({
|
|
|
30587
31068
|
function relayedTools(tools) {
|
|
30588
31069
|
return tools.filter((t) => !NATIVE_WORKSPACE_TOOLS.has(t.name));
|
|
30589
31070
|
}
|
|
31071
|
+
function compactionSteer(notepad, opts = {}) {
|
|
31072
|
+
const notes = notepad?.trim();
|
|
31073
|
+
const reach = "Your context was just compacted: the summary now standing in for the earlier turns is pi's, and every one of those turns is still reachable with the `recall` tool (search by words, or read a turn by its number).\n\n";
|
|
31074
|
+
if (opts.unavailable)
|
|
31075
|
+
return reach + "Your notes for this thread could not be read just now; `notes {}` reads them on demand, and `notes { text }` still writes them.";
|
|
31076
|
+
return reach + (notes ? `YOUR NOTES FOR THIS THREAD, as you last wrote them with \`notes\`:
|
|
31077
|
+
${notes}` : "Your notes for this thread are empty. Write them with the `notes` tool when you have decisions, the names of things you found and what is not yet proven worth keeping \u2014 they survive every compaction and reach the next run in this thread.");
|
|
31078
|
+
}
|
|
30590
31079
|
function splitSeed(seed) {
|
|
30591
31080
|
for (let i = seed.length - 1; i >= 0; i--) {
|
|
30592
31081
|
if (seed[i].role === "user") return { session: seed.slice(0, i), prompt: seed[i] };
|
|
@@ -30631,7 +31120,7 @@ async function runPiHarness(deps, run2) {
|
|
|
30631
31120
|
emit({ type: "run_note", kind, summary, ...mode ? { mode } : {} });
|
|
30632
31121
|
};
|
|
30633
31122
|
const bridge = new PiBridge({ emit, onProgress: run2.onProgress, agentSpan, clock: clock3 });
|
|
30634
|
-
const paths = piRunPaths(run2.runId
|
|
31123
|
+
const paths = piRunPaths(run2.runId);
|
|
30635
31124
|
const remainingMs = run2.resume?.remainingMs ?? run2.agent.maxMinutes * 6e4;
|
|
30636
31125
|
const deadline = now() + remainingMs;
|
|
30637
31126
|
const warnAt = deadline - Math.min(3 * 6e4, run2.agent.maxMinutes * 15e3);
|
|
@@ -30865,7 +31354,23 @@ async function runPiHarness(deps, run2) {
|
|
|
30865
31354
|
}
|
|
30866
31355
|
}
|
|
30867
31356
|
if (obs.message) await mirror.onMessage(obs.message, bridge.turns);
|
|
30868
|
-
if (obs.compaction)
|
|
31357
|
+
if (obs.compaction) {
|
|
31358
|
+
await mirror.onCompaction(obs.compaction, bridge.turns);
|
|
31359
|
+
if (!writeUp && !hardStopped && !catchingUp) {
|
|
31360
|
+
let notepad;
|
|
31361
|
+
let unavailable = false;
|
|
31362
|
+
try {
|
|
31363
|
+
notepad = run2.notepad ? (await run2.notepad())?.text : void 0;
|
|
31364
|
+
} catch (err2) {
|
|
31365
|
+
unavailable = true;
|
|
31366
|
+
note(
|
|
31367
|
+
"harness_error",
|
|
31368
|
+
`the notepad could not be read for the compaction steer (${err2 instanceof Error ? err2.message : String(err2)}); steered without it`
|
|
31369
|
+
);
|
|
31370
|
+
}
|
|
31371
|
+
transport.send({ type: "steer", message: compactionSteer(notepad, { unavailable }) });
|
|
31372
|
+
}
|
|
31373
|
+
}
|
|
30869
31374
|
if (obs.providerError !== void 0) {
|
|
30870
31375
|
if (catchingUp)
|
|
30871
31376
|
note("harness_error", `a model call failed while the bot was away (${obs.providerError}); continuing`);
|
|
@@ -30903,6 +31408,8 @@ async function runPiHarness(deps, run2) {
|
|
|
30903
31408
|
forget();
|
|
30904
31409
|
if (pid !== void 0) await container.kill(pid).catch(() => {
|
|
30905
31410
|
});
|
|
31411
|
+
await container.remove(paths).catch(() => {
|
|
31412
|
+
});
|
|
30906
31413
|
agentSpan?.end(hardStopped || bypass ? "error" : "ok");
|
|
30907
31414
|
}
|
|
30908
31415
|
}
|
|
@@ -31451,6 +31958,7 @@ async function runLoop2(deps, ctx) {
|
|
|
31451
31958
|
runs,
|
|
31452
31959
|
steer,
|
|
31453
31960
|
wait,
|
|
31961
|
+
session,
|
|
31454
31962
|
parentRunId,
|
|
31455
31963
|
coordinator,
|
|
31456
31964
|
seed
|
|
@@ -31577,12 +32085,11 @@ async function runLoop2(deps, ctx) {
|
|
|
31577
32085
|
const uploadTicket = io.uploadTicket?.bind(io);
|
|
31578
32086
|
const nextStagedIndex = ctx.stagingIndex ?? stagingIndex();
|
|
31579
32087
|
let artifactSeq = 0;
|
|
31580
|
-
const runUrl = runPageLink(run2.id);
|
|
31581
32088
|
const artifacts = deps.artifacts ? {
|
|
31582
32089
|
store: deps.artifacts,
|
|
31583
32090
|
runId: run2.id,
|
|
31584
32091
|
nextSeq: () => ++artifactSeq,
|
|
31585
|
-
|
|
32092
|
+
artifactUrl: (key) => artifactLink(run2.id, key, run2.token),
|
|
31586
32093
|
reply: (text) => io.reply(text)
|
|
31587
32094
|
} : void 0;
|
|
31588
32095
|
const toolContext = {
|
|
@@ -31599,6 +32106,7 @@ async function runLoop2(deps, ctx) {
|
|
|
31599
32106
|
...runs ? { runs } : {},
|
|
31600
32107
|
...steer ? { steer } : {},
|
|
31601
32108
|
...wait ? { wait } : {},
|
|
32109
|
+
...session ? { session } : {},
|
|
31602
32110
|
onVerdict,
|
|
31603
32111
|
onDigest,
|
|
31604
32112
|
onPrDescription,
|
|
@@ -31646,15 +32154,12 @@ async function runLoop2(deps, ctx) {
|
|
|
31646
32154
|
messages,
|
|
31647
32155
|
tools: relayedTools(mergeTools(TOOLSETS[agent.toolset] ?? [], mcpForRun?.tools)),
|
|
31648
32156
|
toolContext,
|
|
32157
|
+
...session ? { notepad: () => session.readNotepad() } : {},
|
|
31649
32158
|
rules: {
|
|
31650
32159
|
checkout: binding?.workspace ?? "/workspace",
|
|
31651
32160
|
...ownBranch !== void 0 ? { branch: ownBranch } : {},
|
|
31652
32161
|
protectedBranches
|
|
31653
32162
|
},
|
|
31654
|
-
// The resident runs this thread's commands as its pool user: pi's
|
|
31655
|
-
// files go under that user's own root, never one another thread's
|
|
31656
|
-
// user created (harness-pi.md item 4).
|
|
31657
|
-
...binding?.user ? { user: binding.user } : {},
|
|
31658
32163
|
...round2.selection.backend ? { backend: round2.selection.backend } : {},
|
|
31659
32164
|
span: root,
|
|
31660
32165
|
control: run2.control,
|
|
@@ -33326,15 +33831,7 @@ var init_settle = __esm({
|
|
|
33326
33831
|
});
|
|
33327
33832
|
|
|
33328
33833
|
// ../../src/core/dispatch/lineage.ts
|
|
33329
|
-
|
|
33330
|
-
let newest;
|
|
33331
|
-
try {
|
|
33332
|
-
const page2 = await service.listRuns({ status: "all", visibleTo: { kind: "all" }, threadKey, limit: 1 });
|
|
33333
|
-
newest = page2.runs[0];
|
|
33334
|
-
} catch (err2) {
|
|
33335
|
-
console.warn(`[lineage] ${threadKey}: thread read failed \u2014 ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
33336
|
-
return void 0;
|
|
33337
|
-
}
|
|
33834
|
+
function lineageOf(newest) {
|
|
33338
33835
|
if (newest === void 0 || newest.parentRunId === void 0) return void 0;
|
|
33339
33836
|
return {
|
|
33340
33837
|
parentRunId: newest.parentRunId,
|
|
@@ -33391,6 +33888,150 @@ var init_lineage = __esm({
|
|
|
33391
33888
|
}
|
|
33392
33889
|
});
|
|
33393
33890
|
|
|
33891
|
+
// ../../src/core/dispatch/thread.ts
|
|
33892
|
+
async function readThread(service, threadKey) {
|
|
33893
|
+
try {
|
|
33894
|
+
const page2 = await service.listRuns({
|
|
33895
|
+
status: "all",
|
|
33896
|
+
visibleTo: { kind: "all" },
|
|
33897
|
+
threadKey,
|
|
33898
|
+
limit: THREAD_READ_LIMIT
|
|
33899
|
+
});
|
|
33900
|
+
return page2.runs;
|
|
33901
|
+
} catch (err2) {
|
|
33902
|
+
console.warn(`[thread] ${threadKey}: thread read failed \u2014 ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
33903
|
+
return void 0;
|
|
33904
|
+
}
|
|
33905
|
+
}
|
|
33906
|
+
function continuable(run2) {
|
|
33907
|
+
return run2.finished && run2.session !== void 0 && run2.agent !== void 0;
|
|
33908
|
+
}
|
|
33909
|
+
function stickyAgentOf(runs, onPi) {
|
|
33910
|
+
const newest = runs[0];
|
|
33911
|
+
if (newest === void 0 || !continuable(newest) || !onPi(newest.agent)) return void 0;
|
|
33912
|
+
return newest.agent;
|
|
33913
|
+
}
|
|
33914
|
+
function previousRunOf(runs, agent) {
|
|
33915
|
+
const run2 = runs.find((r) => r.agent === agent && continuable(r));
|
|
33916
|
+
if (run2 === void 0) return void 0;
|
|
33917
|
+
return {
|
|
33918
|
+
...run2.finishedAt !== void 0 ? { finishedAt: run2.finishedAt } : {},
|
|
33919
|
+
broken: run2.session.range === "broken"
|
|
33920
|
+
};
|
|
33921
|
+
}
|
|
33922
|
+
var THREAD_READ_LIMIT;
|
|
33923
|
+
var init_thread = __esm({
|
|
33924
|
+
"../../src/core/dispatch/thread.ts"() {
|
|
33925
|
+
"use strict";
|
|
33926
|
+
THREAD_READ_LIMIT = 8;
|
|
33927
|
+
}
|
|
33928
|
+
});
|
|
33929
|
+
|
|
33930
|
+
// ../../src/core/dispatch/seed.ts
|
|
33931
|
+
function sessionSeed(input) {
|
|
33932
|
+
const { tail, previous, history, request } = input;
|
|
33933
|
+
const { from, transcript } = tail;
|
|
33934
|
+
if (from === 0 && transcript.turns === 0) return void 0;
|
|
33935
|
+
const notes = [];
|
|
33936
|
+
const messages = [];
|
|
33937
|
+
const compactions = transcript.compactions;
|
|
33938
|
+
const afterCompaction = compactions.reduce((n2, c) => Math.max(n2, c.before), 0);
|
|
33939
|
+
let start3 = -1;
|
|
33940
|
+
for (let i = afterCompaction; i < transcript.messages.length; i++) {
|
|
33941
|
+
const m = transcript.messages[i];
|
|
33942
|
+
if (m.role === "user" && m.content.some((p) => p.type === "text")) {
|
|
33943
|
+
start3 = i;
|
|
33944
|
+
break;
|
|
33945
|
+
}
|
|
33946
|
+
}
|
|
33947
|
+
const summary = compactions.length ? compactions[compactions.length - 1].entry.summary : void 0;
|
|
33948
|
+
const logFrom = start3 < 0 ? from + transcript.turns : from + start3 + compactions.length;
|
|
33949
|
+
const kept = start3 < 0 ? [] : transcript.messages.slice(start3).map(withoutThinking);
|
|
33950
|
+
if (kept.length === 0) {
|
|
33951
|
+
notes.push(
|
|
33952
|
+
transcript.turns === 0 ? "session seed: the log's newest turn alone is over the seed budget \u2014 the run starts from the request" : "session seed: no user text turn in the tail within the seed budget \u2014 the run starts from the request"
|
|
33953
|
+
);
|
|
33954
|
+
}
|
|
33955
|
+
messages.push(...kept);
|
|
33956
|
+
const last = kept[kept.length - 1];
|
|
33957
|
+
if (last?.role === "assistant") {
|
|
33958
|
+
const calls = last.content.filter((p) => p.type === "tool_use");
|
|
33959
|
+
if (calls.length > 0) {
|
|
33960
|
+
messages.push({
|
|
33961
|
+
role: "user",
|
|
33962
|
+
content: calls.map((c) => ({
|
|
33963
|
+
type: "tool_result",
|
|
33964
|
+
toolUseId: c.id,
|
|
33965
|
+
content: settledResult(c.name),
|
|
33966
|
+
isError: true
|
|
33967
|
+
}))
|
|
33968
|
+
});
|
|
33969
|
+
}
|
|
33970
|
+
}
|
|
33971
|
+
if (previous?.broken) messages.push({ role: "user", content: [{ type: "text", text: GAP_MARKER }] });
|
|
33972
|
+
if (previous?.finishedAt === void 0) {
|
|
33973
|
+
notes.push(
|
|
33974
|
+
"session seed: the previous run's end is unknown, so the lines written since could not be told apart \u2014 only the request follows the tail"
|
|
33975
|
+
);
|
|
33976
|
+
} else {
|
|
33977
|
+
const end = previous.finishedAt;
|
|
33978
|
+
for (const h of history) {
|
|
33979
|
+
if (h.role !== "user" || h.at === void 0 || h.at <= end) continue;
|
|
33980
|
+
messages.push({ role: "user", content: turnContent(h.text, h.images, h.documents) });
|
|
33981
|
+
}
|
|
33982
|
+
}
|
|
33983
|
+
messages.push({ role: "user", content: turnContent(request.text, request.images, request.documents) });
|
|
33984
|
+
return {
|
|
33985
|
+
messages,
|
|
33986
|
+
log: { from: logFrom, turns: kept.length },
|
|
33987
|
+
...summary !== void 0 ? { summary } : {},
|
|
33988
|
+
notes
|
|
33989
|
+
};
|
|
33990
|
+
}
|
|
33991
|
+
async function sessionSeedFor(input) {
|
|
33992
|
+
const key = sessionKey(input.threadKey, input.agent);
|
|
33993
|
+
let tail;
|
|
33994
|
+
try {
|
|
33995
|
+
tail = await input.ledger.readSessionTail(key, SEED_BUDGET_BYTES);
|
|
33996
|
+
} catch (err2) {
|
|
33997
|
+
const why = err2 instanceof Error ? err2.message : String(err2);
|
|
33998
|
+
return { notes: [`session seed: the log ${key} could not be read (${why}) \u2014 the run seeds from the channel`] };
|
|
33999
|
+
}
|
|
34000
|
+
const seed = sessionSeed({
|
|
34001
|
+
tail,
|
|
34002
|
+
previous: previousRunOf(input.thread, input.agent),
|
|
34003
|
+
history: input.history,
|
|
34004
|
+
request: input.request
|
|
34005
|
+
});
|
|
34006
|
+
if (!seed) return { notes: [] };
|
|
34007
|
+
try {
|
|
34008
|
+
const notepad = await input.ledger.readNotepad(key);
|
|
34009
|
+
if (notepad && notepad.text.trim().length > 0) seed.notepad = notepad.text;
|
|
34010
|
+
} catch (err2) {
|
|
34011
|
+
const why = err2 instanceof Error ? err2.message : String(err2);
|
|
34012
|
+
seed.notes.push(`session seed: the notepad of ${key} could not be read (${why}) \u2014 the run starts without it`);
|
|
34013
|
+
}
|
|
34014
|
+
return { seed, notes: seed.notes };
|
|
34015
|
+
}
|
|
34016
|
+
function withoutThinking(message2) {
|
|
34017
|
+
if (message2.role !== "assistant") return message2;
|
|
34018
|
+
const content = message2.content.filter((p) => p.type !== "thinking" && p.type !== "redacted_thinking");
|
|
34019
|
+
if (content.length === message2.content.length) return message2;
|
|
34020
|
+
return { ...message2, content: content.length ? content : [{ type: "text", text: "(reasoning omitted)" }] };
|
|
34021
|
+
}
|
|
34022
|
+
var SEED_BUDGET_TOKENS, SEED_BUDGET_BYTES, settledResult;
|
|
34023
|
+
var init_seed = __esm({
|
|
34024
|
+
"../../src/core/dispatch/seed.ts"() {
|
|
34025
|
+
"use strict";
|
|
34026
|
+
init_sessionLog();
|
|
34027
|
+
init_messages();
|
|
34028
|
+
init_thread();
|
|
34029
|
+
SEED_BUDGET_TOKENS = 6e4;
|
|
34030
|
+
SEED_BUDGET_BYTES = SEED_BUDGET_TOKENS * 4;
|
|
34031
|
+
settledResult = (toolName) => `The run that made this call ended before this ${toolName} call's result was recorded; its effects are unknown \u2014 re-check them before repeating it.`;
|
|
34032
|
+
}
|
|
34033
|
+
});
|
|
34034
|
+
|
|
33394
34035
|
// ../../src/core/runEnding.ts
|
|
33395
34036
|
function createRunEnding(deps) {
|
|
33396
34037
|
const log = deps.log ?? console.error;
|
|
@@ -33528,7 +34169,8 @@ async function dispatch(deps, msg, io, opts = {}) {
|
|
|
33528
34169
|
const { directives, history } = await readRequest({ msg, io, root });
|
|
33529
34170
|
if (await answerOperation(deps, { msg, io, ending, trace, directives, history })) return ended;
|
|
33530
34171
|
const runsService = deps.runs ?? createRunsService({ registry, store: deps.runStore });
|
|
33531
|
-
const
|
|
34172
|
+
const thread = opts.parent || opts.coordinator || resume || restart || history.length === 0 ? void 0 : await readThread(runsService, msg.threadKey);
|
|
34173
|
+
const lineage = lineageOf(thread?.[0]);
|
|
33532
34174
|
const parent = opts.parent ?? (lineage ? lineageParent(lineage) : void 0);
|
|
33533
34175
|
const tellLineage = async (heard) => {
|
|
33534
34176
|
if (!lineage) return;
|
|
@@ -33539,7 +34181,18 @@ async function dispatch(deps, msg, io, opts = {}) {
|
|
|
33539
34181
|
heard
|
|
33540
34182
|
);
|
|
33541
34183
|
};
|
|
33542
|
-
const
|
|
34184
|
+
const harnessBlock = deps.config.config.harness;
|
|
34185
|
+
const onPi = (name) => {
|
|
34186
|
+
const def = AGENTS[name];
|
|
34187
|
+
return def !== void 0 && effectiveHarness(def, harnessBlock) === "pi";
|
|
34188
|
+
};
|
|
34189
|
+
const stickyAgent = thread ? stickyAgentOf(thread, onPi) : void 0;
|
|
34190
|
+
const settled = resolveRun(deps, {
|
|
34191
|
+
msg,
|
|
34192
|
+
directives,
|
|
34193
|
+
history,
|
|
34194
|
+
...stickyAgent !== void 0 ? { stickyAgent } : {}
|
|
34195
|
+
});
|
|
33543
34196
|
const { sticky } = settled;
|
|
33544
34197
|
let { resolved, agentSource } = settled;
|
|
33545
34198
|
let route3;
|
|
@@ -33677,8 +34330,23 @@ async function dispatch(deps, msg, io, opts = {}) {
|
|
|
33677
34330
|
}
|
|
33678
34331
|
const contractBlock = opts.contract ? renderContract(opts.contract, { maxChars: DEFAULT_CONTRACT_MAX_CHARS }).text : void 0;
|
|
33679
34332
|
const requestText = route3?.parts ? compoundBrief(directives.text, route3.parts) : directives.text;
|
|
33680
|
-
const
|
|
33681
|
-
|
|
34333
|
+
const fromSession = !resume && !opts.seed && thread && effectiveHarness(agent, harnessBlock) === "pi" ? await sessionSeedFor({
|
|
34334
|
+
ledger: deps.runLedger,
|
|
34335
|
+
threadKey: msg.threadKey,
|
|
34336
|
+
agent: agent.name,
|
|
34337
|
+
thread,
|
|
34338
|
+
history,
|
|
34339
|
+
request: {
|
|
34340
|
+
text: requestText,
|
|
34341
|
+
...msg.images ? { images: msg.images } : {},
|
|
34342
|
+
...msg.documents ? { documents: msg.documents } : {}
|
|
34343
|
+
}
|
|
34344
|
+
}) : void 0;
|
|
34345
|
+
const session = fromSession?.seed;
|
|
34346
|
+
const seedNotes = fromSession?.notes ?? [];
|
|
34347
|
+
const seed = opts.seed ? "parent" : session ? "session" : "channel";
|
|
34348
|
+
const seedTurns = opts.seed ?? (session ? textTurnsOf(session.messages.slice(0, -1)) : void 0);
|
|
34349
|
+
const built = session ? session.messages : buildMessages(opts.seed ?? history, requestText, msg.images, msg.documents);
|
|
33682
34350
|
const messages = resume ? resume.plan.messages : contractBlock !== void 0 && agent.name !== "review" ? withContractInFirstUserTurn(built, contractBlock) : built;
|
|
33683
34351
|
const headPreflight = await authorizePrHead({
|
|
33684
34352
|
msg,
|
|
@@ -33716,12 +34384,14 @@ async function dispatch(deps, msg, io, opts = {}) {
|
|
|
33716
34384
|
parentRunId,
|
|
33717
34385
|
coordinator,
|
|
33718
34386
|
seed,
|
|
33719
|
-
...
|
|
34387
|
+
...seedTurns ? { seedTurns } : {},
|
|
33720
34388
|
agentSource,
|
|
33721
34389
|
...routeEvent ? { route: routeEvent } : {}
|
|
33722
34390
|
});
|
|
33723
34391
|
const { run: run2, runId: runId3, channelVisibility, liveUrl, publishText, publishMeta } = registration;
|
|
33724
34392
|
registered = run2;
|
|
34393
|
+
for (const summary of seedNotes)
|
|
34394
|
+
registry.publish(run2.id, { type: "run_note", kind: "seed", summary: oneLine(summary), at: clock3() });
|
|
33725
34395
|
await tellLineage({ kind: "started", runId: runId3 });
|
|
33726
34396
|
const staged = !resume && deps.artifacts && msg.staged && msg.staged.length > 0 ? msg.staged : [];
|
|
33727
34397
|
const nextStagedIndex = stagingIndex();
|
|
@@ -33831,7 +34501,15 @@ async function dispatch(deps, msg, io, opts = {}) {
|
|
|
33831
34501
|
verifiedAtAttach,
|
|
33832
34502
|
resume,
|
|
33833
34503
|
root,
|
|
33834
|
-
...contractBlock !== void 0 && agent.name === "review" ? { contract: contractBlock } : {}
|
|
34504
|
+
...contractBlock !== void 0 && agent.name === "review" ? { contract: contractBlock } : {},
|
|
34505
|
+
// What the session already knows (session-log item 10): the notepad and
|
|
34506
|
+
// the newest compaction's summary the seed brought back, for the prompt.
|
|
34507
|
+
...session && (session.notepad !== void 0 || session.summary !== void 0) ? {
|
|
34508
|
+
session: {
|
|
34509
|
+
...session.notepad !== void 0 ? { notepad: session.notepad } : {},
|
|
34510
|
+
...session.summary !== void 0 ? { summary: session.summary } : {}
|
|
34511
|
+
}
|
|
34512
|
+
} : {}
|
|
33835
34513
|
});
|
|
33836
34514
|
const { mcpForRun, composeSystem, system } = prompt;
|
|
33837
34515
|
const reviewHead = prompt.reviewHead;
|
|
@@ -33886,8 +34564,10 @@ async function dispatch(deps, msg, io, opts = {}) {
|
|
|
33886
34564
|
root,
|
|
33887
34565
|
parentRunId,
|
|
33888
34566
|
coordinator,
|
|
33889
|
-
seed
|
|
34567
|
+
seed,
|
|
34568
|
+
...session ? { seedLog: session.log } : {}
|
|
33890
34569
|
});
|
|
34570
|
+
const sessionTools = sessionCapabilityFor(ledgerRun, deps.runLedger);
|
|
33891
34571
|
const { spawn: spawn7, runs, steer, wait } = runToolCapabilities(
|
|
33892
34572
|
{ core: deps, dispatch, registry, clock: clock3 },
|
|
33893
34573
|
{
|
|
@@ -33938,6 +34618,7 @@ async function dispatch(deps, msg, io, opts = {}) {
|
|
|
33938
34618
|
runs,
|
|
33939
34619
|
steer,
|
|
33940
34620
|
wait,
|
|
34621
|
+
...sessionTools ? { session: sessionTools } : {},
|
|
33941
34622
|
parentRunId,
|
|
33942
34623
|
coordinator,
|
|
33943
34624
|
seed,
|
|
@@ -34067,6 +34748,10 @@ var init_dispatcher = __esm({
|
|
|
34067
34748
|
init_codingChild();
|
|
34068
34749
|
init_settle();
|
|
34069
34750
|
init_lineage();
|
|
34751
|
+
init_seed();
|
|
34752
|
+
init_session();
|
|
34753
|
+
init_thread();
|
|
34754
|
+
init_select();
|
|
34070
34755
|
init_spawn();
|
|
34071
34756
|
init_runsService();
|
|
34072
34757
|
init_runRegistry();
|
|
@@ -34183,7 +34868,7 @@ var init_runHistoryWriter = __esm({
|
|
|
34183
34868
|
|
|
34184
34869
|
// ../../src/core/runStore.ts
|
|
34185
34870
|
import { chmodSync as chmodSync2, existsSync as existsSync12, mkdirSync as mkdirSync8, readFileSync as readFileSync14, renameSync as renameSync2, rmSync as rmSync3, statSync as statSync2, writeFileSync as writeFileSync7 } from "node:fs";
|
|
34186
|
-
import { join as
|
|
34871
|
+
import { join as join11, resolve as resolve9 } from "node:path";
|
|
34187
34872
|
function isValidRunId(id) {
|
|
34188
34873
|
return typeof id === "string" && RUN_ID_PATTERN.test(id);
|
|
34189
34874
|
}
|
|
@@ -34231,7 +34916,7 @@ function buildRunStore(cfg, secrets, deps) {
|
|
|
34231
34916
|
const now = deps.now ?? Date.now;
|
|
34232
34917
|
const policy = retentionPolicyOf(cfg);
|
|
34233
34918
|
if (cfg.store === "file") {
|
|
34234
|
-
const store = new FileRunStore(
|
|
34919
|
+
const store = new FileRunStore(join11(deps.dataDir, RUNS_DIR), { policy, now });
|
|
34235
34920
|
store.sweep();
|
|
34236
34921
|
const timer = (deps.setInterval ?? setInterval)(() => store.sweep(), SWEEP_INTERVAL_MS);
|
|
34237
34922
|
timer.unref?.();
|
|
@@ -34298,7 +34983,7 @@ var init_runStore = __esm({
|
|
|
34298
34983
|
this.now = opts.now ?? Date.now;
|
|
34299
34984
|
}
|
|
34300
34985
|
recordPath(id) {
|
|
34301
|
-
return
|
|
34986
|
+
return join11(this.dir, `${id}.json`);
|
|
34302
34987
|
}
|
|
34303
34988
|
ensureDir() {
|
|
34304
34989
|
mkdirSync8(this.dir, { recursive: true, mode: DIR_MODE });
|
|
@@ -34320,7 +35005,7 @@ var init_runStore = __esm({
|
|
|
34320
35005
|
}
|
|
34321
35006
|
/** Every valid index line, a repeated id resolved to its last line. */
|
|
34322
35007
|
readIndex() {
|
|
34323
|
-
const path =
|
|
35008
|
+
const path = join11(this.dir, INDEX_FILE);
|
|
34324
35009
|
if (!existsSync12(path)) return [];
|
|
34325
35010
|
const byId = /* @__PURE__ */ new Map();
|
|
34326
35011
|
for (const line3 of readFileSync14(path, "utf8").split("\n")) {
|
|
@@ -34352,7 +35037,7 @@ var init_runStore = __esm({
|
|
|
34352
35037
|
const keptIds = new Set(kept.map((r) => r.id));
|
|
34353
35038
|
for (const item of index) if (!keptIds.has(item.id)) rmSync3(this.recordPath(item.id), { force: true });
|
|
34354
35039
|
this.writeAtomic(
|
|
34355
|
-
|
|
35040
|
+
join11(this.dir, INDEX_FILE),
|
|
34356
35041
|
kept.map((r) => JSON.stringify(r)).join("\n") + (kept.length ? "\n" : "")
|
|
34357
35042
|
);
|
|
34358
35043
|
return kept;
|
|
@@ -34468,24 +35153,6 @@ var init_flusher = __esm({
|
|
|
34468
35153
|
}
|
|
34469
35154
|
});
|
|
34470
35155
|
|
|
34471
|
-
// ../../src/core/runLedger/sessionLog.ts
|
|
34472
|
-
function sessionKey(threadKey, agent) {
|
|
34473
|
-
return `${threadKey}:${agent ?? "-"}`;
|
|
34474
|
-
}
|
|
34475
|
-
function requestIndex(seed) {
|
|
34476
|
-
for (let i = seed.length - 1; i >= 0; i--) if (seed[i].role === "user") return i;
|
|
34477
|
-
return 0;
|
|
34478
|
-
}
|
|
34479
|
-
var DEFAULT_SESSION_LOG_MAX_BYTES;
|
|
34480
|
-
var init_sessionLog = __esm({
|
|
34481
|
-
"../../src/core/runLedger/sessionLog.ts"() {
|
|
34482
|
-
"use strict";
|
|
34483
|
-
init_runRecord();
|
|
34484
|
-
init_runRecord();
|
|
34485
|
-
DEFAULT_SESSION_LOG_MAX_BYTES = DEFAULT_RETENTION_POLICY.sessionLogMaxBytes;
|
|
34486
|
-
}
|
|
34487
|
-
});
|
|
34488
|
-
|
|
34489
35156
|
// ../../src/core/runLedger/types.ts
|
|
34490
35157
|
var LEASE_MS, HEARTBEAT_MS, APPEND_FLUSH_MS, APPEND_FLUSH_EVENTS, TRANSCRIPT_REQUEST_BYTES, TRANSCRIPT_PART_BYTES, ATTACHMENT_REF_BYTES, GEN_PATTERN;
|
|
34491
35158
|
var init_types5 = __esm({
|
|
@@ -34538,7 +35205,15 @@ function createLedgerWriteThrough(opts) {
|
|
|
34538
35205
|
if (opts2.seed) {
|
|
34539
35206
|
const key = sessionKey(req.threadKey, req.meta.agent);
|
|
34540
35207
|
const next = await ledger.sessionTail(key);
|
|
34541
|
-
|
|
35208
|
+
let seedFrom = next;
|
|
35209
|
+
if (opts2.log) {
|
|
35210
|
+
if (opts2.log.from + opts2.log.turns === next) seedFrom = opts2.log.from;
|
|
35211
|
+
else
|
|
35212
|
+
warn(
|
|
35213
|
+
`[ledger] ${req.threadKey} run ${req.runId}: the seed names log rows ${opts2.log.from}..${opts2.log.from + opts2.log.turns - 1} but the log's tail is ${next} \u2014 the whole seed is written as new rows`
|
|
35214
|
+
);
|
|
35215
|
+
}
|
|
35216
|
+
session = { key, seedFrom, request: seedFrom + requestIndex(opts2.seed), range: { from: next } };
|
|
34542
35217
|
}
|
|
34543
35218
|
const result = await ledger.claim({
|
|
34544
35219
|
runId: req.runId,
|
|
@@ -34592,7 +35267,10 @@ function createLedgerWriteThrough(opts) {
|
|
|
34592
35267
|
/** The run's place in its session log (session-log item 2); undefined for
|
|
34593
35268
|
* a run without a conversation of its own and for an adopted row claimed
|
|
34594
35269
|
* before the log existed. */
|
|
34595
|
-
|
|
35270
|
+
sessionRow;
|
|
35271
|
+
get session() {
|
|
35272
|
+
return this.sessionRow;
|
|
35273
|
+
}
|
|
34596
35274
|
/** Turns of the run's conversation on the ledger, counted from its seed —
|
|
34597
35275
|
* the last step record's `turnIndex` — so the finish can close the range. */
|
|
34598
35276
|
turnsWritten;
|
|
@@ -34630,11 +35308,11 @@ function createLedgerWriteThrough(opts) {
|
|
|
34630
35308
|
this.stepNo = from.stepNo;
|
|
34631
35309
|
this.lastSeq = from.lastSeq;
|
|
34632
35310
|
this.adopted = from.resumable === true;
|
|
34633
|
-
this.
|
|
35311
|
+
this.sessionRow = from.session;
|
|
34634
35312
|
}
|
|
34635
35313
|
/** A reservation learns its session when the prompt's claim promotes it. */
|
|
34636
35314
|
bindSession(session) {
|
|
34637
|
-
this.
|
|
35315
|
+
this.sessionRow = session;
|
|
34638
35316
|
}
|
|
34639
35317
|
get resumable() {
|
|
34640
35318
|
return !this.detached && (this.seeded || this.adopted);
|
|
@@ -34647,11 +35325,11 @@ function createLedgerWriteThrough(opts) {
|
|
|
34647
35325
|
* ends short of what the model saw, and the next run in the session must
|
|
34648
35326
|
* know. A run that wrote no turn leaves the range open. */
|
|
34649
35327
|
sessionOnRecord() {
|
|
34650
|
-
if (!this.
|
|
34651
|
-
if (this.broken) return { ...this.
|
|
34652
|
-
const from = this.
|
|
34653
|
-
const to = this.turnsWritten !== void 0 && this.turnsWritten > 0 ? this.
|
|
34654
|
-
return { ...this.
|
|
35328
|
+
if (!this.sessionRow) return void 0;
|
|
35329
|
+
if (this.broken) return { ...this.sessionRow, range: "broken" };
|
|
35330
|
+
const from = this.sessionRow.range === "broken" ? this.sessionRow.seedFrom : this.sessionRow.range.from;
|
|
35331
|
+
const to = this.turnsWritten !== void 0 && this.turnsWritten > 0 ? this.sessionRow.seedFrom + this.turnsWritten - 1 : void 0;
|
|
35332
|
+
return { ...this.sessionRow, range: to !== void 0 && to >= from ? { from, to } : { from } };
|
|
34655
35333
|
}
|
|
34656
35334
|
sink = {
|
|
34657
35335
|
put: async (plain4) => {
|
|
@@ -34698,10 +35376,11 @@ function createLedgerWriteThrough(opts) {
|
|
|
34698
35376
|
* no record at all was killed before its conversation was stored and
|
|
34699
35377
|
* closes `interrupted`. */
|
|
34700
35378
|
async seed(messages, budgetMs) {
|
|
34701
|
-
const base = this.
|
|
34702
|
-
const
|
|
35379
|
+
const base = this.sessionRow?.seedFrom ?? 0;
|
|
35380
|
+
const reused = this.sessionRow && this.sessionRow.range !== "broken" ? this.sessionRow.range.from - this.sessionRow.seedFrom : 0;
|
|
35381
|
+
const turns = messages.slice(reused).map((message2, i) => ({ idx: base + reused + i, message: message2 }));
|
|
34703
35382
|
try {
|
|
34704
|
-
const seeded = await ledger.seed(this.runId, gen, turns, this.
|
|
35383
|
+
const seeded = await ledger.seed(this.runId, gen, turns, this.sessionRow?.key);
|
|
34705
35384
|
if (!seeded.ok) {
|
|
34706
35385
|
this.detach(`seed refused (${seeded.reason})`);
|
|
34707
35386
|
return;
|
|
@@ -34721,7 +35400,7 @@ function createLedgerWriteThrough(opts) {
|
|
|
34721
35400
|
iteration: 0
|
|
34722
35401
|
},
|
|
34723
35402
|
[],
|
|
34724
|
-
this.
|
|
35403
|
+
this.sessionRow?.key
|
|
34725
35404
|
);
|
|
34726
35405
|
if (!recorded.ok) this.detach(`seed record refused (${recorded.reason})`);
|
|
34727
35406
|
else this.seeded = true;
|
|
@@ -34731,7 +35410,7 @@ function createLedgerWriteThrough(opts) {
|
|
|
34731
35410
|
}
|
|
34732
35411
|
async step(report2) {
|
|
34733
35412
|
if (this.detached) return;
|
|
34734
|
-
const base = this.
|
|
35413
|
+
const base = this.sessionRow?.seedFrom ?? 0;
|
|
34735
35414
|
const turns = report2.turns.map((message2, i) => ({ idx: base + report2.firstIdx + i, message: message2 }));
|
|
34736
35415
|
if (report2.compaction)
|
|
34737
35416
|
turns.push({ idx: base + report2.firstIdx + report2.turns.length, compaction: report2.compaction });
|
|
@@ -34747,7 +35426,7 @@ function createLedgerWriteThrough(opts) {
|
|
|
34747
35426
|
};
|
|
34748
35427
|
for (let attempt = 1; ; attempt++) {
|
|
34749
35428
|
try {
|
|
34750
|
-
const result = await ledger.step(this.runId, gen, record2, turns, this.
|
|
35429
|
+
const result = await ledger.step(this.runId, gen, record2, turns, this.sessionRow?.key);
|
|
34751
35430
|
if (!result.ok) this.detach(`step ${record2.step} refused (${result.reason})`);
|
|
34752
35431
|
else this.turnsWritten = record2.turnIndex;
|
|
34753
35432
|
return;
|
|
@@ -34869,7 +35548,7 @@ function createLedgerWriteThrough(opts) {
|
|
|
34869
35548
|
const seed = req.seed?.messages;
|
|
34870
35549
|
if (reserved instanceof TrackedRun) {
|
|
34871
35550
|
if (!reserved.tracked()) return void 0;
|
|
34872
|
-
const claimed3 = await claim(req, seed ? { seed } : {});
|
|
35551
|
+
const claimed3 = await claim(req, seed ? { seed, ...req.seed?.log ? { log: req.seed.log } : {} } : {});
|
|
34873
35552
|
if (claimed3.outcome === "fenced") {
|
|
34874
35553
|
reserved.detach("promotion refused (fenced)");
|
|
34875
35554
|
return void 0;
|
|
@@ -34883,7 +35562,7 @@ function createLedgerWriteThrough(opts) {
|
|
|
34883
35562
|
if (req.seed) await reserved.seed(req.seed.messages, req.seed.budgetMs);
|
|
34884
35563
|
return reserved;
|
|
34885
35564
|
}
|
|
34886
|
-
const claimed2 = await claim(req, seed ? { seed } : {});
|
|
35565
|
+
const claimed2 = await claim(req, seed ? { seed, ...req.seed?.log ? { log: req.seed.log } : {} } : {});
|
|
34887
35566
|
if (claimed2.outcome === "fenced") {
|
|
34888
35567
|
warn(`[ledger] ${req.threadKey} not tracked: run ${req.runId} is live under another generation`);
|
|
34889
35568
|
return void 0;
|
|
@@ -34932,6 +35611,11 @@ function createLedgerWriteThrough(opts) {
|
|
|
34932
35611
|
return [];
|
|
34933
35612
|
}
|
|
34934
35613
|
},
|
|
35614
|
+
readSessionTail: (key, maxBytes) => ledger.readSessionTail(key, maxBytes),
|
|
35615
|
+
readSession: (key, from, to) => ledger.readSession(key, from, to),
|
|
35616
|
+
searchSession: (key, query, limit) => ledger.searchSession(key, query, limit),
|
|
35617
|
+
readNotepad: (key) => ledger.readNotepad(key),
|
|
35618
|
+
writeNotepad: (key, text) => ledger.writeNotepad(key, gen, text),
|
|
34935
35619
|
async handoff() {
|
|
34936
35620
|
const candidates = [...live].filter((r) => r.resumable && r.tracked() && !r.handedOff);
|
|
34937
35621
|
if (candidates.length === 0) return { marked: [] };
|
|
@@ -34982,6 +35666,21 @@ var init_writeThrough = __esm({
|
|
|
34982
35666
|
async readInbox(_runId, _afterSeq) {
|
|
34983
35667
|
return [];
|
|
34984
35668
|
}
|
|
35669
|
+
async readSessionTail(_key, _maxBytes) {
|
|
35670
|
+
return { from: 0, transcript: { complete: true, turns: 0, messages: [], compactions: [] } };
|
|
35671
|
+
}
|
|
35672
|
+
async readSession(_key, _from, _to) {
|
|
35673
|
+
return { complete: true, turns: 0, messages: [], compactions: [] };
|
|
35674
|
+
}
|
|
35675
|
+
async searchSession(_key, _query, _limit) {
|
|
35676
|
+
return { hits: [], gaps: [] };
|
|
35677
|
+
}
|
|
35678
|
+
async readNotepad(_key) {
|
|
35679
|
+
return null;
|
|
35680
|
+
}
|
|
35681
|
+
async writeNotepad(_key, _text) {
|
|
35682
|
+
return { ok: false, reason: "unknown-run" };
|
|
35683
|
+
}
|
|
34985
35684
|
async handoff() {
|
|
34986
35685
|
return { marked: [] };
|
|
34987
35686
|
}
|
|
@@ -34995,6 +35694,7 @@ var init_writeThrough = __esm({
|
|
|
34995
35694
|
sink;
|
|
34996
35695
|
resumable = false;
|
|
34997
35696
|
handedOff = false;
|
|
35697
|
+
session = void 0;
|
|
34998
35698
|
tracked() {
|
|
34999
35699
|
return false;
|
|
35000
35700
|
}
|
|
@@ -35474,7 +36174,10 @@ function httpMcpClientFactory(fetchImpl) {
|
|
|
35474
36174
|
return (server) => new StreamableHttpMcpClient({
|
|
35475
36175
|
url: server.url,
|
|
35476
36176
|
fetch: fetchImpl,
|
|
35477
|
-
|
|
36177
|
+
headers: {
|
|
36178
|
+
...server.headers,
|
|
36179
|
+
...server.auth?.type === "bearer" ? { authorization: `Bearer ${server.auth.token}` } : {}
|
|
36180
|
+
}
|
|
35478
36181
|
});
|
|
35479
36182
|
}
|
|
35480
36183
|
var init_mcp2 = __esm({
|
|
@@ -36895,7 +37598,8 @@ ${file.text}`));
|
|
|
36895
37598
|
if (STATUS_PREFIXES.some((p) => text.startsWith(p))) continue;
|
|
36896
37599
|
const files = mm.bot_id ? void 0 : mm.files;
|
|
36897
37600
|
if (!text && !files?.length) continue;
|
|
36898
|
-
|
|
37601
|
+
const at = mm.ts !== void 0 && Number.isFinite(Number(mm.ts)) ? Math.round(Number(mm.ts) * 1e3) : void 0;
|
|
37602
|
+
kept.push({ role: mm.bot_id ? "assistant" : "user", text, ...at !== void 0 ? { at } : {}, files });
|
|
36899
37603
|
}
|
|
36900
37604
|
const imagesByIndex = [];
|
|
36901
37605
|
const documentsByIndex = [];
|
|
@@ -36922,11 +37626,11 @@ ${file.text}`));
|
|
|
36922
37626
|
}
|
|
36923
37627
|
}
|
|
36924
37628
|
for (let i = 0; i < kept.length; i++) {
|
|
36925
|
-
const { role, text } = kept[i];
|
|
37629
|
+
const { role, text, at } = kept[i];
|
|
36926
37630
|
const images = imagesByIndex[i];
|
|
36927
37631
|
const documents = documentsByIndex[i];
|
|
36928
37632
|
if (!text && !images && !documents) continue;
|
|
36929
|
-
items.push({ role, text, images, documents });
|
|
37633
|
+
items.push({ role, text, ...at !== void 0 ? { at } : {}, images, documents });
|
|
36930
37634
|
}
|
|
36931
37635
|
} catch {
|
|
36932
37636
|
}
|
|
@@ -38302,18 +39006,18 @@ var init_liveView = __esm({
|
|
|
38302
39006
|
|
|
38303
39007
|
// ../../src/channels/webAssets.ts
|
|
38304
39008
|
import { readdirSync as readdirSync3, readFileSync as readFileSync16, statSync as statSync3 } from "node:fs";
|
|
38305
|
-
import { join as
|
|
39009
|
+
import { join as join12 } from "node:path";
|
|
38306
39010
|
function contentTypeFor2(path) {
|
|
38307
39011
|
const dot = path.lastIndexOf(".");
|
|
38308
39012
|
return (dot === -1 ? void 0 : CONTENT_TYPES[path.slice(dot)]) ?? "application/octet-stream";
|
|
38309
39013
|
}
|
|
38310
39014
|
function webDistDir(env, packageRoot) {
|
|
38311
|
-
return env.SWITCHBOARD_WEB_DIST ??
|
|
39015
|
+
return env.SWITCHBOARD_WEB_DIST ?? join12(packageRoot, WEB_DIST_DIR);
|
|
38312
39016
|
}
|
|
38313
39017
|
function loadWebAssets(distDir) {
|
|
38314
39018
|
let manifest;
|
|
38315
39019
|
try {
|
|
38316
|
-
manifest = JSON.parse(readFileSync16(
|
|
39020
|
+
manifest = JSON.parse(readFileSync16(join12(distDir, ".vite", "manifest.json"), "utf8"));
|
|
38317
39021
|
} catch (err2) {
|
|
38318
39022
|
throw new Error(
|
|
38319
39023
|
`web app manifest not found under ${distDir} \u2014 build it first (npm run build in web/): ${err2 instanceof Error ? err2.message : String(err2)}`,
|
|
@@ -38327,9 +39031,9 @@ function loadWebAssets(distDir) {
|
|
|
38327
39031
|
css: (entryChunk.css ?? []).map((f) => `/${f}`)
|
|
38328
39032
|
};
|
|
38329
39033
|
const files = /* @__PURE__ */ new Map();
|
|
38330
|
-
const assetsDir =
|
|
39034
|
+
const assetsDir = join12(distDir, "assets");
|
|
38331
39035
|
for (const name of readdirSync3(assetsDir)) {
|
|
38332
|
-
const full =
|
|
39036
|
+
const full = join12(assetsDir, name);
|
|
38333
39037
|
if (statSync3(full).isFile()) files.set(`/assets/${name}`, readFileSync16(full));
|
|
38334
39038
|
}
|
|
38335
39039
|
const serve2 = (req, res) => {
|
|
@@ -39519,6 +40223,25 @@ var init_runLedgerWorker = __esm({
|
|
|
39519
40223
|
)
|
|
39520
40224
|
};
|
|
39521
40225
|
}
|
|
40226
|
+
async searchSession(key, query, limit) {
|
|
40227
|
+
this.checkSessionKey(key);
|
|
40228
|
+
const r = await this.post("/runs/session/search", { key, query, limit });
|
|
40229
|
+
return {
|
|
40230
|
+
hits: Array.isArray(r.data.hits) ? r.data.hits : [],
|
|
40231
|
+
gaps: Array.isArray(r.data.gaps) ? r.data.gaps : []
|
|
40232
|
+
};
|
|
40233
|
+
}
|
|
40234
|
+
async readNotepad(key) {
|
|
40235
|
+
this.checkSessionKey(key);
|
|
40236
|
+
const r = await this.post("/runs/session/notepad", { key });
|
|
40237
|
+
const n2 = r.data.notepad;
|
|
40238
|
+
return n2 && typeof n2.text === "string" && typeof n2.updatedAt === "number" ? { text: n2.text, updatedAt: n2.updatedAt } : null;
|
|
40239
|
+
}
|
|
40240
|
+
async writeNotepad(key, gen, text) {
|
|
40241
|
+
this.checkSessionKey(key);
|
|
40242
|
+
if (!GEN_PATTERN.test(gen)) throw new PermanentStoreError(`run ledger: malformed generation`);
|
|
40243
|
+
return this.fenceResult(await this.post("/runs/session/notepad/write", { key, gen, text }));
|
|
40244
|
+
}
|
|
39522
40245
|
async heartbeat(runId3, gen, leaseMs) {
|
|
39523
40246
|
this.checkIds(runId3, gen);
|
|
39524
40247
|
const r = await this.post("/runs/heartbeat", { storeKey: this.opts.storeKey, runId: runId3, gen, leaseMs });
|
|
@@ -42376,7 +43099,7 @@ __export(index_exports, {
|
|
|
42376
43099
|
runBot: () => runBot
|
|
42377
43100
|
});
|
|
42378
43101
|
import { createServer } from "node:http";
|
|
42379
|
-
import { join as
|
|
43102
|
+
import { join as join13 } from "node:path";
|
|
42380
43103
|
async function runBot() {
|
|
42381
43104
|
for (const v of ["SLACK_BOT_TOKEN", "SLACK_APP_TOKEN"]) {
|
|
42382
43105
|
if (!processSecrets.get(v)) {
|
|
@@ -43054,7 +43777,7 @@ var init_index = __esm({
|
|
|
43054
43777
|
init_secrets2();
|
|
43055
43778
|
DATA_DIR = installationPath(OPERATOR_ROOT, "data");
|
|
43056
43779
|
CONFIG_PATH2 = process.env.SWITCHBOARD_CONFIG ?? installationPath(OPERATOR_ROOT, "config/config.yaml");
|
|
43057
|
-
OVERRIDES_PATH = process.env.SWITCHBOARD_OVERRIDES ??
|
|
43780
|
+
OVERRIDES_PATH = process.env.SWITCHBOARD_OVERRIDES ?? join13(DATA_DIR, "overrides.json");
|
|
43058
43781
|
PROCESS_STARTED_AT = systemClock();
|
|
43059
43782
|
sampleProcessMetrics = startProcessMetrics();
|
|
43060
43783
|
INTERRUPTED_WRITE_BUDGET_MS = 1e4;
|
|
@@ -43101,7 +43824,7 @@ init_secrets2();
|
|
|
43101
43824
|
import { Console } from "node:console";
|
|
43102
43825
|
import { existsSync as existsSync14, mkdtempSync, writeFileSync as writeFileSync9 } from "node:fs";
|
|
43103
43826
|
import { tmpdir } from "node:os";
|
|
43104
|
-
import { basename as basename3, join as
|
|
43827
|
+
import { basename as basename3, join as join14 } from "node:path";
|
|
43105
43828
|
var CONFIG_PATH3 = process.env.SWITCHBOARD_CONFIG ?? installationPath(OPERATOR_ROOT, "config/config.yaml");
|
|
43106
43829
|
var DATA_DIR2 = installationPath(OPERATOR_ROOT, "data");
|
|
43107
43830
|
var CLI_CALLER = { kind: "cli", id: CLI_ACTOR.id, actor: CLI_ACTOR };
|
|
@@ -43275,8 +43998,8 @@ var ConsoleIO = class _ConsoleIO {
|
|
|
43275
43998
|
/** The harness's file upload: the bytes written under the attachments dir,
|
|
43276
43999
|
* the lead printed like a reply with the path a person can open. */
|
|
43277
44000
|
async attachFile(file) {
|
|
43278
|
-
this.attachments.dir ??= mkdtempSync(
|
|
43279
|
-
const path =
|
|
44001
|
+
this.attachments.dir ??= mkdtempSync(join14(tmpdir(), "switchboard-attachments-"));
|
|
44002
|
+
const path = join14(this.attachments.dir, basename3(file.name));
|
|
43280
44003
|
writeFileSync9(path, file.bytes);
|
|
43281
44004
|
await this.reply(`${file.lead}
|
|
43282
44005
|
\u{1F4CE} ${file.name} (${file.bytes.byteLength} bytes) \u2192 ${path}`);
|
|
@@ -43357,7 +44080,7 @@ function cliCapabilities(configPath, env, secrets, opts = {}) {
|
|
|
43357
44080
|
}
|
|
43358
44081
|
function wireCli() {
|
|
43359
44082
|
const warn = (m) => console.error(m);
|
|
43360
|
-
const botConfig = bindBotConfig(CONFIG_PATH3,
|
|
44083
|
+
const botConfig = bindBotConfig(CONFIG_PATH3, join14(DATA_DIR2, "cli-overrides.json"));
|
|
43361
44084
|
let loaded;
|
|
43362
44085
|
const bot = () => loaded ??= botConfig().then((config) => ({
|
|
43363
44086
|
config,
|