@coreplane/switchboard 1.217.0 → 1.219.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/worker.ts +4 -0
- package/dist/assets/deploy/cloudflare-memory/worker.ts +96 -6
- package/dist/assets/deploy/secrets.manifest.json +12 -0
- package/dist/assets/package-lock.json +3 -3
- package/dist/assets/package.json +1 -1
- package/dist/assets/source.json +3 -3
- package/dist/assets/src/agents/registry.ts +20 -0
- package/dist/assets/src/core/runEvents.ts +6 -0
- package/dist/assets/src/core/runFriction.ts +40 -3
- package/dist/assets/src/core/runLedger/sessionLog.ts +22 -0
- package/dist/assets/src/core/runLedger/types.ts +17 -0
- 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-CWyEd5fq.js → ResidentDetailPage-DvCt96Dk.js} +1 -1
- package/dist/assets/web/dist/assets/{ResidentsIndexPage-DqFqRTWb.js → ResidentsIndexPage-BZaYja1-.js} +1 -1
- package/dist/assets/web/dist/assets/{RunRoutePage-CghwVr_X.js → RunRoutePage-DI4vzCUu.js} +4 -4
- package/dist/assets/web/dist/assets/{RunsIndexPage-BcYehvW5.js → RunsIndexPage-Ch9uP4p4.js} +1 -1
- package/dist/assets/web/dist/assets/{ScheduledPage-mKa20Qdd.js → ScheduledPage-DAkAHMCH.js} +1 -1
- package/dist/assets/web/dist/assets/{StatusDot-QMofJG-E.js → StatusDot-CfBonGcV.js} +1 -1
- package/dist/assets/web/dist/assets/{Tooltip-D-zVPexc.js → Tooltip-CBD36E3W.js} +1 -1
- package/dist/assets/web/dist/assets/{dist-D43zKxNV.js → dist-DQ1U73LF.js} +1 -1
- package/dist/assets/web/dist/assets/{main-ClTMtz8n.js → main-CdLnG-yl.js} +2 -2
- package/dist/cli.js +522 -120
- package/package.json +1 -1
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.`;
|
|
@@ -1979,8 +1992,12 @@ TIME. Your budget is up to two hours \u2014 less when a boundary or the request'
|
|
|
1979
1992
|
|
|
1980
1993
|
READ-ONLY: NEVER open a pull request, and never commit or push \u2014 no branch, no \`gh pr create\`, no PR or issue write of any kind. You hold a read credential and your job is to find out, not to change. If the investigation shows a change is needed, say exactly what and where in your write-up and point the user at \`agent:coding\`.
|
|
1981
1994
|
|
|
1995
|
+
You cannot attach or post files: your whole answer is text. Never say a file is attached or below \u2014 name its path in the workspace and describe it (what it shows, its size) instead; a person who needs the file itself asks \`agent:coding\`, which can attach.
|
|
1996
|
+
|
|
1982
1997
|
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
1998
|
|
|
1999
|
+
${NOTEPAD}
|
|
2000
|
+
|
|
1984
2001
|
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
2002
|
COMPOUND_PRESET = "conductor";
|
|
1986
2003
|
WORK_PRESETS = {
|
|
@@ -2603,6 +2620,11 @@ function splitCredentialKey(key) {
|
|
|
2603
2620
|
const name = key.slice(slash + 1);
|
|
2604
2621
|
return parseMcpScopeKey(scopeKey) && MCP_SERVER_NAME_RE.test(name) ? { scopeKey, name } : void 0;
|
|
2605
2622
|
}
|
|
2623
|
+
function isHeadersEnv(v) {
|
|
2624
|
+
if (!v || typeof v !== "object" || Array.isArray(v)) return false;
|
|
2625
|
+
const entries = Object.entries(v);
|
|
2626
|
+
return entries.length > 0 && entries.length <= MCP_HEADERS_MAX && entries.every(([name, envVar]) => isStr(name, 64) && isStr(envVar, 128));
|
|
2627
|
+
}
|
|
2606
2628
|
function isOutcome(v) {
|
|
2607
2629
|
if (!v || typeof v !== "object" || Array.isArray(v)) return false;
|
|
2608
2630
|
const o = v;
|
|
@@ -2611,7 +2633,7 @@ function isOutcome(v) {
|
|
|
2611
2633
|
function isMcpServerEntry(v) {
|
|
2612
2634
|
if (!v || typeof v !== "object" || Array.isArray(v)) return false;
|
|
2613
2635
|
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));
|
|
2636
|
+
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
2637
|
}
|
|
2616
2638
|
function isSealedCredential(v) {
|
|
2617
2639
|
if (!v || typeof v !== "object") return false;
|
|
@@ -2626,7 +2648,7 @@ function isMcpTicket(v) {
|
|
|
2626
2648
|
}
|
|
2627
2649
|
function serverView(scopeKey, name, entry, opts) {
|
|
2628
2650
|
const parsed = parseMcpScopeKey(scopeKey);
|
|
2629
|
-
const state = entry.auth === "none" ? "connected" : entry.tokenEnv ? "static" : opts.hasCredential ? "connected" : "awaiting_credential";
|
|
2651
|
+
const state = entry.auth === "none" ? entry.headersEnv ? "static" : "connected" : entry.tokenEnv ? "static" : opts.hasCredential ? "connected" : "awaiting_credential";
|
|
2630
2652
|
return {
|
|
2631
2653
|
name,
|
|
2632
2654
|
scope: parsed?.kind ?? "org",
|
|
@@ -2648,7 +2670,7 @@ function safeUrl(raw) {
|
|
|
2648
2670
|
return "(invalid url)";
|
|
2649
2671
|
}
|
|
2650
2672
|
}
|
|
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;
|
|
2673
|
+
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
2674
|
var init_registry2 = __esm({
|
|
2653
2675
|
"../../src/mcp/registry.ts"() {
|
|
2654
2676
|
"use strict";
|
|
@@ -2669,6 +2691,8 @@ var init_registry2 = __esm({
|
|
|
2669
2691
|
MCP_SERVERS_PER_SCOPE_MAX = 32;
|
|
2670
2692
|
isStr = (v, max = 4096) => typeof v === "string" && v.length > 0 && v.length <= max;
|
|
2671
2693
|
isNum = (v) => typeof v === "number" && Number.isFinite(v);
|
|
2694
|
+
MCP_HEADERS_MAX = 8;
|
|
2695
|
+
MCP_HEADER_NAME_RE = /^[!#$%&'*+.^_`|~0-9A-Za-z-]{1,64}$/;
|
|
2672
2696
|
}
|
|
2673
2697
|
});
|
|
2674
2698
|
|
|
@@ -2753,7 +2777,9 @@ function validateMcpServers(layer, source2) {
|
|
|
2753
2777
|
`${source2}: ${path}.${name}: server names are slugs (lowercase letters, digits, dashes; \u2264 ${MCP_SERVER_NAME_MAX} chars)`
|
|
2754
2778
|
);
|
|
2755
2779
|
if (!isMcpServerEntry(raw))
|
|
2756
|
-
throw new Error(
|
|
2780
|
+
throw new Error(
|
|
2781
|
+
`${source2}: ${path}.${name} must be { url, auth: none|bearer|oauth, agents?, tokenEnv?, headersEnv? }`
|
|
2782
|
+
);
|
|
2757
2783
|
try {
|
|
2758
2784
|
assertUrlAllowed(raw.url);
|
|
2759
2785
|
} catch (err2) {
|
|
@@ -2774,6 +2800,14 @@ function validateMcpServers(layer, source2) {
|
|
|
2774
2800
|
}
|
|
2775
2801
|
if (raw.tokenEnv !== void 0 && raw.auth !== "bearer")
|
|
2776
2802
|
throw new Error(`${source2}: ${path}.${name}.tokenEnv only applies to auth: bearer`);
|
|
2803
|
+
for (const header4 of Object.keys(raw.headersEnv ?? {})) {
|
|
2804
|
+
if (!MCP_HEADER_NAME_RE.test(header4))
|
|
2805
|
+
throw new Error(`${source2}: ${path}.${name}.headersEnv: "${header4}" is not an HTTP header name`);
|
|
2806
|
+
if (header4.toLowerCase() === "authorization")
|
|
2807
|
+
throw new Error(
|
|
2808
|
+
`${source2}: ${path}.${name}.headersEnv: the Authorization header is \`auth\`'s \u2014 use auth: bearer with tokenEnv`
|
|
2809
|
+
);
|
|
2810
|
+
}
|
|
2777
2811
|
}
|
|
2778
2812
|
};
|
|
2779
2813
|
check("defaults.mcpServers", "org", layer.defaults?.mcpServers);
|
|
@@ -4989,7 +5023,7 @@ function planDeploy(opts, host, loaded, images) {
|
|
|
4989
5023
|
function formatPlan(plan3) {
|
|
4990
5024
|
const missing = plan3.checks.nodeModulesMissing;
|
|
4991
5025
|
const checkout = plan3.root.mode === "checkout";
|
|
4992
|
-
const nodeModules = missing.length === 0 ? "node_modules present in every dir" : checkout ? `
|
|
5026
|
+
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
5027
|
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
5028
|
const lines = [
|
|
4995
5029
|
...plan3.affected ? [formatAffectedText(plan3.affected)] : [],
|
|
@@ -6241,7 +6275,7 @@ var init_sandboxLiveGate = __esm({
|
|
|
6241
6275
|
// ../../src/deploy/run.ts
|
|
6242
6276
|
import { spawn as spawn3 } from "node:child_process";
|
|
6243
6277
|
import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "node:fs";
|
|
6244
|
-
import { dirname as dirname4, isAbsolute as isAbsolute3
|
|
6278
|
+
import { dirname as dirname4, isAbsolute as isAbsolute3 } from "node:path";
|
|
6245
6279
|
function run(cmd, args, opts) {
|
|
6246
6280
|
const env = { ...process.env, ...opts.set ?? {} };
|
|
6247
6281
|
for (const k of opts.unset ?? []) delete env[k];
|
|
@@ -6348,7 +6382,7 @@ function packageModeProblems(plan3) {
|
|
|
6348
6382
|
return problems;
|
|
6349
6383
|
}
|
|
6350
6384
|
function hasNodeModules(dir) {
|
|
6351
|
-
if (OPERATOR_ROOT.mode === "checkout") return
|
|
6385
|
+
if (OPERATOR_ROOT.mode === "checkout") return checkoutInstallHolds(OPERATOR_ROOT.root, dir, existsSync5);
|
|
6352
6386
|
const state = readWorkAreaState(OPERATOR_ROOT.workArea);
|
|
6353
6387
|
return state.kind === "stamped" && state.stamp.installed.includes(dir);
|
|
6354
6388
|
}
|
|
@@ -6514,10 +6548,10 @@ async function pushConfigOnHost(opts) {
|
|
|
6514
6548
|
return pushConfigDocument(read, { stateWorkerUrl: opts.stateWorkerUrl, key: opts.key, env: process.env });
|
|
6515
6549
|
}
|
|
6516
6550
|
async function ensureNodeModules(step, io) {
|
|
6517
|
-
const dir = workerDir(step.dir);
|
|
6518
6551
|
if (hasNodeModules(step.dir)) return true;
|
|
6519
|
-
|
|
6520
|
-
|
|
6552
|
+
const cwd = OPERATOR_ROOT.mode === "checkout" ? OPERATOR_ROOT.root : workerDir(step.dir);
|
|
6553
|
+
io.log(`[deploy:all] ${step.name}: install missing \u2014 npm ci in ${cwd}`);
|
|
6554
|
+
const r = await run("npm", ["ci", "--silent"], { cwd });
|
|
6521
6555
|
if (r.code !== 0) io.warn(r.output);
|
|
6522
6556
|
return r.code === 0;
|
|
6523
6557
|
}
|
|
@@ -7824,9 +7858,9 @@ var init_secrets = __esm({
|
|
|
7824
7858
|
import { spawn as spawn4 } from "node:child_process";
|
|
7825
7859
|
import { existsSync as existsSync6, readFileSync as readFileSync7 } from "node:fs";
|
|
7826
7860
|
import { homedir as homedir2 } from "node:os";
|
|
7827
|
-
import { isAbsolute as isAbsolute4, join as
|
|
7861
|
+
import { isAbsolute as isAbsolute4, join as join5, resolve as resolve4 } from "node:path";
|
|
7828
7862
|
function expandDir(path, root = OPERATOR_ROOT.root) {
|
|
7829
|
-
if (path === "~" || path.startsWith("~/")) return
|
|
7863
|
+
if (path === "~" || path.startsWith("~/")) return join5(homedir2(), path.slice(1));
|
|
7830
7864
|
if (isAbsolute4(path)) return path;
|
|
7831
7865
|
return resolve4(root, path);
|
|
7832
7866
|
}
|
|
@@ -7870,7 +7904,7 @@ async function opFieldLabels(source2) {
|
|
|
7870
7904
|
}
|
|
7871
7905
|
function wranglerBin(dir, workspaceRoot, exists = existsSync6) {
|
|
7872
7906
|
for (const base of [dir, workspaceRoot]) {
|
|
7873
|
-
const bin =
|
|
7907
|
+
const bin = join5(base, "node_modules", ".bin", "wrangler");
|
|
7874
7908
|
if (exists(bin)) return bin;
|
|
7875
7909
|
}
|
|
7876
7910
|
return "wrangler";
|
|
@@ -7891,7 +7925,7 @@ var init_secretsHost = __esm({
|
|
|
7891
7925
|
if (source2.kind === "dir") {
|
|
7892
7926
|
const dir = expandDir(source2.path);
|
|
7893
7927
|
if (!existsSync6(dir)) return { ok: false, problem: `secretsSource ${source2.path}: no such directory (${dir})` };
|
|
7894
|
-
return { ok: true, present: new Set(names.filter((n2) => existsSync6(
|
|
7928
|
+
return { ok: true, present: new Set(names.filter((n2) => existsSync6(join5(dir, n2)))) };
|
|
7895
7929
|
}
|
|
7896
7930
|
const labels = await opFieldLabels(source2);
|
|
7897
7931
|
if (!labels.ok) return labels;
|
|
@@ -7900,7 +7934,7 @@ var init_secretsHost = __esm({
|
|
|
7900
7934
|
put: async (source2, dir, name) => {
|
|
7901
7935
|
let value;
|
|
7902
7936
|
if (source2.kind === "dir") {
|
|
7903
|
-
value = readFileSync7(
|
|
7937
|
+
value = readFileSync7(join5(expandDir(source2.path), name), "utf8");
|
|
7904
7938
|
} else {
|
|
7905
7939
|
const r2 = await spawnCollect("op", ["read", `op://${source2.vault}/${source2.item}/${name}`]);
|
|
7906
7940
|
if (r2.code !== 0) return { code: r2.code, output: `op read exited ${r2.code} \u2014 ${r2.stderr.trim()}` };
|
|
@@ -8114,11 +8148,11 @@ var init_readingDiff = __esm({
|
|
|
8114
8148
|
// ../../src/core/meatProcess.ts
|
|
8115
8149
|
import { spawn as spawn5 } from "node:child_process";
|
|
8116
8150
|
import { accessSync, constants } from "node:fs";
|
|
8117
|
-
import { delimiter, join as
|
|
8151
|
+
import { delimiter, join as join6 } from "node:path";
|
|
8118
8152
|
function meatOnPath(env, binary = MEAT_BINARY) {
|
|
8119
8153
|
return (env.PATH ?? "").split(delimiter).filter((dir) => dir !== "").some((dir) => {
|
|
8120
8154
|
try {
|
|
8121
|
-
accessSync(
|
|
8155
|
+
accessSync(join6(dir, binary), constants.X_OK);
|
|
8122
8156
|
return true;
|
|
8123
8157
|
} catch {
|
|
8124
8158
|
return false;
|
|
@@ -8198,10 +8232,10 @@ import { chmod, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises"
|
|
|
8198
8232
|
import { randomBytes } from "node:crypto";
|
|
8199
8233
|
import { createInterface } from "node:readline/promises";
|
|
8200
8234
|
import { Writable } from "node:stream";
|
|
8201
|
-
import { dirname as dirname5, join as
|
|
8235
|
+
import { dirname as dirname5, join as join7, resolve as resolve5 } from "node:path";
|
|
8202
8236
|
async function readTemplates(root = PACKAGE_ROOT) {
|
|
8203
8237
|
const read = async (key) => {
|
|
8204
|
-
const path =
|
|
8238
|
+
const path = join7(root, TEMPLATE_PATHS[key]);
|
|
8205
8239
|
if (!existsSync7(path)) throw new Error(`${TEMPLATE_PATHS[key]}: no such file under ${root}`);
|
|
8206
8240
|
return readFile(path, "utf8");
|
|
8207
8241
|
};
|
|
@@ -8215,7 +8249,7 @@ function isCheckoutRoot(cwd = process.cwd(), root = PACKAGE_ROOT) {
|
|
|
8215
8249
|
}
|
|
8216
8250
|
}
|
|
8217
8251
|
async function writePlannedFile(file, cwd = process.cwd()) {
|
|
8218
|
-
const path =
|
|
8252
|
+
const path = join7(cwd, file.path);
|
|
8219
8253
|
await mkdir(dirname5(path), { recursive: true });
|
|
8220
8254
|
const temp = `${path}.${randomBytes(6).toString("hex")}.tmp`;
|
|
8221
8255
|
try {
|
|
@@ -8250,7 +8284,7 @@ function ttyPrompter(io = { stdin: process.stdin, stdout: process.stdout }) {
|
|
|
8250
8284
|
};
|
|
8251
8285
|
}
|
|
8252
8286
|
function projectFact(name, root) {
|
|
8253
|
-
const facts2 = JSON.parse(readFileSync8(
|
|
8287
|
+
const facts2 = JSON.parse(readFileSync8(join7(root, "project.json"), "utf8"));
|
|
8254
8288
|
const value = facts2[name];
|
|
8255
8289
|
if (typeof value !== "string") throw new Error(`project.json: no \`${name}\` fact`);
|
|
8256
8290
|
return value;
|
|
@@ -8266,7 +8300,7 @@ function hostSetupIO(cwd = OPERATOR_ROOT.root, startedIn = process.cwd()) {
|
|
|
8266
8300
|
templates: () => readTemplates(),
|
|
8267
8301
|
// Where the files went when that is not where the operator stands — `init` prints it.
|
|
8268
8302
|
root: () => resolve5(cwd) === resolve5(startedIn) ? void 0 : cwd,
|
|
8269
|
-
exists: async (path) => existsSync7(
|
|
8303
|
+
exists: async (path) => existsSync7(join7(cwd, path)),
|
|
8270
8304
|
write: (file) => writePlannedFile(file, cwd),
|
|
8271
8305
|
// A file the operator NAMED (`--github-private-key-file app.pem`) is where the operator stands,
|
|
8272
8306
|
// not in the installation: `init` run from ~/Downloads reads ~/Downloads/app.pem.
|
|
@@ -8322,9 +8356,9 @@ var init_binaryRead = __esm({
|
|
|
8322
8356
|
|
|
8323
8357
|
// ../../src/secrets.ts
|
|
8324
8358
|
import { readFileSync as readFileSync9 } from "node:fs";
|
|
8325
|
-
import { join as
|
|
8359
|
+
import { join as join8 } from "node:path";
|
|
8326
8360
|
function manifestNames() {
|
|
8327
|
-
const raw = JSON.parse(readFileSync9(
|
|
8361
|
+
const raw = JSON.parse(readFileSync9(join8(PACKAGE_ROOT, MANIFEST_PATH), "utf8"));
|
|
8328
8362
|
const parsed = parseManifest2(raw);
|
|
8329
8363
|
if (!parsed.ok) throw new Error(`${MANIFEST_PATH} is invalid \u2014 ${parsed.problems.join("; ")}`);
|
|
8330
8364
|
return parsed.manifest.secrets.map((s) => s.name);
|
|
@@ -12482,7 +12516,7 @@ ${quote(current ?? "")}`;
|
|
|
12482
12516
|
});
|
|
12483
12517
|
|
|
12484
12518
|
// ../../src/core/commands/contract.ts
|
|
12485
|
-
import { basename, dirname as dirname8, join as
|
|
12519
|
+
import { basename, dirname as dirname8, join as join9 } from "node:path";
|
|
12486
12520
|
import { z as z7 } from "zod";
|
|
12487
12521
|
function repoRootOfPlan(planPath) {
|
|
12488
12522
|
const plans = dirname8(planPath);
|
|
@@ -12508,7 +12542,7 @@ var init_contract2 = __esm({
|
|
|
12508
12542
|
init_commandRegistry();
|
|
12509
12543
|
init_contract();
|
|
12510
12544
|
defineCommand4 = commandDefiner();
|
|
12511
|
-
SPECS_DIR =
|
|
12545
|
+
SPECS_DIR = join9("docs", "reference", "specs");
|
|
12512
12546
|
RULES_FILES = ["AGENTS.md", "CLAUDE.md"];
|
|
12513
12547
|
contractRender = defineCommand4({
|
|
12514
12548
|
id: "contract.render",
|
|
@@ -12532,10 +12566,10 @@ var init_contract2 = __esm({
|
|
|
12532
12566
|
const unit = parsePlanUnit(planMarkdown, options.unit);
|
|
12533
12567
|
const specs = /* @__PURE__ */ new Map();
|
|
12534
12568
|
for (const spec of new Set(unit ? specItemRefs(unit.section).map((r) => r.spec) : []))
|
|
12535
|
-
specs.set(spec, await deps.contract.readFile(
|
|
12569
|
+
specs.set(spec, await deps.contract.readFile(join9(root, SPECS_DIR, spec)));
|
|
12536
12570
|
let agentRules;
|
|
12537
12571
|
for (const file of RULES_FILES) {
|
|
12538
|
-
const text = await deps.contract.readFile(
|
|
12572
|
+
const text = await deps.contract.readFile(join9(root, file));
|
|
12539
12573
|
if (text !== void 0) {
|
|
12540
12574
|
agentRules = { file, text };
|
|
12541
12575
|
break;
|
|
@@ -13460,7 +13494,7 @@ function isSetupInstallCommand(summary) {
|
|
|
13460
13494
|
return INSTALL_SEGMENT.test(cmd.replace(QUOTED, '""'));
|
|
13461
13495
|
}
|
|
13462
13496
|
function isNarrative(ev) {
|
|
13463
|
-
return ev.type === "input" || ev.type === "context" || ev.type === "assistant" || ev.type === "answer" || ev.type === "run_meta";
|
|
13497
|
+
return ev.type === "input" || ev.type === "context" || ev.type === "assistant" || ev.type === "answer" || ev.type === "notes" || ev.type === "run_meta";
|
|
13464
13498
|
}
|
|
13465
13499
|
function spanSet(events) {
|
|
13466
13500
|
const normalized = normalizeSpans(events);
|
|
@@ -13581,6 +13615,8 @@ function analyzeRunFriction(events, opts = {}) {
|
|
|
13581
13615
|
let sideFactEvents = 0;
|
|
13582
13616
|
let spanEvents = 0;
|
|
13583
13617
|
let wrapUp;
|
|
13618
|
+
let answer;
|
|
13619
|
+
let outboundArtifacts = 0;
|
|
13584
13620
|
events.forEach((ev, index) => {
|
|
13585
13621
|
flushTurns(index);
|
|
13586
13622
|
if (isSpanRecord(ev)) {
|
|
@@ -13589,8 +13625,10 @@ function analyzeRunFriction(events, opts = {}) {
|
|
|
13589
13625
|
}
|
|
13590
13626
|
if (isNarrative(ev)) {
|
|
13591
13627
|
narrativeEvents++;
|
|
13628
|
+
if (ev.type === "answer") answer = { index, text: ev.text };
|
|
13592
13629
|
return;
|
|
13593
13630
|
}
|
|
13631
|
+
if (ev.type === "artifact" && ev.direction === "out") outboundArtifacts++;
|
|
13594
13632
|
if (ev.type === "skill_use" || ev.type === "artifact" || ev.type === "review_artifact" || ev.type === "pr_description" || ev.type === "pr_opened" || ev.type === "review_posted" || ev.type === "ship_round" || ev.type === "route") {
|
|
13595
13633
|
sideFactEvents++;
|
|
13596
13634
|
return;
|
|
@@ -13736,6 +13774,20 @@ function analyzeRunFriction(events, opts = {}) {
|
|
|
13736
13774
|
f.interval = { start: at, end: Math.max(at, windowEnd) };
|
|
13737
13775
|
}
|
|
13738
13776
|
}
|
|
13777
|
+
if (answer !== void 0 && outboundArtifacts === 0) {
|
|
13778
|
+
const promised = ATTACHMENT_PROMISE.exec(answer.text);
|
|
13779
|
+
if (promised) {
|
|
13780
|
+
const line3 = answer.text.slice(0, promised.index).split("\n").pop() + answer.text.slice(promised.index).split("\n")[0];
|
|
13781
|
+
const trimmed = line3.trim();
|
|
13782
|
+
const quoted = trimmed.length > 120 ? `\u2026${trimmed.slice(trimmed.length - 119)}` : trimmed;
|
|
13783
|
+
findings.push({
|
|
13784
|
+
category: "unkept_promise",
|
|
13785
|
+
severity: "low",
|
|
13786
|
+
summary: `unkept promise: the reply says a file is attached but the run produced none \u2014 "${quoted}"`,
|
|
13787
|
+
eventIndex: answer.index
|
|
13788
|
+
});
|
|
13789
|
+
}
|
|
13790
|
+
}
|
|
13739
13791
|
let toolTimeMs;
|
|
13740
13792
|
for (const s of toolSpans) {
|
|
13741
13793
|
const d = durationOfSpan(s);
|
|
@@ -13812,7 +13864,7 @@ function formatFrictionReport(d) {
|
|
|
13812
13864
|
}
|
|
13813
13865
|
return lines.join("\n");
|
|
13814
13866
|
}
|
|
13815
|
-
var FRICTION_CATEGORIES, CATEGORY_LABEL, DENOMINATOR_OF, DEFAULT_SLOW_TOOL_MS, DEFAULT_SLOW_MODEL_TURN_MS, INSTALL_SEGMENT, QUOTED, callSignature;
|
|
13867
|
+
var FRICTION_CATEGORIES, CATEGORY_LABEL, DENOMINATOR_OF, ATTACHMENT_PROMISE, DEFAULT_SLOW_TOOL_MS, DEFAULT_SLOW_MODEL_TURN_MS, INSTALL_SEGMENT, QUOTED, callSignature;
|
|
13816
13868
|
var init_runFriction = __esm({
|
|
13817
13869
|
"../../src/core/runFriction.ts"() {
|
|
13818
13870
|
"use strict";
|
|
@@ -13829,7 +13881,8 @@ var init_runFriction = __esm({
|
|
|
13829
13881
|
"setup_install",
|
|
13830
13882
|
"wrap_up",
|
|
13831
13883
|
"budget_hit",
|
|
13832
|
-
"infra_failure"
|
|
13884
|
+
"infra_failure",
|
|
13885
|
+
"unkept_promise"
|
|
13833
13886
|
];
|
|
13834
13887
|
CATEGORY_LABEL = {
|
|
13835
13888
|
slow_tool: "slow tool calls",
|
|
@@ -13839,7 +13892,8 @@ var init_runFriction = __esm({
|
|
|
13839
13892
|
setup_install: "the repo's setup/install",
|
|
13840
13893
|
wrap_up: "agent wind-down",
|
|
13841
13894
|
budget_hit: "budget hits",
|
|
13842
|
-
infra_failure: "infra failures"
|
|
13895
|
+
infra_failure: "infra failures",
|
|
13896
|
+
unkept_promise: "unkept promises"
|
|
13843
13897
|
};
|
|
13844
13898
|
DENOMINATOR_OF = {
|
|
13845
13899
|
slow_tool: "tool",
|
|
@@ -13849,8 +13903,10 @@ var init_runFriction = __esm({
|
|
|
13849
13903
|
slow_model_turn: "run",
|
|
13850
13904
|
wrap_up: "run",
|
|
13851
13905
|
budget_hit: "run",
|
|
13852
|
-
infra_failure: "run"
|
|
13906
|
+
infra_failure: "run",
|
|
13907
|
+
unkept_promise: "run"
|
|
13853
13908
|
};
|
|
13909
|
+
ATTACHMENT_PROMISE = /\b(?:attached (?:below|here|above)|see (?:the )?attached|i(?:'ve| have) attached|(?:is|are) attached\b|attachments? (?:(?:is|are) )?(?:below|here))/i;
|
|
13854
13910
|
DEFAULT_SLOW_TOOL_MS = 3e4;
|
|
13855
13911
|
DEFAULT_SLOW_MODEL_TURN_MS = 6e4;
|
|
13856
13912
|
INSTALL_SEGMENT = /(?:^|&&|;|\|\|?)\s*(?:sudo\s+)?(?:corepack\s+)?(?:(?:npm|pnpm|bun)\s+(?:install|i|ci|add)(?=\s|$)|yarn(?:\s+(?:install|add)(?=\s|$)|\s*$)|npx\s+playwright\s+install(?=\s|$)|(?:python3?\s+-m\s+)?pip3?\s+install(?=\s|$)|poetry\s+install(?=\s|$)|uv\s+(?:sync|pip\s+install)(?=\s|$)|apt(?:-get)?\s+install(?=\s|$)|apk\s+add(?=\s|$)|brew\s+install(?=\s|$)|bundle\s+install(?=\s|$)|gem\s+install(?=\s|$)|cargo\s+(?:fetch|build)(?=\s|$)|go\s+mod\s+download(?=\s|$)|git\s+clone(?=\s|$)|make\s+(?:deps|install|setup)(?=\s|$))/;
|
|
@@ -14998,6 +15054,8 @@ function patternSignature(f) {
|
|
|
14998
15054
|
}
|
|
14999
15055
|
case "wrap_up":
|
|
15000
15056
|
return "wrap_up:wrap_up";
|
|
15057
|
+
case "unkept_promise":
|
|
15058
|
+
return "unkept_promise:attachment";
|
|
15001
15059
|
case "slow_model_turn":
|
|
15002
15060
|
return "slow_model_turn:model_turn";
|
|
15003
15061
|
case "infra_failure": {
|
|
@@ -15217,6 +15275,8 @@ ${evidence}`);
|
|
|
15217
15275
|
return `Runs ended with \`${p.signature.replace(/^mid-tool /, "")}\` still outstanding \u2014 the run (or its transport) was cut while the tool ran. Correlate the affected runs with deploys/drains (\`[drain]\` log lines), the sandbox command timeout (exit 124 / heartbeat streaming, docs/reference/specs/execution.md), and the executor's error surfacing.`;
|
|
15218
15276
|
}
|
|
15219
15277
|
return `The exec transport failed during \`${p.signature}\` in ${p.runIds.length} runs (the sandbox, not the command). Check the sandbox/resident Worker logs (\`deploy/bin/cf-logs\`) around the affected runs for the underlying error, and whether the command's runtime exceeds the executor's timeout.`;
|
|
15278
|
+
case "unkept_promise":
|
|
15279
|
+
return `In ${p.runIds.length} runs the reply told the person a file was attached and the run produced none \u2014 no outbound \`artifact\` event, so nothing reached the thread or the run page. Read the affected runs' presets: a preset without \`attach_file\` in its toolset (\`src/agents/registry.ts\`) must say in its prompt that its whole answer is text and name the workspace path instead of promising an attachment; an ask that needs the file belongs on a preset with the full toolset (the router's description of the preset should say it cannot post files). If the run did call \`attach_file\` and it failed, the paired \`failed_tool\` proposal has the cause.`;
|
|
15220
15280
|
case "long_run":
|
|
15221
15281
|
return `These \`${p.signature}\` runs took \u2265 ${LONG_RUN_FACTOR}\xD7 the median run time \u2014 the closest available proxy for a cost spike (there is no per-run token accounting yet). Open each affected run's friction report for its dominant cause; if the shape of work is legitimately long, split it or lower the agent's effort tier; if it is not, tighten \`maxMinutes\` so a runaway run is cut earlier.`;
|
|
15222
15282
|
}
|
|
@@ -15258,6 +15318,7 @@ var init_frictionProposals = __esm({
|
|
|
15258
15318
|
wrap_up: "agent wind-down",
|
|
15259
15319
|
budget_hit: "budget hit",
|
|
15260
15320
|
infra_failure: "infra failure",
|
|
15321
|
+
unkept_promise: "unkept promise",
|
|
15261
15322
|
long_run: "long run"
|
|
15262
15323
|
};
|
|
15263
15324
|
RESULT_TAIL = / → [\s\S]*$/;
|
|
@@ -15383,6 +15444,7 @@ function isRunEvent(v) {
|
|
|
15383
15444
|
case "input":
|
|
15384
15445
|
case "assistant":
|
|
15385
15446
|
case "context":
|
|
15447
|
+
case "notes":
|
|
15386
15448
|
return typeof o.text === "string";
|
|
15387
15449
|
// the narrative events carry text, not a summary
|
|
15388
15450
|
case "run_meta":
|
|
@@ -17231,7 +17293,11 @@ var init_service = __esm({
|
|
|
17231
17293
|
/** A fresh connect link for a runtime bearer/oauth server (first connect, or a re-key). */
|
|
17232
17294
|
async connect(actor, target, name) {
|
|
17233
17295
|
const { scopeKey, entry, source: source2 } = this.owned(target, name);
|
|
17234
|
-
if (entry.auth === "none")
|
|
17296
|
+
if (entry.auth === "none")
|
|
17297
|
+
throw new McpServiceError(
|
|
17298
|
+
"invalid_input",
|
|
17299
|
+
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)`
|
|
17300
|
+
);
|
|
17235
17301
|
if (entry.tokenEnv)
|
|
17236
17302
|
throw new McpServiceError(
|
|
17237
17303
|
"invalid_input",
|
|
@@ -17708,6 +17774,15 @@ var init_service = __esm({
|
|
|
17708
17774
|
url: r.entry.url,
|
|
17709
17775
|
agents: r.entry.agents ?? [...MCP_SELF_SERVE_AGENTS]
|
|
17710
17776
|
};
|
|
17777
|
+
if (r.entry.headersEnv) {
|
|
17778
|
+
const headers = {};
|
|
17779
|
+
for (const [header4, envVar] of Object.entries(r.entry.headersEnv)) {
|
|
17780
|
+
const value = this.opts.bearers.named(envVar);
|
|
17781
|
+
if (!value) return { name: r.name, unavailable: `${envVar} is not set on the bot` };
|
|
17782
|
+
headers[header4] = value.reveal();
|
|
17783
|
+
}
|
|
17784
|
+
base.headers = headers;
|
|
17785
|
+
}
|
|
17711
17786
|
if (r.entry.auth === "none") return { spec: base };
|
|
17712
17787
|
if (r.entry.tokenEnv) {
|
|
17713
17788
|
const token2 = this.opts.bearers.named(r.entry.tokenEnv);
|
|
@@ -17827,7 +17902,7 @@ async function settleConnect(deps, output) {
|
|
|
17827
17902
|
}
|
|
17828
17903
|
}
|
|
17829
17904
|
function renderServerLine(r) {
|
|
17830
|
-
const state = r.state === "connected" ? "\u2705 connected" : r.state === "static" ? "\u2705 static (
|
|
17905
|
+
const state = r.state === "connected" ? "\u2705 connected" : r.state === "static" ? "\u2705 static (bot env)" : "\u23F3 awaiting credential";
|
|
17831
17906
|
const agents = Array.isArray(r.agents) ? r.agents.join(", ") : "";
|
|
17832
17907
|
const pinned = r.source === "config" ? " \xB7 pinned in config.yaml" : "";
|
|
17833
17908
|
return `\u2022 \`${String(r.name)}\` (${String(r.scope)}) ${state} \u2014 ${String(r.url)} \xB7 agents: ${agents} \xB7 auth: ${String(r.auth)}${pinned}`;
|
|
@@ -21929,6 +22004,8 @@ function activityLine(e) {
|
|
|
21929
22004
|
return "request received";
|
|
21930
22005
|
case "context":
|
|
21931
22006
|
return "context recorded";
|
|
22007
|
+
case "notes":
|
|
22008
|
+
return "\u{1F4DD} notes saved";
|
|
21932
22009
|
case "answer":
|
|
21933
22010
|
return "answer ready";
|
|
21934
22011
|
case "run_meta":
|
|
@@ -23347,7 +23424,7 @@ async function resolveRepoContext(msg, history = [], probe, residentSlugs) {
|
|
|
23347
23424
|
};
|
|
23348
23425
|
const resolveAddressed = async (a) => {
|
|
23349
23426
|
if (a.slug !== void 0) return probe && await vet(a.slug) ? a.slug : void 0;
|
|
23350
|
-
const matches = (await listSlugs() ?? []).filter((slug) => slug.split("/")[1] === a.name);
|
|
23427
|
+
const matches = (await listSlugs() ?? []).filter((slug) => slug.split("/")[1]?.toLowerCase() === a.name);
|
|
23351
23428
|
return matches.length === 1 ? matches[0] : void 0;
|
|
23352
23429
|
};
|
|
23353
23430
|
const strongNow = s.pr?.repo ?? (s.repoStrong ? s.repo : void 0) ?? (s.addressed ? await resolveAddressed(s.addressed) : void 0);
|
|
@@ -26916,6 +26993,134 @@ var init_runs2 = __esm({
|
|
|
26916
26993
|
}
|
|
26917
26994
|
});
|
|
26918
26995
|
|
|
26996
|
+
// ../../src/core/runLedger/sessionLog.ts
|
|
26997
|
+
function sessionKey(threadKey, agent) {
|
|
26998
|
+
return `${threadKey}:${agent ?? "-"}`;
|
|
26999
|
+
}
|
|
27000
|
+
function requestIndex(seed) {
|
|
27001
|
+
for (let i = seed.length - 1; i >= 0; i--) if (seed[i].role === "user") return i;
|
|
27002
|
+
return 0;
|
|
27003
|
+
}
|
|
27004
|
+
var DEFAULT_SESSION_LOG_MAX_BYTES, NOTEPAD_MAX_BYTES, GAP_MARKER;
|
|
27005
|
+
var init_sessionLog = __esm({
|
|
27006
|
+
"../../src/core/runLedger/sessionLog.ts"() {
|
|
27007
|
+
"use strict";
|
|
27008
|
+
init_runRecord();
|
|
27009
|
+
init_runRecord();
|
|
27010
|
+
DEFAULT_SESSION_LOG_MAX_BYTES = DEFAULT_RETENTION_POLICY.sessionLogMaxBytes;
|
|
27011
|
+
NOTEPAD_MAX_BYTES = 8192;
|
|
27012
|
+
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.]";
|
|
27013
|
+
}
|
|
27014
|
+
});
|
|
27015
|
+
|
|
27016
|
+
// ../../src/tools/session.ts
|
|
27017
|
+
function sessionCapabilityFor(run2, ledger) {
|
|
27018
|
+
const session = run2?.session;
|
|
27019
|
+
if (!session) return void 0;
|
|
27020
|
+
return {
|
|
27021
|
+
session,
|
|
27022
|
+
search: (query, limit) => ledger.searchSession(session.key, query, limit),
|
|
27023
|
+
readTurn: async (idx) => (await ledger.readSession(session.key, idx, idx)).messages[0],
|
|
27024
|
+
readNotepad: () => ledger.readNotepad(session.key),
|
|
27025
|
+
writeNotepad: (text) => ledger.writeNotepad(session.key, text)
|
|
27026
|
+
};
|
|
27027
|
+
}
|
|
27028
|
+
async function mayRead(ctx) {
|
|
27029
|
+
if (!ctx.runs?.runId) return false;
|
|
27030
|
+
const res = await ctx.runs.service.getRun(ctx.runs.runId);
|
|
27031
|
+
return res.ok && authorize(ctx.runs.actor, "runs:read", runResource(res.value)).allow;
|
|
27032
|
+
}
|
|
27033
|
+
var UNAVAILABLE3, DEFAULT_HITS, MAX_HITS, SNIPPET_CHARS, snippetOf, recallTool, notesTool, SESSION_TOOLS;
|
|
27034
|
+
var init_session = __esm({
|
|
27035
|
+
"../../src/tools/session.ts"() {
|
|
27036
|
+
"use strict";
|
|
27037
|
+
init_authorize();
|
|
27038
|
+
init_runEvents();
|
|
27039
|
+
init_sessionLog();
|
|
27040
|
+
init_runRecord();
|
|
27041
|
+
init_runsService();
|
|
27042
|
+
UNAVAILABLE3 = "the session tools are not available in this context: this run has no session log.";
|
|
27043
|
+
DEFAULT_HITS = 5;
|
|
27044
|
+
MAX_HITS = 50;
|
|
27045
|
+
SNIPPET_CHARS = 300;
|
|
27046
|
+
snippetOf = (text) => {
|
|
27047
|
+
const line3 = text.replace(/\s+/g, " ").trim();
|
|
27048
|
+
return line3.length > SNIPPET_CHARS ? `${line3.slice(0, SNIPPET_CHARS - 1)}\u2026` : line3;
|
|
27049
|
+
};
|
|
27050
|
+
recallTool = {
|
|
27051
|
+
name: "recall",
|
|
27052
|
+
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).",
|
|
27053
|
+
inputSchema: {
|
|
27054
|
+
type: "object",
|
|
27055
|
+
properties: {
|
|
27056
|
+
query: { type: "string", description: "Words to search for; matching is by word, in relevance order" },
|
|
27057
|
+
limit: { type: "integer", description: "How many turns to return (default 5, at most 50)" },
|
|
27058
|
+
turn: {
|
|
27059
|
+
type: "integer",
|
|
27060
|
+
description: "A turn number from an earlier recall: returns that turn whole instead of searching"
|
|
27061
|
+
}
|
|
27062
|
+
}
|
|
27063
|
+
},
|
|
27064
|
+
sideEffectFree: true,
|
|
27065
|
+
async run(input, ctx) {
|
|
27066
|
+
if (!ctx.session) return UNAVAILABLE3;
|
|
27067
|
+
const turn = typeof input.turn === "number" ? Math.trunc(input.turn) : void 0;
|
|
27068
|
+
const query = typeof input.query === "string" ? input.query.trim() : "";
|
|
27069
|
+
if (turn === void 0 && query.length === 0) return "recall needs a query or a turn number.";
|
|
27070
|
+
if (!await mayRead(ctx)) return JSON.stringify(turn !== void 0 ? { turn, content: null } : { hits: [] });
|
|
27071
|
+
if (turn !== void 0) {
|
|
27072
|
+
const message2 = turn >= 0 ? await ctx.session.readTurn(turn) : void 0;
|
|
27073
|
+
if (!message2) return `no turn ${turn} in this session's log.`;
|
|
27074
|
+
return JSON.stringify({ turn, role: message2.role, content: message2.content });
|
|
27075
|
+
}
|
|
27076
|
+
const limit = Math.min(
|
|
27077
|
+
MAX_HITS,
|
|
27078
|
+
Math.max(1, typeof input.limit === "number" ? Math.trunc(input.limit) : DEFAULT_HITS)
|
|
27079
|
+
);
|
|
27080
|
+
const { hits, gaps } = await ctx.session.search(query, limit);
|
|
27081
|
+
const notes = [];
|
|
27082
|
+
if (hits.length === 0)
|
|
27083
|
+
notes.push("no turn of this session's log matches; your notes (notes {}) may hold what you look for");
|
|
27084
|
+
if (ctx.session.session.seedFrom === 0) notes.push("this is the first run of its session: the log begins with it");
|
|
27085
|
+
return JSON.stringify({
|
|
27086
|
+
hits: hits.map((h) => ({
|
|
27087
|
+
turn: h.idx,
|
|
27088
|
+
...h.role !== void 0 ? { role: h.role } : {},
|
|
27089
|
+
snippet: snippetOf(h.text)
|
|
27090
|
+
})),
|
|
27091
|
+
...gaps.length ? {
|
|
27092
|
+
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`
|
|
27093
|
+
} : {},
|
|
27094
|
+
...notes.length ? { note: notes.join("; ") } : {}
|
|
27095
|
+
});
|
|
27096
|
+
}
|
|
27097
|
+
};
|
|
27098
|
+
notesTool = {
|
|
27099
|
+
name: "notes",
|
|
27100
|
+
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.",
|
|
27101
|
+
inputSchema: {
|
|
27102
|
+
type: "object",
|
|
27103
|
+
properties: { text: { type: "string", description: "The whole notepad as it should read from now on" } }
|
|
27104
|
+
},
|
|
27105
|
+
async run(input, ctx) {
|
|
27106
|
+
if (!ctx.session) return UNAVAILABLE3;
|
|
27107
|
+
if (typeof input.text !== "string") {
|
|
27108
|
+
const current = await ctx.session.readNotepad();
|
|
27109
|
+
return current && current.text.length > 0 ? current.text : "(your notes for this thread are empty)";
|
|
27110
|
+
}
|
|
27111
|
+
const bytes = utf8ByteLength(input.text);
|
|
27112
|
+
if (bytes > NOTEPAD_MAX_BYTES)
|
|
27113
|
+
return `your notes are ${bytes} bytes; the notepad holds at most ${NOTEPAD_MAX_BYTES} \u2014 shorten them and write again.`;
|
|
27114
|
+
const written = await ctx.session.writeNotepad(input.text);
|
|
27115
|
+
if (!written.ok) return `notes not saved (${written.reason}): another generation drives this run now.`;
|
|
27116
|
+
ctx.publish?.({ type: "notes", text: redactSecrets(input.text) });
|
|
27117
|
+
return `notes saved (${bytes} bytes); they ride your system prompt at the next run in this thread and reach you again after a compaction.`;
|
|
27118
|
+
}
|
|
27119
|
+
};
|
|
27120
|
+
SESSION_TOOLS = [recallTool, notesTool];
|
|
27121
|
+
}
|
|
27122
|
+
});
|
|
27123
|
+
|
|
26919
27124
|
// ../../src/tools/workspace.ts
|
|
26920
27125
|
import { z as z21 } from "zod";
|
|
26921
27126
|
var bashTool, readFileTool, writeFileTool, NAME_STATUS_MARK, NAME_STATUS_MARK_LINE, OUTPUT_CUT_RE, diffDigestTool, submitVerdictTool, submitDispositionsTool, submitPrDescriptionTool, handoffEntry, submitHandoffTool, updateStatusTool, TOOLSETS;
|
|
@@ -26934,6 +27139,7 @@ var init_workspace = __esm({
|
|
|
26934
27139
|
init_skills();
|
|
26935
27140
|
init_attach();
|
|
26936
27141
|
init_runs2();
|
|
27142
|
+
init_session();
|
|
26937
27143
|
bashTool = {
|
|
26938
27144
|
name: "bash",
|
|
26939
27145
|
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.",
|
|
@@ -27303,7 +27509,8 @@ ${raw.trim()}`;
|
|
|
27303
27509
|
listSkillsTool,
|
|
27304
27510
|
useSkillTool,
|
|
27305
27511
|
...GITHUB_READ_TOOLS,
|
|
27306
|
-
...GITHUB_ISSUE_WRITE_TOOLS
|
|
27512
|
+
...GITHUB_ISSUE_WRITE_TOOLS,
|
|
27513
|
+
...SESSION_TOOLS
|
|
27307
27514
|
],
|
|
27308
27515
|
readonly: [
|
|
27309
27516
|
bashTool,
|
|
@@ -27314,7 +27521,8 @@ ${raw.trim()}`;
|
|
|
27314
27521
|
diffDigestTool,
|
|
27315
27522
|
listSkillsTool,
|
|
27316
27523
|
useSkillTool,
|
|
27317
|
-
...GITHUB_READ_TOOLS
|
|
27524
|
+
...GITHUB_READ_TOOLS,
|
|
27525
|
+
...SESSION_TOOLS
|
|
27318
27526
|
],
|
|
27319
27527
|
web: [webFetchTool, webSearchTool, updateStatusTool, ...GITHUB_READ_TOOLS],
|
|
27320
27528
|
/** The general agent: no workspace, no shell — GitHub reads + issue writes
|
|
@@ -27334,7 +27542,8 @@ ${raw.trim()}`;
|
|
|
27334
27542
|
webSearchTool,
|
|
27335
27543
|
listSkillsTool,
|
|
27336
27544
|
useSkillTool,
|
|
27337
|
-
...GITHUB_READ_TOOLS
|
|
27545
|
+
...GITHUB_READ_TOOLS,
|
|
27546
|
+
...SESSION_TOOLS
|
|
27338
27547
|
],
|
|
27339
27548
|
/** The conductor (docs/reference/specs/agent-conductor.md): the five run
|
|
27340
27549
|
* tools — the only toolset that holds them, so no other preset can start,
|
|
@@ -28212,7 +28421,7 @@ Target repository: ${input.repo}. ` + (workspace ? `Your shell starts in the wor
|
|
|
28212
28421
|
const agentSystem = (head) => {
|
|
28213
28422
|
return [residentSystem ?? agent.system, targetBlock(head), input.contract, blocks.skills, blocks.mcp].filter((b) => Boolean(b)).join("\n\n");
|
|
28214
28423
|
};
|
|
28215
|
-
return (head) => [blocks.memory, blocks.config, blocks.about, blocks.instructions, agentSystem(head)].filter((part) => Boolean(part)).join("\n\n");
|
|
28424
|
+
return (head) => [blocks.memory, blocks.notes, blocks.config, blocks.about, blocks.instructions, agentSystem(head)].filter((part) => Boolean(part)).join("\n\n");
|
|
28216
28425
|
}
|
|
28217
28426
|
async function settleReviewedHead(input) {
|
|
28218
28427
|
return input.span ? input.span.span("run.settle_reviewed_head", (span) => settle(input, span)) : settle(input, void 0);
|
|
@@ -28790,7 +28999,9 @@ function attachmentsLine(outcomes) {
|
|
|
28790
28999
|
const parts = [];
|
|
28791
29000
|
if (landed.length > 0) {
|
|
28792
29001
|
parts.push(
|
|
28793
|
-
`Attached files are in ./${ATTACHMENTS_DIR}/: ${landed.map(
|
|
29002
|
+
`Attached files are in ./${ATTACHMENTS_DIR}/: ${landed.map(
|
|
29003
|
+
(o) => `${o.basename} (${formatSize(o.file.size)}, ${o.file.type}${o.earlier ? ", from earlier in the thread" : ""})`
|
|
29004
|
+
).join(", ")}`
|
|
28794
29005
|
);
|
|
28795
29006
|
}
|
|
28796
29007
|
for (const o of failed) parts.push(`${o.file.name} could not be staged: ${o.error}`);
|
|
@@ -28842,9 +29053,63 @@ async function pullStaged(outcomes, deps) {
|
|
|
28842
29053
|
}
|
|
28843
29054
|
return outcomes.map((o) => {
|
|
28844
29055
|
const error = o.error ?? pulled.get(o.key);
|
|
28845
|
-
|
|
29056
|
+
if (error !== void 0) return { ...o, error };
|
|
29057
|
+
const { error: _dropped, ...landedOutcome } = o;
|
|
29058
|
+
return landedOutcome;
|
|
28846
29059
|
});
|
|
28847
29060
|
}
|
|
29061
|
+
function threadInboundArtifacts(runsOldestFirst) {
|
|
29062
|
+
const seen = /* @__PURE__ */ new Set();
|
|
29063
|
+
const out = [];
|
|
29064
|
+
for (const events of runsOldestFirst) {
|
|
29065
|
+
for (const e of events) {
|
|
29066
|
+
if (e.type !== "artifact" || e.direction !== "in" || seen.has(e.key)) continue;
|
|
29067
|
+
seen.add(e.key);
|
|
29068
|
+
out.push({ key: e.key, name: e.name, size: e.size, contentType: e.contentType });
|
|
29069
|
+
}
|
|
29070
|
+
}
|
|
29071
|
+
return out;
|
|
29072
|
+
}
|
|
29073
|
+
async function stageThreadArtifacts(artifacts, deps) {
|
|
29074
|
+
const outcomes = [];
|
|
29075
|
+
for (const a of artifacts) {
|
|
29076
|
+
const file = { name: a.name, size: a.size, type: a.contentType };
|
|
29077
|
+
let held;
|
|
29078
|
+
try {
|
|
29079
|
+
held = await deps.store.head(a.key) !== null;
|
|
29080
|
+
} catch (err2) {
|
|
29081
|
+
outcomes.push({
|
|
29082
|
+
file,
|
|
29083
|
+
basename: safeBasename(a.name),
|
|
29084
|
+
key: a.key,
|
|
29085
|
+
earlier: true,
|
|
29086
|
+
error: `the store could not be asked: ${describe4(err2)}`
|
|
29087
|
+
});
|
|
29088
|
+
continue;
|
|
29089
|
+
}
|
|
29090
|
+
if (!held) {
|
|
29091
|
+
outcomes.push({
|
|
29092
|
+
file,
|
|
29093
|
+
basename: safeBasename(a.name),
|
|
29094
|
+
key: a.key,
|
|
29095
|
+
earlier: true,
|
|
29096
|
+
error: "the store no longer holds it (its retention passed)"
|
|
29097
|
+
});
|
|
29098
|
+
continue;
|
|
29099
|
+
}
|
|
29100
|
+
const basename4 = stagedBasename(deps.nextIndex(), a.name);
|
|
29101
|
+
deps.publish?.({
|
|
29102
|
+
type: "artifact",
|
|
29103
|
+
direction: "in",
|
|
29104
|
+
key: a.key,
|
|
29105
|
+
name: a.name,
|
|
29106
|
+
size: a.size,
|
|
29107
|
+
contentType: a.contentType
|
|
29108
|
+
});
|
|
29109
|
+
outcomes.push({ file, basename: basename4, key: a.key, earlier: true });
|
|
29110
|
+
}
|
|
29111
|
+
return outcomes;
|
|
29112
|
+
}
|
|
28848
29113
|
async function stageIntoWorkspace(files, deps) {
|
|
28849
29114
|
const outcomes = await pullStaged(await copyStaged(files, deps), deps);
|
|
28850
29115
|
return { line: attachmentsLine(outcomes), outcomes };
|
|
@@ -29055,13 +29320,13 @@ var init_frontmatter = __esm({
|
|
|
29055
29320
|
|
|
29056
29321
|
// ../../src/skills/stores.ts
|
|
29057
29322
|
import { existsSync as existsSync11, readFileSync as readFileSync13, readdirSync as readdirSync2 } from "node:fs";
|
|
29058
|
-
import { join as
|
|
29323
|
+
import { join as join10 } from "node:path";
|
|
29059
29324
|
function loadBundledSkills(dir) {
|
|
29060
29325
|
if (!existsSync11(dir)) return [];
|
|
29061
29326
|
const skills = [];
|
|
29062
29327
|
for (const entry of readdirSync2(dir, { withFileTypes: true })) {
|
|
29063
29328
|
if (!entry.isDirectory()) continue;
|
|
29064
|
-
const file =
|
|
29329
|
+
const file = join10(dir, entry.name, "SKILL.md");
|
|
29065
29330
|
if (!existsSync11(file)) continue;
|
|
29066
29331
|
try {
|
|
29067
29332
|
skills.push(parseSkillMarkdown(readFileSync13(file, "utf8")));
|
|
@@ -29618,6 +29883,20 @@ function mintRunBearer(deps, ctx) {
|
|
|
29618
29883
|
publish: (event) => registry.publish(runId3, event)
|
|
29619
29884
|
});
|
|
29620
29885
|
}
|
|
29886
|
+
function sessionNotesBlock(session) {
|
|
29887
|
+
const parts = [];
|
|
29888
|
+
const notepad = session.notepad?.trim();
|
|
29889
|
+
const summary = session.summary?.trim();
|
|
29890
|
+
if (notepad)
|
|
29891
|
+
parts.push(
|
|
29892
|
+
"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
|
|
29893
|
+
);
|
|
29894
|
+
if (summary)
|
|
29895
|
+
parts.push(
|
|
29896
|
+
"SUMMARY OF THE EARLIER CONVERSATION (written when the context was compacted; every earlier turn is still reachable with `recall`):\n" + summary
|
|
29897
|
+
);
|
|
29898
|
+
return parts.length ? parts.join("\n\n") : void 0;
|
|
29899
|
+
}
|
|
29621
29900
|
async function composePrompt(deps, ctx) {
|
|
29622
29901
|
const {
|
|
29623
29902
|
msg,
|
|
@@ -29680,6 +29959,7 @@ async function composePrompt(deps, ctx) {
|
|
|
29680
29959
|
);
|
|
29681
29960
|
const instructionsBlock = customInstructionsBlock(scopes);
|
|
29682
29961
|
const memoryBlock = await root.span("dispatch.compose", () => memoryBlockP);
|
|
29962
|
+
const notesBlock = ctx.session ? sessionNotesBlock(ctx.session) : void 0;
|
|
29683
29963
|
const composeSystem = makeSystemComposer({
|
|
29684
29964
|
agent,
|
|
29685
29965
|
resident: resident === true,
|
|
@@ -29695,6 +29975,7 @@ async function composePrompt(deps, ctx) {
|
|
|
29695
29975
|
...ctx.contract !== void 0 ? { contract: ctx.contract } : {},
|
|
29696
29976
|
blocks: {
|
|
29697
29977
|
memory: memoryBlock,
|
|
29978
|
+
notes: notesBlock,
|
|
29698
29979
|
config: configBlock,
|
|
29699
29980
|
about: aboutBlock,
|
|
29700
29981
|
instructions: instructionsBlock,
|
|
@@ -30872,6 +31153,14 @@ var init_transport = __esm({
|
|
|
30872
31153
|
function relayedTools(tools) {
|
|
30873
31154
|
return tools.filter((t) => !NATIVE_WORKSPACE_TOOLS.has(t.name));
|
|
30874
31155
|
}
|
|
31156
|
+
function compactionSteer(notepad, opts = {}) {
|
|
31157
|
+
const notes = notepad?.trim();
|
|
31158
|
+
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";
|
|
31159
|
+
if (opts.unavailable)
|
|
31160
|
+
return reach + "Your notes for this thread could not be read just now; `notes {}` reads them on demand, and `notes { text }` still writes them.";
|
|
31161
|
+
return reach + (notes ? `YOUR NOTES FOR THIS THREAD, as you last wrote them with \`notes\`:
|
|
31162
|
+
${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.");
|
|
31163
|
+
}
|
|
30875
31164
|
function splitSeed(seed) {
|
|
30876
31165
|
for (let i = seed.length - 1; i >= 0; i--) {
|
|
30877
31166
|
if (seed[i].role === "user") return { session: seed.slice(0, i), prompt: seed[i] };
|
|
@@ -31050,6 +31339,7 @@ async function runPiHarness(deps, run2) {
|
|
|
31050
31339
|
writeUpAt = now();
|
|
31051
31340
|
transport.send({ type: "steer", message: instruction });
|
|
31052
31341
|
};
|
|
31342
|
+
let steers = Promise.resolve();
|
|
31053
31343
|
const drainFollowUps = () => {
|
|
31054
31344
|
const inputs = run2.inbox?.drain() ?? [];
|
|
31055
31345
|
if (inputs.length === 0) return;
|
|
@@ -31067,7 +31357,26 @@ async function runPiHarness(deps, run2) {
|
|
|
31067
31357
|
const images = inputs.flatMap(
|
|
31068
31358
|
(i) => (i.images ?? []).map((img) => ({ type: "image", data: img.data, mimeType: img.mediaType }))
|
|
31069
31359
|
);
|
|
31070
|
-
|
|
31360
|
+
steers = steers.then(async () => {
|
|
31361
|
+
let stagedLine = "";
|
|
31362
|
+
if (run2.stageFollowUps) {
|
|
31363
|
+
try {
|
|
31364
|
+
stagedLine = await run2.stageFollowUps(inputs);
|
|
31365
|
+
} catch (err2) {
|
|
31366
|
+
stagedLine = `Attached files could not be staged: ${err2 instanceof Error ? err2.message : String(err2)}`;
|
|
31367
|
+
note("follow_up", `staging the follow-up's files failed: ${redactSecrets(stagedLine)}`);
|
|
31368
|
+
}
|
|
31369
|
+
}
|
|
31370
|
+
if (settled) return;
|
|
31371
|
+
const prompt = followUpPrompt(inputs);
|
|
31372
|
+
transport.send({
|
|
31373
|
+
type: "steer",
|
|
31374
|
+
message: stagedLine ? `${prompt}
|
|
31375
|
+
|
|
31376
|
+
${stagedLine}` : prompt,
|
|
31377
|
+
...images.length > 0 ? { images } : {}
|
|
31378
|
+
});
|
|
31379
|
+
});
|
|
31071
31380
|
};
|
|
31072
31381
|
const check = () => {
|
|
31073
31382
|
const requested = run2.control?.requested;
|
|
@@ -31150,7 +31459,23 @@ async function runPiHarness(deps, run2) {
|
|
|
31150
31459
|
}
|
|
31151
31460
|
}
|
|
31152
31461
|
if (obs.message) await mirror.onMessage(obs.message, bridge.turns);
|
|
31153
|
-
if (obs.compaction)
|
|
31462
|
+
if (obs.compaction) {
|
|
31463
|
+
await mirror.onCompaction(obs.compaction, bridge.turns);
|
|
31464
|
+
if (!writeUp && !hardStopped && !catchingUp) {
|
|
31465
|
+
let notepad;
|
|
31466
|
+
let unavailable = false;
|
|
31467
|
+
try {
|
|
31468
|
+
notepad = run2.notepad ? (await run2.notepad())?.text : void 0;
|
|
31469
|
+
} catch (err2) {
|
|
31470
|
+
unavailable = true;
|
|
31471
|
+
note(
|
|
31472
|
+
"harness_error",
|
|
31473
|
+
`the notepad could not be read for the compaction steer (${err2 instanceof Error ? err2.message : String(err2)}); steered without it`
|
|
31474
|
+
);
|
|
31475
|
+
}
|
|
31476
|
+
transport.send({ type: "steer", message: compactionSteer(notepad, { unavailable }) });
|
|
31477
|
+
}
|
|
31478
|
+
}
|
|
31154
31479
|
if (obs.providerError !== void 0) {
|
|
31155
31480
|
if (catchingUp)
|
|
31156
31481
|
note("harness_error", `a model call failed while the bot was away (${obs.providerError}); continuing`);
|
|
@@ -31738,6 +32063,7 @@ async function runLoop2(deps, ctx) {
|
|
|
31738
32063
|
runs,
|
|
31739
32064
|
steer,
|
|
31740
32065
|
wait,
|
|
32066
|
+
session,
|
|
31741
32067
|
parentRunId,
|
|
31742
32068
|
coordinator,
|
|
31743
32069
|
seed
|
|
@@ -31863,6 +32189,19 @@ async function runLoop2(deps, ctx) {
|
|
|
31863
32189
|
const attachFile = io.attachFile?.bind(io);
|
|
31864
32190
|
const uploadTicket = io.uploadTicket?.bind(io);
|
|
31865
32191
|
const nextStagedIndex = ctx.stagingIndex ?? stagingIndex();
|
|
32192
|
+
const stageFollowUps = deps.artifacts ? async (inputs) => {
|
|
32193
|
+
const files = inputs.flatMap((i) => i.staged ?? []);
|
|
32194
|
+
if (files.length === 0) return "";
|
|
32195
|
+
const staged = await stageIntoWorkspace(files, {
|
|
32196
|
+
store: deps.artifacts,
|
|
32197
|
+
threadKey: msg.threadKey,
|
|
32198
|
+
publish: (e) => registry.publish(run2.id, e),
|
|
32199
|
+
nextIndex: nextStagedIndex,
|
|
32200
|
+
executor,
|
|
32201
|
+
resident: round2.selection.resident !== void 0
|
|
32202
|
+
});
|
|
32203
|
+
return staged.line;
|
|
32204
|
+
} : void 0;
|
|
31866
32205
|
let artifactSeq = 0;
|
|
31867
32206
|
const artifacts = deps.artifacts ? {
|
|
31868
32207
|
store: deps.artifacts,
|
|
@@ -31885,6 +32224,7 @@ async function runLoop2(deps, ctx) {
|
|
|
31885
32224
|
...runs ? { runs } : {},
|
|
31886
32225
|
...steer ? { steer } : {},
|
|
31887
32226
|
...wait ? { wait } : {},
|
|
32227
|
+
...session ? { session } : {},
|
|
31888
32228
|
onVerdict,
|
|
31889
32229
|
onDigest,
|
|
31890
32230
|
onPrDescription,
|
|
@@ -31932,6 +32272,7 @@ async function runLoop2(deps, ctx) {
|
|
|
31932
32272
|
messages,
|
|
31933
32273
|
tools: relayedTools(mergeTools(TOOLSETS[agent.toolset] ?? [], mcpForRun?.tools)),
|
|
31934
32274
|
toolContext,
|
|
32275
|
+
...session ? { notepad: () => session.readNotepad() } : {},
|
|
31935
32276
|
rules: {
|
|
31936
32277
|
checkout: binding?.workspace ?? "/workspace",
|
|
31937
32278
|
...ownBranch !== void 0 ? { branch: ownBranch } : {},
|
|
@@ -31941,6 +32282,7 @@ async function runLoop2(deps, ctx) {
|
|
|
31941
32282
|
span: root,
|
|
31942
32283
|
control: run2.control,
|
|
31943
32284
|
inbox: admitted.inbox,
|
|
32285
|
+
...stageFollowUps ? { stageFollowUps } : {},
|
|
31944
32286
|
onEvent,
|
|
31945
32287
|
onProgress,
|
|
31946
32288
|
...ledgerRun ? {
|
|
@@ -31979,23 +32321,7 @@ async function runLoop2(deps, ctx) {
|
|
|
31979
32321
|
// operator stop from /runs
|
|
31980
32322
|
inbox: admitted.inbox,
|
|
31981
32323
|
// thread follow-ups steered into this run (thread-admission item 2)
|
|
31982
|
-
|
|
31983
|
-
// store and pulled into this workspace before the model reads the turn.
|
|
31984
|
-
...deps.artifacts ? {
|
|
31985
|
-
stageFollowUps: async (inputs) => {
|
|
31986
|
-
const files = inputs.flatMap((i) => i.staged ?? []);
|
|
31987
|
-
if (files.length === 0) return "";
|
|
31988
|
-
const staged = await stageIntoWorkspace(files, {
|
|
31989
|
-
store: deps.artifacts,
|
|
31990
|
-
threadKey: msg.threadKey,
|
|
31991
|
-
publish: (e) => registry.publish(run2.id, e),
|
|
31992
|
-
nextIndex: nextStagedIndex,
|
|
31993
|
-
executor,
|
|
31994
|
-
resident: round2.selection.resident !== void 0
|
|
31995
|
-
});
|
|
31996
|
-
return staged.line;
|
|
31997
|
-
}
|
|
31998
|
-
} : {},
|
|
32324
|
+
...stageFollowUps ? { stageFollowUps } : {},
|
|
31999
32325
|
// The step record before each step's tools (run-history item 35).
|
|
32000
32326
|
...ledgerRun ? { onStep: ledgerRun.step.bind(ledgerRun) } : {},
|
|
32001
32327
|
// A resume re-enters the loop from the plan (run-history item 37).
|
|
@@ -33665,25 +33991,32 @@ var init_lineage = __esm({
|
|
|
33665
33991
|
}
|
|
33666
33992
|
});
|
|
33667
33993
|
|
|
33668
|
-
// ../../src/core/runLedger/sessionLog.ts
|
|
33669
|
-
function sessionKey(threadKey, agent) {
|
|
33670
|
-
return `${threadKey}:${agent ?? "-"}`;
|
|
33671
|
-
}
|
|
33672
|
-
function requestIndex(seed) {
|
|
33673
|
-
for (let i = seed.length - 1; i >= 0; i--) if (seed[i].role === "user") return i;
|
|
33674
|
-
return 0;
|
|
33675
|
-
}
|
|
33676
|
-
var DEFAULT_SESSION_LOG_MAX_BYTES;
|
|
33677
|
-
var init_sessionLog = __esm({
|
|
33678
|
-
"../../src/core/runLedger/sessionLog.ts"() {
|
|
33679
|
-
"use strict";
|
|
33680
|
-
init_runRecord();
|
|
33681
|
-
init_runRecord();
|
|
33682
|
-
DEFAULT_SESSION_LOG_MAX_BYTES = DEFAULT_RETENTION_POLICY.sessionLogMaxBytes;
|
|
33683
|
-
}
|
|
33684
|
-
});
|
|
33685
|
-
|
|
33686
33994
|
// ../../src/core/dispatch/thread.ts
|
|
33995
|
+
async function readThreadArtifacts(service, thread, warn = (line3) => console.warn(line3)) {
|
|
33996
|
+
const perRun = [];
|
|
33997
|
+
for (const run2 of [...thread].reverse()) {
|
|
33998
|
+
const events = [];
|
|
33999
|
+
let afterSeq;
|
|
34000
|
+
try {
|
|
34001
|
+
for (; ; ) {
|
|
34002
|
+
const r = await service.getRunEvents(run2.id, afterSeq === void 0 ? {} : { afterSeq });
|
|
34003
|
+
if (!r.ok) {
|
|
34004
|
+
warn(`[thread] ${run2.id}: reading its received files stopped after ${events.length} event(s) \u2014 ${r.error}`);
|
|
34005
|
+
break;
|
|
34006
|
+
}
|
|
34007
|
+
events.push(...r.value.events);
|
|
34008
|
+
if (r.value.nextAfterSeq === void 0) break;
|
|
34009
|
+
afterSeq = r.value.nextAfterSeq;
|
|
34010
|
+
}
|
|
34011
|
+
} catch (err2) {
|
|
34012
|
+
warn(
|
|
34013
|
+
`[thread] ${run2.id}: reading its received files failed after ${events.length} event(s) \u2014 ${err2 instanceof Error ? err2.message : String(err2)}`
|
|
34014
|
+
);
|
|
34015
|
+
}
|
|
34016
|
+
perRun.push(events);
|
|
34017
|
+
}
|
|
34018
|
+
return threadInboundArtifacts(perRun);
|
|
34019
|
+
}
|
|
33687
34020
|
async function readThread(service, threadKey) {
|
|
33688
34021
|
try {
|
|
33689
34022
|
const page2 = await service.listRuns({
|
|
@@ -33718,6 +34051,7 @@ var THREAD_READ_LIMIT;
|
|
|
33718
34051
|
var init_thread = __esm({
|
|
33719
34052
|
"../../src/core/dispatch/thread.ts"() {
|
|
33720
34053
|
"use strict";
|
|
34054
|
+
init_staging();
|
|
33721
34055
|
THREAD_READ_LIMIT = 8;
|
|
33722
34056
|
}
|
|
33723
34057
|
});
|
|
@@ -33798,7 +34132,15 @@ async function sessionSeedFor(input) {
|
|
|
33798
34132
|
history: input.history,
|
|
33799
34133
|
request: input.request
|
|
33800
34134
|
});
|
|
33801
|
-
|
|
34135
|
+
if (!seed) return { notes: [] };
|
|
34136
|
+
try {
|
|
34137
|
+
const notepad = await input.ledger.readNotepad(key);
|
|
34138
|
+
if (notepad && notepad.text.trim().length > 0) seed.notepad = notepad.text;
|
|
34139
|
+
} catch (err2) {
|
|
34140
|
+
const why = err2 instanceof Error ? err2.message : String(err2);
|
|
34141
|
+
seed.notes.push(`session seed: the notepad of ${key} could not be read (${why}) \u2014 the run starts without it`);
|
|
34142
|
+
}
|
|
34143
|
+
return { seed, notes: seed.notes };
|
|
33802
34144
|
}
|
|
33803
34145
|
function withoutThinking(message2) {
|
|
33804
34146
|
if (message2.role !== "assistant") return message2;
|
|
@@ -33806,7 +34148,7 @@ function withoutThinking(message2) {
|
|
|
33806
34148
|
if (content.length === message2.content.length) return message2;
|
|
33807
34149
|
return { ...message2, content: content.length ? content : [{ type: "text", text: "(reasoning omitted)" }] };
|
|
33808
34150
|
}
|
|
33809
|
-
var SEED_BUDGET_TOKENS, SEED_BUDGET_BYTES,
|
|
34151
|
+
var SEED_BUDGET_TOKENS, SEED_BUDGET_BYTES, settledResult;
|
|
33810
34152
|
var init_seed = __esm({
|
|
33811
34153
|
"../../src/core/dispatch/seed.ts"() {
|
|
33812
34154
|
"use strict";
|
|
@@ -33815,7 +34157,6 @@ var init_seed = __esm({
|
|
|
33815
34157
|
init_thread();
|
|
33816
34158
|
SEED_BUDGET_TOKENS = 6e4;
|
|
33817
34159
|
SEED_BUDGET_BYTES = SEED_BUDGET_TOKENS * 4;
|
|
33818
|
-
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.]";
|
|
33819
34160
|
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.`;
|
|
33820
34161
|
}
|
|
33821
34162
|
});
|
|
@@ -34189,6 +34530,13 @@ async function dispatch(deps, msg, io, opts = {}) {
|
|
|
34189
34530
|
nextIndex: nextStagedIndex,
|
|
34190
34531
|
publish: (e) => registry.publish(runId3, e)
|
|
34191
34532
|
}) : void 0;
|
|
34533
|
+
const earlierStaged = !resume && deps.artifacts && agent.machine !== "none" && thread && thread.length > 0 ? readThreadArtifacts(runsService, thread).then(
|
|
34534
|
+
(found) => stageThreadArtifacts(found, {
|
|
34535
|
+
store: deps.artifacts,
|
|
34536
|
+
nextIndex: nextStagedIndex,
|
|
34537
|
+
publish: (e) => registry.publish(runId3, e)
|
|
34538
|
+
})
|
|
34539
|
+
) : void 0;
|
|
34192
34540
|
const reservation = await reserveRun(deps, {
|
|
34193
34541
|
msg,
|
|
34194
34542
|
agent,
|
|
@@ -34238,9 +34586,10 @@ async function dispatch(deps, msg, io, opts = {}) {
|
|
|
34238
34586
|
});
|
|
34239
34587
|
return ended;
|
|
34240
34588
|
}
|
|
34241
|
-
|
|
34242
|
-
|
|
34243
|
-
|
|
34589
|
+
const earlier2 = earlierStaged ? await earlierStaged : [];
|
|
34590
|
+
if (staged.length > 0 || earlier2.length > 0) {
|
|
34591
|
+
const line3 = stagedCopies || earlier2.length > 0 ? attachmentsLine(
|
|
34592
|
+
await pullStaged([...stagedCopies ? await stagedCopies : [], ...earlier2], {
|
|
34244
34593
|
store: deps.artifacts,
|
|
34245
34594
|
executor,
|
|
34246
34595
|
resident: resident !== void 0
|
|
@@ -34289,7 +34638,15 @@ async function dispatch(deps, msg, io, opts = {}) {
|
|
|
34289
34638
|
verifiedAtAttach,
|
|
34290
34639
|
resume,
|
|
34291
34640
|
root,
|
|
34292
|
-
...contractBlock !== void 0 && agent.name === "review" ? { contract: contractBlock } : {}
|
|
34641
|
+
...contractBlock !== void 0 && agent.name === "review" ? { contract: contractBlock } : {},
|
|
34642
|
+
// What the session already knows (session-log item 10): the notepad and
|
|
34643
|
+
// the newest compaction's summary the seed brought back, for the prompt.
|
|
34644
|
+
...session && (session.notepad !== void 0 || session.summary !== void 0) ? {
|
|
34645
|
+
session: {
|
|
34646
|
+
...session.notepad !== void 0 ? { notepad: session.notepad } : {},
|
|
34647
|
+
...session.summary !== void 0 ? { summary: session.summary } : {}
|
|
34648
|
+
}
|
|
34649
|
+
} : {}
|
|
34293
34650
|
});
|
|
34294
34651
|
const { mcpForRun, composeSystem, system } = prompt;
|
|
34295
34652
|
const reviewHead = prompt.reviewHead;
|
|
@@ -34347,6 +34704,7 @@ async function dispatch(deps, msg, io, opts = {}) {
|
|
|
34347
34704
|
seed,
|
|
34348
34705
|
...session ? { seedLog: session.log } : {}
|
|
34349
34706
|
});
|
|
34707
|
+
const sessionTools = sessionCapabilityFor(ledgerRun, deps.runLedger);
|
|
34350
34708
|
const { spawn: spawn7, runs, steer, wait } = runToolCapabilities(
|
|
34351
34709
|
{ core: deps, dispatch, registry, clock: clock3 },
|
|
34352
34710
|
{
|
|
@@ -34397,6 +34755,7 @@ async function dispatch(deps, msg, io, opts = {}) {
|
|
|
34397
34755
|
runs,
|
|
34398
34756
|
steer,
|
|
34399
34757
|
wait,
|
|
34758
|
+
...sessionTools ? { session: sessionTools } : {},
|
|
34400
34759
|
parentRunId,
|
|
34401
34760
|
coordinator,
|
|
34402
34761
|
seed,
|
|
@@ -34527,6 +34886,7 @@ var init_dispatcher = __esm({
|
|
|
34527
34886
|
init_settle();
|
|
34528
34887
|
init_lineage();
|
|
34529
34888
|
init_seed();
|
|
34889
|
+
init_session();
|
|
34530
34890
|
init_thread();
|
|
34531
34891
|
init_select();
|
|
34532
34892
|
init_spawn();
|
|
@@ -34645,7 +35005,7 @@ var init_runHistoryWriter = __esm({
|
|
|
34645
35005
|
|
|
34646
35006
|
// ../../src/core/runStore.ts
|
|
34647
35007
|
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";
|
|
34648
|
-
import { join as
|
|
35008
|
+
import { join as join11, resolve as resolve9 } from "node:path";
|
|
34649
35009
|
function isValidRunId(id) {
|
|
34650
35010
|
return typeof id === "string" && RUN_ID_PATTERN.test(id);
|
|
34651
35011
|
}
|
|
@@ -34693,7 +35053,7 @@ function buildRunStore(cfg, secrets, deps) {
|
|
|
34693
35053
|
const now = deps.now ?? Date.now;
|
|
34694
35054
|
const policy = retentionPolicyOf(cfg);
|
|
34695
35055
|
if (cfg.store === "file") {
|
|
34696
|
-
const store = new FileRunStore(
|
|
35056
|
+
const store = new FileRunStore(join11(deps.dataDir, RUNS_DIR), { policy, now });
|
|
34697
35057
|
store.sweep();
|
|
34698
35058
|
const timer = (deps.setInterval ?? setInterval)(() => store.sweep(), SWEEP_INTERVAL_MS);
|
|
34699
35059
|
timer.unref?.();
|
|
@@ -34760,7 +35120,7 @@ var init_runStore = __esm({
|
|
|
34760
35120
|
this.now = opts.now ?? Date.now;
|
|
34761
35121
|
}
|
|
34762
35122
|
recordPath(id) {
|
|
34763
|
-
return
|
|
35123
|
+
return join11(this.dir, `${id}.json`);
|
|
34764
35124
|
}
|
|
34765
35125
|
ensureDir() {
|
|
34766
35126
|
mkdirSync8(this.dir, { recursive: true, mode: DIR_MODE });
|
|
@@ -34782,7 +35142,7 @@ var init_runStore = __esm({
|
|
|
34782
35142
|
}
|
|
34783
35143
|
/** Every valid index line, a repeated id resolved to its last line. */
|
|
34784
35144
|
readIndex() {
|
|
34785
|
-
const path =
|
|
35145
|
+
const path = join11(this.dir, INDEX_FILE);
|
|
34786
35146
|
if (!existsSync12(path)) return [];
|
|
34787
35147
|
const byId = /* @__PURE__ */ new Map();
|
|
34788
35148
|
for (const line3 of readFileSync14(path, "utf8").split("\n")) {
|
|
@@ -34814,7 +35174,7 @@ var init_runStore = __esm({
|
|
|
34814
35174
|
const keptIds = new Set(kept.map((r) => r.id));
|
|
34815
35175
|
for (const item of index) if (!keptIds.has(item.id)) rmSync3(this.recordPath(item.id), { force: true });
|
|
34816
35176
|
this.writeAtomic(
|
|
34817
|
-
|
|
35177
|
+
join11(this.dir, INDEX_FILE),
|
|
34818
35178
|
kept.map((r) => JSON.stringify(r)).join("\n") + (kept.length ? "\n" : "")
|
|
34819
35179
|
);
|
|
34820
35180
|
return kept;
|
|
@@ -35044,7 +35404,10 @@ function createLedgerWriteThrough(opts) {
|
|
|
35044
35404
|
/** The run's place in its session log (session-log item 2); undefined for
|
|
35045
35405
|
* a run without a conversation of its own and for an adopted row claimed
|
|
35046
35406
|
* before the log existed. */
|
|
35047
|
-
|
|
35407
|
+
sessionRow;
|
|
35408
|
+
get session() {
|
|
35409
|
+
return this.sessionRow;
|
|
35410
|
+
}
|
|
35048
35411
|
/** Turns of the run's conversation on the ledger, counted from its seed —
|
|
35049
35412
|
* the last step record's `turnIndex` — so the finish can close the range. */
|
|
35050
35413
|
turnsWritten;
|
|
@@ -35082,11 +35445,11 @@ function createLedgerWriteThrough(opts) {
|
|
|
35082
35445
|
this.stepNo = from.stepNo;
|
|
35083
35446
|
this.lastSeq = from.lastSeq;
|
|
35084
35447
|
this.adopted = from.resumable === true;
|
|
35085
|
-
this.
|
|
35448
|
+
this.sessionRow = from.session;
|
|
35086
35449
|
}
|
|
35087
35450
|
/** A reservation learns its session when the prompt's claim promotes it. */
|
|
35088
35451
|
bindSession(session) {
|
|
35089
|
-
this.
|
|
35452
|
+
this.sessionRow = session;
|
|
35090
35453
|
}
|
|
35091
35454
|
get resumable() {
|
|
35092
35455
|
return !this.detached && (this.seeded || this.adopted);
|
|
@@ -35099,11 +35462,11 @@ function createLedgerWriteThrough(opts) {
|
|
|
35099
35462
|
* ends short of what the model saw, and the next run in the session must
|
|
35100
35463
|
* know. A run that wrote no turn leaves the range open. */
|
|
35101
35464
|
sessionOnRecord() {
|
|
35102
|
-
if (!this.
|
|
35103
|
-
if (this.broken) return { ...this.
|
|
35104
|
-
const from = this.
|
|
35105
|
-
const to = this.turnsWritten !== void 0 && this.turnsWritten > 0 ? this.
|
|
35106
|
-
return { ...this.
|
|
35465
|
+
if (!this.sessionRow) return void 0;
|
|
35466
|
+
if (this.broken) return { ...this.sessionRow, range: "broken" };
|
|
35467
|
+
const from = this.sessionRow.range === "broken" ? this.sessionRow.seedFrom : this.sessionRow.range.from;
|
|
35468
|
+
const to = this.turnsWritten !== void 0 && this.turnsWritten > 0 ? this.sessionRow.seedFrom + this.turnsWritten - 1 : void 0;
|
|
35469
|
+
return { ...this.sessionRow, range: to !== void 0 && to >= from ? { from, to } : { from } };
|
|
35107
35470
|
}
|
|
35108
35471
|
sink = {
|
|
35109
35472
|
put: async (plain4) => {
|
|
@@ -35150,11 +35513,11 @@ function createLedgerWriteThrough(opts) {
|
|
|
35150
35513
|
* no record at all was killed before its conversation was stored and
|
|
35151
35514
|
* closes `interrupted`. */
|
|
35152
35515
|
async seed(messages, budgetMs) {
|
|
35153
|
-
const base = this.
|
|
35154
|
-
const reused = this.
|
|
35516
|
+
const base = this.sessionRow?.seedFrom ?? 0;
|
|
35517
|
+
const reused = this.sessionRow && this.sessionRow.range !== "broken" ? this.sessionRow.range.from - this.sessionRow.seedFrom : 0;
|
|
35155
35518
|
const turns = messages.slice(reused).map((message2, i) => ({ idx: base + reused + i, message: message2 }));
|
|
35156
35519
|
try {
|
|
35157
|
-
const seeded = await ledger.seed(this.runId, gen, turns, this.
|
|
35520
|
+
const seeded = await ledger.seed(this.runId, gen, turns, this.sessionRow?.key);
|
|
35158
35521
|
if (!seeded.ok) {
|
|
35159
35522
|
this.detach(`seed refused (${seeded.reason})`);
|
|
35160
35523
|
return;
|
|
@@ -35174,7 +35537,7 @@ function createLedgerWriteThrough(opts) {
|
|
|
35174
35537
|
iteration: 0
|
|
35175
35538
|
},
|
|
35176
35539
|
[],
|
|
35177
|
-
this.
|
|
35540
|
+
this.sessionRow?.key
|
|
35178
35541
|
);
|
|
35179
35542
|
if (!recorded.ok) this.detach(`seed record refused (${recorded.reason})`);
|
|
35180
35543
|
else this.seeded = true;
|
|
@@ -35184,7 +35547,7 @@ function createLedgerWriteThrough(opts) {
|
|
|
35184
35547
|
}
|
|
35185
35548
|
async step(report2) {
|
|
35186
35549
|
if (this.detached) return;
|
|
35187
|
-
const base = this.
|
|
35550
|
+
const base = this.sessionRow?.seedFrom ?? 0;
|
|
35188
35551
|
const turns = report2.turns.map((message2, i) => ({ idx: base + report2.firstIdx + i, message: message2 }));
|
|
35189
35552
|
if (report2.compaction)
|
|
35190
35553
|
turns.push({ idx: base + report2.firstIdx + report2.turns.length, compaction: report2.compaction });
|
|
@@ -35200,7 +35563,7 @@ function createLedgerWriteThrough(opts) {
|
|
|
35200
35563
|
};
|
|
35201
35564
|
for (let attempt = 1; ; attempt++) {
|
|
35202
35565
|
try {
|
|
35203
|
-
const result = await ledger.step(this.runId, gen, record2, turns, this.
|
|
35566
|
+
const result = await ledger.step(this.runId, gen, record2, turns, this.sessionRow?.key);
|
|
35204
35567
|
if (!result.ok) this.detach(`step ${record2.step} refused (${result.reason})`);
|
|
35205
35568
|
else this.turnsWritten = record2.turnIndex;
|
|
35206
35569
|
return;
|
|
@@ -35386,6 +35749,10 @@ function createLedgerWriteThrough(opts) {
|
|
|
35386
35749
|
}
|
|
35387
35750
|
},
|
|
35388
35751
|
readSessionTail: (key, maxBytes) => ledger.readSessionTail(key, maxBytes),
|
|
35752
|
+
readSession: (key, from, to) => ledger.readSession(key, from, to),
|
|
35753
|
+
searchSession: (key, query, limit) => ledger.searchSession(key, query, limit),
|
|
35754
|
+
readNotepad: (key) => ledger.readNotepad(key),
|
|
35755
|
+
writeNotepad: (key, text) => ledger.writeNotepad(key, gen, text),
|
|
35389
35756
|
async handoff() {
|
|
35390
35757
|
const candidates = [...live].filter((r) => r.resumable && r.tracked() && !r.handedOff);
|
|
35391
35758
|
if (candidates.length === 0) return { marked: [] };
|
|
@@ -35439,6 +35806,18 @@ var init_writeThrough = __esm({
|
|
|
35439
35806
|
async readSessionTail(_key, _maxBytes) {
|
|
35440
35807
|
return { from: 0, transcript: { complete: true, turns: 0, messages: [], compactions: [] } };
|
|
35441
35808
|
}
|
|
35809
|
+
async readSession(_key, _from, _to) {
|
|
35810
|
+
return { complete: true, turns: 0, messages: [], compactions: [] };
|
|
35811
|
+
}
|
|
35812
|
+
async searchSession(_key, _query, _limit) {
|
|
35813
|
+
return { hits: [], gaps: [] };
|
|
35814
|
+
}
|
|
35815
|
+
async readNotepad(_key) {
|
|
35816
|
+
return null;
|
|
35817
|
+
}
|
|
35818
|
+
async writeNotepad(_key, _text) {
|
|
35819
|
+
return { ok: false, reason: "unknown-run" };
|
|
35820
|
+
}
|
|
35442
35821
|
async handoff() {
|
|
35443
35822
|
return { marked: [] };
|
|
35444
35823
|
}
|
|
@@ -35452,6 +35831,7 @@ var init_writeThrough = __esm({
|
|
|
35452
35831
|
sink;
|
|
35453
35832
|
resumable = false;
|
|
35454
35833
|
handedOff = false;
|
|
35834
|
+
session = void 0;
|
|
35455
35835
|
tracked() {
|
|
35456
35836
|
return false;
|
|
35457
35837
|
}
|
|
@@ -35931,7 +36311,10 @@ function httpMcpClientFactory(fetchImpl) {
|
|
|
35931
36311
|
return (server) => new StreamableHttpMcpClient({
|
|
35932
36312
|
url: server.url,
|
|
35933
36313
|
fetch: fetchImpl,
|
|
35934
|
-
|
|
36314
|
+
headers: {
|
|
36315
|
+
...server.headers,
|
|
36316
|
+
...server.auth?.type === "bearer" ? { authorization: `Bearer ${server.auth.token}` } : {}
|
|
36317
|
+
}
|
|
35935
36318
|
});
|
|
35936
36319
|
}
|
|
35937
36320
|
var init_mcp2 = __esm({
|
|
@@ -38760,18 +39143,18 @@ var init_liveView = __esm({
|
|
|
38760
39143
|
|
|
38761
39144
|
// ../../src/channels/webAssets.ts
|
|
38762
39145
|
import { readdirSync as readdirSync3, readFileSync as readFileSync16, statSync as statSync3 } from "node:fs";
|
|
38763
|
-
import { join as
|
|
39146
|
+
import { join as join12 } from "node:path";
|
|
38764
39147
|
function contentTypeFor2(path) {
|
|
38765
39148
|
const dot = path.lastIndexOf(".");
|
|
38766
39149
|
return (dot === -1 ? void 0 : CONTENT_TYPES[path.slice(dot)]) ?? "application/octet-stream";
|
|
38767
39150
|
}
|
|
38768
39151
|
function webDistDir(env, packageRoot) {
|
|
38769
|
-
return env.SWITCHBOARD_WEB_DIST ??
|
|
39152
|
+
return env.SWITCHBOARD_WEB_DIST ?? join12(packageRoot, WEB_DIST_DIR);
|
|
38770
39153
|
}
|
|
38771
39154
|
function loadWebAssets(distDir) {
|
|
38772
39155
|
let manifest;
|
|
38773
39156
|
try {
|
|
38774
|
-
manifest = JSON.parse(readFileSync16(
|
|
39157
|
+
manifest = JSON.parse(readFileSync16(join12(distDir, ".vite", "manifest.json"), "utf8"));
|
|
38775
39158
|
} catch (err2) {
|
|
38776
39159
|
throw new Error(
|
|
38777
39160
|
`web app manifest not found under ${distDir} \u2014 build it first (npm run build in web/): ${err2 instanceof Error ? err2.message : String(err2)}`,
|
|
@@ -38785,9 +39168,9 @@ function loadWebAssets(distDir) {
|
|
|
38785
39168
|
css: (entryChunk.css ?? []).map((f) => `/${f}`)
|
|
38786
39169
|
};
|
|
38787
39170
|
const files = /* @__PURE__ */ new Map();
|
|
38788
|
-
const assetsDir =
|
|
39171
|
+
const assetsDir = join12(distDir, "assets");
|
|
38789
39172
|
for (const name of readdirSync3(assetsDir)) {
|
|
38790
|
-
const full =
|
|
39173
|
+
const full = join12(assetsDir, name);
|
|
38791
39174
|
if (statSync3(full).isFile()) files.set(`/assets/${name}`, readFileSync16(full));
|
|
38792
39175
|
}
|
|
38793
39176
|
const serve2 = (req, res) => {
|
|
@@ -39977,6 +40360,25 @@ var init_runLedgerWorker = __esm({
|
|
|
39977
40360
|
)
|
|
39978
40361
|
};
|
|
39979
40362
|
}
|
|
40363
|
+
async searchSession(key, query, limit) {
|
|
40364
|
+
this.checkSessionKey(key);
|
|
40365
|
+
const r = await this.post("/runs/session/search", { key, query, limit });
|
|
40366
|
+
return {
|
|
40367
|
+
hits: Array.isArray(r.data.hits) ? r.data.hits : [],
|
|
40368
|
+
gaps: Array.isArray(r.data.gaps) ? r.data.gaps : []
|
|
40369
|
+
};
|
|
40370
|
+
}
|
|
40371
|
+
async readNotepad(key) {
|
|
40372
|
+
this.checkSessionKey(key);
|
|
40373
|
+
const r = await this.post("/runs/session/notepad", { key });
|
|
40374
|
+
const n2 = r.data.notepad;
|
|
40375
|
+
return n2 && typeof n2.text === "string" && typeof n2.updatedAt === "number" ? { text: n2.text, updatedAt: n2.updatedAt } : null;
|
|
40376
|
+
}
|
|
40377
|
+
async writeNotepad(key, gen, text) {
|
|
40378
|
+
this.checkSessionKey(key);
|
|
40379
|
+
if (!GEN_PATTERN.test(gen)) throw new PermanentStoreError(`run ledger: malformed generation`);
|
|
40380
|
+
return this.fenceResult(await this.post("/runs/session/notepad/write", { key, gen, text }));
|
|
40381
|
+
}
|
|
39980
40382
|
async heartbeat(runId3, gen, leaseMs) {
|
|
39981
40383
|
this.checkIds(runId3, gen);
|
|
39982
40384
|
const r = await this.post("/runs/heartbeat", { storeKey: this.opts.storeKey, runId: runId3, gen, leaseMs });
|
|
@@ -42834,7 +43236,7 @@ __export(index_exports, {
|
|
|
42834
43236
|
runBot: () => runBot
|
|
42835
43237
|
});
|
|
42836
43238
|
import { createServer } from "node:http";
|
|
42837
|
-
import { join as
|
|
43239
|
+
import { join as join13 } from "node:path";
|
|
42838
43240
|
async function runBot() {
|
|
42839
43241
|
for (const v of ["SLACK_BOT_TOKEN", "SLACK_APP_TOKEN"]) {
|
|
42840
43242
|
if (!processSecrets.get(v)) {
|
|
@@ -43512,7 +43914,7 @@ var init_index = __esm({
|
|
|
43512
43914
|
init_secrets2();
|
|
43513
43915
|
DATA_DIR = installationPath(OPERATOR_ROOT, "data");
|
|
43514
43916
|
CONFIG_PATH2 = process.env.SWITCHBOARD_CONFIG ?? installationPath(OPERATOR_ROOT, "config/config.yaml");
|
|
43515
|
-
OVERRIDES_PATH = process.env.SWITCHBOARD_OVERRIDES ??
|
|
43917
|
+
OVERRIDES_PATH = process.env.SWITCHBOARD_OVERRIDES ?? join13(DATA_DIR, "overrides.json");
|
|
43516
43918
|
PROCESS_STARTED_AT = systemClock();
|
|
43517
43919
|
sampleProcessMetrics = startProcessMetrics();
|
|
43518
43920
|
INTERRUPTED_WRITE_BUDGET_MS = 1e4;
|
|
@@ -43559,7 +43961,7 @@ init_secrets2();
|
|
|
43559
43961
|
import { Console } from "node:console";
|
|
43560
43962
|
import { existsSync as existsSync14, mkdtempSync, writeFileSync as writeFileSync9 } from "node:fs";
|
|
43561
43963
|
import { tmpdir } from "node:os";
|
|
43562
|
-
import { basename as basename3, join as
|
|
43964
|
+
import { basename as basename3, join as join14 } from "node:path";
|
|
43563
43965
|
var CONFIG_PATH3 = process.env.SWITCHBOARD_CONFIG ?? installationPath(OPERATOR_ROOT, "config/config.yaml");
|
|
43564
43966
|
var DATA_DIR2 = installationPath(OPERATOR_ROOT, "data");
|
|
43565
43967
|
var CLI_CALLER = { kind: "cli", id: CLI_ACTOR.id, actor: CLI_ACTOR };
|
|
@@ -43733,8 +44135,8 @@ var ConsoleIO = class _ConsoleIO {
|
|
|
43733
44135
|
/** The harness's file upload: the bytes written under the attachments dir,
|
|
43734
44136
|
* the lead printed like a reply with the path a person can open. */
|
|
43735
44137
|
async attachFile(file) {
|
|
43736
|
-
this.attachments.dir ??= mkdtempSync(
|
|
43737
|
-
const path =
|
|
44138
|
+
this.attachments.dir ??= mkdtempSync(join14(tmpdir(), "switchboard-attachments-"));
|
|
44139
|
+
const path = join14(this.attachments.dir, basename3(file.name));
|
|
43738
44140
|
writeFileSync9(path, file.bytes);
|
|
43739
44141
|
await this.reply(`${file.lead}
|
|
43740
44142
|
\u{1F4CE} ${file.name} (${file.bytes.byteLength} bytes) \u2192 ${path}`);
|
|
@@ -43815,7 +44217,7 @@ function cliCapabilities(configPath, env, secrets, opts = {}) {
|
|
|
43815
44217
|
}
|
|
43816
44218
|
function wireCli() {
|
|
43817
44219
|
const warn = (m) => console.error(m);
|
|
43818
|
-
const botConfig = bindBotConfig(CONFIG_PATH3,
|
|
44220
|
+
const botConfig = bindBotConfig(CONFIG_PATH3, join14(DATA_DIR2, "cli-overrides.json"));
|
|
43819
44221
|
let loaded;
|
|
43820
44222
|
const bot = () => loaded ??= botConfig().then((config) => ({
|
|
43821
44223
|
config,
|