@coreplane/switchboard 1.224.0 → 1.226.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 +5 -1
- package/dist/assets/deploy/cloudflare-resident/Dockerfile +47 -12
- package/dist/assets/deploy/cloudflare-sandbox/Dockerfile +28 -9
- package/dist/assets/package-lock.json +980 -4
- package/dist/assets/package.json +2 -1
- package/dist/assets/source.json +3 -3
- package/dist/assets/src/agents/registry.ts +1 -1
- package/dist/assets/src/core/chatMessage.ts +77 -0
- package/dist/assets/src/core/provider.ts +111 -0
- package/dist/assets/src/core/reviewVerdict.ts +1 -1
- package/dist/assets/src/core/runEvents.ts +22 -3
- package/dist/assets/src/core/runLedger/sessionLog.ts +1 -1
- package/dist/assets/src/core/runLedger/transcript.ts +1 -1
- package/dist/assets/src/core/runLedger/types.ts +2 -1
- package/dist/assets/web/dist/.vite/manifest.json +20 -20
- package/dist/assets/web/dist/assets/CostsPage-5pl3HB2F.js +2 -0
- package/dist/assets/web/dist/assets/{ResidentDetailPage-BXwWQRJt.js → ResidentDetailPage-BBOpejGX.js} +1 -1
- package/dist/assets/web/dist/assets/{ResidentsIndexPage-CwVN-3tl.js → ResidentsIndexPage-B8kFWHpB.js} +1 -1
- package/dist/assets/web/dist/assets/RunRoutePage-COWeVtIE.js +12 -0
- package/dist/assets/web/dist/assets/{RunsIndexPage-Tx5P767G.js → RunsIndexPage-BjH93cKx.js} +1 -1
- package/dist/assets/web/dist/assets/{ScheduledPage-C80zFu3G.js → ScheduledPage-grlNKvCA.js} +1 -1
- package/dist/assets/web/dist/assets/{StatusDot-BaGkjYs1.js → StatusDot-BQNoaXO6.js} +1 -1
- package/dist/assets/web/dist/assets/{Tooltip-BL-Cwe59.js → Tooltip-DVCeIzaa.js} +1 -1
- package/dist/assets/web/dist/assets/{dist-DZgVjemt.js → dist-B5Wfk-oY.js} +1 -1
- package/dist/assets/web/dist/assets/{main-BB6FMkeU.js → main-CN0U7d6s.js} +2 -2
- package/dist/assets/web/dist/assets/main-xLAsdkfB.css +1 -0
- package/dist/cli.js +1509 -953
- package/package.json +2 -1
- package/dist/assets/src/providers/types.ts +0 -158
- package/dist/assets/web/dist/assets/CostsPage-C-ximW1N.js +0 -2
- package/dist/assets/web/dist/assets/RunRoutePage-C0-RUb8o.js +0 -12
- package/dist/assets/web/dist/assets/main-43n8Y5qO.css +0 -1
package/dist/cli.js
CHANGED
|
@@ -11448,6 +11448,22 @@ var init_config3 = __esm({
|
|
|
11448
11448
|
}
|
|
11449
11449
|
});
|
|
11450
11450
|
|
|
11451
|
+
// ../../src/core/provider.ts
|
|
11452
|
+
function parseModelRef(ref) {
|
|
11453
|
+
const i = ref.indexOf("/");
|
|
11454
|
+
if (i === -1) {
|
|
11455
|
+
throw new Error(`Model "${ref}" must be qualified as "<provider>/<model>", e.g. "anthropic/claude-opus-5"`);
|
|
11456
|
+
}
|
|
11457
|
+
return { provider: ref.slice(0, i), model: ref.slice(i + 1) };
|
|
11458
|
+
}
|
|
11459
|
+
var ANTHROPIC_API_KEY_ENV;
|
|
11460
|
+
var init_provider = __esm({
|
|
11461
|
+
"../../src/core/provider.ts"() {
|
|
11462
|
+
"use strict";
|
|
11463
|
+
ANTHROPIC_API_KEY_ENV = "ANTHROPIC_API_KEY";
|
|
11464
|
+
}
|
|
11465
|
+
});
|
|
11466
|
+
|
|
11451
11467
|
// ../../src/providers/anthropic.ts
|
|
11452
11468
|
import Anthropic from "@anthropic-ai/sdk";
|
|
11453
11469
|
function anthropicApiKey(providers, secrets) {
|
|
@@ -11568,12 +11584,12 @@ function usageFromAnthropic(u) {
|
|
|
11568
11584
|
if (typeof o.cache_creation_input_tokens === "number") usage.cacheWriteTokens = o.cache_creation_input_tokens;
|
|
11569
11585
|
return usage;
|
|
11570
11586
|
}
|
|
11571
|
-
var
|
|
11587
|
+
var AnthropicProvider, EPHEMERAL, CACHEABLE_BLOCKS;
|
|
11572
11588
|
var init_anthropic = __esm({
|
|
11573
11589
|
"../../src/providers/anthropic.ts"() {
|
|
11574
11590
|
"use strict";
|
|
11575
11591
|
init_secrets2();
|
|
11576
|
-
|
|
11592
|
+
init_provider();
|
|
11577
11593
|
AnthropicProvider = class {
|
|
11578
11594
|
name;
|
|
11579
11595
|
client;
|
|
@@ -11671,6 +11687,8 @@ function parseCostsConfig(raw) {
|
|
|
11671
11687
|
const r = raw;
|
|
11672
11688
|
if (typeof r.cloudflareAccountId !== "string" || !r.cloudflareAccountId)
|
|
11673
11689
|
throw new Error("costs.cloudflareAccountId is required");
|
|
11690
|
+
if (r.cloudflareAccountName !== void 0 && typeof r.cloudflareAccountName !== "string")
|
|
11691
|
+
throw new Error("costs.cloudflareAccountName must be a string when given");
|
|
11674
11692
|
if (!r.groups || typeof r.groups !== "object")
|
|
11675
11693
|
throw new Error("costs.groups must be a mapping of group \u2192 { workers, containerApps }");
|
|
11676
11694
|
const groups = {};
|
|
@@ -11693,6 +11711,7 @@ function parseCostsConfig(raw) {
|
|
|
11693
11711
|
}
|
|
11694
11712
|
return {
|
|
11695
11713
|
cloudflareAccountId: r.cloudflareAccountId,
|
|
11714
|
+
...typeof r.cloudflareAccountName === "string" && r.cloudflareAccountName ? { cloudflareAccountName: r.cloudflareAccountName } : {},
|
|
11696
11715
|
cloudflareTokenEnv: typeof r.cloudflareTokenEnv === "string" ? r.cloudflareTokenEnv : DEFAULT_CF_TOKEN_ENV,
|
|
11697
11716
|
anthropicAdminKeyEnv: typeof r.anthropicAdminKeyEnv === "string" ? r.anthropicAdminKeyEnv : DEFAULT_ANTHROPIC_ADMIN_ENV,
|
|
11698
11717
|
groups
|
|
@@ -11772,7 +11791,7 @@ function eachDay(range) {
|
|
|
11772
11791
|
for (let d = range.from; d <= range.to; d = addDays2(d, 1)) out.push(d);
|
|
11773
11792
|
return out;
|
|
11774
11793
|
}
|
|
11775
|
-
function buildCostReport(group, cfg, usage, llm, range) {
|
|
11794
|
+
function buildCostReport(group, cfg, usage, llm, range, meta) {
|
|
11776
11795
|
const attribution = attributionOf(cfg, usage);
|
|
11777
11796
|
const workers = new Set(cfg.workers);
|
|
11778
11797
|
const namespaces = attribution.durableObjectNamespaces;
|
|
@@ -11890,7 +11909,12 @@ function buildCostReport(group, cfg, usage, llm, range) {
|
|
|
11890
11909
|
llmAvailable: llm !== null && !!cfg.anthropicWorkspaceId,
|
|
11891
11910
|
days,
|
|
11892
11911
|
totals: { cloudUsd, llmUsd, total: cloudUsd + llmUsd, byResource },
|
|
11893
|
-
|
|
11912
|
+
generatedAt: meta.generatedAt ?? systemClock(),
|
|
11913
|
+
account: {
|
|
11914
|
+
id: meta.accountId,
|
|
11915
|
+
...meta.accountName ? { name: meta.accountName } : {},
|
|
11916
|
+
cloudUsd: accountCloudUsd
|
|
11917
|
+
},
|
|
11894
11918
|
attribution
|
|
11895
11919
|
};
|
|
11896
11920
|
}
|
|
@@ -11931,9 +11955,14 @@ function createCostsService(cfg, cloudflare, llm, now = () => new Date(systemClo
|
|
|
11931
11955
|
async report(group, daysParam) {
|
|
11932
11956
|
const g = cfg.groups[group];
|
|
11933
11957
|
if (!g) throw new Error(`unknown cost group ${group}`);
|
|
11934
|
-
const
|
|
11958
|
+
const at = now();
|
|
11959
|
+
const range = resolveRange(daysParam, at);
|
|
11935
11960
|
const [usage, llmRows] = await Promise.all([cloudflare.fetchUsage(range), llm.fetchDailyCost(range)]);
|
|
11936
|
-
return buildCostReport(group, g, usage, llmRows, range
|
|
11961
|
+
return buildCostReport(group, g, usage, llmRows, range, {
|
|
11962
|
+
accountId: cfg.cloudflareAccountId,
|
|
11963
|
+
accountName: cfg.cloudflareAccountName,
|
|
11964
|
+
generatedAt: at.getTime()
|
|
11965
|
+
});
|
|
11937
11966
|
}
|
|
11938
11967
|
};
|
|
11939
11968
|
}
|
|
@@ -15906,7 +15935,7 @@ function isRunEvent(v) {
|
|
|
15906
15935
|
case "skill_use":
|
|
15907
15936
|
return typeof o.skill === "string" && typeof o.agent === "string" && typeof o.bodyBytes === "number";
|
|
15908
15937
|
case "artifact":
|
|
15909
|
-
return (o.direction === "in" || o.direction === "out") && typeof o.key === "string" && typeof o.name === "string" && typeof o.size === "number" && typeof o.contentType === "string" && o.url === void 0;
|
|
15938
|
+
return (o.direction === "in" && typeof o.messageId === "string" || o.direction === "out" && typeof o.callId === "string") && typeof o.key === "string" && typeof o.name === "string" && typeof o.size === "number" && typeof o.contentType === "string" && o.url === void 0;
|
|
15910
15939
|
case "review_artifact":
|
|
15911
15940
|
if (o.artifact === "pr_description") {
|
|
15912
15941
|
return (o.origin === "submitted" || o.origin === "parsed") && typeof o.repo === "string" && typeof o.pr === "number" && typeof o.title === "string" && typeof o.body === "string" && Array.isArray(o.tour);
|
|
@@ -18065,23 +18094,31 @@ var init_service = __esm({
|
|
|
18065
18094
|
callbackUrl() {
|
|
18066
18095
|
return `${(this.opts.publicBaseUrl ?? "").replace(/\/+$/, "")}/mcp/oauth/callback`;
|
|
18067
18096
|
}
|
|
18068
|
-
/**
|
|
18097
|
+
/** Refreshes by server id, keyed by the expiry of the credential that
|
|
18098
|
+
* triggered them, so N concurrent runs refresh once. A settled entry stays:
|
|
18099
|
+
* a run whose credential read raced the store write joins it instead of
|
|
18100
|
+
* refreshing again. A failed refresh is dropped so the next run retries; a
|
|
18101
|
+
* newer expiry (the refreshed set aging into the skew) starts a new one. */
|
|
18069
18102
|
refreshing = /* @__PURE__ */ new Map();
|
|
18070
18103
|
/** A usable access token for the run: the stored one while it lives, else
|
|
18071
18104
|
* a refreshed set (stored back, the cached client dropped so it is rebuilt
|
|
18072
18105
|
* with the new header). */
|
|
18073
18106
|
freshOAuth(serverId, cred) {
|
|
18074
18107
|
if (!needsRefresh(cred, this.now())) return Promise.resolve(cred);
|
|
18075
|
-
|
|
18076
|
-
|
|
18077
|
-
|
|
18078
|
-
|
|
18079
|
-
|
|
18080
|
-
|
|
18081
|
-
|
|
18082
|
-
|
|
18083
|
-
}
|
|
18084
|
-
|
|
18108
|
+
const seed = cred.expiresAt ?? 0;
|
|
18109
|
+
const hit = this.refreshing.get(serverId);
|
|
18110
|
+
if (hit && hit.seed >= seed) return hit.done;
|
|
18111
|
+
const done = (async () => {
|
|
18112
|
+
const next = await refreshCredential(this.opts.fetch, cred, this.now());
|
|
18113
|
+
await this.storeOAuth(serverId, next);
|
|
18114
|
+
return next;
|
|
18115
|
+
})();
|
|
18116
|
+
const entry = { seed, done };
|
|
18117
|
+
this.refreshing.set(serverId, entry);
|
|
18118
|
+
done.catch(() => {
|
|
18119
|
+
if (this.refreshing.get(serverId) === entry) this.refreshing.delete(serverId);
|
|
18120
|
+
});
|
|
18121
|
+
return done;
|
|
18085
18122
|
}
|
|
18086
18123
|
// ---- the run-time view ----------------------------------------------------------
|
|
18087
18124
|
/** Servers a run may use: every tier's entries whose agents include the
|
|
@@ -18527,40 +18564,6 @@ var init_mcp = __esm({
|
|
|
18527
18564
|
}
|
|
18528
18565
|
});
|
|
18529
18566
|
|
|
18530
|
-
// ../../src/providers/types.ts
|
|
18531
|
-
function toolResultText(content) {
|
|
18532
|
-
if (typeof content === "string") return content;
|
|
18533
|
-
return content.map((p) => {
|
|
18534
|
-
if (p.type === "text") return p.text;
|
|
18535
|
-
if (p.type === "image") return `[image ${p.mediaType}, ${base64Bytes(p.data)} bytes]`;
|
|
18536
|
-
return `[document ${p.name ?? "document"} (${p.mediaType}), ${base64Bytes(p.data)} bytes]`;
|
|
18537
|
-
}).join("\n");
|
|
18538
|
-
}
|
|
18539
|
-
function capToolResultContent(content, cap = MAX_TOOL_RESULT_CHARS) {
|
|
18540
|
-
const capText = (text) => text.length > cap ? `${text.slice(0, cap)}
|
|
18541
|
-
\u2026[tool result truncated: ${text.length - cap} of ${text.length} characters cut \u2014 ask for a narrower slice]` : text;
|
|
18542
|
-
if (typeof content === "string") return capText(content);
|
|
18543
|
-
return content.map((p) => p.type === "text" ? { ...p, text: capText(p.text) } : p);
|
|
18544
|
-
}
|
|
18545
|
-
function base64Bytes(b64) {
|
|
18546
|
-
const padding = b64.endsWith("==") ? 2 : b64.endsWith("=") ? 1 : 0;
|
|
18547
|
-
return Math.floor(b64.length * 3 / 4) - padding;
|
|
18548
|
-
}
|
|
18549
|
-
function parseModelRef(ref) {
|
|
18550
|
-
const i = ref.indexOf("/");
|
|
18551
|
-
if (i === -1) {
|
|
18552
|
-
throw new Error(`Model "${ref}" must be qualified as "<provider>/<model>", e.g. "anthropic/claude-opus-5"`);
|
|
18553
|
-
}
|
|
18554
|
-
return { provider: ref.slice(0, i), model: ref.slice(i + 1) };
|
|
18555
|
-
}
|
|
18556
|
-
var MAX_TOOL_RESULT_CHARS;
|
|
18557
|
-
var init_types3 = __esm({
|
|
18558
|
-
"../../src/providers/types.ts"() {
|
|
18559
|
-
"use strict";
|
|
18560
|
-
MAX_TOOL_RESULT_CHARS = 12e4;
|
|
18561
|
-
}
|
|
18562
|
-
});
|
|
18563
|
-
|
|
18564
18567
|
// ../../src/core/memory/scorer.ts
|
|
18565
18568
|
function tokenize2(text) {
|
|
18566
18569
|
return text.toLowerCase().match(/[a-z0-9]+/g) ?? [];
|
|
@@ -18827,7 +18830,7 @@ var init_authz = __esm({
|
|
|
18827
18830
|
});
|
|
18828
18831
|
|
|
18829
18832
|
// ../../src/core/llmOutput/types.ts
|
|
18830
|
-
var
|
|
18833
|
+
var init_types3 = __esm({
|
|
18831
18834
|
"../../src/core/llmOutput/types.ts"() {
|
|
18832
18835
|
"use strict";
|
|
18833
18836
|
}
|
|
@@ -18886,10 +18889,26 @@ var init_markdown = __esm({
|
|
|
18886
18889
|
});
|
|
18887
18890
|
|
|
18888
18891
|
// ../../src/core/llmOutput/json.ts
|
|
18889
|
-
function
|
|
18890
|
-
const
|
|
18891
|
-
|
|
18892
|
-
|
|
18892
|
+
function extractJson(text) {
|
|
18893
|
+
const start3 = text.search(/[[{]/);
|
|
18894
|
+
if (start3 === -1) return text.trim();
|
|
18895
|
+
let depth = 0;
|
|
18896
|
+
let inString = false;
|
|
18897
|
+
for (let i = start3; i < text.length; i++) {
|
|
18898
|
+
const ch = text[i];
|
|
18899
|
+
if (inString) {
|
|
18900
|
+
if (ch === "\\") i++;
|
|
18901
|
+
else if (ch === '"') inString = false;
|
|
18902
|
+
} else if (ch === '"') {
|
|
18903
|
+
inString = true;
|
|
18904
|
+
} else if (ch === "{" || ch === "[") {
|
|
18905
|
+
depth++;
|
|
18906
|
+
} else if (ch === "}" || ch === "]") {
|
|
18907
|
+
depth--;
|
|
18908
|
+
if (depth === 0) return text.slice(start3, i + 1);
|
|
18909
|
+
}
|
|
18910
|
+
}
|
|
18911
|
+
return text.slice(start3);
|
|
18893
18912
|
}
|
|
18894
18913
|
function jsonValueEquals(a, b) {
|
|
18895
18914
|
if (Object.is(a, b)) return true;
|
|
@@ -18909,7 +18928,7 @@ function jsonOutput(schema, opts = {}) {
|
|
|
18909
18928
|
parse(raw) {
|
|
18910
18929
|
let value;
|
|
18911
18930
|
try {
|
|
18912
|
-
value = JSON.parse(
|
|
18931
|
+
value = JSON.parse(extractJson(raw));
|
|
18913
18932
|
} catch (err2) {
|
|
18914
18933
|
const observed = `not valid JSON: ${err2 instanceof Error ? err2.message : String(err2)}`;
|
|
18915
18934
|
return { ok: false, failure: { kind: "syntax", observed } };
|
|
@@ -18942,7 +18961,7 @@ var init_json = __esm({
|
|
|
18942
18961
|
var init_llmOutput = __esm({
|
|
18943
18962
|
"../../src/core/llmOutput/index.ts"() {
|
|
18944
18963
|
"use strict";
|
|
18945
|
-
|
|
18964
|
+
init_types3();
|
|
18946
18965
|
init_markdown();
|
|
18947
18966
|
init_json();
|
|
18948
18967
|
}
|
|
@@ -19071,6 +19090,8 @@ function placeCandidate(cand, actor, origin, keys, scopeOf) {
|
|
|
19071
19090
|
async function reflect(deps) {
|
|
19072
19091
|
const warn = deps.onWarn ?? (() => {
|
|
19073
19092
|
});
|
|
19093
|
+
const info = deps.onInfo ?? (() => {
|
|
19094
|
+
});
|
|
19074
19095
|
try {
|
|
19075
19096
|
const query = `${deps.request} ${deps.answer}`.slice(0, MAX_RETRIEVE_QUERY_CHARS);
|
|
19076
19097
|
const existing = (await Promise.all(
|
|
@@ -19088,11 +19109,18 @@ async function reflect(deps) {
|
|
|
19088
19109
|
const reply = result.content.filter((p) => p.type === "text").map((p) => p.text).join("");
|
|
19089
19110
|
const prov = { sourceThreadKey: deps.sourceThreadKey, sourceRunId: deps.sourceRunId };
|
|
19090
19111
|
const parsed = parseReflection(reply, prov, new Set(existing.map((r) => r.id)));
|
|
19112
|
+
const truncated = result.stopReason === "max_tokens" ? `truncated at ${REFLECTION_MAX_TOKENS} max tokens` : void 0;
|
|
19113
|
+
const stopNote = truncated ?? (result.stopReason === "end_turn" ? void 0 : `stop: ${result.stopReason}`);
|
|
19091
19114
|
if (!parsed.ok) {
|
|
19092
|
-
|
|
19115
|
+
const why = truncated ?? (stopNote ? `${parsed.error}; ${stopNote}` : parsed.error);
|
|
19116
|
+
warn(`reflection output rejected (${why}); nothing written`);
|
|
19117
|
+
return;
|
|
19118
|
+
}
|
|
19119
|
+
const outcomeTail = stopNote ? ` (${stopNote})` : "";
|
|
19120
|
+
if (parsed.candidates.length === 0) {
|
|
19121
|
+
info(`reflection: nothing to write${outcomeTail}`);
|
|
19093
19122
|
return;
|
|
19094
19123
|
}
|
|
19095
|
-
if (parsed.candidates.length === 0) return;
|
|
19096
19124
|
const scopeOf = new Map(existing.map((r) => [r.id, r.scopeKey]));
|
|
19097
19125
|
const origin = deps.originChannelVisibility ?? "unknown";
|
|
19098
19126
|
const byScope = /* @__PURE__ */ new Map();
|
|
@@ -19121,6 +19149,10 @@ async function reflect(deps) {
|
|
|
19121
19149
|
if (dropped.length > 0)
|
|
19122
19150
|
warn(`write denied by policy, ${dropped.length} candidate(s) dropped: ${dropped.join(", ")}`);
|
|
19123
19151
|
for (const [scopeKey, records] of byScope) await deps.store.write(scopeKey, records);
|
|
19152
|
+
const written = [...byScope.values()].flat();
|
|
19153
|
+
const facts2 = written.filter((r) => r.kind === "fact").length;
|
|
19154
|
+
const summary = written.some((r) => r.kind === "summary") ? ", summary" : "";
|
|
19155
|
+
info(`reflection wrote ${facts2} fact(s)${summary}${outcomeTail}`);
|
|
19124
19156
|
} catch (err2) {
|
|
19125
19157
|
warn(`reflection failed: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
19126
19158
|
}
|
|
@@ -19332,6 +19364,7 @@ var init_buildStore2 = __esm({
|
|
|
19332
19364
|
function scheduleReflection(input) {
|
|
19333
19365
|
if (!input.cfg?.enabled || !shouldReflect(input.gate)) return;
|
|
19334
19366
|
const warn = (m) => console.warn(`[memory] ${input.threadKey} ${m}`);
|
|
19367
|
+
const info = (m) => console.log(`[memory] ${input.threadKey} ${m}`);
|
|
19335
19368
|
let provider;
|
|
19336
19369
|
let model;
|
|
19337
19370
|
try {
|
|
@@ -19355,7 +19388,8 @@ function scheduleReflection(input) {
|
|
|
19355
19388
|
answer: input.answer,
|
|
19356
19389
|
sourceThreadKey: input.threadKey,
|
|
19357
19390
|
sourceRunId: input.runId,
|
|
19358
|
-
onWarn: warn
|
|
19391
|
+
onWarn: warn,
|
|
19392
|
+
onInfo: info
|
|
19359
19393
|
})
|
|
19360
19394
|
);
|
|
19361
19395
|
}
|
|
@@ -19383,7 +19417,7 @@ async function memoryContextBlock(organization, cfg, injected, query, userId, sc
|
|
|
19383
19417
|
var init_memory = __esm({
|
|
19384
19418
|
"../../src/core/memory/index.ts"() {
|
|
19385
19419
|
"use strict";
|
|
19386
|
-
|
|
19420
|
+
init_provider();
|
|
19387
19421
|
init_scorer();
|
|
19388
19422
|
init_scope();
|
|
19389
19423
|
init_stores();
|
|
@@ -22578,7 +22612,9 @@ function afterReply(deps, ctx) {
|
|
|
22578
22612
|
scheduleReflection({
|
|
22579
22613
|
cfg: deps.config.config.memory,
|
|
22580
22614
|
store: deps.memory,
|
|
22581
|
-
|
|
22615
|
+
// The extractor's one call goes through pi's model library
|
|
22616
|
+
// (harness-pi.md item 13), never the loop's own provider adapters.
|
|
22617
|
+
providers: deps.completions,
|
|
22582
22618
|
runModelRef: resolved.modelRef,
|
|
22583
22619
|
gate: { toolCalls, historyTurns: history.length, agentName: resolved.agentName },
|
|
22584
22620
|
threadKey: msg.threadKey,
|
|
@@ -22625,6 +22661,7 @@ function durableInboxMessage(msg, text, at, from) {
|
|
|
22625
22661
|
...msg.userName !== void 0 ? { userName: msg.userName } : {},
|
|
22626
22662
|
...msg.sourceUrl !== void 0 ? { sourceUrl: msg.sourceUrl } : {},
|
|
22627
22663
|
...msg.channelName !== void 0 ? { channelName: msg.channelName } : {},
|
|
22664
|
+
...msg.messageId !== void 0 ? { messageId: msg.messageId } : {},
|
|
22628
22665
|
...from !== void 0 ? { fromRunId: from.runId } : {},
|
|
22629
22666
|
// Staged references are metadata (record 0033) — a few hundred bytes each —
|
|
22630
22667
|
// so they ride the base row and survive the restart whatever the
|
|
@@ -22665,6 +22702,7 @@ function messageFromInbox(stored, fallbackAt) {
|
|
|
22665
22702
|
const userName = str7("userName");
|
|
22666
22703
|
const sourceUrl = str7("sourceUrl");
|
|
22667
22704
|
const channelName = str7("channelName");
|
|
22705
|
+
const messageId = str7("messageId");
|
|
22668
22706
|
const fromRunId = str7("fromRunId");
|
|
22669
22707
|
const at = typeof m.at === "number" && Number.isFinite(m.at) ? m.at : fallbackAt;
|
|
22670
22708
|
const images = attachmentsFromInbox(m.images);
|
|
@@ -22681,6 +22719,7 @@ ${note}` : text,
|
|
|
22681
22719
|
...userName !== void 0 ? { userName } : {},
|
|
22682
22720
|
...sourceUrl !== void 0 ? { sourceUrl } : {},
|
|
22683
22721
|
...channelName !== void 0 ? { channelName } : {},
|
|
22722
|
+
...messageId !== void 0 ? { messageId } : {},
|
|
22684
22723
|
...images ? { images } : {},
|
|
22685
22724
|
...documents ? { documents } : {},
|
|
22686
22725
|
...staged ? { staged } : {}
|
|
@@ -22719,6 +22758,9 @@ var init_inboxMessage = __esm({
|
|
|
22719
22758
|
});
|
|
22720
22759
|
|
|
22721
22760
|
// ../../src/core/threadAdmission.ts
|
|
22761
|
+
function followUpMessageId(input) {
|
|
22762
|
+
return input.messageId ?? (input.ledgerSeq !== void 0 ? `inbox-${input.ledgerSeq}` : `at-${input.at}`);
|
|
22763
|
+
}
|
|
22722
22764
|
function decideFollowUp(live, requested) {
|
|
22723
22765
|
if (requested.agent !== void 0 && requested.agent !== live.agent)
|
|
22724
22766
|
return { kind: "refuse", reason: "agent_mismatch", requestedAgent: requested.agent };
|
|
@@ -23111,6 +23153,7 @@ function followUpOf(msg, text, at, opts) {
|
|
|
23111
23153
|
...msg.images !== void 0 ? { images: msg.images } : {},
|
|
23112
23154
|
...msg.documents !== void 0 ? { documents: msg.documents } : {},
|
|
23113
23155
|
...msg.staged !== void 0 ? { staged: msg.staged } : {},
|
|
23156
|
+
...msg.messageId !== void 0 ? { messageId: msg.messageId } : {},
|
|
23114
23157
|
at,
|
|
23115
23158
|
...opts.ledgerSeq !== void 0 ? { ledgerSeq: opts.ledgerSeq } : {},
|
|
23116
23159
|
...opts.from !== void 0 ? { from: opts.from } : {},
|
|
@@ -24782,6 +24825,16 @@ var init_runRegistry = __esm({
|
|
|
24782
24825
|
}
|
|
24783
24826
|
});
|
|
24784
24827
|
|
|
24828
|
+
// ../../src/core/types.ts
|
|
24829
|
+
function messageIdOf(msg, runId3) {
|
|
24830
|
+
return msg.messageId ?? runId3;
|
|
24831
|
+
}
|
|
24832
|
+
var init_types4 = __esm({
|
|
24833
|
+
"../../src/core/types.ts"() {
|
|
24834
|
+
"use strict";
|
|
24835
|
+
}
|
|
24836
|
+
});
|
|
24837
|
+
|
|
24785
24838
|
// ../../src/core/dispatch/fastPath.ts
|
|
24786
24839
|
function isInlineRunCommand(id) {
|
|
24787
24840
|
return id.startsWith("friction.") || id === "memory.forget" || /^repo\.(onboard|offboard|rebuild|reconfigure|test|build)$/.test(id) || /^mcp\.(add|connect|remove)$/.test(id);
|
|
@@ -24868,7 +24921,12 @@ async function runInlineCommandRun(deps, msg, command2, io, execute, ending, tra
|
|
|
24868
24921
|
);
|
|
24869
24922
|
trace.bindRun(run2.id, (e) => registry.publish(run2.id, e));
|
|
24870
24923
|
io.runStarted?.({ id: run2.id });
|
|
24871
|
-
registry.publish(run2.id, {
|
|
24924
|
+
registry.publish(run2.id, {
|
|
24925
|
+
type: "input",
|
|
24926
|
+
text: redactSecrets(msg.text),
|
|
24927
|
+
messageId: messageIdOf(msg, run2.id),
|
|
24928
|
+
at: clock3()
|
|
24929
|
+
});
|
|
24872
24930
|
registry.publish(run2.id, { type: "run_meta", agent: COMMAND_RUN_AGENT, traceId: root.traceId, at: clock3() });
|
|
24873
24931
|
let result;
|
|
24874
24932
|
try {
|
|
@@ -24953,6 +25011,7 @@ var init_fastPath = __esm({
|
|
|
24953
25011
|
init_commandChat();
|
|
24954
25012
|
init_commandSurface();
|
|
24955
25013
|
init_runRegistry();
|
|
25014
|
+
init_types4();
|
|
24956
25015
|
init_record();
|
|
24957
25016
|
init_reply();
|
|
24958
25017
|
}
|
|
@@ -25123,431 +25182,299 @@ var init_resolve = __esm({
|
|
|
25123
25182
|
init_registry();
|
|
25124
25183
|
init_profile();
|
|
25125
25184
|
init_directives();
|
|
25126
|
-
|
|
25185
|
+
init_provider();
|
|
25127
25186
|
init_factory();
|
|
25128
25187
|
init_githubRepoProbe();
|
|
25129
25188
|
init_repoContext();
|
|
25130
25189
|
}
|
|
25131
25190
|
});
|
|
25132
25191
|
|
|
25133
|
-
// ../../src/
|
|
25134
|
-
|
|
25135
|
-
|
|
25136
|
-
|
|
25137
|
-
|
|
25138
|
-
function redactPrDescription(d) {
|
|
25139
|
-
return mapStringLeaves(d, redactSecrets);
|
|
25192
|
+
// ../../src/artifacts/contentType.ts
|
|
25193
|
+
function contentTypeFor(name) {
|
|
25194
|
+
const dot = name.lastIndexOf(".");
|
|
25195
|
+
if (dot < 0 || dot === name.length - 1) return OCTET_STREAM;
|
|
25196
|
+
return BY_EXTENSION[name.slice(dot + 1).toLowerCase()] ?? OCTET_STREAM;
|
|
25140
25197
|
}
|
|
25141
|
-
|
|
25142
|
-
|
|
25143
|
-
|
|
25144
|
-
|
|
25145
|
-
|
|
25198
|
+
var BY_EXTENSION, OCTET_STREAM, INLINE_IMAGE_TYPES;
|
|
25199
|
+
var init_contentType = __esm({
|
|
25200
|
+
"../../src/artifacts/contentType.ts"() {
|
|
25201
|
+
"use strict";
|
|
25202
|
+
BY_EXTENSION = {
|
|
25203
|
+
png: "image/png",
|
|
25204
|
+
jpg: "image/jpeg",
|
|
25205
|
+
jpeg: "image/jpeg",
|
|
25206
|
+
gif: "image/gif",
|
|
25207
|
+
webp: "image/webp",
|
|
25208
|
+
svg: "image/svg+xml",
|
|
25209
|
+
pdf: "application/pdf",
|
|
25210
|
+
mp4: "video/mp4",
|
|
25211
|
+
webm: "video/webm",
|
|
25212
|
+
mov: "video/quicktime",
|
|
25213
|
+
mp3: "audio/mpeg",
|
|
25214
|
+
wav: "audio/wav",
|
|
25215
|
+
txt: "text/plain",
|
|
25216
|
+
md: "text/markdown",
|
|
25217
|
+
csv: "text/csv",
|
|
25218
|
+
json: "application/json",
|
|
25219
|
+
html: "text/html",
|
|
25220
|
+
zip: "application/zip",
|
|
25221
|
+
gz: "application/gzip",
|
|
25222
|
+
tar: "application/x-tar"
|
|
25223
|
+
};
|
|
25224
|
+
OCTET_STREAM = "application/octet-stream";
|
|
25225
|
+
INLINE_IMAGE_TYPES = /* @__PURE__ */ new Set(["image/png", "image/jpeg", "image/gif", "image/webp"]);
|
|
25146
25226
|
}
|
|
25147
|
-
|
|
25227
|
+
});
|
|
25228
|
+
|
|
25229
|
+
// ../../src/artifacts/keys.ts
|
|
25230
|
+
function safeBasename(name) {
|
|
25231
|
+
const segments2 = name.split(/[\\/]+/).filter((s) => s.length > 0);
|
|
25232
|
+
const last = segments2[segments2.length - 1] ?? "";
|
|
25233
|
+
const reduced = last.replace(SAFE, "_").replace(/\.{2,}/g, "_").replace(/_+/g, "_");
|
|
25234
|
+
if (reduced === "" || /^[._-]+$/.test(reduced)) return "file";
|
|
25235
|
+
return reduced;
|
|
25148
25236
|
}
|
|
25149
|
-
function
|
|
25150
|
-
return
|
|
25237
|
+
function threadKeySafe(threadKey) {
|
|
25238
|
+
return threadKey.replace(/:/g, "-").replace(SAFE, "_");
|
|
25151
25239
|
}
|
|
25152
|
-
function
|
|
25153
|
-
return `
|
|
25240
|
+
function outboundKey(runId3, seq, name) {
|
|
25241
|
+
return `runs/${runId3}/out/${seq}-${safeBasename(name)}`;
|
|
25154
25242
|
}
|
|
25155
|
-
function
|
|
25156
|
-
return
|
|
25243
|
+
function inboundKey(threadKey, messageTs, index, name) {
|
|
25244
|
+
return `threads/${threadKeySafe(threadKey)}/in/${messageTs}/${index}-${safeBasename(name)}`;
|
|
25157
25245
|
}
|
|
25158
|
-
|
|
25159
|
-
|
|
25160
|
-
|
|
25161
|
-
|
|
25162
|
-
|
|
25163
|
-
const out = [];
|
|
25164
|
-
out.push("## TL;DR", "", desc.tldr, "");
|
|
25165
|
-
out.push("## What & why", "", desc.whatWhy, "");
|
|
25166
|
-
out.push("## Tour", "");
|
|
25167
|
-
desc.tour.forEach((step, i) => {
|
|
25168
|
-
out.push(`### ${i + 1}. ${step.title}`, "", step.description, "");
|
|
25169
|
-
if (step.lookFor) out.push(`**Look for:** ${step.lookFor}`, "");
|
|
25170
|
-
out.push(anchorUrl(ctx, step.anchor), "");
|
|
25171
|
-
});
|
|
25172
|
-
out.push(`### ${desc.tour.length + 1}. Remaining changes`, "");
|
|
25173
|
-
if (desc.remaining.length === 0) out.push("- none \u2014 every touched file is covered by a step above", "");
|
|
25174
|
-
else {
|
|
25175
|
-
for (const r of desc.remaining) out.push(`- \`${r.path}\` \u2014 ${r.note}`);
|
|
25176
|
-
out.push("");
|
|
25246
|
+
var SAFE;
|
|
25247
|
+
var init_keys = __esm({
|
|
25248
|
+
"../../src/artifacts/keys.ts"() {
|
|
25249
|
+
"use strict";
|
|
25250
|
+
SAFE = /[^A-Za-z0-9._-]+/g;
|
|
25177
25251
|
}
|
|
25178
|
-
|
|
25179
|
-
|
|
25180
|
-
|
|
25181
|
-
|
|
25182
|
-
|
|
25183
|
-
if (desc.validation.summary) out.push(desc.validation.summary, "");
|
|
25184
|
-
out.push("| Criterion | Proof |", "|---|---|");
|
|
25185
|
-
for (const c of desc.validation.criteria) out.push(`| ${cell2(c.criterion)} | ${cell2(c.proof)} |`);
|
|
25186
|
-
out.push("", GENERATED_FOOTER, "");
|
|
25187
|
-
return out.join("\n");
|
|
25252
|
+
});
|
|
25253
|
+
|
|
25254
|
+
// ../../src/tools/attach.ts
|
|
25255
|
+
function transferBudgetMs(size) {
|
|
25256
|
+
return Math.ceil(size / TRANSFER_FLOOR_BYTES_PER_MS) + TRANSFER_SETUP_MS;
|
|
25188
25257
|
}
|
|
25189
|
-
function
|
|
25190
|
-
const
|
|
25191
|
-
|
|
25192
|
-
|
|
25193
|
-
|
|
25194
|
-
|
|
25195
|
-
|
|
25196
|
-
|
|
25197
|
-
|
|
25198
|
-
|
|
25199
|
-
|
|
25200
|
-
|
|
25258
|
+
function fileNameOf2(path) {
|
|
25259
|
+
const segments2 = path.split("/").filter(Boolean);
|
|
25260
|
+
return segments2[segments2.length - 1] ?? path;
|
|
25261
|
+
}
|
|
25262
|
+
function putCommandFor(path, contentType, url) {
|
|
25263
|
+
return `curl -fsS -T ${shellQuote(path)} -H ${shellQuote(`Content-Type: ${contentType}`)} -o /dev/null -w ${shellQuote(PUT_REPORT_FORMAT)} ${shellQuote(url)}`;
|
|
25264
|
+
}
|
|
25265
|
+
function parsePutReport(output) {
|
|
25266
|
+
const m = /(\d{3}) (\d{1,15})\s*$/.exec(output);
|
|
25267
|
+
return m ? { httpCode: Number(m[1]), sentBytes: Number(m[2]) } : null;
|
|
25268
|
+
}
|
|
25269
|
+
function postCommandFor(path, url) {
|
|
25270
|
+
return `curl -fsS --upload-file ${shellQuote(path)} -X POST ${shellQuote(url)}`;
|
|
25271
|
+
}
|
|
25272
|
+
async function attachThroughStore(path, name, lead, artifacts, ctx) {
|
|
25273
|
+
const exec = (command2, timeoutMs2) => {
|
|
25274
|
+
const opts = { timeoutMs: timeoutMs2, ...ctx.signal ? { signal: ctx.signal } : {} };
|
|
25275
|
+
return ctx.executor.exec(command2, opts);
|
|
25276
|
+
};
|
|
25277
|
+
const callId = ctx.callId;
|
|
25278
|
+
if (callId === void 0) return "error: attach_file was run without its call id \u2014 the file cannot be recorded";
|
|
25279
|
+
const statOut = await exec(statCommandFor(shellQuote(path)), STAT_TIMEOUT_MS);
|
|
25280
|
+
if (parseExitPrefix(statOut).failed) return `error: could not read ${path}: ${statOut.trim()}`;
|
|
25281
|
+
const size = parseByteSize(statOut);
|
|
25282
|
+
if (size === null) return `error: could not measure ${path}: ${statOut.trim()}`;
|
|
25283
|
+
if (size === 0) return `error: ${path} is empty \u2014 nothing to attach`;
|
|
25284
|
+
if (size > MAX_ARTIFACT_BYTES) {
|
|
25285
|
+
return `error: ${path} is ${size} bytes; attach_file takes files up to ${MAX_ARTIFACT_BYTES} bytes (1 GiB) \u2014 link to the file instead`;
|
|
25201
25286
|
}
|
|
25202
|
-
|
|
25203
|
-
if (
|
|
25204
|
-
|
|
25205
|
-
|
|
25206
|
-
|
|
25207
|
-
description.tour = parsed.tour;
|
|
25208
|
-
if (parsed.remaining) description.remaining = parsed.remaining;
|
|
25287
|
+
let timeoutMs = BASH_TIMEOUT_MAX_MS;
|
|
25288
|
+
if (ctx.remainingMs) {
|
|
25289
|
+
const budget = bashBudgetWithinRun(BASH_TIMEOUT_MAX_MS, ctx.remainingMs());
|
|
25290
|
+
if (budget.kind === "exhausted") return `error: ${budget.note}`;
|
|
25291
|
+
if (budget.kind === "clipped") timeoutMs = budget.timeoutMs;
|
|
25209
25292
|
}
|
|
25210
|
-
const
|
|
25211
|
-
if (
|
|
25212
|
-
|
|
25213
|
-
if (risks) setProse(description, "risks", risks, problems);
|
|
25214
|
-
const validation = sections.get("validation");
|
|
25215
|
-
if (validation) description.validation = parseValidation(validation.lines, problems);
|
|
25216
|
-
for (const [key, heading] of SECTIONS) {
|
|
25217
|
-
if (key === "tldr" || sections.has(key)) continue;
|
|
25218
|
-
problems.push(`no \`## ${heading}\` section`);
|
|
25293
|
+
const needed = transferBudgetMs(size);
|
|
25294
|
+
if (needed > timeoutMs) {
|
|
25295
|
+
return `error: ${name} is ${size} bytes and needs about ${secs(needed)}s to move at ${TRANSFER_FLOOR_BYTES_PER_MS} KB/s; the run has ${secs(timeoutMs)}s of command budget left \u2014 write up what you have and link to the file instead`;
|
|
25219
25296
|
}
|
|
25220
|
-
|
|
25221
|
-
|
|
25222
|
-
|
|
25223
|
-
|
|
25224
|
-
|
|
25225
|
-
|
|
25226
|
-
|
|
25227
|
-
|
|
25228
|
-
|
|
25229
|
-
|
|
25230
|
-
|
|
25231
|
-
return
|
|
25232
|
-
}
|
|
25297
|
+
const contentType = contentTypeFor(name);
|
|
25298
|
+
const key = outboundKey(artifacts.runId, artifacts.nextSeq(), name);
|
|
25299
|
+
const putUrl = await artifacts.store.presignPut(key, contentType);
|
|
25300
|
+
const putOut = await exec(putCommandFor(path, contentType, putUrl), timeoutMs);
|
|
25301
|
+
if (parseExitPrefix(putOut).failed) {
|
|
25302
|
+
return `error: the upload of ${name} to the artifact store failed: ${putOut.trim()}`;
|
|
25303
|
+
}
|
|
25304
|
+
const report2 = parsePutReport(putOut);
|
|
25305
|
+
const curlSaid = report2 ? `; curl reported HTTP ${report2.httpCode} after sending ${report2.sentBytes} bytes` : "";
|
|
25306
|
+
const head = await artifacts.store.head(key);
|
|
25307
|
+
if (!head) {
|
|
25308
|
+
return `error: the artifact store holds nothing under ${key} after the upload of ${name}${curlSaid} \u2014 nothing was posted`;
|
|
25309
|
+
}
|
|
25310
|
+
if (head.size !== size) {
|
|
25311
|
+
return `error: the artifact store holds ${head.size} bytes for ${name}, not the ${size} measured${curlSaid} \u2014 nothing was posted`;
|
|
25312
|
+
}
|
|
25313
|
+
ctx.publish?.({ type: "artifact", direction: "out", key, name, size, contentType, callId });
|
|
25314
|
+
if (!ctx.uploadTicket) {
|
|
25315
|
+
const link = artifacts.artifactUrl?.(key);
|
|
25316
|
+
const where = link ? ` \u2014 ${link}` : ` is on the run page as ${key}`;
|
|
25317
|
+
await artifacts.reply(`${lead}
|
|
25318
|
+
\u{1F4CE} ${name} (${size} bytes)${where}`);
|
|
25319
|
+
return `attached ${name} (${size} bytes) to the run page as ${key}; this conversation's channel takes no file uploads, so the lead and the file's link were posted instead`;
|
|
25320
|
+
}
|
|
25321
|
+
const kept = "the file is kept on the run page";
|
|
25322
|
+
let ticket;
|
|
25323
|
+
try {
|
|
25324
|
+
ticket = await ctx.uploadTicket({ name, size });
|
|
25325
|
+
} catch (err2) {
|
|
25326
|
+
return `error: the channel refused an upload ticket for ${name}: ${describe2(err2)}; ${kept}`;
|
|
25327
|
+
}
|
|
25328
|
+
const postOut = await exec(postCommandFor(path, ticket.url), timeoutMs);
|
|
25329
|
+
if (parseExitPrefix(postOut).failed) {
|
|
25330
|
+
return `error: the upload of ${name} to the channel failed: ${postOut.trim()}; ${kept}`;
|
|
25331
|
+
}
|
|
25332
|
+
try {
|
|
25333
|
+
await ticket.complete(lead);
|
|
25334
|
+
} catch (err2) {
|
|
25335
|
+
return `error: the channel refused to complete the upload of ${name}: ${describe2(err2)}; ${kept}`;
|
|
25336
|
+
}
|
|
25337
|
+
return `attached ${name} (${size} bytes) to the conversation and the run page`;
|
|
25233
25338
|
}
|
|
25234
|
-
|
|
25235
|
-
|
|
25236
|
-
|
|
25237
|
-
|
|
25238
|
-
|
|
25239
|
-
|
|
25240
|
-
|
|
25241
|
-
|
|
25242
|
-
|
|
25243
|
-
|
|
25244
|
-
|
|
25245
|
-
|
|
25246
|
-
|
|
25247
|
-
|
|
25248
|
-
|
|
25249
|
-
|
|
25339
|
+
var MAX_ARTIFACT_BYTES, TRANSFER_FLOOR_BYTES_PER_MS, TRANSFER_SETUP_MS, STAT_TIMEOUT_MS, describe2, secs, PUT_REPORT_FORMAT, attachFileTool;
|
|
25340
|
+
var init_attach = __esm({
|
|
25341
|
+
"../../src/tools/attach.ts"() {
|
|
25342
|
+
"use strict";
|
|
25343
|
+
init_contentType();
|
|
25344
|
+
init_keys();
|
|
25345
|
+
init_runEvents();
|
|
25346
|
+
init_bashTimeout();
|
|
25347
|
+
init_binaryRead();
|
|
25348
|
+
init_shellQuote();
|
|
25349
|
+
MAX_ARTIFACT_BYTES = 1073741824;
|
|
25350
|
+
TRANSFER_FLOOR_BYTES_PER_MS = 1024;
|
|
25351
|
+
TRANSFER_SETUP_MS = 1e4;
|
|
25352
|
+
STAT_TIMEOUT_MS = 3e4;
|
|
25353
|
+
describe2 = (err2) => err2 instanceof Error ? err2.message : String(err2);
|
|
25354
|
+
secs = (ms) => Math.max(0, Math.round(ms / 1e3));
|
|
25355
|
+
PUT_REPORT_FORMAT = "\\n%{http_code} %{size_upload}";
|
|
25356
|
+
attachFileTool = {
|
|
25357
|
+
name: "attach_file",
|
|
25358
|
+
failsInText: true,
|
|
25359
|
+
description: `Post a file from the workspace into the conversation so the person sees it inline \u2014 a screenshot (e.g. from \`playwright screenshot\`), a rendered PDF, a recording, a log. Use it whenever you produce an image worth showing: a link to a file is not a picture. Whole files only: up to ${MAX_ARTIFACT_BYTES} bytes (1 GiB) where the artifact store is configured, ${MAX_READ_BYTES} bytes otherwise \u2014 the result says which.`,
|
|
25360
|
+
inputSchema: {
|
|
25361
|
+
type: "object",
|
|
25362
|
+
properties: {
|
|
25363
|
+
path: { type: "string", description: "Relative path of the file in the workspace" },
|
|
25364
|
+
comment: {
|
|
25365
|
+
type: "string",
|
|
25366
|
+
description: "One line posted with the file saying what it shows (default: the file name)"
|
|
25367
|
+
}
|
|
25368
|
+
},
|
|
25369
|
+
required: ["path"]
|
|
25370
|
+
},
|
|
25371
|
+
async run(input, ctx) {
|
|
25372
|
+
const path = String(input.path ?? "").trim();
|
|
25373
|
+
if (!path) return "error: path is required";
|
|
25374
|
+
const name = fileNameOf2(path);
|
|
25375
|
+
const lead = typeof input.comment === "string" && input.comment.trim() ? input.comment.trim() : name;
|
|
25376
|
+
if (ctx.artifacts) return attachThroughStore(path, name, lead, ctx.artifacts, ctx);
|
|
25377
|
+
if (!ctx.attach) {
|
|
25378
|
+
return "attach_file is not available here: this conversation's channel takes no file uploads \u2014 link to the file instead";
|
|
25379
|
+
}
|
|
25380
|
+
const readBytes = ctx.executor.readBytes?.bind(ctx.executor);
|
|
25381
|
+
if (!readBytes) {
|
|
25382
|
+
return "attach_file is not available here: this workspace cannot hand files over \u2014 link to the file instead";
|
|
25383
|
+
}
|
|
25384
|
+
let bytes;
|
|
25385
|
+
try {
|
|
25386
|
+
bytes = await readBytes(path);
|
|
25387
|
+
} catch (err2) {
|
|
25388
|
+
return `error: could not read ${path}: ${describe2(err2)}`;
|
|
25389
|
+
}
|
|
25390
|
+
if (bytes.byteLength === 0) return `error: ${path} is empty \u2014 nothing to attach`;
|
|
25391
|
+
try {
|
|
25392
|
+
await ctx.attach({ name, bytes, lead });
|
|
25393
|
+
} catch (err2) {
|
|
25394
|
+
return `error: the channel refused the upload of ${name}: ${describe2(err2)}`;
|
|
25395
|
+
}
|
|
25396
|
+
return `attached ${name} (${bytes.byteLength} bytes) to the conversation`;
|
|
25250
25397
|
}
|
|
25251
|
-
|
|
25252
|
-
|
|
25253
|
-
|
|
25254
|
-
|
|
25255
|
-
|
|
25398
|
+
};
|
|
25399
|
+
}
|
|
25400
|
+
});
|
|
25401
|
+
|
|
25402
|
+
// ../../src/core/diffDigest.ts
|
|
25403
|
+
function basename2(p) {
|
|
25404
|
+
const i = p.lastIndexOf("/");
|
|
25405
|
+
return i === -1 ? p : p.slice(i + 1);
|
|
25256
25406
|
}
|
|
25257
|
-
function
|
|
25258
|
-
const
|
|
25259
|
-
for (let i = 0; i <
|
|
25260
|
-
|
|
25261
|
-
|
|
25262
|
-
if (
|
|
25407
|
+
function decodeGitQuoted(s) {
|
|
25408
|
+
const bytes = [];
|
|
25409
|
+
for (let i = 0; i < s.length; i++) {
|
|
25410
|
+
if (s[i] === "\\" && i + 1 < s.length) {
|
|
25411
|
+
const oct = s.slice(i + 1).match(/^[0-7]{1,3}/)?.[0];
|
|
25412
|
+
if (oct) {
|
|
25413
|
+
bytes.push(parseInt(oct, 8) & 255);
|
|
25414
|
+
i += oct.length;
|
|
25415
|
+
continue;
|
|
25416
|
+
}
|
|
25417
|
+
const simple = { t: 9, n: 10, r: 13, '"': 34, "\\": 92 };
|
|
25418
|
+
const nx = s[i + 1];
|
|
25419
|
+
bytes.push(nx in simple ? simple[nx] : nx.charCodeAt(0) & 255);
|
|
25420
|
+
i += 1;
|
|
25263
25421
|
continue;
|
|
25264
25422
|
}
|
|
25265
|
-
|
|
25266
|
-
para.push(l.trim());
|
|
25423
|
+
bytes.push(s.charCodeAt(i) & 255);
|
|
25267
25424
|
}
|
|
25268
|
-
return
|
|
25425
|
+
return Buffer.from(bytes).toString("utf8");
|
|
25269
25426
|
}
|
|
25270
|
-
function
|
|
25271
|
-
const
|
|
25272
|
-
if (
|
|
25273
|
-
|
|
25427
|
+
function unquotePath(raw) {
|
|
25428
|
+
const s = raw.trim();
|
|
25429
|
+
if (s.length >= 2 && s.startsWith('"') && s.endsWith('"')) return decodeGitQuoted(s.slice(1, -1));
|
|
25430
|
+
return s;
|
|
25274
25431
|
}
|
|
25275
|
-
function
|
|
25276
|
-
const
|
|
25277
|
-
|
|
25278
|
-
|
|
25279
|
-
|
|
25280
|
-
|
|
25281
|
-
|
|
25282
|
-
|
|
25283
|
-
|
|
25284
|
-
|
|
25285
|
-
|
|
25286
|
-
|
|
25287
|
-
|
|
25288
|
-
|
|
25289
|
-
|
|
25290
|
-
|
|
25291
|
-
|
|
25292
|
-
|
|
25293
|
-
const
|
|
25294
|
-
|
|
25432
|
+
function riskReasons(entry, paths) {
|
|
25433
|
+
const reasons = [];
|
|
25434
|
+
const candidates = [...new Set(paths.filter(Boolean))];
|
|
25435
|
+
const any = (re) => candidates.some((p) => re.test(p));
|
|
25436
|
+
if (any(MIGRATION_RE)) reasons.push("migration/schema");
|
|
25437
|
+
if (any(AUTH_RE)) reasons.push("auth/permission-sensitive");
|
|
25438
|
+
if (any(INFRA_RE)) reasons.push("infra/deploy config");
|
|
25439
|
+
if (entry.status === "deleted") reasons.push("whole-file deletion");
|
|
25440
|
+
if (candidates.some((p) => LOCKFILES.has(basename2(p).toLowerCase()))) reasons.push("lockfile");
|
|
25441
|
+
const churn = entry.adds + entry.dels;
|
|
25442
|
+
if (churn >= LARGE_CHURN) reasons.push(`large change (${churn} lines)`);
|
|
25443
|
+
return reasons;
|
|
25444
|
+
}
|
|
25445
|
+
function parseNumstat(text) {
|
|
25446
|
+
const out = [];
|
|
25447
|
+
for (const line3 of text.split("\n")) {
|
|
25448
|
+
const m = /^(\d+|-)\t(\d+|-)\t(.+)$/.exec(line3);
|
|
25449
|
+
if (!m) continue;
|
|
25450
|
+
const binary = m[1] === "-" || m[2] === "-";
|
|
25451
|
+
out.push({
|
|
25452
|
+
adds: binary ? 0 : Number(m[1]),
|
|
25453
|
+
dels: binary ? 0 : Number(m[2]),
|
|
25454
|
+
binary,
|
|
25455
|
+
path: unquotePath(m[3])
|
|
25456
|
+
});
|
|
25295
25457
|
}
|
|
25296
|
-
|
|
25297
|
-
if (remaining === void 0) problems.push("Tour has no `Remaining changes` step");
|
|
25298
|
-
return { tour, ...remaining ? { remaining } : {} };
|
|
25458
|
+
return out;
|
|
25299
25459
|
}
|
|
25300
|
-
function
|
|
25301
|
-
const
|
|
25302
|
-
|
|
25303
|
-
|
|
25304
|
-
|
|
25305
|
-
|
|
25306
|
-
|
|
25307
|
-
|
|
25308
|
-
|
|
25309
|
-
|
|
25310
|
-
|
|
25311
|
-
|
|
25312
|
-
|
|
25313
|
-
|
|
25314
|
-
const lf = LOOK_FOR_RE.exec(line3.trim());
|
|
25315
|
-
if (lf) {
|
|
25316
|
-
lookForLines.push(lf[1]);
|
|
25317
|
-
inLookFor = true;
|
|
25318
|
-
continue;
|
|
25319
|
-
}
|
|
25320
|
-
if (inLookFor) {
|
|
25321
|
-
if (line3.trim() === "") inLookFor = false;
|
|
25322
|
-
else lookForLines.push(line3.trim());
|
|
25460
|
+
function parseNameStatus(text) {
|
|
25461
|
+
const out = [];
|
|
25462
|
+
for (const line3 of text.split("\n")) {
|
|
25463
|
+
const m = /^([A-Z])\d*\t(.+)$/.exec(line3);
|
|
25464
|
+
if (!m) continue;
|
|
25465
|
+
const parts = m[2].split(" ").map(unquotePath);
|
|
25466
|
+
const code = m[1];
|
|
25467
|
+
if (code === "R" || code === "C") {
|
|
25468
|
+
const [oldPath, newPath] = parts;
|
|
25469
|
+
out.push({
|
|
25470
|
+
status: code === "R" ? "renamed" : "added",
|
|
25471
|
+
oldPath: oldPath ?? "",
|
|
25472
|
+
newPath: newPath ?? oldPath ?? ""
|
|
25473
|
+
});
|
|
25323
25474
|
continue;
|
|
25324
25475
|
}
|
|
25325
|
-
|
|
25326
|
-
|
|
25327
|
-
if (!link) {
|
|
25328
|
-
problems.push(`${where}: no permalink line \u2014 dropped`);
|
|
25329
|
-
return void 0;
|
|
25330
|
-
}
|
|
25331
|
-
let path;
|
|
25332
|
-
try {
|
|
25333
|
-
path = link[2].split("/").map(decodeURIComponent).join("/");
|
|
25334
|
-
} catch {
|
|
25335
|
-
problems.push(`${where}: permalink path is not valid percent-encoding \u2014 dropped`);
|
|
25336
|
-
return void 0;
|
|
25337
|
-
}
|
|
25338
|
-
const from = Number(link[3]);
|
|
25339
|
-
const anchor = TourAnchorSchema.safeParse({ path, from, to: link[4] === void 0 ? from : Number(link[4]) });
|
|
25340
|
-
if (!anchor.success) {
|
|
25341
|
-
const issue = anchor.error.issues[0];
|
|
25342
|
-
problems.push(`${where}: ${["anchor", ...issue.path].join(".")} ${issue.message} \u2014 dropped`);
|
|
25343
|
-
return void 0;
|
|
25344
|
-
}
|
|
25345
|
-
const lookFor = lookForLines.join("\n").trim();
|
|
25346
|
-
return {
|
|
25347
|
-
title: step.title,
|
|
25348
|
-
// A parsed step may carry an empty description (a human's heading +
|
|
25349
|
-
// permalink): this is a read-back, not a submission the schema gates.
|
|
25350
|
-
description: descLines.join("\n").replace(/\n{3,}/g, "\n\n").trim(),
|
|
25351
|
-
...lookFor ? { lookFor } : {},
|
|
25352
|
-
anchor: { ...anchor.data, sha: link[1] }
|
|
25353
|
-
};
|
|
25354
|
-
}
|
|
25355
|
-
function parseRemaining(lines, problems) {
|
|
25356
|
-
const out = [];
|
|
25357
|
-
for (const raw of lines) {
|
|
25358
|
-
const line3 = raw.trim();
|
|
25359
|
-
if (line3 === "" || REMAINING_NONE_RE.test(line3)) continue;
|
|
25360
|
-
const m = REMAINING_LINE_RE.exec(line3);
|
|
25361
|
-
if (m) out.push({ path: m[1], note: m[2].trim() });
|
|
25362
|
-
else if (!line3.startsWith("-") && out.length) out[out.length - 1].note += `
|
|
25363
|
-
${line3}`;
|
|
25364
|
-
else problems.push(`Remaining changes: unrecognized line \`${line3}\``);
|
|
25365
|
-
}
|
|
25366
|
-
return out;
|
|
25367
|
-
}
|
|
25368
|
-
function parseDecisions(lines, problems) {
|
|
25369
|
-
const out = [];
|
|
25370
|
-
for (const raw of lines) {
|
|
25371
|
-
const line3 = raw.trim();
|
|
25372
|
-
if (line3 === "") continue;
|
|
25373
|
-
const m = DECISION_RE.exec(line3);
|
|
25374
|
-
if (m) out.push({ title: m[1].replace(/\.$/, ""), rationale: m[2].trim() });
|
|
25375
|
-
else if (!line3.startsWith("-") && out.length) out[out.length - 1].rationale += `
|
|
25376
|
-
${line3}`;
|
|
25377
|
-
else problems.push(`Decisions: unrecognized line \`${line3}\``);
|
|
25378
|
-
}
|
|
25379
|
-
return out;
|
|
25380
|
-
}
|
|
25381
|
-
function parseValidation(lines, problems) {
|
|
25382
|
-
const summary = [];
|
|
25383
|
-
const criteria = [];
|
|
25384
|
-
let header4 = false;
|
|
25385
|
-
for (const raw of lines) {
|
|
25386
|
-
const line3 = raw.trim();
|
|
25387
|
-
if (!header4) {
|
|
25388
|
-
if (TABLE_HEADER_RE.test(line3)) header4 = true;
|
|
25389
|
-
else summary.push(raw);
|
|
25390
|
-
continue;
|
|
25391
|
-
}
|
|
25392
|
-
if (line3 === "" || TABLE_RULE_RE.test(line3)) continue;
|
|
25393
|
-
const cells = splitCells(line3);
|
|
25394
|
-
if (cells.length >= 2) criteria.push({ criterion: cells[0], proof: cells[1] });
|
|
25395
|
-
else problems.push(`Validation: unrecognized table line \`${line3}\``);
|
|
25396
|
-
}
|
|
25397
|
-
if (!header4) problems.push("Validation: no `| Criterion | Proof |` table");
|
|
25398
|
-
const text = summary.join("\n").trim();
|
|
25399
|
-
return { ...text ? { summary: text } : {}, criteria };
|
|
25400
|
-
}
|
|
25401
|
-
function splitCells(row) {
|
|
25402
|
-
const cells = [];
|
|
25403
|
-
let cur = "";
|
|
25404
|
-
const inner = row.replace(/^\|/, "").replace(/\|$/, "");
|
|
25405
|
-
for (let i = 0; i < inner.length; i++) {
|
|
25406
|
-
const ch = inner[i];
|
|
25407
|
-
if (ch === "\\" && i + 1 < inner.length) {
|
|
25408
|
-
cur += inner[++i];
|
|
25409
|
-
} else if (ch === "|") {
|
|
25410
|
-
cells.push(cur.trim());
|
|
25411
|
-
cur = "";
|
|
25412
|
-
} else cur += ch;
|
|
25413
|
-
}
|
|
25414
|
-
cells.push(cur.trim());
|
|
25415
|
-
return cells;
|
|
25416
|
-
}
|
|
25417
|
-
var line2, prose, TourAnchorSchema, TourStepSchema, PrDescriptionSchema, REPO_RE, SHA_RE2, GENERATED_FOOTER, SECTIONS, PERMALINK_RE, HEADING_RE, STEP_RE, LOOK_FOR_RE, REMAINING_LINE_RE, REMAINING_NONE_RE, DECISION_RE, TABLE_HEADER_RE, TABLE_RULE_RE;
|
|
25418
|
-
var init_prDescription = __esm({
|
|
25419
|
-
"../../src/core/prDescription.ts"() {
|
|
25420
|
-
"use strict";
|
|
25421
|
-
init_redact();
|
|
25422
|
-
line2 = z20.string().trim().min(1).refine((s) => !/[\r\n]/.test(s), "must be a single line (no embedded newlines)");
|
|
25423
|
-
prose = z20.string().trim().min(1);
|
|
25424
|
-
TourAnchorSchema = z20.object({
|
|
25425
|
-
// Repo-relative: no leading `/`, no `..` SEGMENT (a filename like `a..b.ts` is fine).
|
|
25426
|
-
path: z20.string().trim().min(1).refine((p) => !p.startsWith("/") && !p.split("/").includes(".."), "path must be repo-relative"),
|
|
25427
|
-
from: z20.number().int().positive(),
|
|
25428
|
-
to: z20.number().int().positive()
|
|
25429
|
-
}).refine((a) => a.to >= a.from, "to must be >= from");
|
|
25430
|
-
TourStepSchema = z20.object({
|
|
25431
|
-
title: line2,
|
|
25432
|
-
description: prose,
|
|
25433
|
-
lookFor: prose.optional(),
|
|
25434
|
-
anchor: TourAnchorSchema
|
|
25435
|
-
});
|
|
25436
|
-
PrDescriptionSchema = z20.object({
|
|
25437
|
-
/** The PR title's single source. Metadata for the PR's own title field —
|
|
25438
|
-
* never rendered into the body (GitHub shows the title itself). */
|
|
25439
|
-
title: line2,
|
|
25440
|
-
tldr: prose,
|
|
25441
|
-
whatWhy: prose,
|
|
25442
|
-
tour: z20.array(TourStepSchema).min(1),
|
|
25443
|
-
/** Every touched file the Tour steps did not cover, one line each. */
|
|
25444
|
-
remaining: z20.array(z20.object({ path: line2, note: prose })),
|
|
25445
|
-
decisions: z20.array(z20.object({ title: line2, rationale: prose })).min(1),
|
|
25446
|
-
risks: prose,
|
|
25447
|
-
validation: z20.object({
|
|
25448
|
-
summary: prose.optional(),
|
|
25449
|
-
criteria: z20.array(z20.object({ criterion: prose, proof: prose })).min(1)
|
|
25450
|
-
})
|
|
25451
|
-
});
|
|
25452
|
-
REPO_RE = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/;
|
|
25453
|
-
SHA_RE2 = /^[0-9a-f]{40}$/;
|
|
25454
|
-
GENERATED_FOOTER = "\u{1F916} Generated with [Claude Code](https://claude.com/claude-code)";
|
|
25455
|
-
SECTIONS = [
|
|
25456
|
-
["tldr", "TL;DR"],
|
|
25457
|
-
["whatWhy", "What & why"],
|
|
25458
|
-
["tour", "Tour"],
|
|
25459
|
-
["decisions", "Decisions"],
|
|
25460
|
-
["risks", "Risks & implications"],
|
|
25461
|
-
["validation", "Validation"]
|
|
25462
|
-
];
|
|
25463
|
-
PERMALINK_RE = /^https:\/\/github\.com\/[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+\/blob\/([0-9a-f]{7,40})\/([^#\s]+)#L(\d+)(?:-L(\d+))?$/;
|
|
25464
|
-
HEADING_RE = /^(#{1,6})\s+(.+?)\s*$/;
|
|
25465
|
-
STEP_RE = /^###\s+(?:(\d+)\.\s+)?(.+?)\s*$/;
|
|
25466
|
-
LOOK_FOR_RE = /^\*\*Look for:\*\*\s*(.*)$/;
|
|
25467
|
-
REMAINING_LINE_RE = /^-\s+`([^`]+)`\s+—\s*(.*)$/;
|
|
25468
|
-
REMAINING_NONE_RE = /^-\s+none\b/i;
|
|
25469
|
-
DECISION_RE = /^-\s+\*\*(.+?)\*\*\s*(.*)$/;
|
|
25470
|
-
TABLE_HEADER_RE = /^\|\s*criterion\s*\|\s*proof\s*\|$/i;
|
|
25471
|
-
TABLE_RULE_RE = /^\|?\s*:?-{3,}:?\s*\|/;
|
|
25472
|
-
}
|
|
25473
|
-
});
|
|
25474
|
-
|
|
25475
|
-
// ../../src/core/diffDigest.ts
|
|
25476
|
-
function basename2(p) {
|
|
25477
|
-
const i = p.lastIndexOf("/");
|
|
25478
|
-
return i === -1 ? p : p.slice(i + 1);
|
|
25479
|
-
}
|
|
25480
|
-
function decodeGitQuoted(s) {
|
|
25481
|
-
const bytes = [];
|
|
25482
|
-
for (let i = 0; i < s.length; i++) {
|
|
25483
|
-
if (s[i] === "\\" && i + 1 < s.length) {
|
|
25484
|
-
const oct = s.slice(i + 1).match(/^[0-7]{1,3}/)?.[0];
|
|
25485
|
-
if (oct) {
|
|
25486
|
-
bytes.push(parseInt(oct, 8) & 255);
|
|
25487
|
-
i += oct.length;
|
|
25488
|
-
continue;
|
|
25489
|
-
}
|
|
25490
|
-
const simple = { t: 9, n: 10, r: 13, '"': 34, "\\": 92 };
|
|
25491
|
-
const nx = s[i + 1];
|
|
25492
|
-
bytes.push(nx in simple ? simple[nx] : nx.charCodeAt(0) & 255);
|
|
25493
|
-
i += 1;
|
|
25494
|
-
continue;
|
|
25495
|
-
}
|
|
25496
|
-
bytes.push(s.charCodeAt(i) & 255);
|
|
25497
|
-
}
|
|
25498
|
-
return Buffer.from(bytes).toString("utf8");
|
|
25499
|
-
}
|
|
25500
|
-
function unquotePath(raw) {
|
|
25501
|
-
const s = raw.trim();
|
|
25502
|
-
if (s.length >= 2 && s.startsWith('"') && s.endsWith('"')) return decodeGitQuoted(s.slice(1, -1));
|
|
25503
|
-
return s;
|
|
25504
|
-
}
|
|
25505
|
-
function riskReasons(entry, paths) {
|
|
25506
|
-
const reasons = [];
|
|
25507
|
-
const candidates = [...new Set(paths.filter(Boolean))];
|
|
25508
|
-
const any = (re) => candidates.some((p) => re.test(p));
|
|
25509
|
-
if (any(MIGRATION_RE)) reasons.push("migration/schema");
|
|
25510
|
-
if (any(AUTH_RE)) reasons.push("auth/permission-sensitive");
|
|
25511
|
-
if (any(INFRA_RE)) reasons.push("infra/deploy config");
|
|
25512
|
-
if (entry.status === "deleted") reasons.push("whole-file deletion");
|
|
25513
|
-
if (candidates.some((p) => LOCKFILES.has(basename2(p).toLowerCase()))) reasons.push("lockfile");
|
|
25514
|
-
const churn = entry.adds + entry.dels;
|
|
25515
|
-
if (churn >= LARGE_CHURN) reasons.push(`large change (${churn} lines)`);
|
|
25516
|
-
return reasons;
|
|
25517
|
-
}
|
|
25518
|
-
function parseNumstat(text) {
|
|
25519
|
-
const out = [];
|
|
25520
|
-
for (const line3 of text.split("\n")) {
|
|
25521
|
-
const m = /^(\d+|-)\t(\d+|-)\t(.+)$/.exec(line3);
|
|
25522
|
-
if (!m) continue;
|
|
25523
|
-
const binary = m[1] === "-" || m[2] === "-";
|
|
25524
|
-
out.push({
|
|
25525
|
-
adds: binary ? 0 : Number(m[1]),
|
|
25526
|
-
dels: binary ? 0 : Number(m[2]),
|
|
25527
|
-
binary,
|
|
25528
|
-
path: unquotePath(m[3])
|
|
25529
|
-
});
|
|
25530
|
-
}
|
|
25531
|
-
return out;
|
|
25532
|
-
}
|
|
25533
|
-
function parseNameStatus(text) {
|
|
25534
|
-
const out = [];
|
|
25535
|
-
for (const line3 of text.split("\n")) {
|
|
25536
|
-
const m = /^([A-Z])\d*\t(.+)$/.exec(line3);
|
|
25537
|
-
if (!m) continue;
|
|
25538
|
-
const parts = m[2].split(" ").map(unquotePath);
|
|
25539
|
-
const code = m[1];
|
|
25540
|
-
if (code === "R" || code === "C") {
|
|
25541
|
-
const [oldPath, newPath] = parts;
|
|
25542
|
-
out.push({
|
|
25543
|
-
status: code === "R" ? "renamed" : "added",
|
|
25544
|
-
oldPath: oldPath ?? "",
|
|
25545
|
-
newPath: newPath ?? oldPath ?? ""
|
|
25546
|
-
});
|
|
25547
|
-
continue;
|
|
25548
|
-
}
|
|
25549
|
-
const p = parts[0] ?? "";
|
|
25550
|
-
out.push({ status: code === "A" ? "added" : code === "D" ? "deleted" : "modified", oldPath: p, newPath: p });
|
|
25476
|
+
const p = parts[0] ?? "";
|
|
25477
|
+
out.push({ status: code === "A" ? "added" : code === "D" ? "deleted" : "modified", oldPath: p, newPath: p });
|
|
25551
25478
|
}
|
|
25552
25479
|
return out;
|
|
25553
25480
|
}
|
|
@@ -25630,10 +25557,71 @@ var init_diffDigest = __esm({
|
|
|
25630
25557
|
}
|
|
25631
25558
|
});
|
|
25632
25559
|
|
|
25560
|
+
// ../../src/tools/diffDigest.ts
|
|
25561
|
+
var NAME_STATUS_MARK, NAME_STATUS_MARK_LINE, OUTPUT_CUT_RE, diffDigestTool;
|
|
25562
|
+
var init_diffDigest2 = __esm({
|
|
25563
|
+
"../../src/tools/diffDigest.ts"() {
|
|
25564
|
+
"use strict";
|
|
25565
|
+
init_diffDigest();
|
|
25566
|
+
init_shellQuote();
|
|
25567
|
+
NAME_STATUS_MARK = "@@diff_digest:name-status@@";
|
|
25568
|
+
NAME_STATUS_MARK_LINE = /^@@diff_digest:name-status@@$/m;
|
|
25569
|
+
OUTPUT_CUT_RE = /^\.\.\.\[truncated \d+ chars\]$/m;
|
|
25570
|
+
diffDigestTool = {
|
|
25571
|
+
sideEffectFree: true,
|
|
25572
|
+
name: "diff_digest",
|
|
25573
|
+
description: "Summarize the current branch's whole change against a base ref as a compact digest: per-file +adds/-dels, totals, and risky-file flags (migrations/schema, auth/permission, whole-file deletions, lockfiles, very large files). This is a DISTILLED summary, not the raw diff \u2014 use it to shape a PR description, or to orient before a review. Runs `git diff --numstat` and `--name-status` over the merge-base range `<base>...HEAD` in the workspace, so it covers every file however large the change; base defaults to the repo's default branch (origin/HEAD). The totals it states are the whole change \u2014 a diff you read that shows fewer files or lines was cut short.",
|
|
25574
|
+
inputSchema: {
|
|
25575
|
+
type: "object",
|
|
25576
|
+
properties: {
|
|
25577
|
+
base: {
|
|
25578
|
+
type: "string",
|
|
25579
|
+
description: "Base ref to diff against (e.g. 'main' or a SHA). Defaults to the repo's default branch via origin/HEAD."
|
|
25580
|
+
}
|
|
25581
|
+
}
|
|
25582
|
+
},
|
|
25583
|
+
async run(input, ctx) {
|
|
25584
|
+
const base = String(input.base ?? "").trim();
|
|
25585
|
+
const baseName = base || "origin/HEAD";
|
|
25586
|
+
if (base.startsWith("-")) {
|
|
25587
|
+
return "diff_digest: base ref may not start with '-' (rejected to prevent git option injection).";
|
|
25588
|
+
}
|
|
25589
|
+
const baseExpr = base ? shellQuote(base) : '"$(git rev-parse --abbrev-ref origin/HEAD 2>/dev/null || echo origin/main)"';
|
|
25590
|
+
const range = `--end-of-options ${baseExpr}...HEAD`;
|
|
25591
|
+
const listing = `git diff --numstat ${range} && echo ${NAME_STATUS_MARK} && git diff --name-status ${range}`;
|
|
25592
|
+
let raw = await ctx.executor.exec(listing);
|
|
25593
|
+
const incomplete = () => {
|
|
25594
|
+
const reason = "the file listing exceeded the executor's output cap";
|
|
25595
|
+
ctx.onDigest?.({ complete: false, base: baseName, reason });
|
|
25596
|
+
return `diff_digest: ${reason} \u2014 the digest cannot state totals for this change (base \`${baseName}\`). Read the diff file by file (\`git diff --name-only ${baseName}...HEAD\`).`;
|
|
25597
|
+
};
|
|
25598
|
+
if (OUTPUT_CUT_RE.test(raw)) return incomplete();
|
|
25599
|
+
let marker = NAME_STATUS_MARK_LINE.exec(raw);
|
|
25600
|
+
if (!marker) {
|
|
25601
|
+
const shallow = (await ctx.executor.exec("git rev-parse --is-shallow-repository")).trim() === "true";
|
|
25602
|
+
if (shallow) {
|
|
25603
|
+
await ctx.executor.exec('git fetch --unshallow --quiet origin "+refs/heads/*:refs/remotes/origin/*"');
|
|
25604
|
+
raw = await ctx.executor.exec(listing);
|
|
25605
|
+
if (OUTPUT_CUT_RE.test(raw)) return incomplete();
|
|
25606
|
+
marker = NAME_STATUS_MARK_LINE.exec(raw);
|
|
25607
|
+
}
|
|
25608
|
+
}
|
|
25609
|
+
if (!marker) {
|
|
25610
|
+
return `diff_digest: could not compute the diff (base \`${baseName}\`).
|
|
25611
|
+
${raw.trim()}`;
|
|
25612
|
+
}
|
|
25613
|
+
const digest = distillDiffStats(raw.slice(0, marker.index), raw.slice(marker.index + marker[0].length));
|
|
25614
|
+
ctx.onDigest?.({ complete: true, base: baseName, totals: digest.totals });
|
|
25615
|
+
return digest.text;
|
|
25616
|
+
}
|
|
25617
|
+
};
|
|
25618
|
+
}
|
|
25619
|
+
});
|
|
25620
|
+
|
|
25633
25621
|
// ../../src/tools/github.ts
|
|
25634
25622
|
function repoOf(input) {
|
|
25635
25623
|
const raw = String(input.repo ?? "").trim().replace(/\.git$/i, "");
|
|
25636
|
-
if (!
|
|
25624
|
+
if (!REPO_RE.test(raw))
|
|
25637
25625
|
return {
|
|
25638
25626
|
error: `repo must be an owner/name slug (got ${JSON.stringify(raw)}); github_repos lists the repos you can reach.`
|
|
25639
25627
|
};
|
|
@@ -25681,12 +25669,12 @@ function writeGate(tool, ctx, repo) {
|
|
|
25681
25669
|
return `${tool}: you are not allowed to write to ${repo} (it is restricted and you hold no grant for it) \u2014 say so to the user instead of retrying.`;
|
|
25682
25670
|
return void 0;
|
|
25683
25671
|
}
|
|
25684
|
-
var
|
|
25672
|
+
var REPO_RE, UNAVAILABLE, MAX_TREE_ENTRIES, MAX_ISSUE_BODY_SHOWN, githubReposTool, githubFileTool, githubTreeTool, githubSearchCodeTool, githubIssueListTool, githubIssueGetTool, githubIssueCreateTool, githubIssueUpdateTool, githubIssueCommentTool, githubIssueDeleteTool, GITHUB_READ_TOOLS, GITHUB_ISSUE_WRITE_TOOLS;
|
|
25685
25673
|
var init_github = __esm({
|
|
25686
25674
|
"../../src/tools/github.ts"() {
|
|
25687
25675
|
"use strict";
|
|
25688
25676
|
init_githubApi();
|
|
25689
|
-
|
|
25677
|
+
REPO_RE = /^[A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?\/[A-Za-z0-9](?:[A-Za-z0-9._-]{0,98}[A-Za-z0-9])?$/;
|
|
25690
25678
|
UNAVAILABLE = "GitHub tools are not available in this context.";
|
|
25691
25679
|
MAX_TREE_ENTRIES = 300;
|
|
25692
25680
|
MAX_ISSUE_BODY_SHOWN = 6e3;
|
|
@@ -26043,275 +26031,6 @@ ${url}`;
|
|
|
26043
26031
|
}
|
|
26044
26032
|
});
|
|
26045
26033
|
|
|
26046
|
-
// ../../src/tools/skills.ts
|
|
26047
|
-
var listSkillsTool, useSkillTool;
|
|
26048
|
-
var init_skills = __esm({
|
|
26049
|
-
"../../src/tools/skills.ts"() {
|
|
26050
|
-
"use strict";
|
|
26051
|
-
listSkillsTool = {
|
|
26052
|
-
sideEffectFree: true,
|
|
26053
|
-
name: "list_skills",
|
|
26054
|
-
description: "List the skills available to you: each is a reusable methodology you can load into context with use_skill. Returns each skill's name and a one-line description. The same list is also in your system prompt.",
|
|
26055
|
-
inputSchema: {
|
|
26056
|
-
type: "object",
|
|
26057
|
-
properties: {}
|
|
26058
|
-
},
|
|
26059
|
-
async run(_input, ctx) {
|
|
26060
|
-
if (!ctx.skills) return "Skills are not available in this context.";
|
|
26061
|
-
const metas = ctx.skills.list(ctx.agentName ?? "");
|
|
26062
|
-
if (metas.length === 0) return "No skills are available to you.";
|
|
26063
|
-
return metas.map((m) => `- ${m.name}: ${m.description}`).join("\n");
|
|
26064
|
-
}
|
|
26065
|
-
};
|
|
26066
|
-
useSkillTool = {
|
|
26067
|
-
sideEffectFree: true,
|
|
26068
|
-
name: "use_skill",
|
|
26069
|
-
description: "Load a skill's full instructions into your context by name (get names from list_skills or your system prompt). Returns the skill body \u2014 read it and follow it for the work at hand.",
|
|
26070
|
-
inputSchema: {
|
|
26071
|
-
type: "object",
|
|
26072
|
-
properties: {
|
|
26073
|
-
name: { type: "string", description: "The skill name to load (e.g. code-review-and-quality)" }
|
|
26074
|
-
},
|
|
26075
|
-
required: ["name"]
|
|
26076
|
-
},
|
|
26077
|
-
async run(input, ctx) {
|
|
26078
|
-
if (!ctx.skills) return "Skills are not available in this context.";
|
|
26079
|
-
const name = String(input.name ?? "").trim();
|
|
26080
|
-
if (!name) return "use_skill: provide a skill `name` (see list_skills).";
|
|
26081
|
-
const skill = ctx.skills.get(name);
|
|
26082
|
-
const inScope = skill && skill.agents.includes(ctx.agentName ?? "");
|
|
26083
|
-
if (!skill || !inScope) {
|
|
26084
|
-
const available = ctx.skills.list(ctx.agentName ?? "").map((m) => m.name);
|
|
26085
|
-
return `use_skill: no skill named "${name}" is available to you.` + (available.length > 0 ? ` Available: ${available.join(", ")}.` : "");
|
|
26086
|
-
}
|
|
26087
|
-
ctx.publish?.({
|
|
26088
|
-
type: "skill_use",
|
|
26089
|
-
skill: skill.name,
|
|
26090
|
-
description: skill.description,
|
|
26091
|
-
agent: ctx.agentName ?? "",
|
|
26092
|
-
...skill.source ? { source: skill.source } : {},
|
|
26093
|
-
...skill.upstream ? { upstream: { repo: skill.upstream.repo, commit: skill.upstream.commit } } : {},
|
|
26094
|
-
bodyBytes: Buffer.byteLength(skill.body, "utf8")
|
|
26095
|
-
});
|
|
26096
|
-
const footer = skill.source ? `
|
|
26097
|
-
|
|
26098
|
-
_(Skill source: ${skill.source})_` : "";
|
|
26099
|
-
return `# Skill: ${skill.name}
|
|
26100
|
-
|
|
26101
|
-
${skill.body}${footer}`;
|
|
26102
|
-
}
|
|
26103
|
-
};
|
|
26104
|
-
}
|
|
26105
|
-
});
|
|
26106
|
-
|
|
26107
|
-
// ../../src/artifacts/contentType.ts
|
|
26108
|
-
function contentTypeFor(name) {
|
|
26109
|
-
const dot = name.lastIndexOf(".");
|
|
26110
|
-
if (dot < 0 || dot === name.length - 1) return OCTET_STREAM;
|
|
26111
|
-
return BY_EXTENSION[name.slice(dot + 1).toLowerCase()] ?? OCTET_STREAM;
|
|
26112
|
-
}
|
|
26113
|
-
var BY_EXTENSION, OCTET_STREAM, INLINE_IMAGE_TYPES;
|
|
26114
|
-
var init_contentType = __esm({
|
|
26115
|
-
"../../src/artifacts/contentType.ts"() {
|
|
26116
|
-
"use strict";
|
|
26117
|
-
BY_EXTENSION = {
|
|
26118
|
-
png: "image/png",
|
|
26119
|
-
jpg: "image/jpeg",
|
|
26120
|
-
jpeg: "image/jpeg",
|
|
26121
|
-
gif: "image/gif",
|
|
26122
|
-
webp: "image/webp",
|
|
26123
|
-
svg: "image/svg+xml",
|
|
26124
|
-
pdf: "application/pdf",
|
|
26125
|
-
mp4: "video/mp4",
|
|
26126
|
-
webm: "video/webm",
|
|
26127
|
-
mov: "video/quicktime",
|
|
26128
|
-
mp3: "audio/mpeg",
|
|
26129
|
-
wav: "audio/wav",
|
|
26130
|
-
txt: "text/plain",
|
|
26131
|
-
md: "text/markdown",
|
|
26132
|
-
csv: "text/csv",
|
|
26133
|
-
json: "application/json",
|
|
26134
|
-
html: "text/html",
|
|
26135
|
-
zip: "application/zip",
|
|
26136
|
-
gz: "application/gzip",
|
|
26137
|
-
tar: "application/x-tar"
|
|
26138
|
-
};
|
|
26139
|
-
OCTET_STREAM = "application/octet-stream";
|
|
26140
|
-
INLINE_IMAGE_TYPES = /* @__PURE__ */ new Set(["image/png", "image/jpeg", "image/gif", "image/webp"]);
|
|
26141
|
-
}
|
|
26142
|
-
});
|
|
26143
|
-
|
|
26144
|
-
// ../../src/artifacts/keys.ts
|
|
26145
|
-
function safeBasename(name) {
|
|
26146
|
-
const segments2 = name.split(/[\\/]+/).filter((s) => s.length > 0);
|
|
26147
|
-
const last = segments2[segments2.length - 1] ?? "";
|
|
26148
|
-
const reduced = last.replace(SAFE, "_").replace(/\.{2,}/g, "_").replace(/_+/g, "_");
|
|
26149
|
-
if (reduced === "" || /^[._-]+$/.test(reduced)) return "file";
|
|
26150
|
-
return reduced;
|
|
26151
|
-
}
|
|
26152
|
-
function threadKeySafe(threadKey) {
|
|
26153
|
-
return threadKey.replace(/:/g, "-").replace(SAFE, "_");
|
|
26154
|
-
}
|
|
26155
|
-
function outboundKey(runId3, seq, name) {
|
|
26156
|
-
return `runs/${runId3}/out/${seq}-${safeBasename(name)}`;
|
|
26157
|
-
}
|
|
26158
|
-
function inboundKey(threadKey, messageTs, index, name) {
|
|
26159
|
-
return `threads/${threadKeySafe(threadKey)}/in/${messageTs}/${index}-${safeBasename(name)}`;
|
|
26160
|
-
}
|
|
26161
|
-
var SAFE;
|
|
26162
|
-
var init_keys = __esm({
|
|
26163
|
-
"../../src/artifacts/keys.ts"() {
|
|
26164
|
-
"use strict";
|
|
26165
|
-
SAFE = /[^A-Za-z0-9._-]+/g;
|
|
26166
|
-
}
|
|
26167
|
-
});
|
|
26168
|
-
|
|
26169
|
-
// ../../src/tools/attach.ts
|
|
26170
|
-
function transferBudgetMs(size) {
|
|
26171
|
-
return Math.ceil(size / TRANSFER_FLOOR_BYTES_PER_MS) + TRANSFER_SETUP_MS;
|
|
26172
|
-
}
|
|
26173
|
-
function fileNameOf2(path) {
|
|
26174
|
-
const segments2 = path.split("/").filter(Boolean);
|
|
26175
|
-
return segments2[segments2.length - 1] ?? path;
|
|
26176
|
-
}
|
|
26177
|
-
function putCommandFor(path, contentType, url) {
|
|
26178
|
-
return `curl -fsS -T ${shellQuote(path)} -H ${shellQuote(`Content-Type: ${contentType}`)} -o /dev/null -w ${shellQuote(PUT_REPORT_FORMAT)} ${shellQuote(url)}`;
|
|
26179
|
-
}
|
|
26180
|
-
function parsePutReport(output) {
|
|
26181
|
-
const m = /(\d{3}) (\d{1,15})\s*$/.exec(output);
|
|
26182
|
-
return m ? { httpCode: Number(m[1]), sentBytes: Number(m[2]) } : null;
|
|
26183
|
-
}
|
|
26184
|
-
function postCommandFor(path, url) {
|
|
26185
|
-
return `curl -fsS --upload-file ${shellQuote(path)} -X POST ${shellQuote(url)}`;
|
|
26186
|
-
}
|
|
26187
|
-
async function attachThroughStore(path, name, lead, artifacts, ctx) {
|
|
26188
|
-
const exec = (command2, timeoutMs2) => {
|
|
26189
|
-
const opts = { timeoutMs: timeoutMs2, ...ctx.signal ? { signal: ctx.signal } : {} };
|
|
26190
|
-
return ctx.executor.exec(command2, opts);
|
|
26191
|
-
};
|
|
26192
|
-
const statOut = await exec(statCommandFor(shellQuote(path)), STAT_TIMEOUT_MS);
|
|
26193
|
-
if (parseExitPrefix(statOut).failed) return `error: could not read ${path}: ${statOut.trim()}`;
|
|
26194
|
-
const size = parseByteSize(statOut);
|
|
26195
|
-
if (size === null) return `error: could not measure ${path}: ${statOut.trim()}`;
|
|
26196
|
-
if (size === 0) return `error: ${path} is empty \u2014 nothing to attach`;
|
|
26197
|
-
if (size > MAX_ARTIFACT_BYTES) {
|
|
26198
|
-
return `error: ${path} is ${size} bytes; attach_file takes files up to ${MAX_ARTIFACT_BYTES} bytes (1 GiB) \u2014 link to the file instead`;
|
|
26199
|
-
}
|
|
26200
|
-
let timeoutMs = BASH_TIMEOUT_MAX_MS;
|
|
26201
|
-
if (ctx.remainingMs) {
|
|
26202
|
-
const budget = bashBudgetWithinRun(BASH_TIMEOUT_MAX_MS, ctx.remainingMs());
|
|
26203
|
-
if (budget.kind === "exhausted") return `error: ${budget.note}`;
|
|
26204
|
-
if (budget.kind === "clipped") timeoutMs = budget.timeoutMs;
|
|
26205
|
-
}
|
|
26206
|
-
const needed = transferBudgetMs(size);
|
|
26207
|
-
if (needed > timeoutMs) {
|
|
26208
|
-
return `error: ${name} is ${size} bytes and needs about ${secs(needed)}s to move at ${TRANSFER_FLOOR_BYTES_PER_MS} KB/s; the run has ${secs(timeoutMs)}s of command budget left \u2014 write up what you have and link to the file instead`;
|
|
26209
|
-
}
|
|
26210
|
-
const contentType = contentTypeFor(name);
|
|
26211
|
-
const key = outboundKey(artifacts.runId, artifacts.nextSeq(), name);
|
|
26212
|
-
const putUrl = await artifacts.store.presignPut(key, contentType);
|
|
26213
|
-
const putOut = await exec(putCommandFor(path, contentType, putUrl), timeoutMs);
|
|
26214
|
-
if (parseExitPrefix(putOut).failed) {
|
|
26215
|
-
return `error: the upload of ${name} to the artifact store failed: ${putOut.trim()}`;
|
|
26216
|
-
}
|
|
26217
|
-
const report2 = parsePutReport(putOut);
|
|
26218
|
-
const curlSaid = report2 ? `; curl reported HTTP ${report2.httpCode} after sending ${report2.sentBytes} bytes` : "";
|
|
26219
|
-
const head = await artifacts.store.head(key);
|
|
26220
|
-
if (!head) {
|
|
26221
|
-
return `error: the artifact store holds nothing under ${key} after the upload of ${name}${curlSaid} \u2014 nothing was posted`;
|
|
26222
|
-
}
|
|
26223
|
-
if (head.size !== size) {
|
|
26224
|
-
return `error: the artifact store holds ${head.size} bytes for ${name}, not the ${size} measured${curlSaid} \u2014 nothing was posted`;
|
|
26225
|
-
}
|
|
26226
|
-
ctx.publish?.({ type: "artifact", direction: "out", key, name, size, contentType });
|
|
26227
|
-
if (!ctx.uploadTicket) {
|
|
26228
|
-
const link = artifacts.artifactUrl?.(key);
|
|
26229
|
-
const where = link ? ` \u2014 ${link}` : ` is on the run page as ${key}`;
|
|
26230
|
-
await artifacts.reply(`${lead}
|
|
26231
|
-
\u{1F4CE} ${name} (${size} bytes)${where}`);
|
|
26232
|
-
return `attached ${name} (${size} bytes) to the run page as ${key}; this conversation's channel takes no file uploads, so the lead and the file's link were posted instead`;
|
|
26233
|
-
}
|
|
26234
|
-
const kept = "the file is kept on the run page";
|
|
26235
|
-
let ticket;
|
|
26236
|
-
try {
|
|
26237
|
-
ticket = await ctx.uploadTicket({ name, size });
|
|
26238
|
-
} catch (err2) {
|
|
26239
|
-
return `error: the channel refused an upload ticket for ${name}: ${describe2(err2)}; ${kept}`;
|
|
26240
|
-
}
|
|
26241
|
-
const postOut = await exec(postCommandFor(path, ticket.url), timeoutMs);
|
|
26242
|
-
if (parseExitPrefix(postOut).failed) {
|
|
26243
|
-
return `error: the upload of ${name} to the channel failed: ${postOut.trim()}; ${kept}`;
|
|
26244
|
-
}
|
|
26245
|
-
try {
|
|
26246
|
-
await ticket.complete(lead);
|
|
26247
|
-
} catch (err2) {
|
|
26248
|
-
return `error: the channel refused to complete the upload of ${name}: ${describe2(err2)}; ${kept}`;
|
|
26249
|
-
}
|
|
26250
|
-
return `attached ${name} (${size} bytes) to the conversation and the run page`;
|
|
26251
|
-
}
|
|
26252
|
-
var MAX_ARTIFACT_BYTES, TRANSFER_FLOOR_BYTES_PER_MS, TRANSFER_SETUP_MS, STAT_TIMEOUT_MS, describe2, secs, PUT_REPORT_FORMAT, attachFileTool;
|
|
26253
|
-
var init_attach = __esm({
|
|
26254
|
-
"../../src/tools/attach.ts"() {
|
|
26255
|
-
"use strict";
|
|
26256
|
-
init_contentType();
|
|
26257
|
-
init_keys();
|
|
26258
|
-
init_runEvents();
|
|
26259
|
-
init_bashTimeout();
|
|
26260
|
-
init_binaryRead();
|
|
26261
|
-
init_shellQuote();
|
|
26262
|
-
MAX_ARTIFACT_BYTES = 1073741824;
|
|
26263
|
-
TRANSFER_FLOOR_BYTES_PER_MS = 1024;
|
|
26264
|
-
TRANSFER_SETUP_MS = 1e4;
|
|
26265
|
-
STAT_TIMEOUT_MS = 3e4;
|
|
26266
|
-
describe2 = (err2) => err2 instanceof Error ? err2.message : String(err2);
|
|
26267
|
-
secs = (ms) => Math.max(0, Math.round(ms / 1e3));
|
|
26268
|
-
PUT_REPORT_FORMAT = "\\n%{http_code} %{size_upload}";
|
|
26269
|
-
attachFileTool = {
|
|
26270
|
-
name: "attach_file",
|
|
26271
|
-
failsInText: true,
|
|
26272
|
-
description: `Post a file from the workspace into the conversation so the person sees it inline \u2014 a screenshot (e.g. from \`playwright screenshot\`), a rendered PDF, a recording, a log. Use it whenever you produce an image worth showing: a link to a file is not a picture. Whole files only: up to ${MAX_ARTIFACT_BYTES} bytes (1 GiB) where the artifact store is configured, ${MAX_READ_BYTES} bytes otherwise \u2014 the result says which.`,
|
|
26273
|
-
inputSchema: {
|
|
26274
|
-
type: "object",
|
|
26275
|
-
properties: {
|
|
26276
|
-
path: { type: "string", description: "Relative path of the file in the workspace" },
|
|
26277
|
-
comment: {
|
|
26278
|
-
type: "string",
|
|
26279
|
-
description: "One line posted with the file saying what it shows (default: the file name)"
|
|
26280
|
-
}
|
|
26281
|
-
},
|
|
26282
|
-
required: ["path"]
|
|
26283
|
-
},
|
|
26284
|
-
async run(input, ctx) {
|
|
26285
|
-
const path = String(input.path ?? "").trim();
|
|
26286
|
-
if (!path) return "error: path is required";
|
|
26287
|
-
const name = fileNameOf2(path);
|
|
26288
|
-
const lead = typeof input.comment === "string" && input.comment.trim() ? input.comment.trim() : name;
|
|
26289
|
-
if (ctx.artifacts) return attachThroughStore(path, name, lead, ctx.artifacts, ctx);
|
|
26290
|
-
if (!ctx.attach) {
|
|
26291
|
-
return "attach_file is not available here: this conversation's channel takes no file uploads \u2014 link to the file instead";
|
|
26292
|
-
}
|
|
26293
|
-
const readBytes = ctx.executor.readBytes?.bind(ctx.executor);
|
|
26294
|
-
if (!readBytes) {
|
|
26295
|
-
return "attach_file is not available here: this workspace cannot hand files over \u2014 link to the file instead";
|
|
26296
|
-
}
|
|
26297
|
-
let bytes;
|
|
26298
|
-
try {
|
|
26299
|
-
bytes = await readBytes(path);
|
|
26300
|
-
} catch (err2) {
|
|
26301
|
-
return `error: could not read ${path}: ${describe2(err2)}`;
|
|
26302
|
-
}
|
|
26303
|
-
if (bytes.byteLength === 0) return `error: ${path} is empty \u2014 nothing to attach`;
|
|
26304
|
-
try {
|
|
26305
|
-
await ctx.attach({ name, bytes, lead });
|
|
26306
|
-
} catch (err2) {
|
|
26307
|
-
return `error: the channel refused the upload of ${name}: ${describe2(err2)}`;
|
|
26308
|
-
}
|
|
26309
|
-
return `attached ${name} (${bytes.byteLength} bytes) to the conversation`;
|
|
26310
|
-
}
|
|
26311
|
-
};
|
|
26312
|
-
}
|
|
26313
|
-
});
|
|
26314
|
-
|
|
26315
26034
|
// ../../src/core/dispatch/awaitChildren.ts
|
|
26316
26035
|
function decideWait(inputs) {
|
|
26317
26036
|
const { children, now, budgetEndsAt, timeoutAt, stop, followUpPending } = inputs;
|
|
@@ -27077,7 +26796,8 @@ async function copyStaged(files, deps) {
|
|
|
27077
26796
|
key,
|
|
27078
26797
|
name: file.name,
|
|
27079
26798
|
size: file.size,
|
|
27080
|
-
contentType: file.type
|
|
26799
|
+
contentType: file.type,
|
|
26800
|
+
messageId: file.messageId
|
|
27081
26801
|
});
|
|
27082
26802
|
return { file, basename: basename4, key };
|
|
27083
26803
|
} catch (err2) {
|
|
@@ -27136,7 +26856,8 @@ function stageThreadArtifacts(artifacts, deps) {
|
|
|
27136
26856
|
key: a.key,
|
|
27137
26857
|
name: a.name,
|
|
27138
26858
|
size: a.size,
|
|
27139
|
-
contentType: a.contentType
|
|
26859
|
+
contentType: a.contentType,
|
|
26860
|
+
messageId: deps.messageId
|
|
27140
26861
|
});
|
|
27141
26862
|
outcomes.push({ file, basename: basename4, key: a.key, earlier: true });
|
|
27142
26863
|
}
|
|
@@ -27442,145 +27163,444 @@ var init_session = __esm({
|
|
|
27442
27163
|
}
|
|
27443
27164
|
});
|
|
27444
27165
|
|
|
27445
|
-
// ../../src/tools/
|
|
27446
|
-
|
|
27447
|
-
var
|
|
27448
|
-
|
|
27449
|
-
"../../src/tools/workspace.ts"() {
|
|
27166
|
+
// ../../src/tools/skills.ts
|
|
27167
|
+
var listSkillsTool, useSkillTool;
|
|
27168
|
+
var init_skills = __esm({
|
|
27169
|
+
"../../src/tools/skills.ts"() {
|
|
27450
27170
|
"use strict";
|
|
27451
|
-
|
|
27452
|
-
init_handoff();
|
|
27453
|
-
init_reviewVerdict();
|
|
27454
|
-
init_bashTimeout();
|
|
27455
|
-
init_executor();
|
|
27456
|
-
init_shellQuote();
|
|
27457
|
-
init_diffDigest();
|
|
27458
|
-
init_web();
|
|
27459
|
-
init_github();
|
|
27460
|
-
init_skills();
|
|
27461
|
-
init_attach();
|
|
27462
|
-
init_runs2();
|
|
27463
|
-
init_session();
|
|
27464
|
-
bashTool = {
|
|
27465
|
-
name: "bash",
|
|
27466
|
-
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.",
|
|
27467
|
-
inputSchema: {
|
|
27468
|
-
type: "object",
|
|
27469
|
-
properties: {
|
|
27470
|
-
command: { type: "string", description: "The bash command to run" },
|
|
27471
|
-
timeoutMs: {
|
|
27472
|
-
type: "integer",
|
|
27473
|
-
description: "Optional timeout in milliseconds for this command (default 300000 = 5 min; max 1200000 = 20 min). Values outside [1000, 1200000] are clamped."
|
|
27474
|
-
}
|
|
27475
|
-
},
|
|
27476
|
-
required: ["command"]
|
|
27477
|
-
},
|
|
27478
|
-
async run(input, ctx) {
|
|
27479
|
-
const requested = input.timeoutMs;
|
|
27480
|
-
let timeoutMs = typeof requested === "number" && Number.isFinite(requested) ? clampBashTimeout(requested) : void 0;
|
|
27481
|
-
let clipNote = "";
|
|
27482
|
-
if (ctx.remainingMs) {
|
|
27483
|
-
const budget = bashBudgetWithinRun(timeoutMs ?? BASH_TIMEOUT_MS, ctx.remainingMs());
|
|
27484
|
-
if (budget.kind === "exhausted") return `exit 124:
|
|
27485
|
-
${budget.note}`;
|
|
27486
|
-
if (budget.kind === "clipped") {
|
|
27487
|
-
timeoutMs = budget.timeoutMs;
|
|
27488
|
-
clipNote = budget.note;
|
|
27489
|
-
}
|
|
27490
|
-
}
|
|
27491
|
-
const opts = {
|
|
27492
|
-
...ctx.signal ? { signal: ctx.signal } : {},
|
|
27493
|
-
...timeoutMs !== void 0 ? { timeoutMs } : {}
|
|
27494
|
-
};
|
|
27495
|
-
const out = await ctx.executor.exec(
|
|
27496
|
-
String(input.command ?? ""),
|
|
27497
|
-
opts.signal !== void 0 || opts.timeoutMs !== void 0 ? opts : void 0
|
|
27498
|
-
);
|
|
27499
|
-
return clipNote ? `${out}
|
|
27500
|
-
${clipNote}` : out;
|
|
27501
|
-
}
|
|
27502
|
-
};
|
|
27503
|
-
readFileTool = {
|
|
27171
|
+
listSkillsTool = {
|
|
27504
27172
|
sideEffectFree: true,
|
|
27505
|
-
name: "
|
|
27506
|
-
description: "
|
|
27173
|
+
name: "list_skills",
|
|
27174
|
+
description: "List the skills available to you: each is a reusable methodology you can load into context with use_skill. Returns each skill's name and a one-line description. The same list is also in your system prompt.",
|
|
27507
27175
|
inputSchema: {
|
|
27508
27176
|
type: "object",
|
|
27509
|
-
properties: {
|
|
27510
|
-
path: { type: "string", description: "Relative path to the file" }
|
|
27511
|
-
},
|
|
27512
|
-
required: ["path"]
|
|
27177
|
+
properties: {}
|
|
27513
27178
|
},
|
|
27514
|
-
run(
|
|
27515
|
-
|
|
27179
|
+
async run(_input, ctx) {
|
|
27180
|
+
if (!ctx.skills) return "Skills are not available in this context.";
|
|
27181
|
+
const metas = ctx.skills.list(ctx.agentName ?? "");
|
|
27182
|
+
if (metas.length === 0) return "No skills are available to you.";
|
|
27183
|
+
return metas.map((m) => `- ${m.name}: ${m.description}`).join("\n");
|
|
27516
27184
|
}
|
|
27517
27185
|
};
|
|
27518
|
-
|
|
27519
|
-
|
|
27520
|
-
|
|
27186
|
+
useSkillTool = {
|
|
27187
|
+
sideEffectFree: true,
|
|
27188
|
+
name: "use_skill",
|
|
27189
|
+
description: "Load a skill's full instructions into your context by name (get names from list_skills or your system prompt). Returns the skill body \u2014 read it and follow it for the work at hand.",
|
|
27521
27190
|
inputSchema: {
|
|
27522
27191
|
type: "object",
|
|
27523
27192
|
properties: {
|
|
27524
|
-
|
|
27525
|
-
content: { type: "string", description: "Full file contents" }
|
|
27193
|
+
name: { type: "string", description: "The skill name to load (e.g. code-review-and-quality)" }
|
|
27526
27194
|
},
|
|
27527
|
-
required: ["
|
|
27195
|
+
required: ["name"]
|
|
27528
27196
|
},
|
|
27529
|
-
run(input, ctx) {
|
|
27530
|
-
|
|
27197
|
+
async run(input, ctx) {
|
|
27198
|
+
if (!ctx.skills) return "Skills are not available in this context.";
|
|
27199
|
+
const name = String(input.name ?? "").trim();
|
|
27200
|
+
if (!name) return "use_skill: provide a skill `name` (see list_skills).";
|
|
27201
|
+
const skill = ctx.skills.get(name);
|
|
27202
|
+
const inScope = skill && skill.agents.includes(ctx.agentName ?? "");
|
|
27203
|
+
if (!skill || !inScope) {
|
|
27204
|
+
const available = ctx.skills.list(ctx.agentName ?? "").map((m) => m.name);
|
|
27205
|
+
return `use_skill: no skill named "${name}" is available to you.` + (available.length > 0 ? ` Available: ${available.join(", ")}.` : "");
|
|
27206
|
+
}
|
|
27207
|
+
ctx.publish?.({
|
|
27208
|
+
type: "skill_use",
|
|
27209
|
+
skill: skill.name,
|
|
27210
|
+
description: skill.description,
|
|
27211
|
+
agent: ctx.agentName ?? "",
|
|
27212
|
+
...skill.source ? { source: skill.source } : {},
|
|
27213
|
+
...skill.upstream ? { upstream: { repo: skill.upstream.repo, commit: skill.upstream.commit } } : {},
|
|
27214
|
+
bodyBytes: Buffer.byteLength(skill.body, "utf8")
|
|
27215
|
+
});
|
|
27216
|
+
const footer = skill.source ? `
|
|
27217
|
+
|
|
27218
|
+
_(Skill source: ${skill.source})_` : "";
|
|
27219
|
+
return `# Skill: ${skill.name}
|
|
27220
|
+
|
|
27221
|
+
${skill.body}${footer}`;
|
|
27531
27222
|
}
|
|
27532
27223
|
};
|
|
27533
|
-
|
|
27534
|
-
|
|
27535
|
-
|
|
27536
|
-
|
|
27537
|
-
|
|
27538
|
-
|
|
27539
|
-
|
|
27224
|
+
}
|
|
27225
|
+
});
|
|
27226
|
+
|
|
27227
|
+
// ../../src/tools/status.ts
|
|
27228
|
+
var updateStatusTool;
|
|
27229
|
+
var init_status2 = __esm({
|
|
27230
|
+
"../../src/tools/status.ts"() {
|
|
27231
|
+
"use strict";
|
|
27232
|
+
updateStatusTool = {
|
|
27233
|
+
name: "update_status",
|
|
27234
|
+
description: "Update the short user-facing status checklist shown while you work. Call it right after planning (all items pending) and again whenever an item's state changes. Format: one item per line, prefixed with a state marker: \u2713 done, \u2731 in progress, \u25CB pending. Keep it to 3-6 short outcome-oriented items (what, not how \u2014 never raw commands).",
|
|
27540
27235
|
inputSchema: {
|
|
27541
27236
|
type: "object",
|
|
27542
27237
|
properties: {
|
|
27543
|
-
|
|
27238
|
+
checklist: {
|
|
27544
27239
|
type: "string",
|
|
27545
|
-
description: "
|
|
27240
|
+
description: "The full checklist, one '\u2713|\u2731|\u25CB item' per line (replaces the previous one)"
|
|
27546
27241
|
}
|
|
27547
|
-
}
|
|
27242
|
+
},
|
|
27243
|
+
required: ["checklist"]
|
|
27548
27244
|
},
|
|
27549
27245
|
async run(input, ctx) {
|
|
27550
|
-
|
|
27551
|
-
|
|
27552
|
-
if (base.startsWith("-")) {
|
|
27553
|
-
return "diff_digest: base ref may not start with '-' (rejected to prevent git option injection).";
|
|
27554
|
-
}
|
|
27555
|
-
const baseExpr = base ? shellQuote(base) : '"$(git rev-parse --abbrev-ref origin/HEAD 2>/dev/null || echo origin/main)"';
|
|
27556
|
-
const range = `--end-of-options ${baseExpr}...HEAD`;
|
|
27557
|
-
const listing = `git diff --numstat ${range} && echo ${NAME_STATUS_MARK} && git diff --name-status ${range}`;
|
|
27558
|
-
let raw = await ctx.executor.exec(listing);
|
|
27559
|
-
const incomplete = () => {
|
|
27560
|
-
const reason = "the file listing exceeded the executor's output cap";
|
|
27561
|
-
ctx.onDigest?.({ complete: false, base: baseName, reason });
|
|
27562
|
-
return `diff_digest: ${reason} \u2014 the digest cannot state totals for this change (base \`${baseName}\`). Read the diff file by file (\`git diff --name-only ${baseName}...HEAD\`).`;
|
|
27563
|
-
};
|
|
27564
|
-
if (OUTPUT_CUT_RE.test(raw)) return incomplete();
|
|
27565
|
-
let marker = NAME_STATUS_MARK_LINE.exec(raw);
|
|
27566
|
-
if (!marker) {
|
|
27567
|
-
const shallow = (await ctx.executor.exec("git rev-parse --is-shallow-repository")).trim() === "true";
|
|
27568
|
-
if (shallow) {
|
|
27569
|
-
await ctx.executor.exec('git fetch --unshallow --quiet origin "+refs/heads/*:refs/remotes/origin/*"');
|
|
27570
|
-
raw = await ctx.executor.exec(listing);
|
|
27571
|
-
if (OUTPUT_CUT_RE.test(raw)) return incomplete();
|
|
27572
|
-
marker = NAME_STATUS_MARK_LINE.exec(raw);
|
|
27573
|
-
}
|
|
27574
|
-
}
|
|
27575
|
-
if (!marker) {
|
|
27576
|
-
return `diff_digest: could not compute the diff (base \`${baseName}\`).
|
|
27577
|
-
${raw.trim()}`;
|
|
27578
|
-
}
|
|
27579
|
-
const digest = distillDiffStats(raw.slice(0, marker.index), raw.slice(marker.index + marker[0].length));
|
|
27580
|
-
ctx.onDigest?.({ complete: true, base: baseName, totals: digest.totals });
|
|
27581
|
-
return digest.text;
|
|
27246
|
+
ctx.reportProgress?.(String(input.checklist ?? ""));
|
|
27247
|
+
return "status updated";
|
|
27582
27248
|
}
|
|
27583
27249
|
};
|
|
27250
|
+
}
|
|
27251
|
+
});
|
|
27252
|
+
|
|
27253
|
+
// ../../src/core/prDescription.ts
|
|
27254
|
+
import { z as z20 } from "zod";
|
|
27255
|
+
function parsePrDescription(input) {
|
|
27256
|
+
return PrDescriptionSchema.parse(input);
|
|
27257
|
+
}
|
|
27258
|
+
function redactPrDescription(d) {
|
|
27259
|
+
return mapStringLeaves(d, redactSecrets);
|
|
27260
|
+
}
|
|
27261
|
+
function mapStringLeaves(value, fn) {
|
|
27262
|
+
if (typeof value === "string") return fn(value);
|
|
27263
|
+
if (Array.isArray(value)) return value.map((v) => mapStringLeaves(v, fn));
|
|
27264
|
+
if (typeof value === "object" && value !== null) {
|
|
27265
|
+
return Object.fromEntries(Object.entries(value).map(([key, v]) => [key, mapStringLeaves(v, fn)]));
|
|
27266
|
+
}
|
|
27267
|
+
return value;
|
|
27268
|
+
}
|
|
27269
|
+
function encodeGithubPathSegments(path) {
|
|
27270
|
+
return path.split("/").map(encodeURIComponent).join("/");
|
|
27271
|
+
}
|
|
27272
|
+
function anchorUrl(ctx, a) {
|
|
27273
|
+
return `https://github.com/${ctx.repo}/blob/${ctx.headSha}/${encodeGithubPathSegments(a.path)}#L${a.from}-L${a.to}`;
|
|
27274
|
+
}
|
|
27275
|
+
function cell2(s) {
|
|
27276
|
+
return s.replace(/\\/g, "\\\\").replace(/\|/g, "\\|").replace(/\s*\n\s*/g, " ");
|
|
27277
|
+
}
|
|
27278
|
+
function renderPrDescriptionMarkdown(desc, ctx) {
|
|
27279
|
+
if (!REPO_RE2.test(ctx.repo))
|
|
27280
|
+
throw new Error(`renderPrDescriptionMarkdown: repo must be owner/name, got "${ctx.repo}"`);
|
|
27281
|
+
if (!SHA_RE2.test(ctx.headSha))
|
|
27282
|
+
throw new Error(`renderPrDescriptionMarkdown: headSha must be a full 40-char lowercase sha, got "${ctx.headSha}"`);
|
|
27283
|
+
const out = [];
|
|
27284
|
+
out.push("## TL;DR", "", desc.tldr, "");
|
|
27285
|
+
out.push("## What & why", "", desc.whatWhy, "");
|
|
27286
|
+
out.push("## Tour", "");
|
|
27287
|
+
desc.tour.forEach((step, i) => {
|
|
27288
|
+
out.push(`### ${i + 1}. ${step.title}`, "", step.description, "");
|
|
27289
|
+
if (step.lookFor) out.push(`**Look for:** ${step.lookFor}`, "");
|
|
27290
|
+
out.push(anchorUrl(ctx, step.anchor), "");
|
|
27291
|
+
});
|
|
27292
|
+
out.push(`### ${desc.tour.length + 1}. Remaining changes`, "");
|
|
27293
|
+
if (desc.remaining.length === 0) out.push("- none \u2014 every touched file is covered by a step above", "");
|
|
27294
|
+
else {
|
|
27295
|
+
for (const r of desc.remaining) out.push(`- \`${r.path}\` \u2014 ${r.note}`);
|
|
27296
|
+
out.push("");
|
|
27297
|
+
}
|
|
27298
|
+
out.push("## Decisions", "");
|
|
27299
|
+
for (const d of desc.decisions) out.push(`- **${d.title.replace(/\.?$/, ".")}** ${d.rationale}`);
|
|
27300
|
+
out.push("");
|
|
27301
|
+
out.push("## Risks & implications", "", desc.risks, "");
|
|
27302
|
+
out.push("## Validation", "");
|
|
27303
|
+
if (desc.validation.summary) out.push(desc.validation.summary, "");
|
|
27304
|
+
out.push("| Criterion | Proof |", "|---|---|");
|
|
27305
|
+
for (const c of desc.validation.criteria) out.push(`| ${cell2(c.criterion)} | ${cell2(c.proof)} |`);
|
|
27306
|
+
out.push("", GENERATED_FOOTER, "");
|
|
27307
|
+
return out.join("\n");
|
|
27308
|
+
}
|
|
27309
|
+
function parsePrDescriptionMarkdown(body) {
|
|
27310
|
+
const problems = [];
|
|
27311
|
+
const lines = body.replace(/\r\n?/g, "\n").split("\n").filter((l) => l.trim() !== GENERATED_FOOTER);
|
|
27312
|
+
const fenced = fenceMask(lines);
|
|
27313
|
+
const sections = splitSections(lines, fenced, problems);
|
|
27314
|
+
const description = { tour: [] };
|
|
27315
|
+
const tldr = sections.get("tldr");
|
|
27316
|
+
if (tldr) setProse(description, "tldr", tldr, problems);
|
|
27317
|
+
else {
|
|
27318
|
+
problems.push("no `## TL;DR` section \u2014 the tldr is the body's first paragraph");
|
|
27319
|
+
const first = firstParagraph(lines, fenced);
|
|
27320
|
+
if (first) description.tldr = first;
|
|
27321
|
+
}
|
|
27322
|
+
const whatWhy = sections.get("whatWhy");
|
|
27323
|
+
if (whatWhy) setProse(description, "whatWhy", whatWhy, problems);
|
|
27324
|
+
const tour = sections.get("tour");
|
|
27325
|
+
if (tour) {
|
|
27326
|
+
const parsed = parseTour(tour.lines, fenced.slice(tour.start, tour.start + tour.lines.length), problems);
|
|
27327
|
+
description.tour = parsed.tour;
|
|
27328
|
+
if (parsed.remaining) description.remaining = parsed.remaining;
|
|
27329
|
+
}
|
|
27330
|
+
const decisions = sections.get("decisions");
|
|
27331
|
+
if (decisions) description.decisions = parseDecisions(decisions.lines, problems);
|
|
27332
|
+
const risks = sections.get("risks");
|
|
27333
|
+
if (risks) setProse(description, "risks", risks, problems);
|
|
27334
|
+
const validation = sections.get("validation");
|
|
27335
|
+
if (validation) description.validation = parseValidation(validation.lines, problems);
|
|
27336
|
+
for (const [key, heading] of SECTIONS) {
|
|
27337
|
+
if (key === "tldr" || sections.has(key)) continue;
|
|
27338
|
+
problems.push(`no \`## ${heading}\` section`);
|
|
27339
|
+
}
|
|
27340
|
+
return { description, complete: problems.length === 0, problems };
|
|
27341
|
+
}
|
|
27342
|
+
function fenceMask(lines) {
|
|
27343
|
+
let open2;
|
|
27344
|
+
return lines.map((l) => {
|
|
27345
|
+
const m = /^\s*(`{3,}|~{3,})/.exec(l);
|
|
27346
|
+
if (m && (open2 === void 0 || m[1][0] === open2[0])) {
|
|
27347
|
+
const wasOpen = open2 !== void 0;
|
|
27348
|
+
open2 = wasOpen ? void 0 : m[1];
|
|
27349
|
+
return true;
|
|
27350
|
+
}
|
|
27351
|
+
return open2 !== void 0;
|
|
27352
|
+
});
|
|
27353
|
+
}
|
|
27354
|
+
function splitSections(lines, fenced, problems) {
|
|
27355
|
+
const byHeading = new Map(SECTIONS.map(([key, heading]) => [heading.toLowerCase(), key]));
|
|
27356
|
+
const out = /* @__PURE__ */ new Map();
|
|
27357
|
+
let current;
|
|
27358
|
+
lines.forEach((line3, i) => {
|
|
27359
|
+
const m = fenced[i] ? null : HEADING_RE.exec(line3);
|
|
27360
|
+
if (m && m[1] === "##") {
|
|
27361
|
+
const key = byHeading.get(m[2].replace(/\s+/g, " ").toLowerCase());
|
|
27362
|
+
if (key === void 0)
|
|
27363
|
+
current = void 0;
|
|
27364
|
+
else if (out.has(key)) {
|
|
27365
|
+
problems.push(`duplicate \`## ${SECTIONS.find(([k]) => k === key)[1]}\` section \u2014 the first one stands`);
|
|
27366
|
+
current = void 0;
|
|
27367
|
+
} else {
|
|
27368
|
+
current = { start: i + 1, lines: [] };
|
|
27369
|
+
out.set(key, current);
|
|
27370
|
+
}
|
|
27371
|
+
return;
|
|
27372
|
+
}
|
|
27373
|
+
current?.lines.push(line3);
|
|
27374
|
+
});
|
|
27375
|
+
return out;
|
|
27376
|
+
}
|
|
27377
|
+
function firstParagraph(lines, fenced) {
|
|
27378
|
+
const para = [];
|
|
27379
|
+
for (let i = 0; i < lines.length; i++) {
|
|
27380
|
+
const l = lines[i];
|
|
27381
|
+
if (l.trim() === "") {
|
|
27382
|
+
if (para.length) break;
|
|
27383
|
+
continue;
|
|
27384
|
+
}
|
|
27385
|
+
if (fenced[i] || para.length === 0 && HEADING_RE.test(l)) continue;
|
|
27386
|
+
para.push(l.trim());
|
|
27387
|
+
}
|
|
27388
|
+
return para.length ? para.join("\n") : void 0;
|
|
27389
|
+
}
|
|
27390
|
+
function setProse(description, key, section, problems) {
|
|
27391
|
+
const text = section.lines.join("\n").trim();
|
|
27392
|
+
if (text === "") problems.push(`\`## ${SECTIONS.find(([k]) => k === key)[1]}\` section is empty`);
|
|
27393
|
+
else description[key] = text;
|
|
27394
|
+
}
|
|
27395
|
+
function parseTour(lines, fenced, problems) {
|
|
27396
|
+
const steps = [];
|
|
27397
|
+
let preamble = "";
|
|
27398
|
+
lines.forEach((line3, i) => {
|
|
27399
|
+
const m = fenced[i] ? null : STEP_RE.exec(line3);
|
|
27400
|
+
if (m) steps.push({ label: m[1] ?? String(steps.length + 1), title: m[2], lines: [] });
|
|
27401
|
+
else if (steps.length) steps[steps.length - 1].lines.push(line3);
|
|
27402
|
+
else preamble += line3;
|
|
27403
|
+
});
|
|
27404
|
+
if (preamble.trim() !== "") problems.push("Tour: text before the first `### N.` step is ignored");
|
|
27405
|
+
const isRemaining = (title) => /^remaining changes$/i.test(title);
|
|
27406
|
+
const tour = [];
|
|
27407
|
+
let remaining;
|
|
27408
|
+
for (const step of steps) {
|
|
27409
|
+
if (isRemaining(step.title)) {
|
|
27410
|
+
remaining = parseRemaining(step.lines, problems);
|
|
27411
|
+
continue;
|
|
27412
|
+
}
|
|
27413
|
+
const parsed = parseStep(step, problems);
|
|
27414
|
+
if (parsed) tour.push(parsed);
|
|
27415
|
+
}
|
|
27416
|
+
if (!steps.some((s) => !isRemaining(s.title))) problems.push("Tour has no steps");
|
|
27417
|
+
if (remaining === void 0) problems.push("Tour has no `Remaining changes` step");
|
|
27418
|
+
return { tour, ...remaining ? { remaining } : {} };
|
|
27419
|
+
}
|
|
27420
|
+
function parseStep(step, problems) {
|
|
27421
|
+
const where = `step ${step.label} (${step.title})`;
|
|
27422
|
+
let link;
|
|
27423
|
+
const descLines = [];
|
|
27424
|
+
const lookForLines = [];
|
|
27425
|
+
let inLookFor = false;
|
|
27426
|
+
for (const line3 of step.lines) {
|
|
27427
|
+
const m = PERMALINK_RE.exec(line3.trim());
|
|
27428
|
+
if (m) {
|
|
27429
|
+
if (link) problems.push(`${where}: more than one permalink \u2014 the first is the anchor`);
|
|
27430
|
+
else link = m;
|
|
27431
|
+
inLookFor = false;
|
|
27432
|
+
continue;
|
|
27433
|
+
}
|
|
27434
|
+
const lf = LOOK_FOR_RE.exec(line3.trim());
|
|
27435
|
+
if (lf) {
|
|
27436
|
+
lookForLines.push(lf[1]);
|
|
27437
|
+
inLookFor = true;
|
|
27438
|
+
continue;
|
|
27439
|
+
}
|
|
27440
|
+
if (inLookFor) {
|
|
27441
|
+
if (line3.trim() === "") inLookFor = false;
|
|
27442
|
+
else lookForLines.push(line3.trim());
|
|
27443
|
+
continue;
|
|
27444
|
+
}
|
|
27445
|
+
descLines.push(line3);
|
|
27446
|
+
}
|
|
27447
|
+
if (!link) {
|
|
27448
|
+
problems.push(`${where}: no permalink line \u2014 dropped`);
|
|
27449
|
+
return void 0;
|
|
27450
|
+
}
|
|
27451
|
+
let path;
|
|
27452
|
+
try {
|
|
27453
|
+
path = link[2].split("/").map(decodeURIComponent).join("/");
|
|
27454
|
+
} catch {
|
|
27455
|
+
problems.push(`${where}: permalink path is not valid percent-encoding \u2014 dropped`);
|
|
27456
|
+
return void 0;
|
|
27457
|
+
}
|
|
27458
|
+
const from = Number(link[3]);
|
|
27459
|
+
const anchor = TourAnchorSchema.safeParse({ path, from, to: link[4] === void 0 ? from : Number(link[4]) });
|
|
27460
|
+
if (!anchor.success) {
|
|
27461
|
+
const issue = anchor.error.issues[0];
|
|
27462
|
+
problems.push(`${where}: ${["anchor", ...issue.path].join(".")} ${issue.message} \u2014 dropped`);
|
|
27463
|
+
return void 0;
|
|
27464
|
+
}
|
|
27465
|
+
const lookFor = lookForLines.join("\n").trim();
|
|
27466
|
+
return {
|
|
27467
|
+
title: step.title,
|
|
27468
|
+
// A parsed step may carry an empty description (a human's heading +
|
|
27469
|
+
// permalink): this is a read-back, not a submission the schema gates.
|
|
27470
|
+
description: descLines.join("\n").replace(/\n{3,}/g, "\n\n").trim(),
|
|
27471
|
+
...lookFor ? { lookFor } : {},
|
|
27472
|
+
anchor: { ...anchor.data, sha: link[1] }
|
|
27473
|
+
};
|
|
27474
|
+
}
|
|
27475
|
+
function parseRemaining(lines, problems) {
|
|
27476
|
+
const out = [];
|
|
27477
|
+
for (const raw of lines) {
|
|
27478
|
+
const line3 = raw.trim();
|
|
27479
|
+
if (line3 === "" || REMAINING_NONE_RE.test(line3)) continue;
|
|
27480
|
+
const m = REMAINING_LINE_RE.exec(line3);
|
|
27481
|
+
if (m) out.push({ path: m[1], note: m[2].trim() });
|
|
27482
|
+
else if (!line3.startsWith("-") && out.length) out[out.length - 1].note += `
|
|
27483
|
+
${line3}`;
|
|
27484
|
+
else problems.push(`Remaining changes: unrecognized line \`${line3}\``);
|
|
27485
|
+
}
|
|
27486
|
+
return out;
|
|
27487
|
+
}
|
|
27488
|
+
function parseDecisions(lines, problems) {
|
|
27489
|
+
const out = [];
|
|
27490
|
+
for (const raw of lines) {
|
|
27491
|
+
const line3 = raw.trim();
|
|
27492
|
+
if (line3 === "") continue;
|
|
27493
|
+
const m = DECISION_RE.exec(line3);
|
|
27494
|
+
if (m) out.push({ title: m[1].replace(/\.$/, ""), rationale: m[2].trim() });
|
|
27495
|
+
else if (!line3.startsWith("-") && out.length) out[out.length - 1].rationale += `
|
|
27496
|
+
${line3}`;
|
|
27497
|
+
else problems.push(`Decisions: unrecognized line \`${line3}\``);
|
|
27498
|
+
}
|
|
27499
|
+
return out;
|
|
27500
|
+
}
|
|
27501
|
+
function parseValidation(lines, problems) {
|
|
27502
|
+
const summary = [];
|
|
27503
|
+
const criteria = [];
|
|
27504
|
+
let header4 = false;
|
|
27505
|
+
for (const raw of lines) {
|
|
27506
|
+
const line3 = raw.trim();
|
|
27507
|
+
if (!header4) {
|
|
27508
|
+
if (TABLE_HEADER_RE.test(line3)) header4 = true;
|
|
27509
|
+
else summary.push(raw);
|
|
27510
|
+
continue;
|
|
27511
|
+
}
|
|
27512
|
+
if (line3 === "" || TABLE_RULE_RE.test(line3)) continue;
|
|
27513
|
+
const cells = splitCells(line3);
|
|
27514
|
+
if (cells.length >= 2) criteria.push({ criterion: cells[0], proof: cells[1] });
|
|
27515
|
+
else problems.push(`Validation: unrecognized table line \`${line3}\``);
|
|
27516
|
+
}
|
|
27517
|
+
if (!header4) problems.push("Validation: no `| Criterion | Proof |` table");
|
|
27518
|
+
const text = summary.join("\n").trim();
|
|
27519
|
+
return { ...text ? { summary: text } : {}, criteria };
|
|
27520
|
+
}
|
|
27521
|
+
function splitCells(row) {
|
|
27522
|
+
const cells = [];
|
|
27523
|
+
let cur = "";
|
|
27524
|
+
const inner = row.replace(/^\|/, "").replace(/\|$/, "");
|
|
27525
|
+
for (let i = 0; i < inner.length; i++) {
|
|
27526
|
+
const ch = inner[i];
|
|
27527
|
+
if (ch === "\\" && i + 1 < inner.length) {
|
|
27528
|
+
cur += inner[++i];
|
|
27529
|
+
} else if (ch === "|") {
|
|
27530
|
+
cells.push(cur.trim());
|
|
27531
|
+
cur = "";
|
|
27532
|
+
} else cur += ch;
|
|
27533
|
+
}
|
|
27534
|
+
cells.push(cur.trim());
|
|
27535
|
+
return cells;
|
|
27536
|
+
}
|
|
27537
|
+
var line2, prose, TourAnchorSchema, TourStepSchema, PrDescriptionSchema, REPO_RE2, SHA_RE2, GENERATED_FOOTER, SECTIONS, PERMALINK_RE, HEADING_RE, STEP_RE, LOOK_FOR_RE, REMAINING_LINE_RE, REMAINING_NONE_RE, DECISION_RE, TABLE_HEADER_RE, TABLE_RULE_RE;
|
|
27538
|
+
var init_prDescription = __esm({
|
|
27539
|
+
"../../src/core/prDescription.ts"() {
|
|
27540
|
+
"use strict";
|
|
27541
|
+
init_redact();
|
|
27542
|
+
line2 = z20.string().trim().min(1).refine((s) => !/[\r\n]/.test(s), "must be a single line (no embedded newlines)");
|
|
27543
|
+
prose = z20.string().trim().min(1);
|
|
27544
|
+
TourAnchorSchema = z20.object({
|
|
27545
|
+
// Repo-relative: no leading `/`, no `..` SEGMENT (a filename like `a..b.ts` is fine).
|
|
27546
|
+
path: z20.string().trim().min(1).refine((p) => !p.startsWith("/") && !p.split("/").includes(".."), "path must be repo-relative"),
|
|
27547
|
+
from: z20.number().int().positive(),
|
|
27548
|
+
to: z20.number().int().positive()
|
|
27549
|
+
}).refine((a) => a.to >= a.from, "to must be >= from");
|
|
27550
|
+
TourStepSchema = z20.object({
|
|
27551
|
+
title: line2,
|
|
27552
|
+
description: prose,
|
|
27553
|
+
lookFor: prose.optional(),
|
|
27554
|
+
anchor: TourAnchorSchema
|
|
27555
|
+
});
|
|
27556
|
+
PrDescriptionSchema = z20.object({
|
|
27557
|
+
/** The PR title's single source. Metadata for the PR's own title field —
|
|
27558
|
+
* never rendered into the body (GitHub shows the title itself). */
|
|
27559
|
+
title: line2,
|
|
27560
|
+
tldr: prose,
|
|
27561
|
+
whatWhy: prose,
|
|
27562
|
+
tour: z20.array(TourStepSchema).min(1),
|
|
27563
|
+
/** Every touched file the Tour steps did not cover, one line each. */
|
|
27564
|
+
remaining: z20.array(z20.object({ path: line2, note: prose })),
|
|
27565
|
+
decisions: z20.array(z20.object({ title: line2, rationale: prose })).min(1),
|
|
27566
|
+
risks: prose,
|
|
27567
|
+
validation: z20.object({
|
|
27568
|
+
summary: prose.optional(),
|
|
27569
|
+
criteria: z20.array(z20.object({ criterion: prose, proof: prose })).min(1)
|
|
27570
|
+
})
|
|
27571
|
+
});
|
|
27572
|
+
REPO_RE2 = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/;
|
|
27573
|
+
SHA_RE2 = /^[0-9a-f]{40}$/;
|
|
27574
|
+
GENERATED_FOOTER = "\u{1F916} Generated with [Claude Code](https://claude.com/claude-code)";
|
|
27575
|
+
SECTIONS = [
|
|
27576
|
+
["tldr", "TL;DR"],
|
|
27577
|
+
["whatWhy", "What & why"],
|
|
27578
|
+
["tour", "Tour"],
|
|
27579
|
+
["decisions", "Decisions"],
|
|
27580
|
+
["risks", "Risks & implications"],
|
|
27581
|
+
["validation", "Validation"]
|
|
27582
|
+
];
|
|
27583
|
+
PERMALINK_RE = /^https:\/\/github\.com\/[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+\/blob\/([0-9a-f]{7,40})\/([^#\s]+)#L(\d+)(?:-L(\d+))?$/;
|
|
27584
|
+
HEADING_RE = /^(#{1,6})\s+(.+?)\s*$/;
|
|
27585
|
+
STEP_RE = /^###\s+(?:(\d+)\.\s+)?(.+?)\s*$/;
|
|
27586
|
+
LOOK_FOR_RE = /^\*\*Look for:\*\*\s*(.*)$/;
|
|
27587
|
+
REMAINING_LINE_RE = /^-\s+`([^`]+)`\s+—\s*(.*)$/;
|
|
27588
|
+
REMAINING_NONE_RE = /^-\s+none\b/i;
|
|
27589
|
+
DECISION_RE = /^-\s+\*\*(.+?)\*\*\s*(.*)$/;
|
|
27590
|
+
TABLE_HEADER_RE = /^\|\s*criterion\s*\|\s*proof\s*\|$/i;
|
|
27591
|
+
TABLE_RULE_RE = /^\|?\s*:?-{3,}:?\s*\|/;
|
|
27592
|
+
}
|
|
27593
|
+
});
|
|
27594
|
+
|
|
27595
|
+
// ../../src/tools/submit.ts
|
|
27596
|
+
import { z as z21 } from "zod";
|
|
27597
|
+
var submitVerdictTool, submitDispositionsTool, submitPrDescriptionTool, handoffEntry, submitHandoffTool;
|
|
27598
|
+
var init_submit = __esm({
|
|
27599
|
+
"../../src/tools/submit.ts"() {
|
|
27600
|
+
"use strict";
|
|
27601
|
+
init_prDescription();
|
|
27602
|
+
init_reviewVerdict();
|
|
27603
|
+
init_handoff();
|
|
27584
27604
|
submitVerdictTool = {
|
|
27585
27605
|
name: "submit_verdict",
|
|
27586
27606
|
failsInText: true,
|
|
@@ -27801,22 +27821,92 @@ ${raw.trim()}`;
|
|
|
27801
27821
|
return `handoff recorded: ${count(h.deviations.length, "deviation", "deviations")}, ${count(h.followUps.length, "follow-up", "follow-ups")}, ${h.unproven.length} unproven; a later call replaces this one`;
|
|
27802
27822
|
}
|
|
27803
27823
|
};
|
|
27804
|
-
|
|
27805
|
-
|
|
27806
|
-
|
|
27824
|
+
}
|
|
27825
|
+
});
|
|
27826
|
+
|
|
27827
|
+
// ../../src/tools/workspace.ts
|
|
27828
|
+
var bashTool, readFileTool, writeFileTool, TOOLSETS;
|
|
27829
|
+
var init_workspace = __esm({
|
|
27830
|
+
"../../src/tools/workspace.ts"() {
|
|
27831
|
+
"use strict";
|
|
27832
|
+
init_bashTimeout();
|
|
27833
|
+
init_executor();
|
|
27834
|
+
init_attach();
|
|
27835
|
+
init_diffDigest2();
|
|
27836
|
+
init_github();
|
|
27837
|
+
init_runs2();
|
|
27838
|
+
init_session();
|
|
27839
|
+
init_skills();
|
|
27840
|
+
init_status2();
|
|
27841
|
+
init_submit();
|
|
27842
|
+
init_web();
|
|
27843
|
+
bashTool = {
|
|
27844
|
+
name: "bash",
|
|
27845
|
+
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.",
|
|
27807
27846
|
inputSchema: {
|
|
27808
27847
|
type: "object",
|
|
27809
27848
|
properties: {
|
|
27810
|
-
|
|
27811
|
-
|
|
27812
|
-
|
|
27849
|
+
command: { type: "string", description: "The bash command to run" },
|
|
27850
|
+
timeoutMs: {
|
|
27851
|
+
type: "integer",
|
|
27852
|
+
description: "Optional timeout in milliseconds for this command (default 300000 = 5 min; max 1200000 = 20 min). Values outside [1000, 1200000] are clamped."
|
|
27813
27853
|
}
|
|
27814
27854
|
},
|
|
27815
|
-
required: ["
|
|
27855
|
+
required: ["command"]
|
|
27816
27856
|
},
|
|
27817
27857
|
async run(input, ctx) {
|
|
27818
|
-
|
|
27819
|
-
|
|
27858
|
+
const requested = input.timeoutMs;
|
|
27859
|
+
let timeoutMs = typeof requested === "number" && Number.isFinite(requested) ? clampBashTimeout(requested) : void 0;
|
|
27860
|
+
let clipNote = "";
|
|
27861
|
+
if (ctx.remainingMs) {
|
|
27862
|
+
const budget = bashBudgetWithinRun(timeoutMs ?? BASH_TIMEOUT_MS, ctx.remainingMs());
|
|
27863
|
+
if (budget.kind === "exhausted") return `exit 124:
|
|
27864
|
+
${budget.note}`;
|
|
27865
|
+
if (budget.kind === "clipped") {
|
|
27866
|
+
timeoutMs = budget.timeoutMs;
|
|
27867
|
+
clipNote = budget.note;
|
|
27868
|
+
}
|
|
27869
|
+
}
|
|
27870
|
+
const opts = {
|
|
27871
|
+
...ctx.signal ? { signal: ctx.signal } : {},
|
|
27872
|
+
...timeoutMs !== void 0 ? { timeoutMs } : {}
|
|
27873
|
+
};
|
|
27874
|
+
const out = await ctx.executor.exec(
|
|
27875
|
+
String(input.command ?? ""),
|
|
27876
|
+
opts.signal !== void 0 || opts.timeoutMs !== void 0 ? opts : void 0
|
|
27877
|
+
);
|
|
27878
|
+
return clipNote ? `${out}
|
|
27879
|
+
${clipNote}` : out;
|
|
27880
|
+
}
|
|
27881
|
+
};
|
|
27882
|
+
readFileTool = {
|
|
27883
|
+
sideEffectFree: true,
|
|
27884
|
+
name: "read_file",
|
|
27885
|
+
description: "Read a file from the workspace. Path is relative to the workspace root.",
|
|
27886
|
+
inputSchema: {
|
|
27887
|
+
type: "object",
|
|
27888
|
+
properties: {
|
|
27889
|
+
path: { type: "string", description: "Relative path to the file" }
|
|
27890
|
+
},
|
|
27891
|
+
required: ["path"]
|
|
27892
|
+
},
|
|
27893
|
+
run(input, ctx) {
|
|
27894
|
+
return ctx.executor.readFile(String(input.path ?? ""));
|
|
27895
|
+
}
|
|
27896
|
+
};
|
|
27897
|
+
writeFileTool = {
|
|
27898
|
+
name: "write_file",
|
|
27899
|
+
description: "Write a file in the workspace (creates parent directories). Path is relative to the workspace root.",
|
|
27900
|
+
inputSchema: {
|
|
27901
|
+
type: "object",
|
|
27902
|
+
properties: {
|
|
27903
|
+
path: { type: "string", description: "Relative path to the file" },
|
|
27904
|
+
content: { type: "string", description: "Full file contents" }
|
|
27905
|
+
},
|
|
27906
|
+
required: ["path", "content"]
|
|
27907
|
+
},
|
|
27908
|
+
run(input, ctx) {
|
|
27909
|
+
return ctx.executor.writeFile(String(input.path ?? ""), String(input.content ?? ""));
|
|
27820
27910
|
}
|
|
27821
27911
|
};
|
|
27822
27912
|
TOOLSETS = {
|
|
@@ -28206,7 +28296,7 @@ async function routeRequest(deps, ctx) {
|
|
|
28206
28296
|
if (deps.routeModel) model = deps.routeModel;
|
|
28207
28297
|
else {
|
|
28208
28298
|
const ref = parseModelRef(modelRef2);
|
|
28209
|
-
model = providerRouteModel(deps.
|
|
28299
|
+
model = providerRouteModel(deps.completions.get(ref.provider), ref.model, {
|
|
28210
28300
|
...cfg.routing?.answer ? { answer: cfg.routing.answer } : {}
|
|
28211
28301
|
});
|
|
28212
28302
|
}
|
|
@@ -28276,7 +28366,7 @@ var init_route = __esm({
|
|
|
28276
28366
|
init_registry();
|
|
28277
28367
|
init_workspace();
|
|
28278
28368
|
init_config2();
|
|
28279
|
-
|
|
28369
|
+
init_provider();
|
|
28280
28370
|
init_redact();
|
|
28281
28371
|
init_statusCardFrame();
|
|
28282
28372
|
init_spawn();
|
|
@@ -28293,6 +28383,33 @@ var init_route = __esm({
|
|
|
28293
28383
|
}
|
|
28294
28384
|
});
|
|
28295
28385
|
|
|
28386
|
+
// ../../src/core/chatMessage.ts
|
|
28387
|
+
function toolResultText(content) {
|
|
28388
|
+
if (typeof content === "string") return content;
|
|
28389
|
+
return content.map((p) => {
|
|
28390
|
+
if (p.type === "text") return p.text;
|
|
28391
|
+
if (p.type === "image") return `[image ${p.mediaType}, ${base64Bytes(p.data)} bytes]`;
|
|
28392
|
+
return `[document ${p.name ?? "document"} (${p.mediaType}), ${base64Bytes(p.data)} bytes]`;
|
|
28393
|
+
}).join("\n");
|
|
28394
|
+
}
|
|
28395
|
+
function capToolResultContent(content, cap = MAX_TOOL_RESULT_CHARS) {
|
|
28396
|
+
const capText = (text) => text.length > cap ? `${text.slice(0, cap)}
|
|
28397
|
+
\u2026[tool result truncated: ${text.length - cap} of ${text.length} characters cut \u2014 ask for a narrower slice]` : text;
|
|
28398
|
+
if (typeof content === "string") return capText(content);
|
|
28399
|
+
return content.map((p) => p.type === "text" ? { ...p, text: capText(p.text) } : p);
|
|
28400
|
+
}
|
|
28401
|
+
function base64Bytes(b64) {
|
|
28402
|
+
const padding = b64.endsWith("==") ? 2 : b64.endsWith("=") ? 1 : 0;
|
|
28403
|
+
return Math.floor(b64.length * 3 / 4) - padding;
|
|
28404
|
+
}
|
|
28405
|
+
var MAX_TOOL_RESULT_CHARS;
|
|
28406
|
+
var init_chatMessage = __esm({
|
|
28407
|
+
"../../src/core/chatMessage.ts"() {
|
|
28408
|
+
"use strict";
|
|
28409
|
+
MAX_TOOL_RESULT_CHARS = 12e4;
|
|
28410
|
+
}
|
|
28411
|
+
});
|
|
28412
|
+
|
|
28296
28413
|
// ../../src/execution/tracingExecutor.ts
|
|
28297
28414
|
var TracingExecutor;
|
|
28298
28415
|
var init_tracingExecutor = __esm({
|
|
@@ -28491,7 +28608,12 @@ async function runLoop(opts, now, note, emit, agentSpan) {
|
|
|
28491
28608
|
...input.userName ? { user: input.userName } : {},
|
|
28492
28609
|
...input.from ? { run: input.from.runId } : {}
|
|
28493
28610
|
};
|
|
28494
|
-
emit({
|
|
28611
|
+
emit({
|
|
28612
|
+
type: "input",
|
|
28613
|
+
text: redactSecrets(input.text),
|
|
28614
|
+
messageId: followUpMessageId(input),
|
|
28615
|
+
...Object.keys(source2).length > 0 ? { source: source2 } : {}
|
|
28616
|
+
});
|
|
28495
28617
|
note("follow_up", `follow-up folded in: ${redactSecrets(followUpSnippet(input))}`);
|
|
28496
28618
|
}
|
|
28497
28619
|
const prompt = followUpPrompt(inputs, { superseded });
|
|
@@ -28538,13 +28660,14 @@ ${stagedLine}` : prompt }];
|
|
|
28538
28660
|
}
|
|
28539
28661
|
const ctx = callSpan ? {
|
|
28540
28662
|
...toolContext,
|
|
28663
|
+
callId: tu.id,
|
|
28541
28664
|
span: callSpan,
|
|
28542
28665
|
executor: new TracingExecutor(execTracker, callSpan, opts.backend),
|
|
28543
28666
|
publish: (e) => emit(withSpanId(e, callSpan.id)),
|
|
28544
28667
|
// The GitHub client as a view under this call: its requests and any
|
|
28545
28668
|
// token mint become `github.*` children (docs/reference/specs/tracing.md item 23).
|
|
28546
28669
|
...toolContext.github?.api.withSpan ? { github: { ...toolContext.github, api: toolContext.github.api.withSpan(callSpan) } } : {}
|
|
28547
|
-
} : toolContext;
|
|
28670
|
+
} : { ...toolContext, callId: tu.id };
|
|
28548
28671
|
try {
|
|
28549
28672
|
const output = await untilHardStop(tool.run(tu.input ?? {}, ctx));
|
|
28550
28673
|
const text2 = toolResultText(output);
|
|
@@ -28883,7 +29006,7 @@ var MAX_CONSECUTIVE_INFRA_FAILURES, HardStopError, HARD_STOP_MESSAGE, wrapUpNote
|
|
|
28883
29006
|
var init_runner = __esm({
|
|
28884
29007
|
"../../src/runner.ts"() {
|
|
28885
29008
|
"use strict";
|
|
28886
|
-
|
|
29009
|
+
init_chatMessage();
|
|
28887
29010
|
init_runEvents();
|
|
28888
29011
|
init_threadAdmission();
|
|
28889
29012
|
init_executor();
|
|
@@ -29279,42 +29402,42 @@ async function settle(input, span) {
|
|
|
29279
29402
|
}
|
|
29280
29403
|
verdict = void 0;
|
|
29281
29404
|
reviewHead = current;
|
|
29282
|
-
const
|
|
29405
|
+
const followUpText = rereviewFollowUp({
|
|
29406
|
+
where,
|
|
29407
|
+
reviewed: expected,
|
|
29408
|
+
current,
|
|
29409
|
+
move,
|
|
29410
|
+
before: classified.before,
|
|
29411
|
+
after: classified.after,
|
|
29412
|
+
worktreeMoved
|
|
29413
|
+
});
|
|
29283
29414
|
input.messages.push(
|
|
29284
29415
|
{ role: "assistant", content: [{ type: "text", text: answer }] },
|
|
29285
|
-
{
|
|
29286
|
-
role: "user",
|
|
29287
|
-
content: [
|
|
29288
|
-
{
|
|
29289
|
-
type: "text",
|
|
29290
|
-
text: rereviewFollowUp({
|
|
29291
|
-
where,
|
|
29292
|
-
reviewed: expected,
|
|
29293
|
-
current,
|
|
29294
|
-
move,
|
|
29295
|
-
before: classified.before,
|
|
29296
|
-
after: classified.after,
|
|
29297
|
-
worktreeMoved
|
|
29298
|
-
})
|
|
29299
|
-
}
|
|
29300
|
-
]
|
|
29301
|
-
}
|
|
29416
|
+
{ role: "user", content: [{ type: "text", text: followUpText }] }
|
|
29302
29417
|
);
|
|
29303
|
-
|
|
29418
|
+
const toolContext = {
|
|
29419
|
+
...turn.toolContext,
|
|
29420
|
+
onVerdict: (v) => {
|
|
29421
|
+
verdict = v;
|
|
29422
|
+
}
|
|
29423
|
+
};
|
|
29424
|
+
answer = turn.followUp ? await turn.followUp({
|
|
29425
|
+
text: followUpText,
|
|
29426
|
+
maxTurns: turn.agent.maxTurns,
|
|
29427
|
+
maxMinutes: turn.agent.maxMinutes,
|
|
29428
|
+
toolContext,
|
|
29429
|
+
...span ? { span } : {}
|
|
29430
|
+
}) : await runAgent({
|
|
29304
29431
|
provider: turn.provider,
|
|
29305
29432
|
model: turn.model,
|
|
29306
29433
|
agent: turn.agent,
|
|
29307
29434
|
messages: input.messages,
|
|
29308
|
-
|
|
29435
|
+
// Recomposed at the new head, so the prompt does not contradict the follow-up.
|
|
29436
|
+
system: input.composeSystem({ sha: current, verified: worktreeMoved }),
|
|
29309
29437
|
effort: turn.effort,
|
|
29310
29438
|
...span ? { span } : {},
|
|
29311
29439
|
...turn.backend ? { backend: turn.backend } : {},
|
|
29312
|
-
toolContext
|
|
29313
|
-
...turn.toolContext,
|
|
29314
|
-
onVerdict: (v) => {
|
|
29315
|
-
verdict = v;
|
|
29316
|
-
}
|
|
29317
|
-
},
|
|
29440
|
+
toolContext,
|
|
29318
29441
|
...turn.extraTools && turn.extraTools.length > 0 ? { extraTools: turn.extraTools } : {},
|
|
29319
29442
|
onProgress: turn.onProgress,
|
|
29320
29443
|
onEvent: turn.onEvent,
|
|
@@ -30374,7 +30497,8 @@ async function registerRun(deps, ctx) {
|
|
|
30374
30497
|
trace.bindRun(run2.id, (e) => registry.publish(run2.id, e));
|
|
30375
30498
|
const publishText = (type, text, source3, raw) => {
|
|
30376
30499
|
const redacted = redactSecrets(text);
|
|
30377
|
-
const
|
|
30500
|
+
const body = { text: redacted, ...source3 ? { source: source3 } : {}, at: clock3() };
|
|
30501
|
+
const event = type === "input" ? { type, messageId: messageIdOf(msg, run2.id), ...body } : { type, ...body };
|
|
30378
30502
|
const withRaw = raw !== void 0 ? { ...event, raw: redactSecrets(raw) } : event;
|
|
30379
30503
|
registry.publish(run2.id, utf8ByteLength(JSON.stringify(withRaw)) <= MAX_EVENT_BYTES ? withRaw : event);
|
|
30380
30504
|
console.log(`[event] ${msg.threadKey} type=${type} bytes=${utf8ByteLength(redacted)}`);
|
|
@@ -30673,7 +30797,7 @@ var init_provision = __esm({
|
|
|
30673
30797
|
init_resident();
|
|
30674
30798
|
init_memory();
|
|
30675
30799
|
init_runBearers();
|
|
30676
|
-
|
|
30800
|
+
init_provider();
|
|
30677
30801
|
init_skills2();
|
|
30678
30802
|
init_source();
|
|
30679
30803
|
init_configAwareness();
|
|
@@ -30685,6 +30809,7 @@ var init_provision = __esm({
|
|
|
30685
30809
|
init_inboxMessage();
|
|
30686
30810
|
init_statusCardFrame();
|
|
30687
30811
|
init_statusCoalescer();
|
|
30812
|
+
init_types4();
|
|
30688
30813
|
init_record();
|
|
30689
30814
|
init_reply();
|
|
30690
30815
|
init_messages();
|
|
@@ -31649,6 +31774,7 @@ var init_bridge2 = __esm({
|
|
|
31649
31774
|
PiBridge = class {
|
|
31650
31775
|
constructor(deps) {
|
|
31651
31776
|
this.deps = deps;
|
|
31777
|
+
this.agentSpan = deps.agentSpan;
|
|
31652
31778
|
}
|
|
31653
31779
|
deps;
|
|
31654
31780
|
/** Model turns that did work, the guard's count — the loop's rule: a turn whose every call is `update_status` is bookkeeping. */
|
|
@@ -31670,6 +31796,24 @@ var init_bridge2 = __esm({
|
|
|
31670
31796
|
* were vetted by the bot generation that died, and this one never heard. */
|
|
31671
31797
|
judgeGate = true;
|
|
31672
31798
|
summarizationRetries = 0;
|
|
31799
|
+
/** The span a starting call's `tool.<name>` opens under: the run's
|
|
31800
|
+
* `run.agent` for the loop, a follow-up turn's own for its duration
|
|
31801
|
+
* (`under`; harness-pi item 14). */
|
|
31802
|
+
agentSpan;
|
|
31803
|
+
/** From here on, new tool spans hang under `span` — a follow-up turn's
|
|
31804
|
+
* `run.agent`, opened under the caller's span the way the native loop's
|
|
31805
|
+
* would be (tracing.md item 17); `undefined` opens none. */
|
|
31806
|
+
under(span) {
|
|
31807
|
+
this.agentSpan = span;
|
|
31808
|
+
}
|
|
31809
|
+
/** A new prompt is about to be sent on the session (harness-pi item 14): the
|
|
31810
|
+
* reply pi settled the last loop on is not this turn's answer, so the
|
|
31811
|
+
* answer state starts over — what pi settles on next is the turn's. */
|
|
31812
|
+
newPrompt() {
|
|
31813
|
+
this.answerText = void 0;
|
|
31814
|
+
this.heldAnswer = void 0;
|
|
31815
|
+
this.assistantStartedAt = void 0;
|
|
31816
|
+
}
|
|
31673
31817
|
/** The gate saw this call: the extension's `tool_call` hook asked the bot
|
|
31674
31818
|
* for it, whatever the answer. Called from the authorize route, before pi
|
|
31675
31819
|
* runs the tool — so it always precedes the call's `tool_execution_end`. */
|
|
@@ -31778,7 +31922,7 @@ var init_bridge2 = __esm({
|
|
|
31778
31922
|
onToolStart(event) {
|
|
31779
31923
|
const tool = str5(event.toolName);
|
|
31780
31924
|
const callId = str5(event.toolCallId);
|
|
31781
|
-
const span = this.
|
|
31925
|
+
const span = this.agentSpan?.start(`tool.${tool}`);
|
|
31782
31926
|
this.openTools.set(callId, { span, tool, judged: this.judgeGate });
|
|
31783
31927
|
this.toolCalls++;
|
|
31784
31928
|
const input = isRecord3(event.args) ? event.args : void 0;
|
|
@@ -32301,7 +32445,7 @@ function ledgerTailOf(resume) {
|
|
|
32301
32445
|
function settlementAnswer(s) {
|
|
32302
32446
|
return { content: [{ type: "text", text: settlementText(s) }], isError: true };
|
|
32303
32447
|
}
|
|
32304
|
-
async function
|
|
32448
|
+
async function runPiHarnessOpen(deps, run2) {
|
|
32305
32449
|
const { container, clock: clock3 } = deps;
|
|
32306
32450
|
const now = () => clock3();
|
|
32307
32451
|
const agentSpan = run2.span?.start("run.agent");
|
|
@@ -32396,6 +32540,26 @@ async function runPiHarness(deps, run2) {
|
|
|
32396
32540
|
save();
|
|
32397
32541
|
};
|
|
32398
32542
|
let catchingUp = false;
|
|
32543
|
+
const clearWriteUp = () => {
|
|
32544
|
+
writeUp = void 0;
|
|
32545
|
+
writeUpAt = void 0;
|
|
32546
|
+
};
|
|
32547
|
+
let followUps = 0;
|
|
32548
|
+
let sessionEnded = false;
|
|
32549
|
+
const end = async () => {
|
|
32550
|
+
if (sessionEnded) return;
|
|
32551
|
+
sessionEnded = true;
|
|
32552
|
+
transport?.close();
|
|
32553
|
+
bridge.closeOpenSpans(
|
|
32554
|
+
hardStopped ? "the run was hard-stopped" : bypass ? "the run was stopped: a tool call bypassed the gate" : "the run ended"
|
|
32555
|
+
);
|
|
32556
|
+
save();
|
|
32557
|
+
forget();
|
|
32558
|
+
if (pid !== void 0) await container.kill(pid).catch(() => {
|
|
32559
|
+
});
|
|
32560
|
+
if (paths !== void 0) await container.remove(paths).catch(() => {
|
|
32561
|
+
});
|
|
32562
|
+
};
|
|
32399
32563
|
try {
|
|
32400
32564
|
const recorded = run2.resume?.facts;
|
|
32401
32565
|
let reattached = false;
|
|
@@ -32535,7 +32699,12 @@ async function runPiHarness(deps, run2) {
|
|
|
32535
32699
|
...input.userName ? { user: input.userName } : {},
|
|
32536
32700
|
...input.from ? { run: input.from.runId } : {}
|
|
32537
32701
|
};
|
|
32538
|
-
emit({
|
|
32702
|
+
emit({
|
|
32703
|
+
type: "input",
|
|
32704
|
+
text: redactSecrets(input.text),
|
|
32705
|
+
messageId: followUpMessageId(input),
|
|
32706
|
+
...Object.keys(source2).length > 0 ? { source: source2 } : {}
|
|
32707
|
+
});
|
|
32539
32708
|
note("follow_up", `follow-up folded in: ${redactSecrets(followUpSnippet(input))}`);
|
|
32540
32709
|
}
|
|
32541
32710
|
const images = inputs.flatMap(
|
|
@@ -32673,33 +32842,137 @@ ${stagedLine}` : prompt,
|
|
|
32673
32842
|
check();
|
|
32674
32843
|
if (hardStopped) break;
|
|
32675
32844
|
}
|
|
32845
|
+
let answer;
|
|
32676
32846
|
if (hardStopped) {
|
|
32677
32847
|
note("stopped", hardStopNote(), "hard");
|
|
32678
|
-
|
|
32679
|
-
}
|
|
32680
|
-
|
|
32681
|
-
|
|
32682
|
-
|
|
32683
|
-
|
|
32684
|
-
|
|
32685
|
-
|
|
32686
|
-
|
|
32687
|
-
|
|
32688
|
-
|
|
32689
|
-
|
|
32690
|
-
|
|
32691
|
-
|
|
32692
|
-
|
|
32693
|
-
|
|
32694
|
-
|
|
32695
|
-
|
|
32696
|
-
|
|
32697
|
-
|
|
32698
|
-
|
|
32699
|
-
|
|
32700
|
-
|
|
32701
|
-
|
|
32702
|
-
|
|
32848
|
+
answer = HARD_STOP_MESSAGE;
|
|
32849
|
+
} else {
|
|
32850
|
+
if (bypass) throw bypass;
|
|
32851
|
+
if (!settled) {
|
|
32852
|
+
const tail2 = await container.tail(paths.errLog, 2e3);
|
|
32853
|
+
throw new Error(`pi exited before the run settled${tail2.trim() ? `: ${redactAndCap(tail2.trim(), 400)}` : ""}`);
|
|
32854
|
+
}
|
|
32855
|
+
if (providerError !== void 0) throw new Error(`the model call failed: ${providerError}`);
|
|
32856
|
+
const text = bridge.answer() ?? "";
|
|
32857
|
+
answer = writeUp?.kind === "time" ? timeBudgetAnswer(text, run2.agent.maxMinutes) : writeUp?.kind === "turns" ? turnGuardAnswer(text, writeUp.pace) : writeUp?.kind === "soft" || stopMode === "soft" ? softStopAnswer(text) : text || "_(no response)_";
|
|
32858
|
+
}
|
|
32859
|
+
agentSpan?.end(hardStopped ? "error" : "ok");
|
|
32860
|
+
const rpc = transport;
|
|
32861
|
+
const root = paths;
|
|
32862
|
+
const followUp = async (input) => {
|
|
32863
|
+
if (sessionEnded) throw new Error("the pi session has ended: no follow-up turn can run on it");
|
|
32864
|
+
const turnSpan = input.span?.start("run.agent");
|
|
32865
|
+
if (turnSpan) deps.bearers?.reparent(run2.runId, turnSpan);
|
|
32866
|
+
bridge.under(turnSpan);
|
|
32867
|
+
bridge.newPrompt();
|
|
32868
|
+
const runContext = live.toolContext;
|
|
32869
|
+
live.toolContext = input.toolContext;
|
|
32870
|
+
const turnDeadline = now() + input.maxMinutes * 6e4;
|
|
32871
|
+
input.toolContext.remainingMs = () => turnDeadline - now();
|
|
32872
|
+
const turnsBefore = bridge.turns;
|
|
32873
|
+
clearWriteUp();
|
|
32874
|
+
const id = `${ids.prompt}:follow-up:${++followUps}`;
|
|
32875
|
+
let turnSettled = false;
|
|
32876
|
+
let turnError;
|
|
32877
|
+
let turnFailed = false;
|
|
32878
|
+
const turnCheck = () => {
|
|
32879
|
+
if (run2.control?.requested === "hard") {
|
|
32880
|
+
if (!hardStopped) {
|
|
32881
|
+
hardStopped = true;
|
|
32882
|
+
rpc.send({ type: "abort" });
|
|
32883
|
+
}
|
|
32884
|
+
return;
|
|
32885
|
+
}
|
|
32886
|
+
if (writeUp) {
|
|
32887
|
+
if (writeUpAt !== void 0 && now() - writeUpAt >= (deps.finaleTimeoutMs ?? FINALE_TIMEOUT_MS2)) {
|
|
32888
|
+
writeUpAt = void 0;
|
|
32889
|
+
run2.onProgress?.("finale timed out \u2014 closing the turn without a write-up");
|
|
32890
|
+
rpc.send({ type: "abort" });
|
|
32891
|
+
}
|
|
32892
|
+
return;
|
|
32893
|
+
}
|
|
32894
|
+
if (run2.control?.requested === "soft") {
|
|
32895
|
+
note("stopped", softStopNote(), "soft");
|
|
32896
|
+
startWriteUp({ kind: "soft" }, SOFT_STOP_INSTRUCTION);
|
|
32897
|
+
return;
|
|
32898
|
+
}
|
|
32899
|
+
if (now() >= turnDeadline) {
|
|
32900
|
+
note("time_budget_exhausted", timeBudgetNote());
|
|
32901
|
+
startWriteUp({ kind: "time" }, timeBudgetInstruction());
|
|
32902
|
+
return;
|
|
32903
|
+
}
|
|
32904
|
+
const turns = bridge.turns - turnsBefore;
|
|
32905
|
+
if (turns >= input.maxTurns) {
|
|
32906
|
+
const pace = turnGuardPace(turns, input.maxMinutes * 6e4 - (turnDeadline - now()));
|
|
32907
|
+
note("turn_budget_exhausted", turnGuardNote(pace));
|
|
32908
|
+
startWriteUp({ kind: "turns", pace }, turnGuardInstruction(pace));
|
|
32909
|
+
}
|
|
32910
|
+
};
|
|
32911
|
+
try {
|
|
32912
|
+
rpc.send({ id, type: "prompt", message: input.text });
|
|
32913
|
+
turnCheck();
|
|
32914
|
+
for (; ; ) {
|
|
32915
|
+
pending2 ??= iterator.next();
|
|
32916
|
+
const tick = deps.sleep(deps.tickMs ?? 1e3).then(() => "tick");
|
|
32917
|
+
const next = await Promise.race([pending2, tick]);
|
|
32918
|
+
if (next === "tick") {
|
|
32919
|
+
turnCheck();
|
|
32920
|
+
if (hardStopped) break;
|
|
32921
|
+
continue;
|
|
32922
|
+
}
|
|
32923
|
+
pending2 = void 0;
|
|
32924
|
+
if (next.done) break;
|
|
32925
|
+
const event = parsePiLine(next.value);
|
|
32926
|
+
if (!event) continue;
|
|
32927
|
+
const obs = bridge.observe(event);
|
|
32928
|
+
for (const reply of obs.replies) rpc.send(reply);
|
|
32929
|
+
if (obs.gateBypassed) {
|
|
32930
|
+
bypass = new GateBypassed(obs.gateBypassed.tool, obs.gateBypassed.callId);
|
|
32931
|
+
note("harness_error", `${bypass.message} \u2014 the turn is stopped`);
|
|
32932
|
+
rpc.send({ type: "abort" });
|
|
32933
|
+
break;
|
|
32934
|
+
}
|
|
32935
|
+
if (obs.response?.id === id && obs.response.success === false)
|
|
32936
|
+
throw new PromptRefused(String(obs.response.error ?? "no reason"));
|
|
32937
|
+
if (obs.providerError !== void 0) turnError = obs.providerError;
|
|
32938
|
+
if (obs.settled) {
|
|
32939
|
+
turnSettled = true;
|
|
32940
|
+
break;
|
|
32941
|
+
}
|
|
32942
|
+
turnCheck();
|
|
32943
|
+
if (hardStopped) break;
|
|
32944
|
+
}
|
|
32945
|
+
if (hardStopped) {
|
|
32946
|
+
note("stopped", hardStopNote(), "hard");
|
|
32947
|
+
return HARD_STOP_MESSAGE;
|
|
32948
|
+
}
|
|
32949
|
+
if (bypass) throw bypass;
|
|
32950
|
+
if (!turnSettled) {
|
|
32951
|
+
const tail2 = await container.tail(root.errLog, 2e3);
|
|
32952
|
+
throw new Error(
|
|
32953
|
+
`pi exited before the turn settled${tail2.trim() ? `: ${redactAndCap(tail2.trim(), 400)}` : ""}`
|
|
32954
|
+
);
|
|
32955
|
+
}
|
|
32956
|
+
if (turnError !== void 0) throw new Error(`the model call failed: ${turnError}`);
|
|
32957
|
+
const text = bridge.answer() ?? "";
|
|
32958
|
+
if (writeUp?.kind === "time") return timeBudgetAnswer(text, input.maxMinutes);
|
|
32959
|
+
if (writeUp?.kind === "turns") return turnGuardAnswer(text, writeUp.pace);
|
|
32960
|
+
if (writeUp?.kind === "soft") return softStopAnswer(text);
|
|
32961
|
+
return text || "_(no response)_";
|
|
32962
|
+
} catch (err2) {
|
|
32963
|
+
turnFailed = true;
|
|
32964
|
+
throw err2;
|
|
32965
|
+
} finally {
|
|
32966
|
+
live.toolContext = runContext;
|
|
32967
|
+
bridge.under(void 0);
|
|
32968
|
+
turnSpan?.end(hardStopped || bypass || turnFailed ? "error" : "ok");
|
|
32969
|
+
}
|
|
32970
|
+
};
|
|
32971
|
+
return { answer, followUp, end };
|
|
32972
|
+
} catch (err2) {
|
|
32973
|
+
agentSpan?.end("error");
|
|
32974
|
+
await end();
|
|
32975
|
+
throw err2;
|
|
32703
32976
|
}
|
|
32704
32977
|
}
|
|
32705
32978
|
var NATIVE_WORKSPACE_TOOLS, FINALE_TIMEOUT_MS2, CALL_SEEN_WAIT_MS, CALL_SEEN_TICK_MS, CONTINUE_PROMPT, PromptRefused, GateBypassed;
|
|
@@ -33330,37 +33603,49 @@ async function runDescriptionTurn(input) {
|
|
|
33330
33603
|
turn.onEvent({ type: "run_note", kind: "description_turn", summary, at: systemClock() });
|
|
33331
33604
|
turn.onProgress(`re-evaluating the description of ${t.repo}#${t.pr.number} at ${t.headSha.slice(0, 7)}`);
|
|
33332
33605
|
let submitted;
|
|
33606
|
+
const followUpText = descriptionFollowUp(t);
|
|
33333
33607
|
input.messages.push(
|
|
33334
33608
|
{ role: "assistant", content: [{ type: "text", text: input.answer }] },
|
|
33335
|
-
{ role: "user", content: [{ type: "text", text:
|
|
33609
|
+
{ role: "user", content: [{ type: "text", text: followUpText }] }
|
|
33336
33610
|
);
|
|
33337
33611
|
const agent = {
|
|
33338
33612
|
...turn.agent,
|
|
33339
33613
|
maxTurns: Math.min(turn.agent.maxTurns, DESCRIPTION_TURN_MAX_TURNS),
|
|
33340
33614
|
maxMinutes: Math.min(turn.agent.maxMinutes, DESCRIPTION_TURN_MAX_MINUTES)
|
|
33341
33615
|
};
|
|
33616
|
+
const toolContext = {
|
|
33617
|
+
...turn.toolContext,
|
|
33618
|
+
onPrDescription: (d) => {
|
|
33619
|
+
submitted = d;
|
|
33620
|
+
turn.toolContext.onPrDescription?.(d);
|
|
33621
|
+
}
|
|
33622
|
+
};
|
|
33342
33623
|
try {
|
|
33343
|
-
|
|
33344
|
-
|
|
33345
|
-
|
|
33346
|
-
|
|
33347
|
-
|
|
33348
|
-
|
|
33349
|
-
|
|
33350
|
-
|
|
33351
|
-
|
|
33352
|
-
|
|
33353
|
-
|
|
33354
|
-
|
|
33355
|
-
|
|
33356
|
-
|
|
33357
|
-
}
|
|
33358
|
-
|
|
33359
|
-
|
|
33360
|
-
|
|
33361
|
-
|
|
33362
|
-
|
|
33363
|
-
|
|
33624
|
+
if (turn.followUp) {
|
|
33625
|
+
await turn.followUp({
|
|
33626
|
+
text: followUpText,
|
|
33627
|
+
maxTurns: agent.maxTurns,
|
|
33628
|
+
maxMinutes: agent.maxMinutes,
|
|
33629
|
+
toolContext,
|
|
33630
|
+
...input.span ? { span: input.span } : {}
|
|
33631
|
+
});
|
|
33632
|
+
} else {
|
|
33633
|
+
await runAgent({
|
|
33634
|
+
provider: turn.provider,
|
|
33635
|
+
model: turn.model,
|
|
33636
|
+
agent,
|
|
33637
|
+
messages: input.messages,
|
|
33638
|
+
...input.system !== void 0 ? { system: input.system } : {},
|
|
33639
|
+
...turn.effort !== void 0 ? { effort: turn.effort } : {},
|
|
33640
|
+
...input.span ? { span: input.span } : {},
|
|
33641
|
+
...turn.backend ? { backend: turn.backend } : {},
|
|
33642
|
+
toolContext,
|
|
33643
|
+
...turn.extraTools && turn.extraTools.length > 0 ? { extraTools: turn.extraTools } : {},
|
|
33644
|
+
onProgress: turn.onProgress,
|
|
33645
|
+
onEvent: turn.onEvent,
|
|
33646
|
+
control: turn.control
|
|
33647
|
+
});
|
|
33648
|
+
}
|
|
33364
33649
|
} catch (err2) {
|
|
33365
33650
|
console.error(`[description-turn] ${logKey} turn failed: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
33366
33651
|
}
|
|
@@ -33558,6 +33843,7 @@ async function runLoop2(deps, ctx) {
|
|
|
33558
33843
|
let carried;
|
|
33559
33844
|
let runFailed = false;
|
|
33560
33845
|
let runDiagnosis;
|
|
33846
|
+
let piSession;
|
|
33561
33847
|
const releaseWorkspace = (span) => round2.release({ hardStopped: run2.control.requested === "hard", ...span ? { span } : {} });
|
|
33562
33848
|
const attachFile = io.attachFile?.bind(io);
|
|
33563
33849
|
const uploadTicket = io.uploadTicket?.bind(io);
|
|
@@ -33660,7 +33946,7 @@ async function runLoop2(deps, ctx) {
|
|
|
33660
33946
|
)
|
|
33661
33947
|
)
|
|
33662
33948
|
];
|
|
33663
|
-
|
|
33949
|
+
piSession = await runPiHarnessOpen(
|
|
33664
33950
|
{
|
|
33665
33951
|
container: deps.harness.containerFor?.(executor, profile.machine) ?? piContainerFor(executor, profile.machine),
|
|
33666
33952
|
bearer: ctx.bearer,
|
|
@@ -33712,6 +33998,7 @@ async function runLoop2(deps, ctx) {
|
|
|
33712
33998
|
} : {}
|
|
33713
33999
|
}
|
|
33714
34000
|
);
|
|
34001
|
+
answer = piSession.answer;
|
|
33715
34002
|
} else {
|
|
33716
34003
|
answer = await runAgent({
|
|
33717
34004
|
provider,
|
|
@@ -33768,7 +34055,8 @@ async function runLoop2(deps, ctx) {
|
|
|
33768
34055
|
onProgress,
|
|
33769
34056
|
onEvent,
|
|
33770
34057
|
control: run2.control,
|
|
33771
|
-
...round2.selection.backend ? { backend: round2.selection.backend } : {}
|
|
34058
|
+
...round2.selection.backend ? { backend: round2.selection.backend } : {},
|
|
34059
|
+
...piSession ? { followUp: piSession.followUp } : {}
|
|
33772
34060
|
},
|
|
33773
34061
|
fetchPrHead: deps.fetchPrHead ?? currentPrHeadSha,
|
|
33774
34062
|
fetchPrCommits: deps.fetchPrCommits ?? prCommitsSince,
|
|
@@ -33848,7 +34136,8 @@ async function runLoop2(deps, ctx) {
|
|
|
33848
34136
|
onProgress,
|
|
33849
34137
|
onEvent,
|
|
33850
34138
|
control: run2.control,
|
|
33851
|
-
...round2.selection.backend ? { backend: round2.selection.backend } : {}
|
|
34139
|
+
...round2.selection.backend ? { backend: round2.selection.backend } : {},
|
|
34140
|
+
...piSession ? { followUp: piSession.followUp } : {}
|
|
33852
34141
|
},
|
|
33853
34142
|
logKey: msg.threadKey
|
|
33854
34143
|
})
|
|
@@ -33856,6 +34145,7 @@ async function runLoop2(deps, ctx) {
|
|
|
33856
34145
|
if (!run2.control.hardSignal.aborted) await observeWorkspaceNow();
|
|
33857
34146
|
}
|
|
33858
34147
|
}
|
|
34148
|
+
await piSession?.end();
|
|
33859
34149
|
if (prDescription) {
|
|
33860
34150
|
registry.publish(run2.id, {
|
|
33861
34151
|
type: "pr_description",
|
|
@@ -33916,6 +34206,7 @@ async function runLoop2(deps, ctx) {
|
|
|
33916
34206
|
);
|
|
33917
34207
|
} catch (err2) {
|
|
33918
34208
|
runFailed = true;
|
|
34209
|
+
await piSession?.end();
|
|
33919
34210
|
await root.span("post.workspace_release", (span) => releaseWorkspace(span));
|
|
33920
34211
|
throw err2;
|
|
33921
34212
|
} finally {
|
|
@@ -34006,7 +34297,7 @@ var init_runLoop = __esm({
|
|
|
34006
34297
|
"use strict";
|
|
34007
34298
|
init_profile();
|
|
34008
34299
|
init_runner();
|
|
34009
|
-
|
|
34300
|
+
init_provider();
|
|
34010
34301
|
init_workspace();
|
|
34011
34302
|
init_select();
|
|
34012
34303
|
init_botHostContainer();
|
|
@@ -35083,7 +35374,11 @@ async function runShipBranch(deps, msg, io, ctx) {
|
|
|
35083
35374
|
io.runStarted?.({ id: run2.id });
|
|
35084
35375
|
const publishText = (type, text, source3) => {
|
|
35085
35376
|
const redacted = redactSecrets(text);
|
|
35086
|
-
|
|
35377
|
+
const body = { text: redacted, ...source3 ? { source: source3 } : {}, at: clock3() };
|
|
35378
|
+
registry.publish(
|
|
35379
|
+
run2.id,
|
|
35380
|
+
type === "input" ? { type, messageId: messageIdOf(msg, run2.id), ...body } : { type, ...body }
|
|
35381
|
+
);
|
|
35087
35382
|
console.log(`[event] ${msg.threadKey} type=${type} bytes=${utf8ByteLength(redacted)}`);
|
|
35088
35383
|
};
|
|
35089
35384
|
const humanize = isMrkdwnChannel(msg.channelId);
|
|
@@ -35281,6 +35576,7 @@ var init_ship = __esm({
|
|
|
35281
35576
|
init_run2();
|
|
35282
35577
|
init_runRegistry();
|
|
35283
35578
|
init_statusCardFrame();
|
|
35579
|
+
init_types4();
|
|
35284
35580
|
}
|
|
35285
35581
|
});
|
|
35286
35582
|
|
|
@@ -35555,6 +35851,16 @@ function sessionSeed(input) {
|
|
|
35555
35851
|
const summary = compactions.length ? compactions[compactions.length - 1].entry.summary : void 0;
|
|
35556
35852
|
const logFrom = start3 < 0 ? from + transcript.turns : from + start3 + compactions.length;
|
|
35557
35853
|
const kept = start3 < 0 ? [] : transcript.messages.slice(start3).map(withoutThinking);
|
|
35854
|
+
if (kept.length > 0 && kept[0].role === "user") {
|
|
35855
|
+
const first = kept[0];
|
|
35856
|
+
const orphans = first.content.filter((p) => p.type === "tool_result").length;
|
|
35857
|
+
if (orphans > 0) {
|
|
35858
|
+
kept[0] = { ...first, content: first.content.filter((p) => p.type !== "tool_result") };
|
|
35859
|
+
notes.push(
|
|
35860
|
+
orphans === 1 ? "session seed: one tool result answering a call before the cut was dropped from the tail's first turn" : `session seed: ${orphans} tool results answering calls before the cut were dropped from the tail's first turn`
|
|
35861
|
+
);
|
|
35862
|
+
}
|
|
35863
|
+
}
|
|
35558
35864
|
if (kept.length === 0) {
|
|
35559
35865
|
notes.push(
|
|
35560
35866
|
transcript.turns === 0 ? "session seed: the log's newest turn alone is over the seed budget \u2014 the run starts from the request" : "session seed: no user text turn in the tail within the seed budget \u2014 the run starts from the request"
|
|
@@ -36020,7 +36326,11 @@ async function dispatch(deps, msg, io, opts = {}) {
|
|
|
36020
36326
|
const earlierStaged = threadAssets && agent.machine !== "none" ? threadAssets.then(
|
|
36021
36327
|
(assets) => stageThreadArtifacts(
|
|
36022
36328
|
assets.filter((a) => a.direction === "in"),
|
|
36023
|
-
{
|
|
36329
|
+
{
|
|
36330
|
+
nextIndex: nextStagedIndex,
|
|
36331
|
+
messageId: messageIdOf(msg, runId3),
|
|
36332
|
+
publish: (e) => registry.publish(runId3, e)
|
|
36333
|
+
}
|
|
36024
36334
|
)
|
|
36025
36335
|
) : void 0;
|
|
36026
36336
|
const reservation = await reserveRun(deps, {
|
|
@@ -36430,6 +36740,7 @@ var init_dispatcher = __esm({
|
|
|
36430
36740
|
init_runsService();
|
|
36431
36741
|
init_runRegistry();
|
|
36432
36742
|
init_runEnding();
|
|
36743
|
+
init_types4();
|
|
36433
36744
|
activeRuns = 0;
|
|
36434
36745
|
}
|
|
36435
36746
|
});
|
|
@@ -37567,7 +37878,7 @@ var OpenAICompatProvider;
|
|
|
37567
37878
|
var init_openaiCompat = __esm({
|
|
37568
37879
|
"../../src/providers/openaiCompat.ts"() {
|
|
37569
37880
|
"use strict";
|
|
37570
|
-
|
|
37881
|
+
init_chatMessage();
|
|
37571
37882
|
init_secrets2();
|
|
37572
37883
|
OpenAICompatProvider = class {
|
|
37573
37884
|
name;
|
|
@@ -37675,6 +37986,241 @@ var init_registry3 = __esm({
|
|
|
37675
37986
|
}
|
|
37676
37987
|
});
|
|
37677
37988
|
|
|
37989
|
+
// ../../src/core/harness/piAi.ts
|
|
37990
|
+
import { anthropicMessagesApi } from "@earendil-works/pi-ai/api/anthropic-messages.lazy";
|
|
37991
|
+
import { openAICompletionsApi } from "@earendil-works/pi-ai/api/openai-completions.lazy";
|
|
37992
|
+
function piApiFor(type) {
|
|
37993
|
+
return type === "anthropic" ? "anthropic-messages" : "openai-completions";
|
|
37994
|
+
}
|
|
37995
|
+
function piStreamOptions(api, req, key) {
|
|
37996
|
+
const shared = {
|
|
37997
|
+
...key !== void 0 ? { apiKey: key } : { apiKey: "unused", headers: { Authorization: null } },
|
|
37998
|
+
maxTokens: req.maxTokens,
|
|
37999
|
+
...req.signal ? { signal: req.signal } : {},
|
|
38000
|
+
cacheRetention: req.cacheTtl === "1h" ? "long" : "short"
|
|
38001
|
+
};
|
|
38002
|
+
if (!req.toolChoice) return shared;
|
|
38003
|
+
return api === "anthropic-messages" ? { ...shared, toolChoice: { type: "tool", name: req.toolChoice.name } } : { ...shared, toolChoice: { type: "function", function: { name: req.toolChoice.name } } };
|
|
38004
|
+
}
|
|
38005
|
+
function toPiContext(req, model, clock3) {
|
|
38006
|
+
const callNames = /* @__PURE__ */ new Map();
|
|
38007
|
+
const messages = [];
|
|
38008
|
+
for (const turn of req.messages) {
|
|
38009
|
+
if (turn.role === "assistant") {
|
|
38010
|
+
for (const part of turn.content) if (part.type === "tool_use") callNames.set(part.id, part.name);
|
|
38011
|
+
messages.push(assistantMessage(turn, model, clock3()));
|
|
38012
|
+
} else {
|
|
38013
|
+
messages.push(...userMessages(turn, callNames, clock3()));
|
|
38014
|
+
}
|
|
38015
|
+
}
|
|
38016
|
+
const tools = req.tools?.map((t) => ({
|
|
38017
|
+
name: t.name,
|
|
38018
|
+
description: t.description,
|
|
38019
|
+
parameters: t.inputSchema
|
|
38020
|
+
}));
|
|
38021
|
+
return {
|
|
38022
|
+
...req.system !== void 0 ? { systemPrompt: req.system } : {},
|
|
38023
|
+
messages,
|
|
38024
|
+
...tools && tools.length > 0 ? { tools } : {}
|
|
38025
|
+
};
|
|
38026
|
+
}
|
|
38027
|
+
function assistantMessage(turn, model, timestamp) {
|
|
38028
|
+
const content = [];
|
|
38029
|
+
for (const part of turn.content) {
|
|
38030
|
+
if (part.type === "text") content.push({ type: "text", text: part.text });
|
|
38031
|
+
else if (part.type === "tool_use")
|
|
38032
|
+
content.push({ type: "toolCall", id: part.id, name: part.name, arguments: argumentsOf(part.input) });
|
|
38033
|
+
else if (part.type === "thinking")
|
|
38034
|
+
content.push({ type: "thinking", thinking: part.thinking, thinkingSignature: part.signature });
|
|
38035
|
+
else if (part.type === "redacted_thinking")
|
|
38036
|
+
content.push({ type: "thinking", thinking: "", thinkingSignature: part.data, redacted: true });
|
|
38037
|
+
}
|
|
38038
|
+
return {
|
|
38039
|
+
role: "assistant",
|
|
38040
|
+
content,
|
|
38041
|
+
api: model.api,
|
|
38042
|
+
provider: model.provider,
|
|
38043
|
+
model: model.id,
|
|
38044
|
+
usage: { ...ZERO_USAGE, cost: { ...ZERO_USAGE.cost } },
|
|
38045
|
+
stopReason: content.some((p) => p.type === "toolCall") ? "toolUse" : "stop",
|
|
38046
|
+
timestamp
|
|
38047
|
+
};
|
|
38048
|
+
}
|
|
38049
|
+
function argumentsOf(input) {
|
|
38050
|
+
return typeof input === "object" && input !== null && !Array.isArray(input) ? input : {};
|
|
38051
|
+
}
|
|
38052
|
+
function userMessages(turn, callNames, timestamp) {
|
|
38053
|
+
const out = [];
|
|
38054
|
+
const rest = [];
|
|
38055
|
+
for (const part of turn.content) {
|
|
38056
|
+
if (part.type === "tool_result") {
|
|
38057
|
+
const result = {
|
|
38058
|
+
role: "toolResult",
|
|
38059
|
+
toolCallId: part.toolUseId,
|
|
38060
|
+
toolName: callNames.get(part.toolUseId) ?? part.toolUseId,
|
|
38061
|
+
content: toolResultParts(part.content),
|
|
38062
|
+
isError: part.isError === true,
|
|
38063
|
+
timestamp
|
|
38064
|
+
};
|
|
38065
|
+
out.push(result);
|
|
38066
|
+
} else {
|
|
38067
|
+
const visible = visiblePart(part);
|
|
38068
|
+
if (visible) rest.push(visible);
|
|
38069
|
+
}
|
|
38070
|
+
}
|
|
38071
|
+
if (rest.length > 0) out.push({ role: "user", content: rest, timestamp });
|
|
38072
|
+
return out;
|
|
38073
|
+
}
|
|
38074
|
+
function toolResultParts(content) {
|
|
38075
|
+
if (typeof content === "string") return [{ type: "text", text: content }];
|
|
38076
|
+
return content.map((part) => visiblePart(part)).filter((p) => p !== void 0);
|
|
38077
|
+
}
|
|
38078
|
+
function visiblePart(part) {
|
|
38079
|
+
switch (part.type) {
|
|
38080
|
+
case "text":
|
|
38081
|
+
return { type: "text", text: part.text };
|
|
38082
|
+
case "image":
|
|
38083
|
+
return { type: "image", data: part.data, mimeType: part.mediaType };
|
|
38084
|
+
case "document":
|
|
38085
|
+
return {
|
|
38086
|
+
type: "text",
|
|
38087
|
+
text: `
|
|
38088
|
+
|
|
38089
|
+
[attached file: ${part.name ?? "document"} (${part.mediaType}); not supported by this provider]
|
|
38090
|
+
`
|
|
38091
|
+
};
|
|
38092
|
+
default:
|
|
38093
|
+
return void 0;
|
|
38094
|
+
}
|
|
38095
|
+
}
|
|
38096
|
+
function fromPiMessage(message3, provider) {
|
|
38097
|
+
if (message3.stopReason === "error" || message3.stopReason === "aborted") {
|
|
38098
|
+
throw new Error(`Provider "${provider}" (${message3.model}): ${message3.errorMessage ?? "the model call failed"}`);
|
|
38099
|
+
}
|
|
38100
|
+
const content = [];
|
|
38101
|
+
for (const part of message3.content) {
|
|
38102
|
+
if (part.type === "text") content.push({ type: "text", text: part.text });
|
|
38103
|
+
else if (part.type === "toolCall")
|
|
38104
|
+
content.push({ type: "tool_use", id: part.id, name: part.name, input: part.arguments });
|
|
38105
|
+
else if (part.type === "thinking") {
|
|
38106
|
+
content.push(
|
|
38107
|
+
part.redacted ? { type: "redacted_thinking", data: part.thinkingSignature ?? "" } : { type: "thinking", thinking: part.thinking, signature: part.thinkingSignature ?? "" }
|
|
38108
|
+
);
|
|
38109
|
+
}
|
|
38110
|
+
}
|
|
38111
|
+
return { content, stopReason: STOP_REASONS[message3.stopReason] ?? "other", usage: usageOf(message3.usage) };
|
|
38112
|
+
}
|
|
38113
|
+
function usageOf(usage) {
|
|
38114
|
+
return {
|
|
38115
|
+
inputTokens: usage.input,
|
|
38116
|
+
outputTokens: usage.output,
|
|
38117
|
+
cacheReadTokens: usage.cacheRead,
|
|
38118
|
+
cacheWriteTokens: usage.cacheWrite
|
|
38119
|
+
};
|
|
38120
|
+
}
|
|
38121
|
+
var ANTHROPIC_BASE_URL, ZERO_RATES, ZERO_USAGE, CONTEXT_WINDOW, PiAiProviders, PiAiProvider, STOP_REASONS;
|
|
38122
|
+
var init_piAi = __esm({
|
|
38123
|
+
"../../src/core/harness/piAi.ts"() {
|
|
38124
|
+
"use strict";
|
|
38125
|
+
init_provider();
|
|
38126
|
+
init_secrets2();
|
|
38127
|
+
init_clock();
|
|
38128
|
+
ANTHROPIC_BASE_URL = "https://api.anthropic.com";
|
|
38129
|
+
ZERO_RATES = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
|
|
38130
|
+
ZERO_USAGE = { ...ZERO_RATES, totalTokens: 0, cost: { ...ZERO_RATES, total: 0 } };
|
|
38131
|
+
CONTEXT_WINDOW = 2e5;
|
|
38132
|
+
PiAiProviders = class {
|
|
38133
|
+
providers = /* @__PURE__ */ new Map();
|
|
38134
|
+
constructor(configs, opts = {}) {
|
|
38135
|
+
for (const [name, cfg] of Object.entries(configs)) this.providers.set(name, new PiAiProvider(name, cfg, opts));
|
|
38136
|
+
}
|
|
38137
|
+
get(name) {
|
|
38138
|
+
const provider = this.providers.get(name);
|
|
38139
|
+
if (!provider) {
|
|
38140
|
+
throw new Error(`Unknown provider "${name}". Configured providers: ${[...this.providers.keys()].join(", ")}`);
|
|
38141
|
+
}
|
|
38142
|
+
return provider;
|
|
38143
|
+
}
|
|
38144
|
+
names() {
|
|
38145
|
+
return [...this.providers.keys()];
|
|
38146
|
+
}
|
|
38147
|
+
};
|
|
38148
|
+
PiAiProvider = class {
|
|
38149
|
+
name;
|
|
38150
|
+
api;
|
|
38151
|
+
baseUrl;
|
|
38152
|
+
keyEnv;
|
|
38153
|
+
secrets;
|
|
38154
|
+
clock;
|
|
38155
|
+
scripted;
|
|
38156
|
+
fetchImpl;
|
|
38157
|
+
streams;
|
|
38158
|
+
constructor(name, cfg, opts = {}) {
|
|
38159
|
+
this.name = name;
|
|
38160
|
+
this.api = piApiFor(cfg.type);
|
|
38161
|
+
if (cfg.type === "openai-compatible" && !cfg.baseUrl) {
|
|
38162
|
+
throw new Error(`Provider "${name}": openai-compatible providers require baseUrl`);
|
|
38163
|
+
}
|
|
38164
|
+
this.baseUrl = (cfg.baseUrl ?? ANTHROPIC_BASE_URL).replace(/\/+$/, "");
|
|
38165
|
+
this.keyEnv = cfg.apiKeyEnv ?? (cfg.type === "anthropic" ? ANTHROPIC_API_KEY_ENV : void 0);
|
|
38166
|
+
this.secrets = opts.secrets ?? processSecrets;
|
|
38167
|
+
this.clock = opts.clock ?? systemClock;
|
|
38168
|
+
this.scripted = opts.apis?.[this.api];
|
|
38169
|
+
this.fetchImpl = opts.fetch;
|
|
38170
|
+
}
|
|
38171
|
+
/** The model pi is handed for one request: the block as its provider, the
|
|
38172
|
+
* request's output cap as its own, no thinking (neither caller asks for
|
|
38173
|
+
* any), text and images in, the zero rate card. */
|
|
38174
|
+
model(id, maxTokens) {
|
|
38175
|
+
return {
|
|
38176
|
+
id,
|
|
38177
|
+
name: id,
|
|
38178
|
+
api: this.api,
|
|
38179
|
+
provider: this.name,
|
|
38180
|
+
baseUrl: this.baseUrl,
|
|
38181
|
+
reasoning: false,
|
|
38182
|
+
input: ["text", "image"],
|
|
38183
|
+
cost: { ...ZERO_RATES },
|
|
38184
|
+
contextWindow: CONTEXT_WINDOW,
|
|
38185
|
+
maxTokens
|
|
38186
|
+
};
|
|
38187
|
+
}
|
|
38188
|
+
/** One completion: the key revealed into pi's request options and nowhere
|
|
38189
|
+
* else, the request in pi's shape, pi's adapter on the wire, its final
|
|
38190
|
+
* message back in the vocabulary. A missing key fails here, by variable
|
|
38191
|
+
* name, before any request; a provider error or an abort is thrown with
|
|
38192
|
+
* pi's message — `route()` and `reflect()` catch either as they catch a
|
|
38193
|
+
* native adapter's throw. */
|
|
38194
|
+
async complete(req) {
|
|
38195
|
+
let key;
|
|
38196
|
+
if (this.keyEnv !== void 0) {
|
|
38197
|
+
const secret2 = this.secrets.named(this.keyEnv);
|
|
38198
|
+
if (!secret2) throw new Error(`Provider "${this.name}": ${this.keyEnv} is not set`);
|
|
38199
|
+
key = secret2.reveal();
|
|
38200
|
+
}
|
|
38201
|
+
const model = this.model(req.model, req.maxTokens);
|
|
38202
|
+
const options = {
|
|
38203
|
+
...piStreamOptions(this.api, req, key),
|
|
38204
|
+
...this.fetchImpl ? { fetch: this.fetchImpl } : {}
|
|
38205
|
+
};
|
|
38206
|
+
const message3 = await this.api$().stream(model, toPiContext(req, model, this.clock), options).result();
|
|
38207
|
+
return fromPiMessage(message3, this.name);
|
|
38208
|
+
}
|
|
38209
|
+
/** pi's implementation of this provider's API, loaded on the first call and
|
|
38210
|
+
* kept; a scripted one stands in for it in tests. */
|
|
38211
|
+
api$() {
|
|
38212
|
+
this.streams ??= this.scripted ?? (this.api === "anthropic-messages" ? anthropicMessagesApi() : openAICompletionsApi());
|
|
38213
|
+
return this.streams;
|
|
38214
|
+
}
|
|
38215
|
+
};
|
|
38216
|
+
STOP_REASONS = {
|
|
38217
|
+
stop: "end_turn",
|
|
38218
|
+
toolUse: "tool_use",
|
|
38219
|
+
length: "max_tokens"
|
|
38220
|
+
};
|
|
38221
|
+
}
|
|
38222
|
+
});
|
|
38223
|
+
|
|
37678
38224
|
// ../../src/mcp/secretStore.ts
|
|
37679
38225
|
import { existsSync as existsSync13, mkdirSync as mkdirSync9, readFileSync as readFileSync15, writeFileSync as writeFileSync8 } from "node:fs";
|
|
37680
38226
|
import { dirname as dirname10, resolve as resolve10 } from "node:path";
|
|
@@ -38981,6 +39527,7 @@ async function receiveSlackMessage(client, ev, span, policy) {
|
|
|
38981
39527
|
userId: `${PLATFORM}:${ev.user}`,
|
|
38982
39528
|
threadKey: `${PLATFORM}:${ev.channel}:${ev.threadTs}`,
|
|
38983
39529
|
text: ev.text + note,
|
|
39530
|
+
messageId: ev.ts,
|
|
38984
39531
|
channelName,
|
|
38985
39532
|
userName,
|
|
38986
39533
|
sourceUrl: team ? slackPermalink(team, ev.channel, ev.ts, ev.threadTs) : void 0,
|
|
@@ -40038,7 +40585,7 @@ var init_webShell = __esm({
|
|
|
40038
40585
|
init_favicon();
|
|
40039
40586
|
init_residentsModel();
|
|
40040
40587
|
init_webSeed();
|
|
40041
|
-
PAGE_CSP = "default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'";
|
|
40588
|
+
PAGE_CSP = "default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; media-src 'self'; font-src 'self'; connect-src 'self'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'";
|
|
40042
40589
|
FORM_PAGE_CSP = PAGE_CSP.replace("form-action 'none'", "form-action 'self'");
|
|
40043
40590
|
WEB_HTML_HEADERS = {
|
|
40044
40591
|
"content-type": "text/html; charset=utf-8",
|
|
@@ -42771,6 +43318,7 @@ var init_modelProxy = __esm({
|
|
|
42771
43318
|
"use strict";
|
|
42772
43319
|
init_anthropic();
|
|
42773
43320
|
init_openaiCompat();
|
|
43321
|
+
init_provider();
|
|
42774
43322
|
init_http();
|
|
42775
43323
|
ANTHROPIC_MESSAGES_PATH = "/v1/messages";
|
|
42776
43324
|
OPENAI_CHAT_COMPLETIONS_PATH = "/v1/chat/completions";
|
|
@@ -43099,7 +43647,9 @@ async function runRelayedTool(harness, ask, opts = {}) {
|
|
|
43099
43647
|
...base.github?.api.withSpan ? { github: { ...base.github, api: base.github.api.withSpan(span) } } : {}
|
|
43100
43648
|
} : { ...base, publish: (e) => harness.emit(e) },
|
|
43101
43649
|
...opts.signal && !base.signal ? { signal: opts.signal } : {},
|
|
43102
|
-
...readConversation && harness.callSeen ? { conversation: () => harness.callSeen(ask.toolCallId).then(() => readConversation()) } : {}
|
|
43650
|
+
...readConversation && harness.callSeen ? { conversation: () => harness.callSeen(ask.toolCallId).then(() => readConversation()) } : {},
|
|
43651
|
+
// After the spreads, so the call's own id can never be shadowed by one on the base context.
|
|
43652
|
+
callId: ask.toolCallId
|
|
43103
43653
|
};
|
|
43104
43654
|
try {
|
|
43105
43655
|
const input = typeof ask.input === "object" && ask.input !== null ? ask.input : {};
|
|
@@ -43133,7 +43683,7 @@ var init_relay = __esm({
|
|
|
43133
43683
|
"../../src/core/harness/pi/relay.ts"() {
|
|
43134
43684
|
"use strict";
|
|
43135
43685
|
init_tracingExecutor();
|
|
43136
|
-
|
|
43686
|
+
init_chatMessage();
|
|
43137
43687
|
init_awaitChildren();
|
|
43138
43688
|
init_runEvents();
|
|
43139
43689
|
init_toolRules();
|
|
@@ -44732,6 +45282,7 @@ async function runBot() {
|
|
|
44732
45282
|
});
|
|
44733
45283
|
console.log(`[capabilities] ${JSON.stringify(capabilities)}`);
|
|
44734
45284
|
const providers = new ProviderRegistry(config.config.providers);
|
|
45285
|
+
const completions = new PiAiProviders(config.config.providers);
|
|
44735
45286
|
const skills = new BundledSkillStore(DEFAULT_SKILLS_DIR);
|
|
44736
45287
|
let slackEmailLookup;
|
|
44737
45288
|
const mcpWiring = buildMcp(config, processSecrets, {
|
|
@@ -44839,6 +45390,7 @@ async function runBot() {
|
|
|
44839
45390
|
const deps = {
|
|
44840
45391
|
config,
|
|
44841
45392
|
providers,
|
|
45393
|
+
completions,
|
|
44842
45394
|
spanLog,
|
|
44843
45395
|
runBearers,
|
|
44844
45396
|
harness: {
|
|
@@ -45309,6 +45861,7 @@ var init_index = __esm({
|
|
|
45309
45861
|
init_config2();
|
|
45310
45862
|
init_capabilities();
|
|
45311
45863
|
init_registry3();
|
|
45864
|
+
init_piAi();
|
|
45312
45865
|
init_slack();
|
|
45313
45866
|
init_slackChannelDirectory();
|
|
45314
45867
|
init_http();
|
|
@@ -45427,6 +45980,7 @@ init_memory();
|
|
|
45427
45980
|
init_residentAdmin();
|
|
45428
45981
|
init_residentFleet();
|
|
45429
45982
|
init_registry3();
|
|
45983
|
+
init_piAi();
|
|
45430
45984
|
init_skills2();
|
|
45431
45985
|
init_mcp2();
|
|
45432
45986
|
init_source();
|
|
@@ -45754,6 +46308,7 @@ async function main() {
|
|
|
45754
46308
|
globalThis.console = new Console({ stdout: process.stderr, stderr: process.stderr });
|
|
45755
46309
|
const { config, runStore } = await bot();
|
|
45756
46310
|
const providers = new ProviderRegistry(config.config.providers);
|
|
46311
|
+
const completions = new PiAiProviders(config.config.providers);
|
|
45757
46312
|
const skills = new BundledSkillStore(DEFAULT_SKILLS_DIR);
|
|
45758
46313
|
const capabilities = capabilitiesFrom(config.config, publicEnv(), processSecrets, {
|
|
45759
46314
|
meatBinary: meatOnPath(publicEnv())
|
|
@@ -45770,6 +46325,7 @@ async function main() {
|
|
|
45770
46325
|
const deps = {
|
|
45771
46326
|
config,
|
|
45772
46327
|
providers,
|
|
46328
|
+
completions,
|
|
45773
46329
|
capabilities,
|
|
45774
46330
|
residentFleet,
|
|
45775
46331
|
...artifacts ? { artifacts } : {},
|