@coreplane/switchboard 1.217.0 → 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/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 +18 -0
- package/dist/assets/src/core/runEvents.ts +6 -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/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 +357 -92
- 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.`;
|
|
@@ -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 = {
|
|
@@ -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
|
}
|
|
@@ -7824,9 +7856,9 @@ var init_secrets = __esm({
|
|
|
7824
7856
|
import { spawn as spawn4 } from "node:child_process";
|
|
7825
7857
|
import { existsSync as existsSync6, readFileSync as readFileSync7 } from "node:fs";
|
|
7826
7858
|
import { homedir as homedir2 } from "node:os";
|
|
7827
|
-
import { isAbsolute as isAbsolute4, join as
|
|
7859
|
+
import { isAbsolute as isAbsolute4, join as join5, resolve as resolve4 } from "node:path";
|
|
7828
7860
|
function expandDir(path, root = OPERATOR_ROOT.root) {
|
|
7829
|
-
if (path === "~" || path.startsWith("~/")) return
|
|
7861
|
+
if (path === "~" || path.startsWith("~/")) return join5(homedir2(), path.slice(1));
|
|
7830
7862
|
if (isAbsolute4(path)) return path;
|
|
7831
7863
|
return resolve4(root, path);
|
|
7832
7864
|
}
|
|
@@ -7870,7 +7902,7 @@ async function opFieldLabels(source2) {
|
|
|
7870
7902
|
}
|
|
7871
7903
|
function wranglerBin(dir, workspaceRoot, exists = existsSync6) {
|
|
7872
7904
|
for (const base of [dir, workspaceRoot]) {
|
|
7873
|
-
const bin =
|
|
7905
|
+
const bin = join5(base, "node_modules", ".bin", "wrangler");
|
|
7874
7906
|
if (exists(bin)) return bin;
|
|
7875
7907
|
}
|
|
7876
7908
|
return "wrangler";
|
|
@@ -7891,7 +7923,7 @@ var init_secretsHost = __esm({
|
|
|
7891
7923
|
if (source2.kind === "dir") {
|
|
7892
7924
|
const dir = expandDir(source2.path);
|
|
7893
7925
|
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(
|
|
7926
|
+
return { ok: true, present: new Set(names.filter((n2) => existsSync6(join5(dir, n2)))) };
|
|
7895
7927
|
}
|
|
7896
7928
|
const labels = await opFieldLabels(source2);
|
|
7897
7929
|
if (!labels.ok) return labels;
|
|
@@ -7900,7 +7932,7 @@ var init_secretsHost = __esm({
|
|
|
7900
7932
|
put: async (source2, dir, name) => {
|
|
7901
7933
|
let value;
|
|
7902
7934
|
if (source2.kind === "dir") {
|
|
7903
|
-
value = readFileSync7(
|
|
7935
|
+
value = readFileSync7(join5(expandDir(source2.path), name), "utf8");
|
|
7904
7936
|
} else {
|
|
7905
7937
|
const r2 = await spawnCollect("op", ["read", `op://${source2.vault}/${source2.item}/${name}`]);
|
|
7906
7938
|
if (r2.code !== 0) return { code: r2.code, output: `op read exited ${r2.code} \u2014 ${r2.stderr.trim()}` };
|
|
@@ -8114,11 +8146,11 @@ var init_readingDiff = __esm({
|
|
|
8114
8146
|
// ../../src/core/meatProcess.ts
|
|
8115
8147
|
import { spawn as spawn5 } from "node:child_process";
|
|
8116
8148
|
import { accessSync, constants } from "node:fs";
|
|
8117
|
-
import { delimiter, join as
|
|
8149
|
+
import { delimiter, join as join6 } from "node:path";
|
|
8118
8150
|
function meatOnPath(env, binary = MEAT_BINARY) {
|
|
8119
8151
|
return (env.PATH ?? "").split(delimiter).filter((dir) => dir !== "").some((dir) => {
|
|
8120
8152
|
try {
|
|
8121
|
-
accessSync(
|
|
8153
|
+
accessSync(join6(dir, binary), constants.X_OK);
|
|
8122
8154
|
return true;
|
|
8123
8155
|
} catch {
|
|
8124
8156
|
return false;
|
|
@@ -8198,10 +8230,10 @@ import { chmod, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises"
|
|
|
8198
8230
|
import { randomBytes } from "node:crypto";
|
|
8199
8231
|
import { createInterface } from "node:readline/promises";
|
|
8200
8232
|
import { Writable } from "node:stream";
|
|
8201
|
-
import { dirname as dirname5, join as
|
|
8233
|
+
import { dirname as dirname5, join as join7, resolve as resolve5 } from "node:path";
|
|
8202
8234
|
async function readTemplates(root = PACKAGE_ROOT) {
|
|
8203
8235
|
const read = async (key) => {
|
|
8204
|
-
const path =
|
|
8236
|
+
const path = join7(root, TEMPLATE_PATHS[key]);
|
|
8205
8237
|
if (!existsSync7(path)) throw new Error(`${TEMPLATE_PATHS[key]}: no such file under ${root}`);
|
|
8206
8238
|
return readFile(path, "utf8");
|
|
8207
8239
|
};
|
|
@@ -8215,7 +8247,7 @@ function isCheckoutRoot(cwd = process.cwd(), root = PACKAGE_ROOT) {
|
|
|
8215
8247
|
}
|
|
8216
8248
|
}
|
|
8217
8249
|
async function writePlannedFile(file, cwd = process.cwd()) {
|
|
8218
|
-
const path =
|
|
8250
|
+
const path = join7(cwd, file.path);
|
|
8219
8251
|
await mkdir(dirname5(path), { recursive: true });
|
|
8220
8252
|
const temp = `${path}.${randomBytes(6).toString("hex")}.tmp`;
|
|
8221
8253
|
try {
|
|
@@ -8250,7 +8282,7 @@ function ttyPrompter(io = { stdin: process.stdin, stdout: process.stdout }) {
|
|
|
8250
8282
|
};
|
|
8251
8283
|
}
|
|
8252
8284
|
function projectFact(name, root) {
|
|
8253
|
-
const facts2 = JSON.parse(readFileSync8(
|
|
8285
|
+
const facts2 = JSON.parse(readFileSync8(join7(root, "project.json"), "utf8"));
|
|
8254
8286
|
const value = facts2[name];
|
|
8255
8287
|
if (typeof value !== "string") throw new Error(`project.json: no \`${name}\` fact`);
|
|
8256
8288
|
return value;
|
|
@@ -8266,7 +8298,7 @@ function hostSetupIO(cwd = OPERATOR_ROOT.root, startedIn = process.cwd()) {
|
|
|
8266
8298
|
templates: () => readTemplates(),
|
|
8267
8299
|
// Where the files went when that is not where the operator stands — `init` prints it.
|
|
8268
8300
|
root: () => resolve5(cwd) === resolve5(startedIn) ? void 0 : cwd,
|
|
8269
|
-
exists: async (path) => existsSync7(
|
|
8301
|
+
exists: async (path) => existsSync7(join7(cwd, path)),
|
|
8270
8302
|
write: (file) => writePlannedFile(file, cwd),
|
|
8271
8303
|
// A file the operator NAMED (`--github-private-key-file app.pem`) is where the operator stands,
|
|
8272
8304
|
// not in the installation: `init` run from ~/Downloads reads ~/Downloads/app.pem.
|
|
@@ -8322,9 +8354,9 @@ var init_binaryRead = __esm({
|
|
|
8322
8354
|
|
|
8323
8355
|
// ../../src/secrets.ts
|
|
8324
8356
|
import { readFileSync as readFileSync9 } from "node:fs";
|
|
8325
|
-
import { join as
|
|
8357
|
+
import { join as join8 } from "node:path";
|
|
8326
8358
|
function manifestNames() {
|
|
8327
|
-
const raw = JSON.parse(readFileSync9(
|
|
8359
|
+
const raw = JSON.parse(readFileSync9(join8(PACKAGE_ROOT, MANIFEST_PATH), "utf8"));
|
|
8328
8360
|
const parsed = parseManifest2(raw);
|
|
8329
8361
|
if (!parsed.ok) throw new Error(`${MANIFEST_PATH} is invalid \u2014 ${parsed.problems.join("; ")}`);
|
|
8330
8362
|
return parsed.manifest.secrets.map((s) => s.name);
|
|
@@ -12482,7 +12514,7 @@ ${quote(current ?? "")}`;
|
|
|
12482
12514
|
});
|
|
12483
12515
|
|
|
12484
12516
|
// ../../src/core/commands/contract.ts
|
|
12485
|
-
import { basename, dirname as dirname8, join as
|
|
12517
|
+
import { basename, dirname as dirname8, join as join9 } from "node:path";
|
|
12486
12518
|
import { z as z7 } from "zod";
|
|
12487
12519
|
function repoRootOfPlan(planPath) {
|
|
12488
12520
|
const plans = dirname8(planPath);
|
|
@@ -12508,7 +12540,7 @@ var init_contract2 = __esm({
|
|
|
12508
12540
|
init_commandRegistry();
|
|
12509
12541
|
init_contract();
|
|
12510
12542
|
defineCommand4 = commandDefiner();
|
|
12511
|
-
SPECS_DIR =
|
|
12543
|
+
SPECS_DIR = join9("docs", "reference", "specs");
|
|
12512
12544
|
RULES_FILES = ["AGENTS.md", "CLAUDE.md"];
|
|
12513
12545
|
contractRender = defineCommand4({
|
|
12514
12546
|
id: "contract.render",
|
|
@@ -12532,10 +12564,10 @@ var init_contract2 = __esm({
|
|
|
12532
12564
|
const unit = parsePlanUnit(planMarkdown, options.unit);
|
|
12533
12565
|
const specs = /* @__PURE__ */ new Map();
|
|
12534
12566
|
for (const spec of new Set(unit ? specItemRefs(unit.section).map((r) => r.spec) : []))
|
|
12535
|
-
specs.set(spec, await deps.contract.readFile(
|
|
12567
|
+
specs.set(spec, await deps.contract.readFile(join9(root, SPECS_DIR, spec)));
|
|
12536
12568
|
let agentRules;
|
|
12537
12569
|
for (const file of RULES_FILES) {
|
|
12538
|
-
const text = await deps.contract.readFile(
|
|
12570
|
+
const text = await deps.contract.readFile(join9(root, file));
|
|
12539
12571
|
if (text !== void 0) {
|
|
12540
12572
|
agentRules = { file, text };
|
|
12541
12573
|
break;
|
|
@@ -13460,7 +13492,7 @@ function isSetupInstallCommand(summary) {
|
|
|
13460
13492
|
return INSTALL_SEGMENT.test(cmd.replace(QUOTED, '""'));
|
|
13461
13493
|
}
|
|
13462
13494
|
function isNarrative(ev) {
|
|
13463
|
-
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";
|
|
13464
13496
|
}
|
|
13465
13497
|
function spanSet(events) {
|
|
13466
13498
|
const normalized = normalizeSpans(events);
|
|
@@ -15383,6 +15415,7 @@ function isRunEvent(v) {
|
|
|
15383
15415
|
case "input":
|
|
15384
15416
|
case "assistant":
|
|
15385
15417
|
case "context":
|
|
15418
|
+
case "notes":
|
|
15386
15419
|
return typeof o.text === "string";
|
|
15387
15420
|
// the narrative events carry text, not a summary
|
|
15388
15421
|
case "run_meta":
|
|
@@ -17231,7 +17264,11 @@ var init_service = __esm({
|
|
|
17231
17264
|
/** A fresh connect link for a runtime bearer/oauth server (first connect, or a re-key). */
|
|
17232
17265
|
async connect(actor, target, name) {
|
|
17233
17266
|
const { scopeKey, entry, source: source2 } = this.owned(target, name);
|
|
17234
|
-
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
|
+
);
|
|
17235
17272
|
if (entry.tokenEnv)
|
|
17236
17273
|
throw new McpServiceError(
|
|
17237
17274
|
"invalid_input",
|
|
@@ -17708,6 +17745,15 @@ var init_service = __esm({
|
|
|
17708
17745
|
url: r.entry.url,
|
|
17709
17746
|
agents: r.entry.agents ?? [...MCP_SELF_SERVE_AGENTS]
|
|
17710
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
|
+
}
|
|
17711
17757
|
if (r.entry.auth === "none") return { spec: base };
|
|
17712
17758
|
if (r.entry.tokenEnv) {
|
|
17713
17759
|
const token2 = this.opts.bearers.named(r.entry.tokenEnv);
|
|
@@ -17827,7 +17873,7 @@ async function settleConnect(deps, output) {
|
|
|
17827
17873
|
}
|
|
17828
17874
|
}
|
|
17829
17875
|
function renderServerLine(r) {
|
|
17830
|
-
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";
|
|
17831
17877
|
const agents = Array.isArray(r.agents) ? r.agents.join(", ") : "";
|
|
17832
17878
|
const pinned = r.source === "config" ? " \xB7 pinned in config.yaml" : "";
|
|
17833
17879
|
return `\u2022 \`${String(r.name)}\` (${String(r.scope)}) ${state} \u2014 ${String(r.url)} \xB7 agents: ${agents} \xB7 auth: ${String(r.auth)}${pinned}`;
|
|
@@ -21929,6 +21975,8 @@ function activityLine(e) {
|
|
|
21929
21975
|
return "request received";
|
|
21930
21976
|
case "context":
|
|
21931
21977
|
return "context recorded";
|
|
21978
|
+
case "notes":
|
|
21979
|
+
return "\u{1F4DD} notes saved";
|
|
21932
21980
|
case "answer":
|
|
21933
21981
|
return "answer ready";
|
|
21934
21982
|
case "run_meta":
|
|
@@ -26916,6 +26964,134 @@ var init_runs2 = __esm({
|
|
|
26916
26964
|
}
|
|
26917
26965
|
});
|
|
26918
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
|
+
|
|
26919
27095
|
// ../../src/tools/workspace.ts
|
|
26920
27096
|
import { z as z21 } from "zod";
|
|
26921
27097
|
var bashTool, readFileTool, writeFileTool, NAME_STATUS_MARK, NAME_STATUS_MARK_LINE, OUTPUT_CUT_RE, diffDigestTool, submitVerdictTool, submitDispositionsTool, submitPrDescriptionTool, handoffEntry, submitHandoffTool, updateStatusTool, TOOLSETS;
|
|
@@ -26934,6 +27110,7 @@ var init_workspace = __esm({
|
|
|
26934
27110
|
init_skills();
|
|
26935
27111
|
init_attach();
|
|
26936
27112
|
init_runs2();
|
|
27113
|
+
init_session();
|
|
26937
27114
|
bashTool = {
|
|
26938
27115
|
name: "bash",
|
|
26939
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.",
|
|
@@ -27303,7 +27480,8 @@ ${raw.trim()}`;
|
|
|
27303
27480
|
listSkillsTool,
|
|
27304
27481
|
useSkillTool,
|
|
27305
27482
|
...GITHUB_READ_TOOLS,
|
|
27306
|
-
...GITHUB_ISSUE_WRITE_TOOLS
|
|
27483
|
+
...GITHUB_ISSUE_WRITE_TOOLS,
|
|
27484
|
+
...SESSION_TOOLS
|
|
27307
27485
|
],
|
|
27308
27486
|
readonly: [
|
|
27309
27487
|
bashTool,
|
|
@@ -27314,7 +27492,8 @@ ${raw.trim()}`;
|
|
|
27314
27492
|
diffDigestTool,
|
|
27315
27493
|
listSkillsTool,
|
|
27316
27494
|
useSkillTool,
|
|
27317
|
-
...GITHUB_READ_TOOLS
|
|
27495
|
+
...GITHUB_READ_TOOLS,
|
|
27496
|
+
...SESSION_TOOLS
|
|
27318
27497
|
],
|
|
27319
27498
|
web: [webFetchTool, webSearchTool, updateStatusTool, ...GITHUB_READ_TOOLS],
|
|
27320
27499
|
/** The general agent: no workspace, no shell — GitHub reads + issue writes
|
|
@@ -27334,7 +27513,8 @@ ${raw.trim()}`;
|
|
|
27334
27513
|
webSearchTool,
|
|
27335
27514
|
listSkillsTool,
|
|
27336
27515
|
useSkillTool,
|
|
27337
|
-
...GITHUB_READ_TOOLS
|
|
27516
|
+
...GITHUB_READ_TOOLS,
|
|
27517
|
+
...SESSION_TOOLS
|
|
27338
27518
|
],
|
|
27339
27519
|
/** The conductor (docs/reference/specs/agent-conductor.md): the five run
|
|
27340
27520
|
* tools — the only toolset that holds them, so no other preset can start,
|
|
@@ -28212,7 +28392,7 @@ Target repository: ${input.repo}. ` + (workspace ? `Your shell starts in the wor
|
|
|
28212
28392
|
const agentSystem = (head) => {
|
|
28213
28393
|
return [residentSystem ?? agent.system, targetBlock(head), input.contract, blocks.skills, blocks.mcp].filter((b) => Boolean(b)).join("\n\n");
|
|
28214
28394
|
};
|
|
28215
|
-
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");
|
|
28216
28396
|
}
|
|
28217
28397
|
async function settleReviewedHead(input) {
|
|
28218
28398
|
return input.span ? input.span.span("run.settle_reviewed_head", (span) => settle(input, span)) : settle(input, void 0);
|
|
@@ -29055,13 +29235,13 @@ var init_frontmatter = __esm({
|
|
|
29055
29235
|
|
|
29056
29236
|
// ../../src/skills/stores.ts
|
|
29057
29237
|
import { existsSync as existsSync11, readFileSync as readFileSync13, readdirSync as readdirSync2 } from "node:fs";
|
|
29058
|
-
import { join as
|
|
29238
|
+
import { join as join10 } from "node:path";
|
|
29059
29239
|
function loadBundledSkills(dir) {
|
|
29060
29240
|
if (!existsSync11(dir)) return [];
|
|
29061
29241
|
const skills = [];
|
|
29062
29242
|
for (const entry of readdirSync2(dir, { withFileTypes: true })) {
|
|
29063
29243
|
if (!entry.isDirectory()) continue;
|
|
29064
|
-
const file =
|
|
29244
|
+
const file = join10(dir, entry.name, "SKILL.md");
|
|
29065
29245
|
if (!existsSync11(file)) continue;
|
|
29066
29246
|
try {
|
|
29067
29247
|
skills.push(parseSkillMarkdown(readFileSync13(file, "utf8")));
|
|
@@ -29618,6 +29798,20 @@ function mintRunBearer(deps, ctx) {
|
|
|
29618
29798
|
publish: (event) => registry.publish(runId3, event)
|
|
29619
29799
|
});
|
|
29620
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
|
+
}
|
|
29621
29815
|
async function composePrompt(deps, ctx) {
|
|
29622
29816
|
const {
|
|
29623
29817
|
msg,
|
|
@@ -29680,6 +29874,7 @@ async function composePrompt(deps, ctx) {
|
|
|
29680
29874
|
);
|
|
29681
29875
|
const instructionsBlock = customInstructionsBlock(scopes);
|
|
29682
29876
|
const memoryBlock = await root.span("dispatch.compose", () => memoryBlockP);
|
|
29877
|
+
const notesBlock = ctx.session ? sessionNotesBlock(ctx.session) : void 0;
|
|
29683
29878
|
const composeSystem = makeSystemComposer({
|
|
29684
29879
|
agent,
|
|
29685
29880
|
resident: resident === true,
|
|
@@ -29695,6 +29890,7 @@ async function composePrompt(deps, ctx) {
|
|
|
29695
29890
|
...ctx.contract !== void 0 ? { contract: ctx.contract } : {},
|
|
29696
29891
|
blocks: {
|
|
29697
29892
|
memory: memoryBlock,
|
|
29893
|
+
notes: notesBlock,
|
|
29698
29894
|
config: configBlock,
|
|
29699
29895
|
about: aboutBlock,
|
|
29700
29896
|
instructions: instructionsBlock,
|
|
@@ -30872,6 +31068,14 @@ var init_transport = __esm({
|
|
|
30872
31068
|
function relayedTools(tools) {
|
|
30873
31069
|
return tools.filter((t) => !NATIVE_WORKSPACE_TOOLS.has(t.name));
|
|
30874
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
|
+
}
|
|
30875
31079
|
function splitSeed(seed) {
|
|
30876
31080
|
for (let i = seed.length - 1; i >= 0; i--) {
|
|
30877
31081
|
if (seed[i].role === "user") return { session: seed.slice(0, i), prompt: seed[i] };
|
|
@@ -31150,7 +31354,23 @@ async function runPiHarness(deps, run2) {
|
|
|
31150
31354
|
}
|
|
31151
31355
|
}
|
|
31152
31356
|
if (obs.message) await mirror.onMessage(obs.message, bridge.turns);
|
|
31153
|
-
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
|
+
}
|
|
31154
31374
|
if (obs.providerError !== void 0) {
|
|
31155
31375
|
if (catchingUp)
|
|
31156
31376
|
note("harness_error", `a model call failed while the bot was away (${obs.providerError}); continuing`);
|
|
@@ -31738,6 +31958,7 @@ async function runLoop2(deps, ctx) {
|
|
|
31738
31958
|
runs,
|
|
31739
31959
|
steer,
|
|
31740
31960
|
wait,
|
|
31961
|
+
session,
|
|
31741
31962
|
parentRunId,
|
|
31742
31963
|
coordinator,
|
|
31743
31964
|
seed
|
|
@@ -31885,6 +32106,7 @@ async function runLoop2(deps, ctx) {
|
|
|
31885
32106
|
...runs ? { runs } : {},
|
|
31886
32107
|
...steer ? { steer } : {},
|
|
31887
32108
|
...wait ? { wait } : {},
|
|
32109
|
+
...session ? { session } : {},
|
|
31888
32110
|
onVerdict,
|
|
31889
32111
|
onDigest,
|
|
31890
32112
|
onPrDescription,
|
|
@@ -31932,6 +32154,7 @@ async function runLoop2(deps, ctx) {
|
|
|
31932
32154
|
messages,
|
|
31933
32155
|
tools: relayedTools(mergeTools(TOOLSETS[agent.toolset] ?? [], mcpForRun?.tools)),
|
|
31934
32156
|
toolContext,
|
|
32157
|
+
...session ? { notepad: () => session.readNotepad() } : {},
|
|
31935
32158
|
rules: {
|
|
31936
32159
|
checkout: binding?.workspace ?? "/workspace",
|
|
31937
32160
|
...ownBranch !== void 0 ? { branch: ownBranch } : {},
|
|
@@ -33665,24 +33888,6 @@ var init_lineage = __esm({
|
|
|
33665
33888
|
}
|
|
33666
33889
|
});
|
|
33667
33890
|
|
|
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
33891
|
// ../../src/core/dispatch/thread.ts
|
|
33687
33892
|
async function readThread(service, threadKey) {
|
|
33688
33893
|
try {
|
|
@@ -33798,7 +34003,15 @@ async function sessionSeedFor(input) {
|
|
|
33798
34003
|
history: input.history,
|
|
33799
34004
|
request: input.request
|
|
33800
34005
|
});
|
|
33801
|
-
|
|
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 };
|
|
33802
34015
|
}
|
|
33803
34016
|
function withoutThinking(message2) {
|
|
33804
34017
|
if (message2.role !== "assistant") return message2;
|
|
@@ -33806,7 +34019,7 @@ function withoutThinking(message2) {
|
|
|
33806
34019
|
if (content.length === message2.content.length) return message2;
|
|
33807
34020
|
return { ...message2, content: content.length ? content : [{ type: "text", text: "(reasoning omitted)" }] };
|
|
33808
34021
|
}
|
|
33809
|
-
var SEED_BUDGET_TOKENS, SEED_BUDGET_BYTES,
|
|
34022
|
+
var SEED_BUDGET_TOKENS, SEED_BUDGET_BYTES, settledResult;
|
|
33810
34023
|
var init_seed = __esm({
|
|
33811
34024
|
"../../src/core/dispatch/seed.ts"() {
|
|
33812
34025
|
"use strict";
|
|
@@ -33815,7 +34028,6 @@ var init_seed = __esm({
|
|
|
33815
34028
|
init_thread();
|
|
33816
34029
|
SEED_BUDGET_TOKENS = 6e4;
|
|
33817
34030
|
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
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.`;
|
|
33820
34032
|
}
|
|
33821
34033
|
});
|
|
@@ -34289,7 +34501,15 @@ async function dispatch(deps, msg, io, opts = {}) {
|
|
|
34289
34501
|
verifiedAtAttach,
|
|
34290
34502
|
resume,
|
|
34291
34503
|
root,
|
|
34292
|
-
...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
|
+
} : {}
|
|
34293
34513
|
});
|
|
34294
34514
|
const { mcpForRun, composeSystem, system } = prompt;
|
|
34295
34515
|
const reviewHead = prompt.reviewHead;
|
|
@@ -34347,6 +34567,7 @@ async function dispatch(deps, msg, io, opts = {}) {
|
|
|
34347
34567
|
seed,
|
|
34348
34568
|
...session ? { seedLog: session.log } : {}
|
|
34349
34569
|
});
|
|
34570
|
+
const sessionTools = sessionCapabilityFor(ledgerRun, deps.runLedger);
|
|
34350
34571
|
const { spawn: spawn7, runs, steer, wait } = runToolCapabilities(
|
|
34351
34572
|
{ core: deps, dispatch, registry, clock: clock3 },
|
|
34352
34573
|
{
|
|
@@ -34397,6 +34618,7 @@ async function dispatch(deps, msg, io, opts = {}) {
|
|
|
34397
34618
|
runs,
|
|
34398
34619
|
steer,
|
|
34399
34620
|
wait,
|
|
34621
|
+
...sessionTools ? { session: sessionTools } : {},
|
|
34400
34622
|
parentRunId,
|
|
34401
34623
|
coordinator,
|
|
34402
34624
|
seed,
|
|
@@ -34527,6 +34749,7 @@ var init_dispatcher = __esm({
|
|
|
34527
34749
|
init_settle();
|
|
34528
34750
|
init_lineage();
|
|
34529
34751
|
init_seed();
|
|
34752
|
+
init_session();
|
|
34530
34753
|
init_thread();
|
|
34531
34754
|
init_select();
|
|
34532
34755
|
init_spawn();
|
|
@@ -34645,7 +34868,7 @@ var init_runHistoryWriter = __esm({
|
|
|
34645
34868
|
|
|
34646
34869
|
// ../../src/core/runStore.ts
|
|
34647
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";
|
|
34648
|
-
import { join as
|
|
34871
|
+
import { join as join11, resolve as resolve9 } from "node:path";
|
|
34649
34872
|
function isValidRunId(id) {
|
|
34650
34873
|
return typeof id === "string" && RUN_ID_PATTERN.test(id);
|
|
34651
34874
|
}
|
|
@@ -34693,7 +34916,7 @@ function buildRunStore(cfg, secrets, deps) {
|
|
|
34693
34916
|
const now = deps.now ?? Date.now;
|
|
34694
34917
|
const policy = retentionPolicyOf(cfg);
|
|
34695
34918
|
if (cfg.store === "file") {
|
|
34696
|
-
const store = new FileRunStore(
|
|
34919
|
+
const store = new FileRunStore(join11(deps.dataDir, RUNS_DIR), { policy, now });
|
|
34697
34920
|
store.sweep();
|
|
34698
34921
|
const timer = (deps.setInterval ?? setInterval)(() => store.sweep(), SWEEP_INTERVAL_MS);
|
|
34699
34922
|
timer.unref?.();
|
|
@@ -34760,7 +34983,7 @@ var init_runStore = __esm({
|
|
|
34760
34983
|
this.now = opts.now ?? Date.now;
|
|
34761
34984
|
}
|
|
34762
34985
|
recordPath(id) {
|
|
34763
|
-
return
|
|
34986
|
+
return join11(this.dir, `${id}.json`);
|
|
34764
34987
|
}
|
|
34765
34988
|
ensureDir() {
|
|
34766
34989
|
mkdirSync8(this.dir, { recursive: true, mode: DIR_MODE });
|
|
@@ -34782,7 +35005,7 @@ var init_runStore = __esm({
|
|
|
34782
35005
|
}
|
|
34783
35006
|
/** Every valid index line, a repeated id resolved to its last line. */
|
|
34784
35007
|
readIndex() {
|
|
34785
|
-
const path =
|
|
35008
|
+
const path = join11(this.dir, INDEX_FILE);
|
|
34786
35009
|
if (!existsSync12(path)) return [];
|
|
34787
35010
|
const byId = /* @__PURE__ */ new Map();
|
|
34788
35011
|
for (const line3 of readFileSync14(path, "utf8").split("\n")) {
|
|
@@ -34814,7 +35037,7 @@ var init_runStore = __esm({
|
|
|
34814
35037
|
const keptIds = new Set(kept.map((r) => r.id));
|
|
34815
35038
|
for (const item of index) if (!keptIds.has(item.id)) rmSync3(this.recordPath(item.id), { force: true });
|
|
34816
35039
|
this.writeAtomic(
|
|
34817
|
-
|
|
35040
|
+
join11(this.dir, INDEX_FILE),
|
|
34818
35041
|
kept.map((r) => JSON.stringify(r)).join("\n") + (kept.length ? "\n" : "")
|
|
34819
35042
|
);
|
|
34820
35043
|
return kept;
|
|
@@ -35044,7 +35267,10 @@ function createLedgerWriteThrough(opts) {
|
|
|
35044
35267
|
/** The run's place in its session log (session-log item 2); undefined for
|
|
35045
35268
|
* a run without a conversation of its own and for an adopted row claimed
|
|
35046
35269
|
* before the log existed. */
|
|
35047
|
-
|
|
35270
|
+
sessionRow;
|
|
35271
|
+
get session() {
|
|
35272
|
+
return this.sessionRow;
|
|
35273
|
+
}
|
|
35048
35274
|
/** Turns of the run's conversation on the ledger, counted from its seed —
|
|
35049
35275
|
* the last step record's `turnIndex` — so the finish can close the range. */
|
|
35050
35276
|
turnsWritten;
|
|
@@ -35082,11 +35308,11 @@ function createLedgerWriteThrough(opts) {
|
|
|
35082
35308
|
this.stepNo = from.stepNo;
|
|
35083
35309
|
this.lastSeq = from.lastSeq;
|
|
35084
35310
|
this.adopted = from.resumable === true;
|
|
35085
|
-
this.
|
|
35311
|
+
this.sessionRow = from.session;
|
|
35086
35312
|
}
|
|
35087
35313
|
/** A reservation learns its session when the prompt's claim promotes it. */
|
|
35088
35314
|
bindSession(session) {
|
|
35089
|
-
this.
|
|
35315
|
+
this.sessionRow = session;
|
|
35090
35316
|
}
|
|
35091
35317
|
get resumable() {
|
|
35092
35318
|
return !this.detached && (this.seeded || this.adopted);
|
|
@@ -35099,11 +35325,11 @@ function createLedgerWriteThrough(opts) {
|
|
|
35099
35325
|
* ends short of what the model saw, and the next run in the session must
|
|
35100
35326
|
* know. A run that wrote no turn leaves the range open. */
|
|
35101
35327
|
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.
|
|
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 } };
|
|
35107
35333
|
}
|
|
35108
35334
|
sink = {
|
|
35109
35335
|
put: async (plain4) => {
|
|
@@ -35150,11 +35376,11 @@ function createLedgerWriteThrough(opts) {
|
|
|
35150
35376
|
* no record at all was killed before its conversation was stored and
|
|
35151
35377
|
* closes `interrupted`. */
|
|
35152
35378
|
async seed(messages, budgetMs) {
|
|
35153
|
-
const base = this.
|
|
35154
|
-
const reused = this.
|
|
35379
|
+
const base = this.sessionRow?.seedFrom ?? 0;
|
|
35380
|
+
const reused = this.sessionRow && this.sessionRow.range !== "broken" ? this.sessionRow.range.from - this.sessionRow.seedFrom : 0;
|
|
35155
35381
|
const turns = messages.slice(reused).map((message2, i) => ({ idx: base + reused + i, message: message2 }));
|
|
35156
35382
|
try {
|
|
35157
|
-
const seeded = await ledger.seed(this.runId, gen, turns, this.
|
|
35383
|
+
const seeded = await ledger.seed(this.runId, gen, turns, this.sessionRow?.key);
|
|
35158
35384
|
if (!seeded.ok) {
|
|
35159
35385
|
this.detach(`seed refused (${seeded.reason})`);
|
|
35160
35386
|
return;
|
|
@@ -35174,7 +35400,7 @@ function createLedgerWriteThrough(opts) {
|
|
|
35174
35400
|
iteration: 0
|
|
35175
35401
|
},
|
|
35176
35402
|
[],
|
|
35177
|
-
this.
|
|
35403
|
+
this.sessionRow?.key
|
|
35178
35404
|
);
|
|
35179
35405
|
if (!recorded.ok) this.detach(`seed record refused (${recorded.reason})`);
|
|
35180
35406
|
else this.seeded = true;
|
|
@@ -35184,7 +35410,7 @@ function createLedgerWriteThrough(opts) {
|
|
|
35184
35410
|
}
|
|
35185
35411
|
async step(report2) {
|
|
35186
35412
|
if (this.detached) return;
|
|
35187
|
-
const base = this.
|
|
35413
|
+
const base = this.sessionRow?.seedFrom ?? 0;
|
|
35188
35414
|
const turns = report2.turns.map((message2, i) => ({ idx: base + report2.firstIdx + i, message: message2 }));
|
|
35189
35415
|
if (report2.compaction)
|
|
35190
35416
|
turns.push({ idx: base + report2.firstIdx + report2.turns.length, compaction: report2.compaction });
|
|
@@ -35200,7 +35426,7 @@ function createLedgerWriteThrough(opts) {
|
|
|
35200
35426
|
};
|
|
35201
35427
|
for (let attempt = 1; ; attempt++) {
|
|
35202
35428
|
try {
|
|
35203
|
-
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);
|
|
35204
35430
|
if (!result.ok) this.detach(`step ${record2.step} refused (${result.reason})`);
|
|
35205
35431
|
else this.turnsWritten = record2.turnIndex;
|
|
35206
35432
|
return;
|
|
@@ -35386,6 +35612,10 @@ function createLedgerWriteThrough(opts) {
|
|
|
35386
35612
|
}
|
|
35387
35613
|
},
|
|
35388
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),
|
|
35389
35619
|
async handoff() {
|
|
35390
35620
|
const candidates = [...live].filter((r) => r.resumable && r.tracked() && !r.handedOff);
|
|
35391
35621
|
if (candidates.length === 0) return { marked: [] };
|
|
@@ -35439,6 +35669,18 @@ var init_writeThrough = __esm({
|
|
|
35439
35669
|
async readSessionTail(_key, _maxBytes) {
|
|
35440
35670
|
return { from: 0, transcript: { complete: true, turns: 0, messages: [], compactions: [] } };
|
|
35441
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
|
+
}
|
|
35442
35684
|
async handoff() {
|
|
35443
35685
|
return { marked: [] };
|
|
35444
35686
|
}
|
|
@@ -35452,6 +35694,7 @@ var init_writeThrough = __esm({
|
|
|
35452
35694
|
sink;
|
|
35453
35695
|
resumable = false;
|
|
35454
35696
|
handedOff = false;
|
|
35697
|
+
session = void 0;
|
|
35455
35698
|
tracked() {
|
|
35456
35699
|
return false;
|
|
35457
35700
|
}
|
|
@@ -35931,7 +36174,10 @@ function httpMcpClientFactory(fetchImpl) {
|
|
|
35931
36174
|
return (server) => new StreamableHttpMcpClient({
|
|
35932
36175
|
url: server.url,
|
|
35933
36176
|
fetch: fetchImpl,
|
|
35934
|
-
|
|
36177
|
+
headers: {
|
|
36178
|
+
...server.headers,
|
|
36179
|
+
...server.auth?.type === "bearer" ? { authorization: `Bearer ${server.auth.token}` } : {}
|
|
36180
|
+
}
|
|
35935
36181
|
});
|
|
35936
36182
|
}
|
|
35937
36183
|
var init_mcp2 = __esm({
|
|
@@ -38760,18 +39006,18 @@ var init_liveView = __esm({
|
|
|
38760
39006
|
|
|
38761
39007
|
// ../../src/channels/webAssets.ts
|
|
38762
39008
|
import { readdirSync as readdirSync3, readFileSync as readFileSync16, statSync as statSync3 } from "node:fs";
|
|
38763
|
-
import { join as
|
|
39009
|
+
import { join as join12 } from "node:path";
|
|
38764
39010
|
function contentTypeFor2(path) {
|
|
38765
39011
|
const dot = path.lastIndexOf(".");
|
|
38766
39012
|
return (dot === -1 ? void 0 : CONTENT_TYPES[path.slice(dot)]) ?? "application/octet-stream";
|
|
38767
39013
|
}
|
|
38768
39014
|
function webDistDir(env, packageRoot) {
|
|
38769
|
-
return env.SWITCHBOARD_WEB_DIST ??
|
|
39015
|
+
return env.SWITCHBOARD_WEB_DIST ?? join12(packageRoot, WEB_DIST_DIR);
|
|
38770
39016
|
}
|
|
38771
39017
|
function loadWebAssets(distDir) {
|
|
38772
39018
|
let manifest;
|
|
38773
39019
|
try {
|
|
38774
|
-
manifest = JSON.parse(readFileSync16(
|
|
39020
|
+
manifest = JSON.parse(readFileSync16(join12(distDir, ".vite", "manifest.json"), "utf8"));
|
|
38775
39021
|
} catch (err2) {
|
|
38776
39022
|
throw new Error(
|
|
38777
39023
|
`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 +39031,9 @@ function loadWebAssets(distDir) {
|
|
|
38785
39031
|
css: (entryChunk.css ?? []).map((f) => `/${f}`)
|
|
38786
39032
|
};
|
|
38787
39033
|
const files = /* @__PURE__ */ new Map();
|
|
38788
|
-
const assetsDir =
|
|
39034
|
+
const assetsDir = join12(distDir, "assets");
|
|
38789
39035
|
for (const name of readdirSync3(assetsDir)) {
|
|
38790
|
-
const full =
|
|
39036
|
+
const full = join12(assetsDir, name);
|
|
38791
39037
|
if (statSync3(full).isFile()) files.set(`/assets/${name}`, readFileSync16(full));
|
|
38792
39038
|
}
|
|
38793
39039
|
const serve2 = (req, res) => {
|
|
@@ -39977,6 +40223,25 @@ var init_runLedgerWorker = __esm({
|
|
|
39977
40223
|
)
|
|
39978
40224
|
};
|
|
39979
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
|
+
}
|
|
39980
40245
|
async heartbeat(runId3, gen, leaseMs) {
|
|
39981
40246
|
this.checkIds(runId3, gen);
|
|
39982
40247
|
const r = await this.post("/runs/heartbeat", { storeKey: this.opts.storeKey, runId: runId3, gen, leaseMs });
|
|
@@ -42834,7 +43099,7 @@ __export(index_exports, {
|
|
|
42834
43099
|
runBot: () => runBot
|
|
42835
43100
|
});
|
|
42836
43101
|
import { createServer } from "node:http";
|
|
42837
|
-
import { join as
|
|
43102
|
+
import { join as join13 } from "node:path";
|
|
42838
43103
|
async function runBot() {
|
|
42839
43104
|
for (const v of ["SLACK_BOT_TOKEN", "SLACK_APP_TOKEN"]) {
|
|
42840
43105
|
if (!processSecrets.get(v)) {
|
|
@@ -43512,7 +43777,7 @@ var init_index = __esm({
|
|
|
43512
43777
|
init_secrets2();
|
|
43513
43778
|
DATA_DIR = installationPath(OPERATOR_ROOT, "data");
|
|
43514
43779
|
CONFIG_PATH2 = process.env.SWITCHBOARD_CONFIG ?? installationPath(OPERATOR_ROOT, "config/config.yaml");
|
|
43515
|
-
OVERRIDES_PATH = process.env.SWITCHBOARD_OVERRIDES ??
|
|
43780
|
+
OVERRIDES_PATH = process.env.SWITCHBOARD_OVERRIDES ?? join13(DATA_DIR, "overrides.json");
|
|
43516
43781
|
PROCESS_STARTED_AT = systemClock();
|
|
43517
43782
|
sampleProcessMetrics = startProcessMetrics();
|
|
43518
43783
|
INTERRUPTED_WRITE_BUDGET_MS = 1e4;
|
|
@@ -43559,7 +43824,7 @@ init_secrets2();
|
|
|
43559
43824
|
import { Console } from "node:console";
|
|
43560
43825
|
import { existsSync as existsSync14, mkdtempSync, writeFileSync as writeFileSync9 } from "node:fs";
|
|
43561
43826
|
import { tmpdir } from "node:os";
|
|
43562
|
-
import { basename as basename3, join as
|
|
43827
|
+
import { basename as basename3, join as join14 } from "node:path";
|
|
43563
43828
|
var CONFIG_PATH3 = process.env.SWITCHBOARD_CONFIG ?? installationPath(OPERATOR_ROOT, "config/config.yaml");
|
|
43564
43829
|
var DATA_DIR2 = installationPath(OPERATOR_ROOT, "data");
|
|
43565
43830
|
var CLI_CALLER = { kind: "cli", id: CLI_ACTOR.id, actor: CLI_ACTOR };
|
|
@@ -43733,8 +43998,8 @@ var ConsoleIO = class _ConsoleIO {
|
|
|
43733
43998
|
/** The harness's file upload: the bytes written under the attachments dir,
|
|
43734
43999
|
* the lead printed like a reply with the path a person can open. */
|
|
43735
44000
|
async attachFile(file) {
|
|
43736
|
-
this.attachments.dir ??= mkdtempSync(
|
|
43737
|
-
const path =
|
|
44001
|
+
this.attachments.dir ??= mkdtempSync(join14(tmpdir(), "switchboard-attachments-"));
|
|
44002
|
+
const path = join14(this.attachments.dir, basename3(file.name));
|
|
43738
44003
|
writeFileSync9(path, file.bytes);
|
|
43739
44004
|
await this.reply(`${file.lead}
|
|
43740
44005
|
\u{1F4CE} ${file.name} (${file.bytes.byteLength} bytes) \u2192 ${path}`);
|
|
@@ -43815,7 +44080,7 @@ function cliCapabilities(configPath, env, secrets, opts = {}) {
|
|
|
43815
44080
|
}
|
|
43816
44081
|
function wireCli() {
|
|
43817
44082
|
const warn = (m) => console.error(m);
|
|
43818
|
-
const botConfig = bindBotConfig(CONFIG_PATH3,
|
|
44083
|
+
const botConfig = bindBotConfig(CONFIG_PATH3, join14(DATA_DIR2, "cli-overrides.json"));
|
|
43819
44084
|
let loaded;
|
|
43820
44085
|
const bot = () => loaded ??= botConfig().then((config) => ({
|
|
43821
44086
|
config,
|