@coreplane/switchboard 1.218.0 → 1.219.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "switchboard",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.219.0",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "switchboard",
|
|
9
|
-
"version": "1.
|
|
9
|
+
"version": "1.219.0",
|
|
10
10
|
"license": "Apache-2.0",
|
|
11
11
|
"workspaces": [
|
|
12
12
|
"web",
|
|
@@ -16710,7 +16710,7 @@
|
|
|
16710
16710
|
},
|
|
16711
16711
|
"packages/switchboard": {
|
|
16712
16712
|
"name": "@coreplane/switchboard",
|
|
16713
|
-
"version": "1.
|
|
16713
|
+
"version": "1.219.0",
|
|
16714
16714
|
"license": "Apache-2.0",
|
|
16715
16715
|
"dependencies": {
|
|
16716
16716
|
"@anthropic-ai/sdk": "^0.124.0",
|
package/dist/assets/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "switchboard",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.219.0",
|
|
4
4
|
"private": true,
|
|
5
5
|
"description": "Mention it in Slack and an agent reviews the PR, ships the fix, or answers the question — on the model you choose, with its tools running where you decide.",
|
|
6
6
|
"license": "Apache-2.0",
|
package/dist/assets/source.json
CHANGED
|
@@ -435,6 +435,8 @@ TIME. Your budget is up to two hours — less when a boundary or the request's \
|
|
|
435
435
|
|
|
436
436
|
READ-ONLY: NEVER open a pull request, and never commit or push — no branch, no \`gh pr create\`, no PR or issue write of any kind. You hold a read credential and your job is to find out, not to change. If the investigation shows a change is needed, say exactly what and where in your write-up and point the user at \`agent:coding\`.
|
|
437
437
|
|
|
438
|
+
You cannot attach or post files: your whole answer is text. Never say a file is attached or below — name its path in the workspace and describe it (what it shows, its size) instead; a person who needs the file itself asks \`agent:coding\`, which can attach.
|
|
439
|
+
|
|
438
440
|
Maintain the user-facing status card with the update_status tool: post your plan as a checklist (○ pending) once you have it, and update items as they start (✱) and finish (✓ — only after they actually happened). Items are short outcomes ("Clone and install", "Time the full suite"), never commands.
|
|
439
441
|
|
|
440
442
|
${NOTEPAD}
|
|
@@ -38,7 +38,8 @@ export type FrictionCategory =
|
|
|
38
38
|
| "setup_install"
|
|
39
39
|
| "wrap_up"
|
|
40
40
|
| "budget_hit"
|
|
41
|
-
| "infra_failure"
|
|
41
|
+
| "infra_failure"
|
|
42
|
+
| "unkept_promise";
|
|
42
43
|
|
|
43
44
|
export const FRICTION_CATEGORIES: readonly FrictionCategory[] = [
|
|
44
45
|
"slow_tool",
|
|
@@ -49,6 +50,7 @@ export const FRICTION_CATEGORIES: readonly FrictionCategory[] = [
|
|
|
49
50
|
"wrap_up",
|
|
50
51
|
"budget_hit",
|
|
51
52
|
"infra_failure",
|
|
53
|
+
"unkept_promise",
|
|
52
54
|
];
|
|
53
55
|
|
|
54
56
|
/** Human labels — the verdict line, the report's table and its finding lines. */
|
|
@@ -61,6 +63,7 @@ export const CATEGORY_LABEL: Record<FrictionCategory, string> = {
|
|
|
61
63
|
wrap_up: "agent wind-down",
|
|
62
64
|
budget_hit: "budget hits",
|
|
63
65
|
infra_failure: "infra failures",
|
|
66
|
+
unkept_promise: "unkept promises",
|
|
64
67
|
};
|
|
65
68
|
|
|
66
69
|
/** What a category's time is a share OF: tool time for the categories whose
|
|
@@ -75,8 +78,17 @@ export const DENOMINATOR_OF: Record<FrictionCategory, "tool" | "run"> = {
|
|
|
75
78
|
wrap_up: "run",
|
|
76
79
|
budget_hit: "run",
|
|
77
80
|
infra_failure: "run",
|
|
81
|
+
unkept_promise: "run",
|
|
78
82
|
};
|
|
79
83
|
|
|
84
|
+
/** The ways a reply tells the person a file is attached — the promise `unkept_promise` holds a run to.
|
|
85
|
+
* One deliberate expression, case-insensitive: "attached below/here/above", "see (the) attached",
|
|
86
|
+
* "I've/I have attached", "is/are attached", "attachment(s) (is/are) below/here". A path such as
|
|
87
|
+
* `./attachments/1-clip.mp4` or the bare word "attachments/" names a place, not a promise, and
|
|
88
|
+
* does not match; nor does prose about attaching in an `assistant` turn — only the `answer` counts. */
|
|
89
|
+
export const ATTACHMENT_PROMISE =
|
|
90
|
+
/\b(?:attached (?:below|here|above)|see (?:the )?attached|i(?:'ve| have) attached|(?:is|are) attached\b|attachments? (?:(?:is|are) )?(?:below|here))/i;
|
|
91
|
+
|
|
80
92
|
export type FrictionSeverity = "low" | "medium" | "high";
|
|
81
93
|
|
|
82
94
|
export interface FrictionFinding {
|
|
@@ -382,6 +394,9 @@ export function analyzeRunFriction(events: readonly RunEvent[], opts: FrictionOp
|
|
|
382
394
|
let sideFactEvents = 0; // skill_use / review_artifact / pr_description / pr_opened / review_posted / ship_round / route: facts about the run, not steps
|
|
383
395
|
let spanEvents = 0; // span_start / span_end (docs/reference/specs/tracing.md): timing records, not steps
|
|
384
396
|
let wrapUp: { index: number; at?: number } | undefined;
|
|
397
|
+
// The reply and the files the run sent out: what `unkept_promise` compares after the loop.
|
|
398
|
+
let answer: { index: number; text: string } | undefined;
|
|
399
|
+
let outboundArtifacts = 0;
|
|
385
400
|
events.forEach((ev, index) => {
|
|
386
401
|
flushTurns(index);
|
|
387
402
|
if (isSpanRecord(ev)) {
|
|
@@ -390,8 +405,10 @@ export function analyzeRunFriction(events: readonly RunEvent[], opts: FrictionOp
|
|
|
390
405
|
}
|
|
391
406
|
if (isNarrative(ev)) {
|
|
392
407
|
narrativeEvents++; // the narrative and `run_meta` are neither steps nor findings
|
|
408
|
+
if (ev.type === "answer") answer = { index, text: ev.text };
|
|
393
409
|
return;
|
|
394
410
|
}
|
|
411
|
+
if (ev.type === "artifact" && ev.direction === "out") outboundArtifacts++;
|
|
395
412
|
// Side facts about the run, not steps: skill_use rides beside a use_skill
|
|
396
413
|
// call that already produced its own tool pair, and artifact beside the
|
|
397
414
|
// attach_file call (or the dispatcher's staging) that moved the file;
|
|
@@ -582,6 +599,24 @@ export function analyzeRunFriction(events: readonly RunEvent[], opts: FrictionOp
|
|
|
582
599
|
f.interval = { start: at, end: Math.max(at, windowEnd) };
|
|
583
600
|
}
|
|
584
601
|
}
|
|
602
|
+
// The reply promised a file the run never produced: no tool failed (a preset without
|
|
603
|
+
// attach_file makes no call), so the answer's own words are the only witness. Once per
|
|
604
|
+
// run, anchored to the answer, no extent; the line that made the promise is quoted.
|
|
605
|
+
if (answer !== undefined && outboundArtifacts === 0) {
|
|
606
|
+
const promised = ATTACHMENT_PROMISE.exec(answer.text);
|
|
607
|
+
if (promised) {
|
|
608
|
+
const line =
|
|
609
|
+
answer.text.slice(0, promised.index).split("\n").pop()! + answer.text.slice(promised.index).split("\n")[0]!;
|
|
610
|
+
const trimmed = line.trim();
|
|
611
|
+
const quoted = trimmed.length > 120 ? `…${trimmed.slice(trimmed.length - 119)}` : trimmed;
|
|
612
|
+
findings.push({
|
|
613
|
+
category: "unkept_promise",
|
|
614
|
+
severity: "low",
|
|
615
|
+
summary: `unkept promise: the reply says a file is attached but the run produced none — "${quoted}"`,
|
|
616
|
+
eventIndex: answer.index,
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
}
|
|
585
620
|
|
|
586
621
|
// ---- totals ----------------------------------------------------------------
|
|
587
622
|
let toolTimeMs: number | undefined;
|
package/dist/cli.js
CHANGED
|
@@ -1992,6 +1992,8 @@ TIME. Your budget is up to two hours \u2014 less when a boundary or the request'
|
|
|
1992
1992
|
|
|
1993
1993
|
READ-ONLY: NEVER open a pull request, and never commit or push \u2014 no branch, no \`gh pr create\`, no PR or issue write of any kind. You hold a read credential and your job is to find out, not to change. If the investigation shows a change is needed, say exactly what and where in your write-up and point the user at \`agent:coding\`.
|
|
1994
1994
|
|
|
1995
|
+
You cannot attach or post files: your whole answer is text. Never say a file is attached or below \u2014 name its path in the workspace and describe it (what it shows, its size) instead; a person who needs the file itself asks \`agent:coding\`, which can attach.
|
|
1996
|
+
|
|
1995
1997
|
Maintain the user-facing status card with the update_status tool: post your plan as a checklist (\u25CB pending) once you have it, and update items as they start (\u2731) and finish (\u2713 \u2014 only after they actually happened). Items are short outcomes ("Clone and install", "Time the full suite"), never commands.
|
|
1996
1998
|
|
|
1997
1999
|
${NOTEPAD}
|
|
@@ -13613,6 +13615,8 @@ function analyzeRunFriction(events, opts = {}) {
|
|
|
13613
13615
|
let sideFactEvents = 0;
|
|
13614
13616
|
let spanEvents = 0;
|
|
13615
13617
|
let wrapUp;
|
|
13618
|
+
let answer;
|
|
13619
|
+
let outboundArtifacts = 0;
|
|
13616
13620
|
events.forEach((ev, index) => {
|
|
13617
13621
|
flushTurns(index);
|
|
13618
13622
|
if (isSpanRecord(ev)) {
|
|
@@ -13621,8 +13625,10 @@ function analyzeRunFriction(events, opts = {}) {
|
|
|
13621
13625
|
}
|
|
13622
13626
|
if (isNarrative(ev)) {
|
|
13623
13627
|
narrativeEvents++;
|
|
13628
|
+
if (ev.type === "answer") answer = { index, text: ev.text };
|
|
13624
13629
|
return;
|
|
13625
13630
|
}
|
|
13631
|
+
if (ev.type === "artifact" && ev.direction === "out") outboundArtifacts++;
|
|
13626
13632
|
if (ev.type === "skill_use" || ev.type === "artifact" || ev.type === "review_artifact" || ev.type === "pr_description" || ev.type === "pr_opened" || ev.type === "review_posted" || ev.type === "ship_round" || ev.type === "route") {
|
|
13627
13633
|
sideFactEvents++;
|
|
13628
13634
|
return;
|
|
@@ -13768,6 +13774,20 @@ function analyzeRunFriction(events, opts = {}) {
|
|
|
13768
13774
|
f.interval = { start: at, end: Math.max(at, windowEnd) };
|
|
13769
13775
|
}
|
|
13770
13776
|
}
|
|
13777
|
+
if (answer !== void 0 && outboundArtifacts === 0) {
|
|
13778
|
+
const promised = ATTACHMENT_PROMISE.exec(answer.text);
|
|
13779
|
+
if (promised) {
|
|
13780
|
+
const line3 = answer.text.slice(0, promised.index).split("\n").pop() + answer.text.slice(promised.index).split("\n")[0];
|
|
13781
|
+
const trimmed = line3.trim();
|
|
13782
|
+
const quoted = trimmed.length > 120 ? `\u2026${trimmed.slice(trimmed.length - 119)}` : trimmed;
|
|
13783
|
+
findings.push({
|
|
13784
|
+
category: "unkept_promise",
|
|
13785
|
+
severity: "low",
|
|
13786
|
+
summary: `unkept promise: the reply says a file is attached but the run produced none \u2014 "${quoted}"`,
|
|
13787
|
+
eventIndex: answer.index
|
|
13788
|
+
});
|
|
13789
|
+
}
|
|
13790
|
+
}
|
|
13771
13791
|
let toolTimeMs;
|
|
13772
13792
|
for (const s of toolSpans) {
|
|
13773
13793
|
const d = durationOfSpan(s);
|
|
@@ -13844,7 +13864,7 @@ function formatFrictionReport(d) {
|
|
|
13844
13864
|
}
|
|
13845
13865
|
return lines.join("\n");
|
|
13846
13866
|
}
|
|
13847
|
-
var FRICTION_CATEGORIES, CATEGORY_LABEL, DENOMINATOR_OF, DEFAULT_SLOW_TOOL_MS, DEFAULT_SLOW_MODEL_TURN_MS, INSTALL_SEGMENT, QUOTED, callSignature;
|
|
13867
|
+
var FRICTION_CATEGORIES, CATEGORY_LABEL, DENOMINATOR_OF, ATTACHMENT_PROMISE, DEFAULT_SLOW_TOOL_MS, DEFAULT_SLOW_MODEL_TURN_MS, INSTALL_SEGMENT, QUOTED, callSignature;
|
|
13848
13868
|
var init_runFriction = __esm({
|
|
13849
13869
|
"../../src/core/runFriction.ts"() {
|
|
13850
13870
|
"use strict";
|
|
@@ -13861,7 +13881,8 @@ var init_runFriction = __esm({
|
|
|
13861
13881
|
"setup_install",
|
|
13862
13882
|
"wrap_up",
|
|
13863
13883
|
"budget_hit",
|
|
13864
|
-
"infra_failure"
|
|
13884
|
+
"infra_failure",
|
|
13885
|
+
"unkept_promise"
|
|
13865
13886
|
];
|
|
13866
13887
|
CATEGORY_LABEL = {
|
|
13867
13888
|
slow_tool: "slow tool calls",
|
|
@@ -13871,7 +13892,8 @@ var init_runFriction = __esm({
|
|
|
13871
13892
|
setup_install: "the repo's setup/install",
|
|
13872
13893
|
wrap_up: "agent wind-down",
|
|
13873
13894
|
budget_hit: "budget hits",
|
|
13874
|
-
infra_failure: "infra failures"
|
|
13895
|
+
infra_failure: "infra failures",
|
|
13896
|
+
unkept_promise: "unkept promises"
|
|
13875
13897
|
};
|
|
13876
13898
|
DENOMINATOR_OF = {
|
|
13877
13899
|
slow_tool: "tool",
|
|
@@ -13881,8 +13903,10 @@ var init_runFriction = __esm({
|
|
|
13881
13903
|
slow_model_turn: "run",
|
|
13882
13904
|
wrap_up: "run",
|
|
13883
13905
|
budget_hit: "run",
|
|
13884
|
-
infra_failure: "run"
|
|
13906
|
+
infra_failure: "run",
|
|
13907
|
+
unkept_promise: "run"
|
|
13885
13908
|
};
|
|
13909
|
+
ATTACHMENT_PROMISE = /\b(?:attached (?:below|here|above)|see (?:the )?attached|i(?:'ve| have) attached|(?:is|are) attached\b|attachments? (?:(?:is|are) )?(?:below|here))/i;
|
|
13886
13910
|
DEFAULT_SLOW_TOOL_MS = 3e4;
|
|
13887
13911
|
DEFAULT_SLOW_MODEL_TURN_MS = 6e4;
|
|
13888
13912
|
INSTALL_SEGMENT = /(?:^|&&|;|\|\|?)\s*(?:sudo\s+)?(?:corepack\s+)?(?:(?:npm|pnpm|bun)\s+(?:install|i|ci|add)(?=\s|$)|yarn(?:\s+(?:install|add)(?=\s|$)|\s*$)|npx\s+playwright\s+install(?=\s|$)|(?:python3?\s+-m\s+)?pip3?\s+install(?=\s|$)|poetry\s+install(?=\s|$)|uv\s+(?:sync|pip\s+install)(?=\s|$)|apt(?:-get)?\s+install(?=\s|$)|apk\s+add(?=\s|$)|brew\s+install(?=\s|$)|bundle\s+install(?=\s|$)|gem\s+install(?=\s|$)|cargo\s+(?:fetch|build)(?=\s|$)|go\s+mod\s+download(?=\s|$)|git\s+clone(?=\s|$)|make\s+(?:deps|install|setup)(?=\s|$))/;
|
|
@@ -15030,6 +15054,8 @@ function patternSignature(f) {
|
|
|
15030
15054
|
}
|
|
15031
15055
|
case "wrap_up":
|
|
15032
15056
|
return "wrap_up:wrap_up";
|
|
15057
|
+
case "unkept_promise":
|
|
15058
|
+
return "unkept_promise:attachment";
|
|
15033
15059
|
case "slow_model_turn":
|
|
15034
15060
|
return "slow_model_turn:model_turn";
|
|
15035
15061
|
case "infra_failure": {
|
|
@@ -15249,6 +15275,8 @@ ${evidence}`);
|
|
|
15249
15275
|
return `Runs ended with \`${p.signature.replace(/^mid-tool /, "")}\` still outstanding \u2014 the run (or its transport) was cut while the tool ran. Correlate the affected runs with deploys/drains (\`[drain]\` log lines), the sandbox command timeout (exit 124 / heartbeat streaming, docs/reference/specs/execution.md), and the executor's error surfacing.`;
|
|
15250
15276
|
}
|
|
15251
15277
|
return `The exec transport failed during \`${p.signature}\` in ${p.runIds.length} runs (the sandbox, not the command). Check the sandbox/resident Worker logs (\`deploy/bin/cf-logs\`) around the affected runs for the underlying error, and whether the command's runtime exceeds the executor's timeout.`;
|
|
15278
|
+
case "unkept_promise":
|
|
15279
|
+
return `In ${p.runIds.length} runs the reply told the person a file was attached and the run produced none \u2014 no outbound \`artifact\` event, so nothing reached the thread or the run page. Read the affected runs' presets: a preset without \`attach_file\` in its toolset (\`src/agents/registry.ts\`) must say in its prompt that its whole answer is text and name the workspace path instead of promising an attachment; an ask that needs the file belongs on a preset with the full toolset (the router's description of the preset should say it cannot post files). If the run did call \`attach_file\` and it failed, the paired \`failed_tool\` proposal has the cause.`;
|
|
15252
15280
|
case "long_run":
|
|
15253
15281
|
return `These \`${p.signature}\` runs took \u2265 ${LONG_RUN_FACTOR}\xD7 the median run time \u2014 the closest available proxy for a cost spike (there is no per-run token accounting yet). Open each affected run's friction report for its dominant cause; if the shape of work is legitimately long, split it or lower the agent's effort tier; if it is not, tighten \`maxMinutes\` so a runaway run is cut earlier.`;
|
|
15254
15282
|
}
|
|
@@ -15290,6 +15318,7 @@ var init_frictionProposals = __esm({
|
|
|
15290
15318
|
wrap_up: "agent wind-down",
|
|
15291
15319
|
budget_hit: "budget hit",
|
|
15292
15320
|
infra_failure: "infra failure",
|
|
15321
|
+
unkept_promise: "unkept promise",
|
|
15293
15322
|
long_run: "long run"
|
|
15294
15323
|
};
|
|
15295
15324
|
RESULT_TAIL = / → [\s\S]*$/;
|
|
@@ -23395,7 +23424,7 @@ async function resolveRepoContext(msg, history = [], probe, residentSlugs) {
|
|
|
23395
23424
|
};
|
|
23396
23425
|
const resolveAddressed = async (a) => {
|
|
23397
23426
|
if (a.slug !== void 0) return probe && await vet(a.slug) ? a.slug : void 0;
|
|
23398
|
-
const matches = (await listSlugs() ?? []).filter((slug) => slug.split("/")[1] === a.name);
|
|
23427
|
+
const matches = (await listSlugs() ?? []).filter((slug) => slug.split("/")[1]?.toLowerCase() === a.name);
|
|
23399
23428
|
return matches.length === 1 ? matches[0] : void 0;
|
|
23400
23429
|
};
|
|
23401
23430
|
const strongNow = s.pr?.repo ?? (s.repoStrong ? s.repo : void 0) ?? (s.addressed ? await resolveAddressed(s.addressed) : void 0);
|
|
@@ -28970,7 +28999,9 @@ function attachmentsLine(outcomes) {
|
|
|
28970
28999
|
const parts = [];
|
|
28971
29000
|
if (landed.length > 0) {
|
|
28972
29001
|
parts.push(
|
|
28973
|
-
`Attached files are in ./${ATTACHMENTS_DIR}/: ${landed.map(
|
|
29002
|
+
`Attached files are in ./${ATTACHMENTS_DIR}/: ${landed.map(
|
|
29003
|
+
(o) => `${o.basename} (${formatSize(o.file.size)}, ${o.file.type}${o.earlier ? ", from earlier in the thread" : ""})`
|
|
29004
|
+
).join(", ")}`
|
|
28974
29005
|
);
|
|
28975
29006
|
}
|
|
28976
29007
|
for (const o of failed) parts.push(`${o.file.name} could not be staged: ${o.error}`);
|
|
@@ -29022,9 +29053,63 @@ async function pullStaged(outcomes, deps) {
|
|
|
29022
29053
|
}
|
|
29023
29054
|
return outcomes.map((o) => {
|
|
29024
29055
|
const error = o.error ?? pulled.get(o.key);
|
|
29025
|
-
|
|
29056
|
+
if (error !== void 0) return { ...o, error };
|
|
29057
|
+
const { error: _dropped, ...landedOutcome } = o;
|
|
29058
|
+
return landedOutcome;
|
|
29026
29059
|
});
|
|
29027
29060
|
}
|
|
29061
|
+
function threadInboundArtifacts(runsOldestFirst) {
|
|
29062
|
+
const seen = /* @__PURE__ */ new Set();
|
|
29063
|
+
const out = [];
|
|
29064
|
+
for (const events of runsOldestFirst) {
|
|
29065
|
+
for (const e of events) {
|
|
29066
|
+
if (e.type !== "artifact" || e.direction !== "in" || seen.has(e.key)) continue;
|
|
29067
|
+
seen.add(e.key);
|
|
29068
|
+
out.push({ key: e.key, name: e.name, size: e.size, contentType: e.contentType });
|
|
29069
|
+
}
|
|
29070
|
+
}
|
|
29071
|
+
return out;
|
|
29072
|
+
}
|
|
29073
|
+
async function stageThreadArtifacts(artifacts, deps) {
|
|
29074
|
+
const outcomes = [];
|
|
29075
|
+
for (const a of artifacts) {
|
|
29076
|
+
const file = { name: a.name, size: a.size, type: a.contentType };
|
|
29077
|
+
let held;
|
|
29078
|
+
try {
|
|
29079
|
+
held = await deps.store.head(a.key) !== null;
|
|
29080
|
+
} catch (err2) {
|
|
29081
|
+
outcomes.push({
|
|
29082
|
+
file,
|
|
29083
|
+
basename: safeBasename(a.name),
|
|
29084
|
+
key: a.key,
|
|
29085
|
+
earlier: true,
|
|
29086
|
+
error: `the store could not be asked: ${describe4(err2)}`
|
|
29087
|
+
});
|
|
29088
|
+
continue;
|
|
29089
|
+
}
|
|
29090
|
+
if (!held) {
|
|
29091
|
+
outcomes.push({
|
|
29092
|
+
file,
|
|
29093
|
+
basename: safeBasename(a.name),
|
|
29094
|
+
key: a.key,
|
|
29095
|
+
earlier: true,
|
|
29096
|
+
error: "the store no longer holds it (its retention passed)"
|
|
29097
|
+
});
|
|
29098
|
+
continue;
|
|
29099
|
+
}
|
|
29100
|
+
const basename4 = stagedBasename(deps.nextIndex(), a.name);
|
|
29101
|
+
deps.publish?.({
|
|
29102
|
+
type: "artifact",
|
|
29103
|
+
direction: "in",
|
|
29104
|
+
key: a.key,
|
|
29105
|
+
name: a.name,
|
|
29106
|
+
size: a.size,
|
|
29107
|
+
contentType: a.contentType
|
|
29108
|
+
});
|
|
29109
|
+
outcomes.push({ file, basename: basename4, key: a.key, earlier: true });
|
|
29110
|
+
}
|
|
29111
|
+
return outcomes;
|
|
29112
|
+
}
|
|
29028
29113
|
async function stageIntoWorkspace(files, deps) {
|
|
29029
29114
|
const outcomes = await pullStaged(await copyStaged(files, deps), deps);
|
|
29030
29115
|
return { line: attachmentsLine(outcomes), outcomes };
|
|
@@ -31254,6 +31339,7 @@ async function runPiHarness(deps, run2) {
|
|
|
31254
31339
|
writeUpAt = now();
|
|
31255
31340
|
transport.send({ type: "steer", message: instruction });
|
|
31256
31341
|
};
|
|
31342
|
+
let steers = Promise.resolve();
|
|
31257
31343
|
const drainFollowUps = () => {
|
|
31258
31344
|
const inputs = run2.inbox?.drain() ?? [];
|
|
31259
31345
|
if (inputs.length === 0) return;
|
|
@@ -31271,7 +31357,26 @@ async function runPiHarness(deps, run2) {
|
|
|
31271
31357
|
const images = inputs.flatMap(
|
|
31272
31358
|
(i) => (i.images ?? []).map((img) => ({ type: "image", data: img.data, mimeType: img.mediaType }))
|
|
31273
31359
|
);
|
|
31274
|
-
|
|
31360
|
+
steers = steers.then(async () => {
|
|
31361
|
+
let stagedLine = "";
|
|
31362
|
+
if (run2.stageFollowUps) {
|
|
31363
|
+
try {
|
|
31364
|
+
stagedLine = await run2.stageFollowUps(inputs);
|
|
31365
|
+
} catch (err2) {
|
|
31366
|
+
stagedLine = `Attached files could not be staged: ${err2 instanceof Error ? err2.message : String(err2)}`;
|
|
31367
|
+
note("follow_up", `staging the follow-up's files failed: ${redactSecrets(stagedLine)}`);
|
|
31368
|
+
}
|
|
31369
|
+
}
|
|
31370
|
+
if (settled) return;
|
|
31371
|
+
const prompt = followUpPrompt(inputs);
|
|
31372
|
+
transport.send({
|
|
31373
|
+
type: "steer",
|
|
31374
|
+
message: stagedLine ? `${prompt}
|
|
31375
|
+
|
|
31376
|
+
${stagedLine}` : prompt,
|
|
31377
|
+
...images.length > 0 ? { images } : {}
|
|
31378
|
+
});
|
|
31379
|
+
});
|
|
31275
31380
|
};
|
|
31276
31381
|
const check = () => {
|
|
31277
31382
|
const requested = run2.control?.requested;
|
|
@@ -32084,6 +32189,19 @@ async function runLoop2(deps, ctx) {
|
|
|
32084
32189
|
const attachFile = io.attachFile?.bind(io);
|
|
32085
32190
|
const uploadTicket = io.uploadTicket?.bind(io);
|
|
32086
32191
|
const nextStagedIndex = ctx.stagingIndex ?? stagingIndex();
|
|
32192
|
+
const stageFollowUps = deps.artifacts ? async (inputs) => {
|
|
32193
|
+
const files = inputs.flatMap((i) => i.staged ?? []);
|
|
32194
|
+
if (files.length === 0) return "";
|
|
32195
|
+
const staged = await stageIntoWorkspace(files, {
|
|
32196
|
+
store: deps.artifacts,
|
|
32197
|
+
threadKey: msg.threadKey,
|
|
32198
|
+
publish: (e) => registry.publish(run2.id, e),
|
|
32199
|
+
nextIndex: nextStagedIndex,
|
|
32200
|
+
executor,
|
|
32201
|
+
resident: round2.selection.resident !== void 0
|
|
32202
|
+
});
|
|
32203
|
+
return staged.line;
|
|
32204
|
+
} : void 0;
|
|
32087
32205
|
let artifactSeq = 0;
|
|
32088
32206
|
const artifacts = deps.artifacts ? {
|
|
32089
32207
|
store: deps.artifacts,
|
|
@@ -32164,6 +32282,7 @@ async function runLoop2(deps, ctx) {
|
|
|
32164
32282
|
span: root,
|
|
32165
32283
|
control: run2.control,
|
|
32166
32284
|
inbox: admitted.inbox,
|
|
32285
|
+
...stageFollowUps ? { stageFollowUps } : {},
|
|
32167
32286
|
onEvent,
|
|
32168
32287
|
onProgress,
|
|
32169
32288
|
...ledgerRun ? {
|
|
@@ -32202,23 +32321,7 @@ async function runLoop2(deps, ctx) {
|
|
|
32202
32321
|
// operator stop from /runs
|
|
32203
32322
|
inbox: admitted.inbox,
|
|
32204
32323
|
// thread follow-ups steered into this run (thread-admission item 2)
|
|
32205
|
-
|
|
32206
|
-
// store and pulled into this workspace before the model reads the turn.
|
|
32207
|
-
...deps.artifacts ? {
|
|
32208
|
-
stageFollowUps: async (inputs) => {
|
|
32209
|
-
const files = inputs.flatMap((i) => i.staged ?? []);
|
|
32210
|
-
if (files.length === 0) return "";
|
|
32211
|
-
const staged = await stageIntoWorkspace(files, {
|
|
32212
|
-
store: deps.artifacts,
|
|
32213
|
-
threadKey: msg.threadKey,
|
|
32214
|
-
publish: (e) => registry.publish(run2.id, e),
|
|
32215
|
-
nextIndex: nextStagedIndex,
|
|
32216
|
-
executor,
|
|
32217
|
-
resident: round2.selection.resident !== void 0
|
|
32218
|
-
});
|
|
32219
|
-
return staged.line;
|
|
32220
|
-
}
|
|
32221
|
-
} : {},
|
|
32324
|
+
...stageFollowUps ? { stageFollowUps } : {},
|
|
32222
32325
|
// The step record before each step's tools (run-history item 35).
|
|
32223
32326
|
...ledgerRun ? { onStep: ledgerRun.step.bind(ledgerRun) } : {},
|
|
32224
32327
|
// A resume re-enters the loop from the plan (run-history item 37).
|
|
@@ -33889,6 +33992,31 @@ var init_lineage = __esm({
|
|
|
33889
33992
|
});
|
|
33890
33993
|
|
|
33891
33994
|
// ../../src/core/dispatch/thread.ts
|
|
33995
|
+
async function readThreadArtifacts(service, thread, warn = (line3) => console.warn(line3)) {
|
|
33996
|
+
const perRun = [];
|
|
33997
|
+
for (const run2 of [...thread].reverse()) {
|
|
33998
|
+
const events = [];
|
|
33999
|
+
let afterSeq;
|
|
34000
|
+
try {
|
|
34001
|
+
for (; ; ) {
|
|
34002
|
+
const r = await service.getRunEvents(run2.id, afterSeq === void 0 ? {} : { afterSeq });
|
|
34003
|
+
if (!r.ok) {
|
|
34004
|
+
warn(`[thread] ${run2.id}: reading its received files stopped after ${events.length} event(s) \u2014 ${r.error}`);
|
|
34005
|
+
break;
|
|
34006
|
+
}
|
|
34007
|
+
events.push(...r.value.events);
|
|
34008
|
+
if (r.value.nextAfterSeq === void 0) break;
|
|
34009
|
+
afterSeq = r.value.nextAfterSeq;
|
|
34010
|
+
}
|
|
34011
|
+
} catch (err2) {
|
|
34012
|
+
warn(
|
|
34013
|
+
`[thread] ${run2.id}: reading its received files failed after ${events.length} event(s) \u2014 ${err2 instanceof Error ? err2.message : String(err2)}`
|
|
34014
|
+
);
|
|
34015
|
+
}
|
|
34016
|
+
perRun.push(events);
|
|
34017
|
+
}
|
|
34018
|
+
return threadInboundArtifacts(perRun);
|
|
34019
|
+
}
|
|
33892
34020
|
async function readThread(service, threadKey) {
|
|
33893
34021
|
try {
|
|
33894
34022
|
const page2 = await service.listRuns({
|
|
@@ -33923,6 +34051,7 @@ var THREAD_READ_LIMIT;
|
|
|
33923
34051
|
var init_thread = __esm({
|
|
33924
34052
|
"../../src/core/dispatch/thread.ts"() {
|
|
33925
34053
|
"use strict";
|
|
34054
|
+
init_staging();
|
|
33926
34055
|
THREAD_READ_LIMIT = 8;
|
|
33927
34056
|
}
|
|
33928
34057
|
});
|
|
@@ -34401,6 +34530,13 @@ async function dispatch(deps, msg, io, opts = {}) {
|
|
|
34401
34530
|
nextIndex: nextStagedIndex,
|
|
34402
34531
|
publish: (e) => registry.publish(runId3, e)
|
|
34403
34532
|
}) : void 0;
|
|
34533
|
+
const earlierStaged = !resume && deps.artifacts && agent.machine !== "none" && thread && thread.length > 0 ? readThreadArtifacts(runsService, thread).then(
|
|
34534
|
+
(found) => stageThreadArtifacts(found, {
|
|
34535
|
+
store: deps.artifacts,
|
|
34536
|
+
nextIndex: nextStagedIndex,
|
|
34537
|
+
publish: (e) => registry.publish(runId3, e)
|
|
34538
|
+
})
|
|
34539
|
+
) : void 0;
|
|
34404
34540
|
const reservation = await reserveRun(deps, {
|
|
34405
34541
|
msg,
|
|
34406
34542
|
agent,
|
|
@@ -34450,9 +34586,10 @@ async function dispatch(deps, msg, io, opts = {}) {
|
|
|
34450
34586
|
});
|
|
34451
34587
|
return ended;
|
|
34452
34588
|
}
|
|
34453
|
-
|
|
34454
|
-
|
|
34455
|
-
|
|
34589
|
+
const earlier2 = earlierStaged ? await earlierStaged : [];
|
|
34590
|
+
if (staged.length > 0 || earlier2.length > 0) {
|
|
34591
|
+
const line3 = stagedCopies || earlier2.length > 0 ? attachmentsLine(
|
|
34592
|
+
await pullStaged([...stagedCopies ? await stagedCopies : [], ...earlier2], {
|
|
34456
34593
|
store: deps.artifacts,
|
|
34457
34594
|
executor,
|
|
34458
34595
|
resident: resident !== void 0
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coreplane/switchboard",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.219.0",
|
|
4
4
|
"description": "Mention it in Slack and an agent reviews the PR, ships the fix, or answers the question — on the model you choose, with its tools running where you decide.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://openswitchboard.dev",
|