feat(ai): bind each conversation to its own connection

This commit is contained in:
Abeautifulsnow authored and GitHub committed 2026-09-23 17:56:05 +08:00
1 parent 47e7f56b14
commit c373f322d2
35 files changed
+1584 -272

No files matched your search

+5 -1
View File
@@ -51,7 +51,10 @@ function functionBody(script: string, signature: string): string {
test("send() acquires the guard synchronously before the first await", () => {
const body = functionBody(scriptSetupContent(), "async function send()");
const entryCheck = body.search(/\|\|\s*isGenerating\.value\)\s*return/);
// Anchor on the condition, not on a one-line `return`: the branch may run a
// synchronous cleanup first (a declined write-confirmation grant), so the
// shape is a block now. The assertions below still require a return inside it.
const entryCheck = body.search(/\|\|\s*isGenerating\.value\)\s*\{/);
const guardSet = body.indexOf("isGenerating.value = true");
// `await` as a whole word (not the substring inside e.g. the
// `awaiting_write_confirmation` run status), so the first suspension point is
@@ -61,6 +64,7 @@ test("send() acquires the guard synchronously before the first await", () => {
assert.notEqual(entryCheck, -1, "send() should early-return when isGenerating is already set");
assert.notEqual(guardSet, -1, "send() should set the isGenerating guard");
assert.notEqual(firstAwait, -1, "send() should contain an await");
assert.ok(body.slice(entryCheck, guardSet).includes("return"), "the isGenerating branch must return before the guard set");
// The entry check runs first, then the guard is set, then (and only then) the
// first await suspends. Because nothing awaits between the check and the set,
+8 -2
View File
@@ -75,7 +75,13 @@ test("AI request failures use localized backend diagnostics", () => {
test("AI analysis export keeps the connection that produced each assistant response", () => {
assert.match(source, /sourceConnectionName\?: string/);
assert.match(source, /const runSourceName = runPluginContext\?\.pluginName \?\? connection\?\.name \?\? ""/);
assert.match(source, /runMessages\.push\(\{ role: "assistant", content: "", sourceConnectionName: runSourceName \}\)/);
assert.match(source, /connectionName: msg\.sourceConnectionName \?\? props\.connection\?\.name/);
// The assistant placeholder must record the connection that produced it. The
// literal also carries per-run fields (the frozen `sourceBinding` target,
// #9902), so pin the pair instead of the closing brace.
assert.match(source, /runMessages\.push\(\{ role: "assistant", content: "", sourceConnectionName: runSourceName,/);
assert.match(source, /sourceBinding: runBinding \}\);/);
// The fallback for a message that predates `sourceConnectionName` is the
// conversation's own bound connection, not whatever tab is visible (#9902).
assert.match(source, /connectionName: msg\.sourceConnectionName \?\? boundConnection\.value\?\.name/);
assert.match(source, /sourceConnectionName: m\.role === "assistant" \? conv\.connectionName : undefined/);
});