Files
UHD_Media_Tool/apps/web/app/components/agent/mock.ts
T

678 lines
24 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { AgentPlanStep, AgentRunEvent, AgentRunEventBody, AgentUsageTotals } from "@umt/contracts";
import type { AgentRunView, AgentSessionOptions, GoalReport, RunMessage, RunSnapshot } from "~/api/agent.ts";
// Agent 会话 mock(W2-D 端点就绪前开发用;?mock=1 打开)。事件按时间逐步"到达",模拟流式输出。
type Step = { delayMs: number; body: AgentRunEventBody };
type Entry = {
run: AgentRunView;
steps: Step[];
startedAt: number;
abortedAt: number | null;
appended: RunMessage[];
/** 挂起等用户(没有结束事件时状态为 waiting,summary 用它) */
waiting?: string | null;
};
const store = new Map<string, Entry>();
let seeded = false;
const uuid = (n: number) => `00000000-0000-4000-8000-${String(n).padStart(12, "0")}`;
const iso = (ms: number) => new Date(ms).toISOString();
const USAGE: AgentUsageTotals = {
inputTokens: 18_420,
outputTokens: 1_236,
cacheReadTokens: 12_800,
cacheWriteTokens: 2_048,
totalTokens: 19_656,
costUsd: 0.042,
};
function chunks(text: string, size = 6): string[] {
const out: string[] = [];
for (let i = 0; i < text.length; i += size) out.push(text.slice(i, i + size));
return out;
}
const MOCK_DECISION = "00000000-0000-4000-8000-00000000d001";
const MOCK_APPROVAL = "00000000-0000-4000-8000-00000000a001";
const MOCK_PLAN = "00000000-0000-4000-8000-00000000b001";
function script(goal: string, opts: AgentSessionOptions): Step[] {
const s: Step[] = [];
let t = 0;
const push = (dt: number, body: AgentRunEventBody) => {
t += dt;
s.push({ delayMs: t, body });
};
const say = (text: string) => {
for (const c of chunks(text)) push(60, { type: "text_delta", delta: c });
};
const steps: AgentPlanStep[] = [
{ id: "s1", title: "查本地库存和收录版本", status: "running", note: null },
{ id: "s2", title: "在站点搜 4K 候选", status: "pending", note: null },
{ id: "s3", title: "选资源并准备下载", status: "pending", note: null },
{ id: "s4", title: "提交下载", status: "pending", note: null },
];
let revision = 0;
const plan = (id: string, status: AgentPlanStep["status"], next?: string) => {
for (const st of steps) {
if (st.id === id) st.status = status;
if (next && st.id === next) st.status = "running";
}
push(150, { type: "plan_updated", steps: steps.map((x) => ({ ...x })), revision: ++revision });
};
push(0, { type: "run_started", goal, resumed: false });
push(100, { type: "plan_updated", steps: steps.map((x) => ({ ...x })), revision: ++revision });
say("先确认本地库存与收录版本,再去站点找候选。");
push(400, {
type: "tool_call_start",
toolCallId: "t1",
capabilityId: "inventory.query",
inputSummary: '{"query":"沙丘:第二部","year":2024}',
recovery: false,
});
push(900, {
type: "tool_call_end",
toolCallId: "t1",
capabilityId: "inventory.query",
isError: false,
outputSummary: "作品 沙丘:第二部 (2024):已有 1080p WEB-DL(已核验);Slot「archival」缺 2160p;无下载中任务",
recovery: false,
});
plan("s1", "done", "s2");
push(300, {
type: "tool_call_start",
toolCallId: "t2",
capabilityId: "release.search",
inputSummary: '{"site":"audiences","q":"Dune Part Two 2160p","passkey":"[REDACTED]"}',
recovery: false,
});
push(1200, {
type: "tool_call_end",
toolCallId: "t2",
capabilityId: "release.search",
isError: false,
outputSummary:
"3 条候选:① Dune.Part.Two.2024.2160p.UHD.BluRay.REMUX.HDR.DV-FraMeSToR 78.4 GB 免费 · 做种 41;② 2160p WEB-DL DDP5.1 Atmos 24.1 GB;③ 2160p x265 10bit 18.9 GB",
recovery: false,
});
push(300, { type: "compaction", phase: "start", reason: "threshold" });
push(700, { type: "compaction", phase: "end", reason: "threshold", status: "completed" });
say("①符合 archival 的硬条件(REMUX、HDR),免费截止时间在 6 小时后。");
plan("s2", "done", "s3");
if (opts.proposeFirst) {
push(300, {
type: "options_proposed",
invocationId: "mock-options",
decisionId: MOCK_DECISION,
question: "收哪一个 4K 版本?",
options: [
{
id: "a",
title: "① REMUX 78.4 GB",
summary: "画质最好,符合 archival 的硬条件;免费还剩 6 小时。",
risks: ["缓存卷使用率会到 86%"],
estimatedCost: "78.4 GB",
},
{
id: "b",
title: "② WEB-DL 24.1 GB",
summary: "体积小三分之二,不满足 archival 的 REMUX 条件。",
risks: ["之后可能还要升级"],
estimatedCost: "24.1 GB",
},
],
recommendedId: "a",
stepId: "s3",
expiresAt: iso(Date.now() + 1_800_000),
timeoutAction: "recommended",
});
push(200, { type: "run_completed", finalText: "", usage: USAGE });
return s;
}
push(300, {
type: "tool_call_start",
toolCallId: "t3",
capabilityId: "download.plan",
inputSummary: '{"sourceReleaseId":"…c3f1","slot":"archival"}',
recovery: false,
});
push(800, {
type: "tool_call_end",
toolCallId: "t3",
capabilityId: "download.plan",
isError: false,
outputSummary: "计划已预留:78.4 GB,缓存卷 cache-a 使用率 71% → 预计 86%(硬阈值 90%)",
recovery: false,
});
plan("s3", "done", "s4");
push(300, {
type: "tool_call_rejected",
toolCallId: "t4",
toolName: "qb_delete_torrent",
capabilityId: "download.cleanup",
reason: "未授权:删除下载器任务不在本次委托范围内(需要显式授权)",
});
push(300, {
type: "tool_call_start",
toolCallId: "t5",
capabilityId: "download.submit",
inputSummary: '{"planId":"…9a2e"}',
recovery: false,
});
push(1000, {
type: "tool_call_end",
toolCallId: "t5",
capabilityId: "download.submit",
isError: false,
outputSummary: '{"error":{"code":"unauthorized","message":"已在步骤处发起审批,等待管理员批准"}}',
recovery: false,
});
push(200, {
type: "approval_requested",
approvalId: MOCK_APPROVAL,
decisionId: null,
invocationId: "mock-submit",
capabilityId: "download.submit",
planType: "acquisition_plan",
planId: MOCK_PLAN,
payloadHash: "sha256:9a2e…",
expectedRevision: 1,
riskLevel: "high",
summary: "下载《沙丘:第二部》:Dune.Part.Two.2024.2160p.UHD.BluRay.REMUX(78.4 GB)",
stepId: "s4",
expiresAt: iso(Date.now() + 86_400_000),
});
say("提交下载需要审批,已在「提交下载」这一步发起。批准后继续。");
push(200, { type: "run_completed", finalText: "", usage: USAGE });
return s;
}
function baseRun(
id: string,
goal: string,
opts: AgentSessionOptions,
createdAt: number,
status: AgentRunView["status"],
) {
return {
id,
createdAt: iso(createdAt),
updatedAt: iso(createdAt),
taskId: null,
parentRunId: null,
trigger: "user",
goal,
status,
provider: "anthropic",
model: "claude-sonnet",
thinkingLevel: "medium",
scope: { proposeFirst: opts.proposeFirst, interactive: true },
budget: { maxToolCalls: 20, maxTokens: 200_000, maxDurationSec: 600, maxCostUsd: 1 },
usage: {},
evidenceVersion: null,
sessionState: null,
summary: null,
toolCallCount: 0,
lastEventSeq: 0,
error: null,
cancelRequestedAt: null,
startedAt: iso(createdAt),
finishedAt: null,
createdBy: "admin",
dedupeKey: null,
depth: 0,
spawnReason: null,
} satisfies AgentRunView;
}
function add(goal: string, opts: AgentSessionOptions, startedAt: number, n: number): Entry {
const id = uuid(n);
const e: Entry = {
run: baseRun(id, goal, opts, startedAt, "running"),
steps: script(goal, opts),
startedAt,
abortedAt: null,
appended: [],
};
store.set(id, e);
return e;
}
// ── AGUI-3 演示:审查报告的 3 个示例(9883534f 巡检汇报、31d16a1a 方案 + 审批),截图 before / after 共用 ──
const at0 = (steps: [number, AgentRunEventBody][]): Step[] => {
let t = 0;
return steps.map(([dt, body]) => {
t += dt;
return { delayMs: t, body };
});
};
const toolPair = (
id: string,
cap: string,
input: string,
out: string,
isError = false,
): [number, AgentRunEventBody][] => [
[120, { type: "tool_call_start", toolCallId: id, capabilityId: cap, inputSummary: input, recovery: false }],
[380, { type: "tool_call_end", toolCallId: id, capabilityId: cap, isError, outputSummary: out, recovery: false }],
];
const textSteps = (text: string): [number, AgentRunEventBody][] =>
chunks(text, 40).map((c) => [20, { type: "text_delta", delta: c }]);
/** 旧汇报格式(改 prompt 之前模型写的):函数名、字段名、UUID、6 列表格 */
export const LEGACY_REPORT = `## 巡检结果
已调用 \`site.list(includeDisabled=true)\` 与 \`download.inspect\`、\`inventory.query\` 核对,结论如下:
| 站点 ID | 名称 | enabled | loginStatus | seeding | health_check |
| --- | --- | --- | --- | --- | --- |
| 062e5c51-9d1e-4c55-a5a2-3f7b1f3e0c11 | 憨憨 | true | ok | 24 | unauthorized |
- \`download.inspect\` 返回 downloader=null:下载器未配置(qb-main 未登记)。
- \`inventory.query\` 返回 mediaServer=null:媒体服务器未接入。
- \`site.health_check\` 被拒绝(缺少 site.health_check 的持续授权),未能检测连通性。
下一步:配置下载器后再调用 download.inspect;授权 site.health_check 后重试。`;
/** 新汇报格式(AGUI-3 prompt):第一句结论、用名字、≤10 行 */
export const NEW_REPORT =
"下载器和媒体服务器都还没接入,所以没法检查。站点「憨憨」正常,在做种 24 个。\n\n- 接入下载器:设置 → 下载器\n- 接入媒体服务器:设置 → 媒体服务器";
function checkupSteps(goal: string, report: string): Step[] {
const plan = (rev: number, s1: AgentPlanStep["status"], s2: AgentPlanStep["status"], s3: AgentPlanStep["status"]) =>
[
[
80,
{
type: "plan_updated",
revision: rev,
steps: [
{ id: "s1", title: "看站点状态", status: s1, note: null },
{ id: "s2", title: "核对下载器", status: s2, note: null },
{ id: "s3", title: "核对媒体服务器", status: s3, note: null },
],
},
],
] as [number, AgentRunEventBody][];
return at0([
[0, { type: "run_started", goal, resumed: false }],
...toolPair("p1", "agent.plan", '{"steps":[{"id":"s1"}]}', '{"revision":1}'),
...plan(1, "running", "pending", "pending"),
[60, { type: "thinking_delta", delta: "先列站点,再看下载器和媒体服务器有没有接入。" }],
...toolPair("c1", "site.list", '{"includeDisabled":true}', '{"items":[{"name":"憨憨","seeding":24}]}'),
...toolPair(
"c2",
"site.health_check",
'{"siteId":"062e5c51-9d1e-4c55-a5a2-3f7b1f3e0c11"}',
'{"error":{"code":"unauthorized","message":"缺少 site.health_check 的持续授权或有效的单次审批"}}',
true,
),
...toolPair("p2", "agent.plan", '{"steps":[{"id":"s1","status":"done"}]}', '{"revision":2}'),
...plan(2, "done", "running", "pending"),
...toolPair("c3", "download.inspect", '{"status":"downloading"}', '{"summary":"没有配置下载器"}'),
...toolPair("c4", "download.inspect", '{"status":"stalled"}', '{"summary":"没有配置下载器"}'),
[60, { type: "thinking_delta", delta: "下载器没配置。再看媒体库。" }],
...toolPair("c5", "inventory.query", "{}", '{"items":[],"mediaServer":null}'),
...toolPair("p3", "agent.plan", '{"steps":[{"id":"s3","status":"done"}]}', '{"revision":3}'),
...plan(3, "done", "done", "done"),
...textSteps(report),
[200, { type: "run_completed", finalText: report, usage: { ...USAGE, costUsd: 0.014, totalTokens: 320_700 } }],
]);
}
/** 例 3:方案绑定计划(选定即批准),选了之后直接提交 */
const DUNE_PLAN = "00000000-0000-4000-8000-00000000b003";
const DUNE_DECISION = "00000000-0000-4000-8000-00000000d003";
function optionSteps(goal: string): Step[] {
return at0([
[0, { type: "run_started", goal, resumed: false }],
...toolPair("p1", "agent.plan", "{}", '{"revision":1}'),
[
60,
{
type: "plan_updated",
revision: 1,
steps: [
{ id: "s1", title: "找 1080p 资源", status: "done", note: null },
{ id: "s2", title: "准备下载", status: "done", note: null },
{ id: "s3", title: "等你选方案", status: "running", note: null },
{ id: "s4", title: "提交下载", status: "pending", note: null },
],
},
],
...toolPair("c1", "release.search", '{"q":"Dune Part Two 1080p"}', '{"items":[1,2]}'),
...toolPair("c2", "download.plan", `{"sourceReleaseId":"…"}`, `{"plan":{"id":"${DUNE_PLAN}","status":"draft"}}`),
...textSteps("找到 1 个合适的 1080p 资源,站点「憨憨」的登录明天过期。"),
...toolPair("p2", "agent.propose_options", "{}", '{"outcome":"pending"}'),
[
80,
{
type: "options_proposed",
invocationId: "mock-dune-options",
decisionId: DUNE_DECISION,
question: "《沙丘:第二部》1080p 怎么收?",
options: [
{
id: "a",
title: "提交下载",
summary: "Dune.Part.Two.2024.1080p.BluRay.x264-GRP,免费,做种 41",
risks: ["站点登录明天过期,之后下载可能断", "缓存卷会用到 86%"],
estimatedCost: "12 GB",
action: {
capabilityId: "download.submit",
planId: DUNE_PLAN,
label: "提交下载",
detail: "《沙丘:第二部》· 12.0 GB",
approves: true,
},
},
{
id: "b",
title: "先修站点",
summary: "先刷新「憨憨」的登录,再提交",
risks: [],
estimatedCost: null,
action: null,
},
{ id: "c", title: "先不下", summary: "等 REMUX 出现再说", risks: [], estimatedCost: null, action: null },
],
recommendedId: "a",
stepId: "s3",
expiresAt: iso(Date.now() + 1_800_000),
timeoutAction: "recommended",
},
],
]);
}
/** 选定方案后的续跑:选了带动作的方案 → 直接提交,不出审批卡 */
function afterChoice(e: Entry, optionId: string | null) {
const base = Date.now() - e.startedAt;
const done: [number, AgentRunEventBody][] =
optionId === "a"
? [
[
100,
{
type: "options_resolved",
invocationId: "mock-dune-options",
decisionId: DUNE_DECISION,
outcome: "chosen",
choice: "a",
reason: null,
by: "user",
},
],
...toolPair("c3", "download.submit", `{"planId":"${DUNE_PLAN}"}`, '{"taskId":"…","status":"submitted"}'),
...textSteps("已提交《沙丘:第二部》1080p(12 GB),下载完成后自动整理。"),
[100, { type: "run_completed", finalText: "", usage: USAGE }],
]
: [
[
100,
{
type: "options_resolved",
invocationId: "mock-dune-options",
decisionId: DUNE_DECISION,
outcome: optionId ? "chosen" : "rejected",
choice: optionId,
reason: null,
by: "user",
},
],
...textSteps(optionId === "b" ? "好,先不提交,等站点登录刷新后再说。" : "好,这次先不下。"),
[100, { type: "run_completed", finalText: "", usage: USAGE }],
];
let t = base;
for (const [dt, body] of done) {
t += dt;
e.steps.push({ delayMs: t, body });
}
e.waiting = null;
}
/** mock 的方案作答 */
export function mockChoose(runId: string, optionId: string | null): RunSnapshot {
seed();
const e = store.get(runId);
if (e?.waiting === "等待选择方案") afterChoice(e, optionId);
return mockDetail(runId);
}
function seed() {
if (seeded) return;
seeded = true;
const now = Date.now();
const fixture = (n: number, goal: string, steps: Step[], ago: number, extra: Partial<Entry> = {}) => {
const e: Entry = {
run: baseRun(uuid(n), goal, { proposeFirst: false }, now - ago, "running"),
steps,
startedAt: now - ago,
abortedAt: null,
appended: [],
...extra,
};
e.run = { ...e.run, provider: "opencode-go", model: "deepseek-v4.1-flash" };
store.set(e.run.id, e);
return e;
};
const CHECKUP = "检查站点、下载器和媒体服务器是否正常";
fixture(11, `${CHECKUP}(旧汇报格式)`, checkupSteps(CHECKUP, LEGACY_REPORT), 40 * 60_000);
const parent = fixture(12, CHECKUP, checkupSteps(CHECKUP, NEW_REPORT), 30 * 60_000);
const child = fixture(
14,
"看站点「憨憨」最近的抓取记录",
checkupSteps("看站点「憨憨」最近的抓取记录", "「憨憨」最近 24 小时抓取 96 次,全部成功。"),
29 * 60_000,
);
child.run = { ...child.run, parentRunId: parent.run.id, trigger: "subagent", depth: 1 };
fixture(
13,
"[上下文 作品:沙丘:第二部 · entityId=74beab51-0000-4000-8000-000000000000] 把 1080p 收进来",
optionSteps("把沙丘:第二部的 1080p 收进来"),
20 * 60_000,
{ waiting: "等待选择方案" },
);
add("把 沙丘:第二部 的 4K 收进来", { proposeFirst: false }, now - 3_600_000, 1);
add("对比一下 奥本海默 现有版本和站点上的 4K", { proposeFirst: true }, now - 7_200_000, 2);
const failed = add("巡检:检查 qb-main 为什么占满磁盘", { proposeFirst: false }, now - 86_400_000, 3);
failed.run = { ...failed.run, trigger: "schedule" };
const evt = add(
"新资源到达:看看 奥本海默 的 REMUX 是否值得替换现有版本",
{ proposeFirst: false },
now - 3 * 86_400_000,
4,
);
evt.run = { ...evt.run, trigger: "event" };
failed.steps = [
{ delayMs: 0, body: { type: "run_started", goal: failed.run.goal, resumed: false } },
{
delayMs: 100,
body: {
type: "tool_call_start",
toolCallId: "x1",
capabilityId: "task.inspect",
inputSummary: '{"instance":"qb-main"}',
recovery: false,
},
},
{
delayMs: 200,
body: {
type: "tool_call_end",
toolCallId: "x1",
capabilityId: "task.inspect",
isError: true,
outputSummary: "下载器连接超时(qb-main 10.0.0.12:8080)",
recovery: false,
},
},
{
delayMs: 300,
body: {
type: "run_failed",
reason: "budget",
budget: "duration",
message: "超出单次运行时长预算(600 秒)",
usage: { ...USAGE, totalTokens: 4_210, inputTokens: 3_900, outputTokens: 310, costUsd: 0.009 },
},
},
];
}
function revealed(e: Entry, now = Date.now()): AgentRunEvent[] {
const cut = e.abortedAt ?? now;
const out: AgentRunEvent[] = [];
let seq = 0;
for (const st of e.steps) {
if (e.startedAt + st.delayMs > cut) break;
out.push({ ...st.body, runId: e.run.id, seq: seq++, at: iso(e.startedAt + st.delayMs) } as AgentRunEvent);
}
if (e.abortedAt && !out.some((x) => x.type === "run_completed" || x.type === "run_failed")) {
out.push({
type: "run_failed",
reason: "aborted",
message: "用户中止",
usage: USAGE,
runId: e.run.id,
seq: seq++,
at: iso(e.abortedAt),
});
}
return out;
}
function view(e: Entry): AgentRunView {
const evs = revealed(e);
const end = evs.find((x) => x.type === "run_completed" || x.type === "run_failed");
const tools = evs.filter((x) => x.type === "tool_call_start").length;
const text = evs
.filter((x) => x.type === "text_delta")
.map((x) => (x.type === "text_delta" ? x.delta : ""))
.join("");
const status: AgentRunView["status"] = !end
? e.waiting && evs.length === e.steps.length
? "waiting"
: "running"
: end.type === "run_completed"
? "succeeded"
: end.reason === "budget"
? "budget_exceeded"
: end.reason === "aborted"
? "cancelled"
: "failed";
const frac = end ? 1 : Math.min(1, evs.length / Math.max(1, e.steps.length));
return {
...e.run,
status,
toolCallCount: tools,
lastEventSeq: evs.length ? evs[evs.length - 1]!.seq : 0,
usage: end
? { ...end.usage, durationMs: new Date(end.at).getTime() - e.startedAt }
: {
inputTokens: Math.round(USAGE.inputTokens * frac),
outputTokens: Math.round(USAGE.outputTokens * frac),
totalTokens: Math.round(USAGE.totalTokens * frac),
costUsd: +(USAGE.costUsd * frac).toFixed(4),
durationMs: Date.now() - e.startedAt,
},
summary: end ? (end.type === "run_failed" ? end.message : text) : status === "waiting" ? (e.waiting ?? null) : null,
error: end?.type === "run_failed" ? { reason: end.reason, message: end.message } : null,
finishedAt: end ? end.at : null,
updatedAt: evs.length ? evs[evs.length - 1]!.at : e.run.updatedAt,
};
}
export function MOCK_RUNS(): AgentRunView[] {
seed();
return [...store.values()].map(view).sort((a, b) => b.createdAt.localeCompare(a.createdAt));
}
export function mockDetail(id: string, op?: { abort?: boolean; append?: string }): RunSnapshot {
seed();
const e = store.get(id);
if (!e) throw Object.assign(new Error("会话不存在(mock)"), { code: "not_found" });
if (op?.abort && !e.abortedAt) e.abortedAt = Date.now();
if (op?.append) {
// 追加消息:模拟 Agent 回一句确认
const at = Date.now() - e.startedAt;
e.appended.push({
id: uuid(9000 + e.appended.length),
runId: id,
createdAt: iso(Date.now()),
updatedAt: iso(Date.now()),
text: op.append,
status: "pending",
createdBy: "user:mock",
consumedAt: null,
});
const last = e.steps.findIndex((s) => s.body.type === "run_completed" || s.body.type === "run_failed");
const reply: Step[] = chunks(`收到补充:「${op.append}」,按此调整。`).map((c, i) => ({
delayMs: at + 200 + i * 60,
body: { type: "text_delta", delta: c },
}));
if (last >= 0 && e.startedAt + e.steps[last]!.delayMs > Date.now()) e.steps.splice(last, 0, ...reply);
e.steps.sort((a, b) => a.delayMs - b.delayMs);
}
const run = view(e);
return { run, messages: e.appended, report: report(e, run) };
}
/** 服务端目标汇报的 mock:只按"业务表"口径给数,模型文本单独放 modelText */
function report(e: Entry, run: AgentRunView): GoalReport | null {
if (!run.finishedAt) return null;
const evs = revealed(e);
const submitted = evs.some((x) => x.type === "tool_call_end" && x.capabilityId === "download.submit" && !x.isError);
const text = evs
.filter((x) => x.type === "text_delta")
.map((x) => (x.type === "text_delta" ? x.delta : ""))
.join("");
return {
state: submitted ? "waiting" : run.status === "succeeded" ? "no_actions" : "failed",
summary: submitted
? "1 个下载已进入下载器(qb-main),尚未整理;整理以整理记录 succeeded 为准"
: run.status === "succeeded"
? "本次没有产生下载或整理动作"
: "运行未完成,没有产生业务动作",
submitted: submitted ? 1 : 0,
imported: 0,
inProgress: submitted ? 1 : 0,
failed: 0,
plans: submitted
? [{ planId: uuid(7001), status: "submitted", slotId: uuid(7003), sourceReleaseId: uuid(7004) }]
: [],
downloads: submitted
? [
{
downloadTaskId: uuid(7002),
planId: uuid(7001),
status: "downloading",
name: run.goal.includes("1080p")
? "Dune.Part.Two.2024.1080p.BluRay.x264-GRP"
: "Dune.Part.Two.2024.2160p.UHD.BluRay.REMUX",
},
]
: [],
organization: [],
modelText: text || null,
generatedAt: run.finishedAt,
};
}
export function mockEventsAfter(id: string, after: number): AgentRunEvent[] {
seed();
const e = store.get(id);
if (!e) return [];
return revealed(e).filter((x) => x.seq > after);
}
export function mockCreateRun(goal: string, opts: AgentSessionOptions): AgentRunView {
seed();
const e = add(goal, opts, Date.now(), store.size + 1);
return view(e);
}