gsd-pi 2.75.0-dev.a44b82572 → 2.75.0-dev.e41b70b10
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/resources/extensions/gsd/auto/phases.js +2 -0
- package/dist/resources/extensions/gsd/auto-dashboard.js +22 -1
- package/dist/resources/extensions/gsd/auto-direct-dispatch.js +8 -2
- package/dist/resources/extensions/gsd/auto-dispatch.js +11 -11
- package/dist/resources/extensions/gsd/auto-model-selection.js +3 -1
- package/dist/resources/extensions/gsd/auto-prompts.js +19 -9
- package/dist/resources/extensions/gsd/auto-worktree.js +16 -1
- package/dist/resources/extensions/gsd/doctor-git-checks.js +22 -2
- package/dist/resources/extensions/gsd/pre-execution-checks.js +12 -8
- package/dist/resources/extensions/gsd/prompts/add-tests.md +1 -0
- package/dist/resources/extensions/gsd/prompts/execute-task.md +1 -1
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -0
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +14 -0
- package/dist/resources/extensions/search-the-web/command-search-provider.js +4 -1
- package/dist/resources/extensions/search-the-web/native-search.js +13 -2
- package/dist/tsconfig.extensions.tsbuildinfo +1 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +15 -15
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +15 -15
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware-manifest.json +5 -5
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/package.json +1 -1
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +102 -65
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/src/workflow-tools.test.ts +255 -0
- package/packages/mcp-server/src/workflow-tools.ts +108 -65
- package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-agent-core/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-ai/dist/index.d.ts +1 -0
- package/packages/pi-ai/dist/index.d.ts.map +1 -1
- package/packages/pi-ai/dist/index.js +1 -0
- package/packages/pi-ai/dist/index.js.map +1 -1
- package/packages/pi-ai/dist/providers/api-family.d.ts +27 -0
- package/packages/pi-ai/dist/providers/api-family.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/api-family.js +47 -0
- package/packages/pi-ai/dist/providers/api-family.js.map +1 -0
- package/packages/pi-ai/dist/providers/api-family.test.d.ts +2 -0
- package/packages/pi-ai/dist/providers/api-family.test.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/api-family.test.js +101 -0
- package/packages/pi-ai/dist/providers/api-family.test.js.map +1 -0
- package/packages/pi-ai/src/index.ts +1 -0
- package/packages/pi-ai/src/providers/api-family.test.ts +129 -0
- package/packages/pi-ai/src/providers/api-family.ts +57 -0
- package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts +1 -0
- package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +2 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/retry-handler.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/retry-handler.js +4 -1
- package/packages/pi-coding-agent/dist/core/retry-handler.js.map +1 -1
- package/packages/pi-coding-agent/src/core/extensions/runner.ts +4 -1
- package/packages/pi-coding-agent/src/core/extensions/types.ts +2 -2
- package/packages/pi-coding-agent/src/core/retry-handler.ts +4 -1
- package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
- package/src/resources/extensions/gsd/auto/loop-deps.ts +2 -10
- package/src/resources/extensions/gsd/auto/phases.ts +3 -0
- package/src/resources/extensions/gsd/auto-dashboard.ts +25 -1
- package/src/resources/extensions/gsd/auto-direct-dispatch.ts +15 -2
- package/src/resources/extensions/gsd/auto-dispatch.ts +21 -7
- package/src/resources/extensions/gsd/auto-model-selection.ts +3 -1
- package/src/resources/extensions/gsd/auto-prompts.ts +33 -9
- package/src/resources/extensions/gsd/auto-worktree.ts +16 -1
- package/src/resources/extensions/gsd/doctor-git-checks.ts +23 -2
- package/src/resources/extensions/gsd/pre-execution-checks.ts +12 -8
- package/src/resources/extensions/gsd/prompts/add-tests.md +1 -0
- package/src/resources/extensions/gsd/prompts/execute-task.md +1 -1
- package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -0
- package/src/resources/extensions/gsd/tests/auto-dashboard.test.ts +49 -0
- package/src/resources/extensions/gsd/tests/integration/doctor-git-symlink-cwd.test.ts +79 -0
- package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +66 -0
- package/src/resources/extensions/gsd/tests/prompt-budget-enforcement.test.ts +132 -8
- package/src/resources/extensions/gsd/tests/prompts-no-gitignored-test-refs.test.ts +56 -0
- package/src/resources/extensions/gsd/tests/stash-queued-context-files.test.ts +54 -0
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +97 -0
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +14 -0
- package/src/resources/extensions/search-the-web/command-search-provider.ts +4 -1
- package/src/resources/extensions/search-the-web/native-search.ts +13 -3
- /package/dist/web/standalone/.next/static/{iBwPQUj73sn8jxegTo320 → By_yegSJ-AA1OP0QjYbSl}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{iBwPQUj73sn8jxegTo320 → By_yegSJ-AA1OP0QjYbSl}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { execSync } from "node:child_process";
|
|
3
|
+
import { mkdtempSync, mkdirSync, realpathSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { test } from "node:test";
|
|
7
|
+
|
|
8
|
+
import { runGSDDoctor } from "../../doctor.ts";
|
|
9
|
+
|
|
10
|
+
function run(cmd: string, cwd: string): string {
|
|
11
|
+
return execSync(cmd, { cwd, stdio: ["ignore", "pipe", "pipe"], encoding: "utf-8" }).trim();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function createRepoWithCompletedMilestone(): string {
|
|
15
|
+
const dir = realpathSync(mkdtempSync(join(tmpdir(), "doc-git-symlink-cwd-")));
|
|
16
|
+
run("git init", dir);
|
|
17
|
+
run("git config user.email test@test.com", dir);
|
|
18
|
+
run("git config user.name Test", dir);
|
|
19
|
+
|
|
20
|
+
writeFileSync(join(dir, "README.md"), "# test\n");
|
|
21
|
+
run("git add .", dir);
|
|
22
|
+
run("git commit -m init", dir);
|
|
23
|
+
run("git branch -M main", dir);
|
|
24
|
+
|
|
25
|
+
const milestoneDir = join(dir, ".gsd", "milestones", "M001");
|
|
26
|
+
mkdirSync(milestoneDir, { recursive: true });
|
|
27
|
+
writeFileSync(join(milestoneDir, "ROADMAP.md"), `---
|
|
28
|
+
id: M001
|
|
29
|
+
title: "Test Milestone"
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
# M001: Test Milestone
|
|
33
|
+
|
|
34
|
+
## Vision
|
|
35
|
+
Test
|
|
36
|
+
|
|
37
|
+
## Success Criteria
|
|
38
|
+
- Done
|
|
39
|
+
|
|
40
|
+
## Slices
|
|
41
|
+
- [x] **S01: Test slice** \`risk:low\` \`depends:[]\`
|
|
42
|
+
> After this: done
|
|
43
|
+
|
|
44
|
+
## Boundary Map
|
|
45
|
+
_None_
|
|
46
|
+
`);
|
|
47
|
+
|
|
48
|
+
run("git add -A", dir);
|
|
49
|
+
run("git commit -m \"add milestone\"", dir);
|
|
50
|
+
|
|
51
|
+
return dir;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
test("doctor removes orphaned milestone worktree when cwd uses a symlink alias", { skip: process.platform === "win32" }, async (t) => {
|
|
55
|
+
const previousCwd = process.cwd();
|
|
56
|
+
const dir = createRepoWithCompletedMilestone();
|
|
57
|
+
const alias = join(tmpdir(), `doc-git-alias-${Date.now()}-${Math.random().toString(16).slice(2)}`);
|
|
58
|
+
|
|
59
|
+
t.after(() => {
|
|
60
|
+
try { process.chdir(previousCwd); } catch { process.chdir(tmpdir()); }
|
|
61
|
+
rmSync(alias, { recursive: true, force: true });
|
|
62
|
+
rmSync(dir, { recursive: true, force: true });
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
mkdirSync(join(dir, ".gsd", "worktrees"), { recursive: true });
|
|
66
|
+
run("git worktree add -b milestone/M001 .gsd/worktrees/M001", dir);
|
|
67
|
+
|
|
68
|
+
symlinkSync(dir, alias);
|
|
69
|
+
process.chdir(join(alias, ".gsd", "worktrees", "M001"));
|
|
70
|
+
|
|
71
|
+
const fixed = await runGSDDoctor(dir, { fix: true, isolationMode: "worktree" });
|
|
72
|
+
assert.ok(
|
|
73
|
+
fixed.fixesApplied.some(f => f.includes("removed orphaned worktree")),
|
|
74
|
+
`removes orphaned worktree even when cwd uses a symlink alias (got: ${JSON.stringify(fixed.fixesApplied)})`,
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
const wtList = run("git worktree list", dir);
|
|
78
|
+
assert.ok(!wtList.includes("milestone/M001"), "worktree removed after symlink-cwd fix");
|
|
79
|
+
});
|
|
@@ -1579,3 +1579,69 @@ describe("checkTaskOrdering directory inputs (#4446)", () => {
|
|
|
1579
1579
|
);
|
|
1580
1580
|
});
|
|
1581
1581
|
});
|
|
1582
|
+
|
|
1583
|
+
describe("checkFilePathConsistency self-referential inputs (#4459)", () => {
|
|
1584
|
+
test("input that is also in the same task's expected_output is not blocking when missing on disk", (t) => {
|
|
1585
|
+
const tempDir = join(tmpdir(), `pre-exec-self-output-${Date.now()}`);
|
|
1586
|
+
mkdirSync(tempDir, { recursive: true });
|
|
1587
|
+
t.after(() => rmSync(tempDir, { recursive: true, force: true }));
|
|
1588
|
+
|
|
1589
|
+
const tasks = [
|
|
1590
|
+
createTask({
|
|
1591
|
+
id: "T02",
|
|
1592
|
+
sequence: 0,
|
|
1593
|
+
inputs: ["src/components/email/SnoozePopover.jsx"],
|
|
1594
|
+
expected_output: ["src/components/email/SnoozePopover.jsx"],
|
|
1595
|
+
}),
|
|
1596
|
+
];
|
|
1597
|
+
|
|
1598
|
+
const results = checkFilePathConsistency(tasks, tempDir);
|
|
1599
|
+
assert.deepEqual(
|
|
1600
|
+
results,
|
|
1601
|
+
[],
|
|
1602
|
+
"File declared as both input and expected_output of the same task should not block — the task itself produces it",
|
|
1603
|
+
);
|
|
1604
|
+
});
|
|
1605
|
+
|
|
1606
|
+
test("input missing from disk, missing from prior outputs, and missing from own expected_output still blocks", (t) => {
|
|
1607
|
+
const tempDir = join(tmpdir(), `pre-exec-self-output-missing-${Date.now()}`);
|
|
1608
|
+
mkdirSync(tempDir, { recursive: true });
|
|
1609
|
+
t.after(() => rmSync(tempDir, { recursive: true, force: true }));
|
|
1610
|
+
|
|
1611
|
+
const tasks = [
|
|
1612
|
+
createTask({
|
|
1613
|
+
id: "T02",
|
|
1614
|
+
sequence: 0,
|
|
1615
|
+
inputs: ["src/components/email/SnoozePopover.jsx"],
|
|
1616
|
+
expected_output: ["src/other/unrelated.jsx"],
|
|
1617
|
+
}),
|
|
1618
|
+
];
|
|
1619
|
+
|
|
1620
|
+
const results = checkFilePathConsistency(tasks, tempDir);
|
|
1621
|
+
assert.equal(results.length, 1, "Genuinely missing input should still be reported");
|
|
1622
|
+
assert.equal(results[0].blocking, true);
|
|
1623
|
+
assert.equal(results[0].target, "src/components/email/SnoozePopover.jsx");
|
|
1624
|
+
});
|
|
1625
|
+
|
|
1626
|
+
test("self-output exemption matches across path normalization (./ prefix)", (t) => {
|
|
1627
|
+
const tempDir = join(tmpdir(), `pre-exec-self-output-norm-${Date.now()}`);
|
|
1628
|
+
mkdirSync(tempDir, { recursive: true });
|
|
1629
|
+
t.after(() => rmSync(tempDir, { recursive: true, force: true }));
|
|
1630
|
+
|
|
1631
|
+
const tasks = [
|
|
1632
|
+
createTask({
|
|
1633
|
+
id: "T02",
|
|
1634
|
+
sequence: 0,
|
|
1635
|
+
inputs: ["./src/generated.ts"],
|
|
1636
|
+
expected_output: ["src/generated.ts"],
|
|
1637
|
+
}),
|
|
1638
|
+
];
|
|
1639
|
+
|
|
1640
|
+
const results = checkFilePathConsistency(tasks, tempDir);
|
|
1641
|
+
assert.deepEqual(
|
|
1642
|
+
results,
|
|
1643
|
+
[],
|
|
1644
|
+
"./src/generated.ts and src/generated.ts should compare equal after normalization",
|
|
1645
|
+
);
|
|
1646
|
+
});
|
|
1647
|
+
});
|
|
@@ -1,13 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Prompt budget enforcement tests — verifies that budget-aware prompt builders
|
|
3
|
-
* truncate content at section boundaries
|
|
4
|
-
* context constraints
|
|
5
|
-
*
|
|
6
|
-
* Tests:
|
|
7
|
-
* 1. inlineDependencySummaries() truncates when budget is small, passes through when large
|
|
8
|
-
* 2. plan-slice.md template includes {{executorContextConstraints}} placeholder
|
|
9
|
-
* 3. Executor constraints formatting varies with context window size
|
|
10
|
-
* 4. Different context windows produce different budget-constrained outputs
|
|
3
|
+
* truncate content at section boundaries, that plan-slice includes executor
|
|
4
|
+
* context constraints, and that prompt builders thread the real executor
|
|
5
|
+
* context window through to the budget engine (issue #4142).
|
|
11
6
|
*/
|
|
12
7
|
|
|
13
8
|
import { describe, it, beforeEach, afterEach } from "node:test";
|
|
@@ -488,4 +483,133 @@ describe("prompt-budget: execute-task builder truncation pattern", () => {
|
|
|
488
483
|
assert.ok(result.droppedSections > 0, "should report dropped sections");
|
|
489
484
|
}
|
|
490
485
|
});
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
// ─── Regression: prompt builders must thread modelRegistry + sessionContextWindow (issue #4142) ───
|
|
489
|
+
//
|
|
490
|
+
// `resolveExecutorContextWindow()` resolves the executor context window in 3
|
|
491
|
+
// steps: (1) look up the configured executor model in `modelRegistry`, (2) fall
|
|
492
|
+
// back to `sessionContextWindow`, (3) fall back to `DEFAULT_CONTEXT_WINDOW`
|
|
493
|
+
// (200K). Before this fix, prompt-builder call sites passed `undefined` for
|
|
494
|
+
// both knobs and always landed on Step 3 — even on 1M-token models. These
|
|
495
|
+
// source-level assertions pin the wiring so future refactors cannot regress it.
|
|
496
|
+
|
|
497
|
+
describe("prompt-budget: modelRegistry + sessionContextWindow wiring", () => {
|
|
498
|
+
const autoPromptsSrc = readFileSync(join(__dirname, "..", "auto-prompts.ts"), "utf-8");
|
|
499
|
+
const autoDispatchSrc = readFileSync(join(__dirname, "..", "auto-dispatch.ts"), "utf-8");
|
|
500
|
+
const autoDirectDispatchSrc = readFileSync(join(__dirname, "..", "auto-direct-dispatch.ts"), "utf-8");
|
|
501
|
+
const phasesSrc = readFileSync(join(__dirname, "..", "auto", "phases.ts"), "utf-8");
|
|
502
|
+
|
|
503
|
+
it("formatExecutorConstraints accepts and forwards both knobs", () => {
|
|
504
|
+
assert.match(
|
|
505
|
+
autoPromptsSrc,
|
|
506
|
+
/function formatExecutorConstraints\([^)]*sessionContextWindow[^)]*modelRegistry[^)]*\)/s,
|
|
507
|
+
"formatExecutorConstraints must accept sessionContextWindow and modelRegistry",
|
|
508
|
+
);
|
|
509
|
+
assert.match(
|
|
510
|
+
autoPromptsSrc,
|
|
511
|
+
/resolveExecutorContextWindow\(\s*modelRegistry\s*,\s*prefs\?\.preferences\s*,\s*sessionContextWindow/,
|
|
512
|
+
"formatExecutorConstraints must forward both to resolveExecutorContextWindow",
|
|
513
|
+
);
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
it("renderSlicePrompt options declare both knobs and forward to formatExecutorConstraints", () => {
|
|
517
|
+
assert.match(
|
|
518
|
+
autoPromptsSrc,
|
|
519
|
+
/async function renderSlicePrompt\(options:\s*\{[^}]*sessionContextWindow\?[^}]*modelRegistry\?/s,
|
|
520
|
+
"renderSlicePrompt options must declare both fields",
|
|
521
|
+
);
|
|
522
|
+
assert.match(
|
|
523
|
+
autoPromptsSrc,
|
|
524
|
+
/formatExecutorConstraints\(sessionContextWindow,\s*modelRegistry\)/,
|
|
525
|
+
"renderSlicePrompt must forward both to formatExecutorConstraints",
|
|
526
|
+
);
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
it("buildPlanSlicePrompt options declare both knobs and thread them into renderSlicePrompt", () => {
|
|
530
|
+
assert.match(
|
|
531
|
+
autoPromptsSrc,
|
|
532
|
+
/export async function buildPlanSlicePrompt\([\s\S]*?options\?:\s*\{[^}]*sessionContextWindow\?[^}]*modelRegistry\?/,
|
|
533
|
+
"buildPlanSlicePrompt options must declare both fields",
|
|
534
|
+
);
|
|
535
|
+
assert.match(
|
|
536
|
+
autoPromptsSrc,
|
|
537
|
+
/sessionContextWindow:\s*options\?\.sessionContextWindow,\s*modelRegistry:\s*options\?\.modelRegistry/,
|
|
538
|
+
"buildPlanSlicePrompt must forward both into renderSlicePrompt",
|
|
539
|
+
);
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
it("ExecuteTaskPromptOptions declares both knobs", () => {
|
|
543
|
+
assert.match(
|
|
544
|
+
autoPromptsSrc,
|
|
545
|
+
/interface ExecuteTaskPromptOptions\s*\{[^}]*sessionContextWindow\?[^}]*modelRegistry\?/s,
|
|
546
|
+
"ExecuteTaskPromptOptions must declare both fields",
|
|
547
|
+
);
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
it("buildExecuteTaskPrompt forwards opts.modelRegistry + opts.sessionContextWindow to resolveExecutorContextWindow", () => {
|
|
551
|
+
assert.match(
|
|
552
|
+
autoPromptsSrc,
|
|
553
|
+
/resolveExecutorContextWindow\(\s*opts\.modelRegistry\s*,\s*prefs\?\.preferences\s*,\s*opts\.sessionContextWindow/,
|
|
554
|
+
"buildExecuteTaskPrompt must forward both into resolveExecutorContextWindow",
|
|
555
|
+
);
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
it("buildReactiveExecutePrompt accepts opts and forwards to embedded buildExecuteTaskPrompt", () => {
|
|
559
|
+
assert.match(
|
|
560
|
+
autoPromptsSrc,
|
|
561
|
+
/export async function buildReactiveExecutePrompt\([\s\S]*?opts\?:\s*\{[^}]*sessionContextWindow\?[^}]*modelRegistry\?/,
|
|
562
|
+
"buildReactiveExecutePrompt must accept sessionContextWindow + modelRegistry",
|
|
563
|
+
);
|
|
564
|
+
assert.match(
|
|
565
|
+
autoPromptsSrc,
|
|
566
|
+
/sessionContextWindow:\s*opts\?\.sessionContextWindow,\s*modelRegistry:\s*opts\?\.modelRegistry/,
|
|
567
|
+
"buildReactiveExecutePrompt must forward both into the embedded buildExecuteTaskPrompt call",
|
|
568
|
+
);
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
it("DispatchContext declares both knobs", () => {
|
|
572
|
+
assert.match(
|
|
573
|
+
autoDispatchSrc,
|
|
574
|
+
/interface DispatchContext\s*\{[^}]*sessionContextWindow\?[^}]*modelRegistry\?/s,
|
|
575
|
+
"DispatchContext must declare both fields so dispatch rules can thread them",
|
|
576
|
+
);
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
it("DISPATCH_RULES destructure and forward both knobs at every prompt-builder call site", () => {
|
|
580
|
+
// Every plan-slice / execute-task / reactive-execute rule must destructure
|
|
581
|
+
// both names from its match context so new call sites can't silently drop them.
|
|
582
|
+
const matchLines = autoDispatchSrc.match(/match:\s*async\s*\(\{[^}]*sessionContextWindow[^}]*modelRegistry[^}]*\}/g) ?? [];
|
|
583
|
+
assert.ok(
|
|
584
|
+
matchLines.length >= 5,
|
|
585
|
+
`expected ≥5 dispatch rules destructuring both knobs, got ${matchLines.length}`,
|
|
586
|
+
);
|
|
587
|
+
|
|
588
|
+
const forwardCount = (autoDispatchSrc.match(/sessionContextWindow,\s*modelRegistry/g) ?? []).length;
|
|
589
|
+
assert.ok(
|
|
590
|
+
forwardCount >= 5,
|
|
591
|
+
`expected ≥5 forward sites of { sessionContextWindow, modelRegistry }, got ${forwardCount}`,
|
|
592
|
+
);
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
it("runDispatch populates both knobs from ctx.model / ctx.modelRegistry", () => {
|
|
596
|
+
assert.match(
|
|
597
|
+
phasesSrc,
|
|
598
|
+
/sessionContextWindow:\s*ctx\.model\?\.contextWindow/,
|
|
599
|
+
"runDispatch must populate sessionContextWindow from ctx.model?.contextWindow",
|
|
600
|
+
);
|
|
601
|
+
assert.match(
|
|
602
|
+
phasesSrc,
|
|
603
|
+
/modelRegistry:\s*ctx\.modelRegistry/,
|
|
604
|
+
"runDispatch must populate modelRegistry from ctx.modelRegistry",
|
|
605
|
+
);
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
it("dispatchDirectPhase forwards both knobs to buildPlanSlicePrompt and buildExecuteTaskPrompt", () => {
|
|
609
|
+
const passes = (autoDirectDispatchSrc.match(/sessionContextWindow:\s*ctx\.model\?\.contextWindow[\s\S]*?modelRegistry:\s*ctx\.modelRegistry/g) ?? []).length;
|
|
610
|
+
assert.ok(
|
|
611
|
+
passes >= 2,
|
|
612
|
+
`dispatchDirectPhase must forward the pair at both prompt-builder call sites (≥2), got ${passes}`,
|
|
613
|
+
);
|
|
614
|
+
});
|
|
491
615
|
});
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const promptsDir = join(__dirname, "..", "prompts");
|
|
9
|
+
|
|
10
|
+
function readPrompt(name: string): string {
|
|
11
|
+
return readFileSync(join(promptsDir, name), "utf-8");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
test("add-tests prompt forbids referencing gitignored paths", () => {
|
|
15
|
+
const prompt = readPrompt("add-tests.md");
|
|
16
|
+
|
|
17
|
+
assert.match(
|
|
18
|
+
prompt,
|
|
19
|
+
/gitignore/i,
|
|
20
|
+
"add-tests prompt should mention .gitignore to rule out referencing local-only files",
|
|
21
|
+
);
|
|
22
|
+
assert.match(prompt, /\.gsd\//, "add-tests prompt should name .gsd/ as off-limits for tests");
|
|
23
|
+
assert.match(prompt, /\.planning\//, "add-tests prompt should name .planning/ as off-limits for tests");
|
|
24
|
+
assert.match(prompt, /\.audits\//, "add-tests prompt should name .audits/ as off-limits for tests");
|
|
25
|
+
assert.match(
|
|
26
|
+
prompt,
|
|
27
|
+
/tracked/i,
|
|
28
|
+
"add-tests prompt should frame the rule in terms of tracked files",
|
|
29
|
+
);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("plan-slice prompt warns against planning tests that depend on gitignored files", () => {
|
|
33
|
+
const prompt = readPrompt("plan-slice.md");
|
|
34
|
+
|
|
35
|
+
assert.match(
|
|
36
|
+
prompt,
|
|
37
|
+
/gitignore/i,
|
|
38
|
+
"plan-slice prompt should warn against planning tests that depend on .gitignore paths",
|
|
39
|
+
);
|
|
40
|
+
assert.match(prompt, /\.gsd\//, "plan-slice prompt should name .gsd/ as off-limits for planned tests");
|
|
41
|
+
assert.match(prompt, /\.planning\//, "plan-slice prompt should name .planning/ as off-limits for planned tests");
|
|
42
|
+
assert.match(prompt, /\.audits\//, "plan-slice prompt should name .audits/ as off-limits for planned tests");
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("execute-task prompt forbids tests that reference gitignored paths", () => {
|
|
46
|
+
const prompt = readPrompt("execute-task.md");
|
|
47
|
+
|
|
48
|
+
assert.match(
|
|
49
|
+
prompt,
|
|
50
|
+
/gitignore/i,
|
|
51
|
+
"execute-task prompt should forbid referencing gitignored paths from tests",
|
|
52
|
+
);
|
|
53
|
+
assert.match(prompt, /\.gsd\//, "execute-task prompt should name .gsd/ as off-limits for tests");
|
|
54
|
+
assert.match(prompt, /\.planning\//, "execute-task prompt should name .planning/ as off-limits for tests");
|
|
55
|
+
assert.match(prompt, /\.audits\//, "execute-task prompt should name .audits/ as off-limits for tests");
|
|
56
|
+
});
|
|
@@ -21,6 +21,8 @@ import {
|
|
|
21
21
|
existsSync,
|
|
22
22
|
readFileSync,
|
|
23
23
|
realpathSync,
|
|
24
|
+
symlinkSync,
|
|
25
|
+
lstatSync,
|
|
24
26
|
} from "node:fs";
|
|
25
27
|
import { join } from "node:path";
|
|
26
28
|
import { tmpdir } from "node:os";
|
|
@@ -76,6 +78,20 @@ function createTempRepo(): string {
|
|
|
76
78
|
return dir;
|
|
77
79
|
}
|
|
78
80
|
|
|
81
|
+
function createTempRepoWithSymlinkedGsd(): { repo: string; stateDir: string } {
|
|
82
|
+
const repo = realpathSync(mkdtempSync(join(tmpdir(), "wt-symlink-stash-test-")));
|
|
83
|
+
const stateDir = realpathSync(mkdtempSync(join(tmpdir(), "wt-symlink-state-")));
|
|
84
|
+
run("git init", repo);
|
|
85
|
+
run("git config user.email test@test.com", repo);
|
|
86
|
+
run("git config user.name Test", repo);
|
|
87
|
+
writeFileSync(join(repo, "README.md"), "# test\n");
|
|
88
|
+
symlinkSync(stateDir, join(repo, ".gsd"));
|
|
89
|
+
run("git add README.md", repo);
|
|
90
|
+
run("git commit -m init", repo);
|
|
91
|
+
run("git branch -M main", repo);
|
|
92
|
+
return { repo, stateDir };
|
|
93
|
+
}
|
|
94
|
+
|
|
79
95
|
function makeRoadmap(
|
|
80
96
|
milestoneId: string,
|
|
81
97
|
title: string,
|
|
@@ -259,6 +275,44 @@ test("#2505: mergeMilestoneToMain preserves queued CONTEXT files (not swept into
|
|
|
259
275
|
}
|
|
260
276
|
});
|
|
261
277
|
|
|
278
|
+
test("#2505: pre-merge stash handles symlinked .gsd without traversing it", () => {
|
|
279
|
+
const { repo, stateDir } = createTempRepoWithSymlinkedGsd();
|
|
280
|
+
try {
|
|
281
|
+
const wtPath = createAutoWorktree(repo, "M016");
|
|
282
|
+
const normalizedPath = wtPath.replaceAll("\\", "/");
|
|
283
|
+
const worktreeName = normalizedPath.split("/").pop() || "M016";
|
|
284
|
+
const sliceBranch = `slice/${worktreeName}/S01`;
|
|
285
|
+
run(`git checkout -b "${sliceBranch}"`, wtPath);
|
|
286
|
+
writeFileSync(join(wtPath, "app.ts"), "export const app = true;\n");
|
|
287
|
+
run("git add app.ts", wtPath);
|
|
288
|
+
run('git commit -m "add app feature"', wtPath);
|
|
289
|
+
run("git checkout milestone/M016", wtPath);
|
|
290
|
+
run(`git merge --no-ff "${sliceBranch}" -m "merge S01"`, wtPath);
|
|
291
|
+
|
|
292
|
+
const queuedDir = join(stateDir, "milestones", "M017");
|
|
293
|
+
mkdirSync(queuedDir, { recursive: true });
|
|
294
|
+
writeFileSync(join(queuedDir, "M017-CONTEXT.md"), "# M017: Queued\n");
|
|
295
|
+
|
|
296
|
+
// Trigger the pre-merge stash with both tracked and untracked project files.
|
|
297
|
+
writeFileSync(join(repo, "README.md"), "# test\n\nDirty change.\n");
|
|
298
|
+
writeFileSync(join(repo, "local-note.txt"), "local scratch\n");
|
|
299
|
+
|
|
300
|
+
const result = mergeMilestoneToMain(repo, "M016", makeRoadmap("M016", "App Feature", [
|
|
301
|
+
{ id: "S01", title: "Feature" },
|
|
302
|
+
]));
|
|
303
|
+
|
|
304
|
+
assert.ok(result.commitMessage.includes("GSD-Milestone: M016"), "merge should succeed");
|
|
305
|
+
assert.ok(existsSync(join(repo, "app.ts")), "milestone code merged to main");
|
|
306
|
+
assert.equal(lstatSync(join(repo, ".gsd")).isSymbolicLink(), true, ".gsd symlink remains in place");
|
|
307
|
+
assert.ok(existsSync(join(queuedDir, "M017-CONTEXT.md")), "queued context remains in external state");
|
|
308
|
+
assert.equal(readFileSync(join(repo, "README.md"), "utf-8").replace(/\r\n/g, "\n"), "# test\n\nDirty change.\n");
|
|
309
|
+
assert.equal(readFileSync(join(repo, "local-note.txt"), "utf-8"), "local scratch\n");
|
|
310
|
+
} finally {
|
|
311
|
+
rmSync(repo, { recursive: true, force: true });
|
|
312
|
+
rmSync(stateDir, { recursive: true, force: true });
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
|
|
262
316
|
test("#2505: back-to-back merges preserve queued CONTEXT files", () => {
|
|
263
317
|
const repo = createTempRepo();
|
|
264
318
|
try {
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
_getAdapter,
|
|
12
12
|
insertGateRow,
|
|
13
13
|
} from "../gsd-db.ts";
|
|
14
|
+
import { markDepthVerified, clearDiscussionFlowState } from "../bootstrap/write-gate.ts";
|
|
14
15
|
import {
|
|
15
16
|
executeCompleteMilestone,
|
|
16
17
|
executePlanMilestone,
|
|
@@ -645,3 +646,99 @@ test("executeReplanSlice rewrites pending tasks and renders replan artifacts", a
|
|
|
645
646
|
cleanup(base);
|
|
646
647
|
}
|
|
647
648
|
});
|
|
649
|
+
|
|
650
|
+
test("executeSummarySave removes sibling CONTEXT-DRAFT when writing milestone CONTEXT (#4442)", async () => {
|
|
651
|
+
const base = makeTmpBase();
|
|
652
|
+
try {
|
|
653
|
+
openTestDb(base);
|
|
654
|
+
markDepthVerified("M001", base);
|
|
655
|
+
|
|
656
|
+
const milestoneDir = join(base, ".gsd", "milestones", "M001");
|
|
657
|
+
mkdirSync(milestoneDir, { recursive: true });
|
|
658
|
+
const draftPath = join(milestoneDir, "M001-CONTEXT-DRAFT.md");
|
|
659
|
+
writeFileSync(draftPath, "# Draft\n\nincremental notes");
|
|
660
|
+
assert.ok(existsSync(draftPath), "precondition: draft exists");
|
|
661
|
+
|
|
662
|
+
const result = await inProjectDir(base, () => executeSummarySave({
|
|
663
|
+
milestone_id: "M001",
|
|
664
|
+
artifact_type: "CONTEXT",
|
|
665
|
+
content: "# Context\n\nfinal discussion output",
|
|
666
|
+
}, base));
|
|
667
|
+
|
|
668
|
+
assert.equal(result.details.operation, "save_summary");
|
|
669
|
+
assert.equal(result.details.artifact_type, "CONTEXT");
|
|
670
|
+
|
|
671
|
+
const contextPath = join(milestoneDir, "M001-CONTEXT.md");
|
|
672
|
+
assert.ok(existsSync(contextPath), "CONTEXT.md should be written");
|
|
673
|
+
assert.equal(
|
|
674
|
+
existsSync(draftPath),
|
|
675
|
+
false,
|
|
676
|
+
"CONTEXT-DRAFT.md should be removed after final CONTEXT.md is written",
|
|
677
|
+
);
|
|
678
|
+
} finally {
|
|
679
|
+
clearDiscussionFlowState();
|
|
680
|
+
closeDatabase();
|
|
681
|
+
cleanup(base);
|
|
682
|
+
}
|
|
683
|
+
});
|
|
684
|
+
|
|
685
|
+
test("executeSummarySave removes sibling CONTEXT-DRAFT when writing slice CONTEXT (#4442)", async () => {
|
|
686
|
+
const base = makeTmpBase();
|
|
687
|
+
try {
|
|
688
|
+
openTestDb(base);
|
|
689
|
+
|
|
690
|
+
const sliceDir = join(base, ".gsd", "milestones", "M001", "slices", "S01");
|
|
691
|
+
mkdirSync(sliceDir, { recursive: true });
|
|
692
|
+
const draftPath = join(sliceDir, "S01-CONTEXT-DRAFT.md");
|
|
693
|
+
writeFileSync(draftPath, "# Slice Draft\n\nincremental slice notes");
|
|
694
|
+
assert.ok(existsSync(draftPath), "precondition: slice draft exists");
|
|
695
|
+
|
|
696
|
+
const result = await inProjectDir(base, () => executeSummarySave({
|
|
697
|
+
milestone_id: "M001",
|
|
698
|
+
slice_id: "S01",
|
|
699
|
+
artifact_type: "CONTEXT",
|
|
700
|
+
content: "# Slice Context\n\nfinal slice output",
|
|
701
|
+
}, base));
|
|
702
|
+
|
|
703
|
+
assert.equal(result.details.operation, "save_summary");
|
|
704
|
+
assert.equal(result.details.artifact_type, "CONTEXT");
|
|
705
|
+
|
|
706
|
+
const contextPath = join(sliceDir, "S01-CONTEXT.md");
|
|
707
|
+
assert.ok(existsSync(contextPath), "slice CONTEXT.md should be written");
|
|
708
|
+
assert.equal(
|
|
709
|
+
existsSync(draftPath),
|
|
710
|
+
false,
|
|
711
|
+
"slice CONTEXT-DRAFT.md should be removed after final CONTEXT.md is written",
|
|
712
|
+
);
|
|
713
|
+
} finally {
|
|
714
|
+
closeDatabase();
|
|
715
|
+
cleanup(base);
|
|
716
|
+
}
|
|
717
|
+
});
|
|
718
|
+
|
|
719
|
+
test("executeSummarySave leaves sibling CONTEXT-DRAFT intact for non-CONTEXT artifacts (#4442)", async () => {
|
|
720
|
+
const base = makeTmpBase();
|
|
721
|
+
try {
|
|
722
|
+
openTestDb(base);
|
|
723
|
+
|
|
724
|
+
const milestoneDir = join(base, ".gsd", "milestones", "M001");
|
|
725
|
+
mkdirSync(milestoneDir, { recursive: true });
|
|
726
|
+
const draftPath = join(milestoneDir, "M001-CONTEXT-DRAFT.md");
|
|
727
|
+
writeFileSync(draftPath, "# Draft\n\nstill in progress");
|
|
728
|
+
|
|
729
|
+
const result = await inProjectDir(base, () => executeSummarySave({
|
|
730
|
+
milestone_id: "M001",
|
|
731
|
+
artifact_type: "RESEARCH",
|
|
732
|
+
content: "# Research\n\nresearch notes",
|
|
733
|
+
}, base));
|
|
734
|
+
|
|
735
|
+
assert.equal(result.details.artifact_type, "RESEARCH");
|
|
736
|
+
assert.ok(
|
|
737
|
+
existsSync(draftPath),
|
|
738
|
+
"CONTEXT-DRAFT.md must survive RESEARCH/SUMMARY/ASSESSMENT writes",
|
|
739
|
+
);
|
|
740
|
+
} finally {
|
|
741
|
+
closeDatabase();
|
|
742
|
+
cleanup(base);
|
|
743
|
+
}
|
|
744
|
+
});
|
|
@@ -10,6 +10,8 @@ import {
|
|
|
10
10
|
} from "../gsd-db.js";
|
|
11
11
|
import { GATE_REGISTRY } from "../gate-registry.js";
|
|
12
12
|
import { saveArtifactToDb } from "../db-writer.js";
|
|
13
|
+
import { resolveMilestoneFile, resolveSliceFile } from "../paths.js";
|
|
14
|
+
import { unlinkSync } from "node:fs";
|
|
13
15
|
import type { CompleteMilestoneParams } from "./complete-milestone.js";
|
|
14
16
|
import { handleCompleteMilestone } from "./complete-milestone.js";
|
|
15
17
|
import { handleCompleteTask } from "./complete-task.js";
|
|
@@ -103,6 +105,18 @@ export async function executeSummarySave(
|
|
|
103
105
|
},
|
|
104
106
|
basePath,
|
|
105
107
|
);
|
|
108
|
+
|
|
109
|
+
if (params.artifact_type === "CONTEXT" && !params.task_id) {
|
|
110
|
+
try {
|
|
111
|
+
const draftFile = params.slice_id
|
|
112
|
+
? resolveSliceFile(basePath, params.milestone_id, params.slice_id, "CONTEXT-DRAFT")
|
|
113
|
+
: resolveMilestoneFile(basePath, params.milestone_id, "CONTEXT-DRAFT");
|
|
114
|
+
if (draftFile) unlinkSync(draftFile);
|
|
115
|
+
} catch (e) {
|
|
116
|
+
logWarning("tool", `CONTEXT-DRAFT.md unlink failed: ${(e as Error).message}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
106
120
|
return {
|
|
107
121
|
content: [{ type: "text", text: `Saved ${params.artifact_type} artifact to ${relativePath}` }],
|
|
108
122
|
details: { operation: "save_summary", path: relativePath, artifact_type: params.artifact_type },
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* All provider logic lives in provider.ts (S01) — this is pure UI wiring.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
+
import { isAnthropicApi } from '@gsd/pi-ai'
|
|
11
12
|
import type { ExtensionAPI } from '@gsd/pi-coding-agent'
|
|
12
13
|
import type { AutocompleteItem } from '@gsd/pi-tui'
|
|
13
14
|
import {
|
|
@@ -90,7 +91,9 @@ export function registerSearchProviderCommand(pi: ExtensionAPI): void {
|
|
|
90
91
|
|
|
91
92
|
setSearchProviderPreference(chosen)
|
|
92
93
|
const effective = resolveSearchProvider()
|
|
93
|
-
|
|
94
|
+
// Gate on api (#4478 / ADR-012): covers claude-code, anthropic-vertex, and
|
|
95
|
+
// other Anthropic-fronting transports — not just the plain `anthropic` provider.
|
|
96
|
+
const isAnthropic = isAnthropicApi(ctx.model)
|
|
94
97
|
const nativeNote = isAnthropic ? '\nNote: Native Anthropic web search is also active (automatic, no API key needed).' : ''
|
|
95
98
|
ctx.ui.notify(
|
|
96
99
|
`Search provider set to ${chosen}. Effective provider: ${effective ?? 'none (no API keys)'}${nativeNote}`,
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* the heavy tool-registration modules.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import { isAnthropicApi } from "@gsd/pi-ai";
|
|
8
9
|
import { resolveSearchProviderFromPreferences } from "../gsd/preferences.js";
|
|
9
10
|
|
|
10
11
|
/** Tool names for the Brave-backed custom search tools */
|
|
@@ -94,7 +95,10 @@ export function registerNativeSearchHooks(pi: NativeSearchPI): { getIsAnthropic:
|
|
|
94
95
|
pi.on("model_select", async (event: any, ctx: any) => {
|
|
95
96
|
modelSelectFired = true;
|
|
96
97
|
const wasAnthropic = isAnthropicProvider;
|
|
97
|
-
|
|
98
|
+
// Gate on `api` not `provider` (#4478 / ADR-012): covers claude-code OAuth,
|
|
99
|
+
// anthropic-vertex, and Vercel-gateway-hosted Anthropic — all serve the
|
|
100
|
+
// Messages API and accept the native web_search tool.
|
|
101
|
+
isAnthropicProvider = isAnthropicApi(event.model);
|
|
98
102
|
|
|
99
103
|
const hasBrave = !!process.env.BRAVE_API_KEY;
|
|
100
104
|
|
|
@@ -139,9 +143,15 @@ export function registerNativeSearchHooks(pi: NativeSearchPI): { getIsAnthropic:
|
|
|
139
143
|
// the model_select flag, then to the model name heuristic (last resort).
|
|
140
144
|
// The model name heuristic is needed for session restores where
|
|
141
145
|
// modelsAreEqual suppresses model_select AND the SDK doesn't pass model.
|
|
142
|
-
const eventModel = event.model as { provider
|
|
146
|
+
const eventModel = event.model as { provider?: string; api?: string } | undefined;
|
|
143
147
|
let isAnthropic: boolean;
|
|
144
|
-
if (eventModel?.
|
|
148
|
+
if (eventModel?.api) {
|
|
149
|
+
// Preferred path: gate on wire protocol (#4478 / ADR-012).
|
|
150
|
+
isAnthropic = isAnthropicApi(eventModel);
|
|
151
|
+
} else if (eventModel?.provider) {
|
|
152
|
+
// Fallback for event shapes that carry provider but not api — only plain
|
|
153
|
+
// `anthropic` maps unambiguously without the api field. Other Anthropic
|
|
154
|
+
// transports will arrive via the modelSelectFired or model-name branch.
|
|
145
155
|
isAnthropic = eventModel.provider === "anthropic";
|
|
146
156
|
} else if (modelSelectFired) {
|
|
147
157
|
isAnthropic = isAnthropicProvider;
|
|
File without changes
|
|
File without changes
|