feat(core,server,web): images reach every input — steering, goal objectives, one fold (#71)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
de6380f278
commit
97852f43b8
40 files changed
+1474
-254
No files matched your search
@@ -20,6 +20,7 @@ import {
|
||||
toolCall,
|
||||
toolCallOutput,
|
||||
userText,
|
||||
withOrigin,
|
||||
} from "@prismshadow/penguin-core";
|
||||
import type { OmniMessage, SessionMetaPayload, TokenCounts } from "@prismshadow/penguin-core";
|
||||
import { TraceService } from "../src/services/trace-service.js";
|
||||
@@ -661,6 +662,38 @@ describe("trace-service", () => {
|
||||
expect(a.modelSegments.map((s) => s.taskIndex)).toEqual([0, 0, 0, 1]);
|
||||
});
|
||||
|
||||
// The Web's live-stream twin of this case lives in stream-model.test.ts.
|
||||
it("Task grouping: images sent with a steering message don't start a Task either (a Prompt's do)", async () => {
|
||||
const T = (sec: string) => `2026-07-05T10:04:${sec}Z`;
|
||||
await writeTraceFile(root, P, A, "2026-07-05", S, 13, [
|
||||
sessionMeta(metaPayload()),
|
||||
// Task 0: a normal turn that calls a tool.
|
||||
at(T("00.000"), userText("q1")),
|
||||
at(T("01.000"), requestBegin()),
|
||||
at(T("02.000"), toolCall({ name: "read_file", arguments: "{}", toolCallId: "t1" })),
|
||||
at(T("02.500"), requestEnd("completed")),
|
||||
at(T("03.000"), toolCallOutput({ output: "o", toolCallId: "t1" })),
|
||||
// Steering with two images: core delivers them right behind the text, and the whole
|
||||
// batch stays inside Task 0 — an image is a turn starter everywhere except here.
|
||||
at(T("03.200"), userText("[user_steering]\nlike this mock\n[/user_steering]")),
|
||||
at(T("03.300"), imageUrlMessage("data:image/png;base64,AAAA")),
|
||||
// A subagent message belongs to another session's stream and says nothing about this
|
||||
// one's grouping, so it leaves the window open (the Web skips these even earlier).
|
||||
at(T("03.350"), withOrigin(assistantText("child thinking"), "child-1")),
|
||||
at(T("03.400"), imageUrlMessage("data:image/png;base64,BBBB")),
|
||||
at(T("03.500"), requestBegin()),
|
||||
at(T("04.000"), assistantText("answer 1")),
|
||||
at(T("04.500"), requestEnd("completed")),
|
||||
// An images-only Prompt after all that is a genuine new turn.
|
||||
at(T("20.000"), imageUrlMessage("data:image/png;base64,CCCC")),
|
||||
at(T("21.000"), requestBegin()),
|
||||
at(T("22.000"), assistantText("answer 2")),
|
||||
at(T("22.500"), requestEnd("completed")),
|
||||
]);
|
||||
const a = await service.analyze(P, A, S, 13);
|
||||
expect(a.requests.map((r) => r.taskIndex)).toEqual([0, 0, 1]);
|
||||
});
|
||||
|
||||
// Compaction is its own turn: the previous turn called a tool and would
|
||||
// otherwise "continue", but compaction_begin breaks that continuation, so the
|
||||
// compaction request lands on a new taskIndex. A successful compaction splits
|
||||
|
||||
Reference in new issue
Block a user