@celestea/studio 2.7.1
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/LICENSE +21 -0
- package/dist/app.d.ts +111 -0
- package/dist/app.js +219 -0
- package/dist/auth/api-token.d.ts +76 -0
- package/dist/auth/api-token.js +172 -0
- package/dist/auth/htpasswd.d.ts +22 -0
- package/dist/auth/htpasswd.js +61 -0
- package/dist/auth/index.d.ts +14 -0
- package/dist/auth/index.js +14 -0
- package/dist/auth/page.d.ts +19 -0
- package/dist/auth/page.js +73 -0
- package/dist/auth/rate-limit.d.ts +27 -0
- package/dist/auth/rate-limit.js +47 -0
- package/dist/auth/token.d.ts +49 -0
- package/dist/auth/token.js +112 -0
- package/dist/config.d.ts +78 -0
- package/dist/config.js +72 -0
- package/dist/deployment.d.ts +92 -0
- package/dist/deployment.js +154 -0
- package/dist/fake-runtime-adapter.d.ts +37 -0
- package/dist/fake-runtime-adapter.js +276 -0
- package/dist/handlers/auth.d.ts +24 -0
- package/dist/handlers/auth.js +155 -0
- package/dist/handlers/common.d.ts +76 -0
- package/dist/handlers/common.js +127 -0
- package/dist/handlers/config-shape.d.ts +76 -0
- package/dist/handlers/config-shape.js +227 -0
- package/dist/handlers/config.d.ts +26 -0
- package/dist/handlers/config.js +161 -0
- package/dist/handlers/context-shape.d.ts +33 -0
- package/dist/handlers/context-shape.js +59 -0
- package/dist/handlers/dialog.d.ts +22 -0
- package/dist/handlers/dialog.js +292 -0
- package/dist/handlers/exec.d.ts +22 -0
- package/dist/handlers/exec.js +131 -0
- package/dist/handlers/fs-read.d.ts +63 -0
- package/dist/handlers/fs-read.js +159 -0
- package/dist/handlers/fs.d.ts +57 -0
- package/dist/handlers/fs.js +153 -0
- package/dist/handlers/grants-shape.d.ts +45 -0
- package/dist/handlers/grants-shape.js +130 -0
- package/dist/handlers/grants.d.ts +21 -0
- package/dist/handlers/grants.js +267 -0
- package/dist/handlers/health.d.ts +37 -0
- package/dist/handlers/health.js +139 -0
- package/dist/handlers/index.d.ts +40 -0
- package/dist/handlers/index.js +85 -0
- package/dist/handlers/permissions.d.ts +9 -0
- package/dist/handlers/permissions.js +183 -0
- package/dist/handlers/plugins.d.ts +18 -0
- package/dist/handlers/plugins.js +23 -0
- package/dist/handlers/prompts.d.ts +12 -0
- package/dist/handlers/prompts.js +120 -0
- package/dist/handlers/providers.d.ts +12 -0
- package/dist/handlers/providers.js +178 -0
- package/dist/handlers/questions.d.ts +23 -0
- package/dist/handlers/questions.js +115 -0
- package/dist/handlers/session-model.d.ts +38 -0
- package/dist/handlers/session-model.js +105 -0
- package/dist/handlers/session-move.d.ts +22 -0
- package/dist/handlers/session-move.js +188 -0
- package/dist/handlers/session-tools.d.ts +19 -0
- package/dist/handlers/session-tools.js +75 -0
- package/dist/handlers/sessions.d.ts +32 -0
- package/dist/handlers/sessions.js +242 -0
- package/dist/handlers/usage.d.ts +26 -0
- package/dist/handlers/usage.js +77 -0
- package/dist/handlers/worker.d.ts +12 -0
- package/dist/handlers/worker.js +82 -0
- package/dist/handlers/workspaces.d.ts +12 -0
- package/dist/handlers/workspaces.js +88 -0
- package/dist/harness.test-util.d.ts +101 -0
- package/dist/harness.test-util.js +176 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.js +33 -0
- package/dist/main.d.ts +13 -0
- package/dist/main.js +35 -0
- package/dist/plugins.d.ts +105 -0
- package/dist/plugins.js +97 -0
- package/dist/question-registry.d.ts +124 -0
- package/dist/question-registry.js +212 -0
- package/dist/question-rows.d.ts +32 -0
- package/dist/question-rows.js +43 -0
- package/dist/replay/compare.d.ts +47 -0
- package/dist/replay/compare.js +89 -0
- package/dist/replay/e2e-replay.d.ts +46 -0
- package/dist/replay/e2e-replay.js +91 -0
- package/dist/replay/expect-compact.d.ts +31 -0
- package/dist/replay/expect-compact.js +92 -0
- package/dist/replay/fixtures.d.ts +52 -0
- package/dist/replay/fixtures.js +53 -0
- package/dist/replay/host.d.ts +32 -0
- package/dist/replay/host.js +62 -0
- package/dist/replay/index.d.ts +16 -0
- package/dist/replay/index.js +16 -0
- package/dist/replay/probes.d.ts +55 -0
- package/dist/replay/probes.js +225 -0
- package/dist/replay/report.d.ts +13 -0
- package/dist/replay/report.js +103 -0
- package/dist/replay/session-e2e.d.ts +44 -0
- package/dist/replay/session-e2e.js +91 -0
- package/dist/routes.d.ts +47 -0
- package/dist/routes.js +59 -0
- package/dist/runtime/attachments-llm.d.ts +13 -0
- package/dist/runtime/attachments-llm.js +37 -0
- package/dist/runtime/boot-recovery.d.ts +32 -0
- package/dist/runtime/boot-recovery.js +71 -0
- package/dist/runtime/context-snapshot.d.ts +59 -0
- package/dist/runtime/context-snapshot.js +113 -0
- package/dist/runtime/engine-grants.d.ts +121 -0
- package/dist/runtime/engine-grants.js +338 -0
- package/dist/runtime/engine-permissions.d.ts +29 -0
- package/dist/runtime/engine-permissions.js +92 -0
- package/dist/runtime/engine-plugins.d.ts +189 -0
- package/dist/runtime/engine-plugins.js +292 -0
- package/dist/runtime/engine-profile.d.ts +28 -0
- package/dist/runtime/engine-profile.js +88 -0
- package/dist/runtime/engine-session.d.ts +9 -0
- package/dist/runtime/engine-session.js +9 -0
- package/dist/runtime/fallback-contract.d.ts +32 -0
- package/dist/runtime/fallback-contract.js +10 -0
- package/dist/runtime/fallback-host.d.ts +134 -0
- package/dist/runtime/fallback-host.js +299 -0
- package/dist/runtime/host-autowake.d.ts +79 -0
- package/dist/runtime/host-autowake.js +86 -0
- package/dist/runtime/image-downgrade.d.ts +49 -0
- package/dist/runtime/image-downgrade.js +90 -0
- package/dist/runtime/inbox-message.d.ts +11 -0
- package/dist/runtime/inbox-message.js +20 -0
- package/dist/runtime/index.d.ts +17 -0
- package/dist/runtime/index.js +17 -0
- package/dist/runtime/ledger-view.d.ts +40 -0
- package/dist/runtime/ledger-view.js +56 -0
- package/dist/runtime/llm-assembly.d.ts +66 -0
- package/dist/runtime/llm-assembly.js +114 -0
- package/dist/runtime/offline-llm.d.ts +71 -0
- package/dist/runtime/offline-llm.js +132 -0
- package/dist/runtime/provider-target.d.ts +10 -0
- package/dist/runtime/provider-target.js +10 -0
- package/dist/runtime/question-host.d.ts +33 -0
- package/dist/runtime/question-host.js +41 -0
- package/dist/runtime/question-view.d.ts +48 -0
- package/dist/runtime/question-view.js +76 -0
- package/dist/runtime/real-runtime-adapter.d.ts +95 -0
- package/dist/runtime/real-runtime-adapter.js +553 -0
- package/dist/runtime/recovery-audit.d.ts +81 -0
- package/dist/runtime/recovery-audit.js +95 -0
- package/dist/runtime/recovery-view.d.ts +36 -0
- package/dist/runtime/recovery-view.js +63 -0
- package/dist/runtime/session-compose.d.ts +235 -0
- package/dist/runtime/session-compose.js +350 -0
- package/dist/runtime/session-grants.d.ts +36 -0
- package/dist/runtime/session-grants.js +69 -0
- package/dist/runtime/session-lifecycle.d.ts +40 -0
- package/dist/runtime/session-lifecycle.js +86 -0
- package/dist/runtime/session-publisher.d.ts +40 -0
- package/dist/runtime/session-publisher.js +48 -0
- package/dist/runtime/session-release.d.ts +53 -0
- package/dist/runtime/session-release.js +80 -0
- package/dist/runtime/test-util.d.ts +78 -0
- package/dist/runtime/test-util.js +162 -0
- package/dist/runtime/watchdog-view.d.ts +33 -0
- package/dist/runtime/watchdog-view.js +45 -0
- package/dist/runtime/worker-bridge.d.ts +76 -0
- package/dist/runtime/worker-bridge.js +156 -0
- package/dist/runtime/worker-live.d.ts +22 -0
- package/dist/runtime/worker-live.js +25 -0
- package/dist/runtime/worker-recovery.d.ts +35 -0
- package/dist/runtime/worker-recovery.js +64 -0
- package/dist/runtime/worker-table.d.ts +58 -0
- package/dist/runtime/worker-table.js +70 -0
- package/dist/runtime-adapter.d.ts +421 -0
- package/dist/runtime-adapter.js +76 -0
- package/dist/serial-queue.d.ts +16 -0
- package/dist/serial-queue.js +22 -0
- package/dist/server.d.ts +44 -0
- package/dist/server.js +119 -0
- package/dist/settings.d.ts +23 -0
- package/dist/settings.js +31 -0
- package/dist/sse.d.ts +67 -0
- package/dist/sse.js +186 -0
- package/dist/static.d.ts +20 -0
- package/dist/static.js +120 -0
- package/dist/store/builtin-sections.d.ts +52 -0
- package/dist/store/builtin-sections.js +118 -0
- package/dist/store/celestea-home.d.ts +12 -0
- package/dist/store/celestea-home.js +11 -0
- package/dist/store/fs-json.d.ts +51 -0
- package/dist/store/fs-json.js +119 -0
- package/dist/store/grants-audit.d.ts +84 -0
- package/dist/store/grants-audit.js +109 -0
- package/dist/store/grants-service.d.ts +44 -0
- package/dist/store/grants-service.js +41 -0
- package/dist/store/grants-tokens.d.ts +62 -0
- package/dist/store/grants-tokens.js +105 -0
- package/dist/store/grants.d.ts +113 -0
- package/dist/store/grants.js +238 -0
- package/dist/store/index.d.ts +36 -0
- package/dist/store/index.js +36 -0
- package/dist/store/mode.d.ts +35 -0
- package/dist/store/mode.js +41 -0
- package/dist/store/permissions.d.ts +61 -0
- package/dist/store/permissions.js +139 -0
- package/dist/store/prompts-compose.d.ts +56 -0
- package/dist/store/prompts-compose.js +62 -0
- package/dist/store/prompts-template.d.ts +26 -0
- package/dist/store/prompts-template.js +99 -0
- package/dist/store/prompts.d.ts +142 -0
- package/dist/store/prompts.js +273 -0
- package/dist/store/provider-probe.d.ts +81 -0
- package/dist/store/provider-probe.js +154 -0
- package/dist/store/providers.d.ts +97 -0
- package/dist/store/providers.js +219 -0
- package/dist/store/result.d.ts +29 -0
- package/dist/store/result.js +30 -0
- package/dist/store/session-id.d.ts +119 -0
- package/dist/store/session-id.js +220 -0
- package/dist/store/session-meta.d.ts +41 -0
- package/dist/store/session-meta.js +66 -0
- package/dist/store/session-ops.d.ts +109 -0
- package/dist/store/session-ops.js +264 -0
- package/dist/store/session-tools.d.ts +42 -0
- package/dist/store/session-tools.js +69 -0
- package/dist/store/sessions.d.ts +162 -0
- package/dist/store/sessions.js +307 -0
- package/dist/store/validate.d.ts +13 -0
- package/dist/store/validate.js +38 -0
- package/dist/store/workspaces.d.ts +67 -0
- package/dist/store/workspaces.js +228 -0
- package/dist/user-questions.d.ts +58 -0
- package/dist/user-questions.js +157 -0
- package/dist/version.d.ts +14 -0
- package/dist/version.js +52 -0
- package/package.json +38 -0
- package/webdist/assets/index-Bngs7gUm.js +98 -0
- package/webdist/assets/index-Bu6ci_rN.css +1 -0
- package/webdist/assets/katex-CCbS2qAY.js +257 -0
- package/webdist/assets/mhchem-CnUN8HwY.js +1 -0
- package/webdist/build-meta.json +7 -0
- package/webdist/index.html +261 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Markdown / console rendering of the P5 double-run report.
|
|
3
|
+
*
|
|
4
|
+
* The markdown is the deliverable of the replay: it states, per finding, WHICH
|
|
5
|
+
* evidence class backs the claim (byte-exact / frozen golden / spec-derived /
|
|
6
|
+
* self-check) so a reader can tell a proven byte-level match from a
|
|
7
|
+
* self-consistency check, and it lists the P6 gaps next to the verdict.
|
|
8
|
+
*/
|
|
9
|
+
const KIND_LABEL = {
|
|
10
|
+
"byte-exact": "逐字节",
|
|
11
|
+
golden: "冻结黄金",
|
|
12
|
+
"spec-derived": "独立重推导",
|
|
13
|
+
"self-check": "自洽校验",
|
|
14
|
+
info: "信息",
|
|
15
|
+
};
|
|
16
|
+
const VERDICT_LABEL = { match: "一致", diff: "差异", skip: "跳过" };
|
|
17
|
+
function escape(text) {
|
|
18
|
+
return text.replace(/\|/g, "\\|");
|
|
19
|
+
}
|
|
20
|
+
function verdictTable(report) {
|
|
21
|
+
const s = report.summary;
|
|
22
|
+
return [
|
|
23
|
+
"| 指标 | 值 |",
|
|
24
|
+
"|---|---|",
|
|
25
|
+
`| 回放会话数 | ${s.sessions} |`,
|
|
26
|
+
`| 比对项(findings) | ${s.findings} |`,
|
|
27
|
+
`| 一致 | ${s.matched}(其中逐字节 ${s.byteExact} / 冻结黄金 ${s.golden}) |`,
|
|
28
|
+
`| **差异** | **${s.diffed}** |`,
|
|
29
|
+
`| 跳过(结构性说明) | ${s.skipped} |`,
|
|
30
|
+
`| harness 错误 | ${s.errors} |`,
|
|
31
|
+
`| 结论 | ${s.verdict} |`,
|
|
32
|
+
"",
|
|
33
|
+
];
|
|
34
|
+
}
|
|
35
|
+
function findingsTable(title, findings, predicate) {
|
|
36
|
+
const rows = findings.filter(predicate);
|
|
37
|
+
const out = [`### ${title}(${rows.length})`, ""];
|
|
38
|
+
if (rows.length === 0)
|
|
39
|
+
return [...out, "_无_", ""];
|
|
40
|
+
out.push("| scope | 证据类别 | 说明 |", "|---|---|---|");
|
|
41
|
+
for (const f of rows)
|
|
42
|
+
out.push(`| ${escape(f.scope)} | ${KIND_LABEL[f.kind]} | ${escape(f.detail)} |`);
|
|
43
|
+
out.push("");
|
|
44
|
+
return out;
|
|
45
|
+
}
|
|
46
|
+
function sessionTable(report) {
|
|
47
|
+
const out = ["| 会话 | roles | events | turns | 比对项 | 差异 | 结论 |", "|---|---|---|---|---|---|---|"];
|
|
48
|
+
for (const s of report.sessions) {
|
|
49
|
+
const diffed = s.findings.filter((f) => f.verdict === "diff").length;
|
|
50
|
+
out.push(`| ${s.id} | ${s.roles.join(", ")} | ${s.events} | ${s.turns} | ${s.findings.length} | ${diffed} | ${s.verdict} |`);
|
|
51
|
+
}
|
|
52
|
+
out.push("");
|
|
53
|
+
return out;
|
|
54
|
+
}
|
|
55
|
+
function diffSection(report) {
|
|
56
|
+
const diffs = report.findings.filter((f) => f.verdict === "diff");
|
|
57
|
+
const out = ["## 差异清单与原因", ""];
|
|
58
|
+
if (diffs.length === 0)
|
|
59
|
+
return [...out, "**无差异**:所有比对项一致。", ""];
|
|
60
|
+
out.push("| scope | 证据类别 | 差异 | 首个分歧 |", "|---|---|---|---|");
|
|
61
|
+
for (const f of diffs)
|
|
62
|
+
out.push(`| ${escape(f.scope)} | ${KIND_LABEL[f.kind]} | ${escape(f.detail)} | ${escape(f.diffs?.[0] ?? "-")} |`);
|
|
63
|
+
out.push("");
|
|
64
|
+
return out;
|
|
65
|
+
}
|
|
66
|
+
/** The full markdown report. */
|
|
67
|
+
export function renderReplayMarkdown(report) {
|
|
68
|
+
const lines = [];
|
|
69
|
+
lines.push("# P5 双跑对拍报告(replay:e2e)", "");
|
|
70
|
+
lines.push(`- 生成时间:${report.generatedAt}`);
|
|
71
|
+
lines.push(`- fixtures:\`${report.fixtures}\`(导出时间 ${report.fixturesGeneratedAt})`);
|
|
72
|
+
lines.push(`- 探针输入:\`${report.probeInput}\`(真实 agent-loop + 离线 mock LLM,禁真网)`);
|
|
73
|
+
lines.push("");
|
|
74
|
+
lines.push("## 结论", "");
|
|
75
|
+
lines.push(...verdictTable(report));
|
|
76
|
+
if (report.errors.length > 0) {
|
|
77
|
+
lines.push("### harness 错误", "");
|
|
78
|
+
for (const e of report.errors)
|
|
79
|
+
lines.push(`- ${e}`);
|
|
80
|
+
lines.push("");
|
|
81
|
+
}
|
|
82
|
+
lines.push("## 会话总览", "");
|
|
83
|
+
lines.push(...sessionTable(report));
|
|
84
|
+
lines.push(...findingsTable("已逐字节一致项", report.findings, (f) => f.verdict === "match" && f.kind === "byte-exact"));
|
|
85
|
+
lines.push(...findingsTable("冻结黄金一致项", report.findings, (f) => f.verdict === "match" && f.kind === "golden"));
|
|
86
|
+
lines.push(...findingsTable("独立重推导 / 自洽校验一致项", report.findings, (f) => f.verdict === "match" && (f.kind === "spec-derived" || f.kind === "self-check")));
|
|
87
|
+
lines.push(...diffSection(report));
|
|
88
|
+
lines.push("## P6 前还差什么", "");
|
|
89
|
+
for (const gap of report.gaps)
|
|
90
|
+
lines.push(`- ${gap}`);
|
|
91
|
+
lines.push("");
|
|
92
|
+
lines.push("## 全部比对项", "");
|
|
93
|
+
lines.push("| scope | 证据类别 | 结论 | 说明 |", "|---|---|---|---|");
|
|
94
|
+
for (const f of report.findings)
|
|
95
|
+
lines.push(`| ${escape(f.scope)} | ${KIND_LABEL[f.kind]} | ${VERDICT_LABEL[f.verdict]} | ${escape(f.detail)} |`);
|
|
96
|
+
lines.push("");
|
|
97
|
+
return lines.join("\n");
|
|
98
|
+
}
|
|
99
|
+
/** One-line console summary (the CLI prints this plus the session rows). */
|
|
100
|
+
export function renderReplayConsole(report) {
|
|
101
|
+
const s = report.summary;
|
|
102
|
+
return `[replay:e2e] sessions=${s.sessions} findings=${s.findings} matched=${s.matched} (byte-exact=${s.byteExact}, golden=${s.golden}) diffed=${s.diffed} skipped=${s.skipped} errors=${s.errors} verdict=${s.verdict}`;
|
|
103
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One replayed session: the four comparison families of the P5 double run.
|
|
3
|
+
*
|
|
4
|
+
* 1. **session log JSONL** — replay the golden log through the TS engine's own
|
|
5
|
+
* persistent log and re-serialize it: the bytes must come back identical
|
|
6
|
+
* (codec + replay fidelity), and the file itself must stay untouched;
|
|
7
|
+
* 2. **messages projection** — `GET /api/sessions/{id}/messages` over the REAL
|
|
8
|
+
* host vs the frozen capture (`messages-expected.json`) + the engine's own
|
|
9
|
+
* `derive_messages` vs its stored derivation;
|
|
10
|
+
* 3. **SSE sequence** — the derived transcript vs the stored golden, and the
|
|
11
|
+
* same frames pushed through the real `GET /api/events` endpoint;
|
|
12
|
+
* 4. **real turn + compaction** — a probe turn appended by the engine (byte
|
|
13
|
+
* level), then compaction over HTTP with an independently re-derived plan.
|
|
14
|
+
*/
|
|
15
|
+
import type { SessionEvent } from "@celestea/core";
|
|
16
|
+
import { type Finding } from "./compare.js";
|
|
17
|
+
import { type FixtureSession } from "./fixtures.js";
|
|
18
|
+
import type { ReplayHost } from "./host.js";
|
|
19
|
+
export interface SessionE2E {
|
|
20
|
+
id: string;
|
|
21
|
+
slug: string;
|
|
22
|
+
roles: string[];
|
|
23
|
+
events: number;
|
|
24
|
+
turns: number;
|
|
25
|
+
findings: Finding[];
|
|
26
|
+
verdict: "match" | "diff";
|
|
27
|
+
}
|
|
28
|
+
export interface ReplaySessionOptions {
|
|
29
|
+
host: ReplayHost;
|
|
30
|
+
fixturesDir: string;
|
|
31
|
+
entry: FixtureSession;
|
|
32
|
+
/** Input of the probe turn appended to the session copy. */
|
|
33
|
+
probeInput?: string;
|
|
34
|
+
}
|
|
35
|
+
/** Replay one fixture session end to end and collect its findings. */
|
|
36
|
+
export declare function replaySession(opts: ReplaySessionOptions): Promise<SessionE2E>;
|
|
37
|
+
/** Family 1: the golden log replayed and re-serialized, byte for byte. */
|
|
38
|
+
export declare function logFindings(id: string, goldenText: string): Finding[];
|
|
39
|
+
/** Family 2: the Studio projection (golden) + the engine projection (self-check). */
|
|
40
|
+
export declare function messagesFindings(host: ReplayHost, id: string, messagesFile: string, derivedFile: string, events: readonly SessionEvent[]): Promise<Finding[]>;
|
|
41
|
+
/** Family 3: derived transcript vs the stored golden, then the SSE transport. */
|
|
42
|
+
export declare function sseChecks(host: ReplayHost, id: string, sseFile: string | null, events: readonly SessionEvent[]): Promise<Finding[]>;
|
|
43
|
+
/** Where a planted session copy lives (report / debugging aid). */
|
|
44
|
+
export declare function plantedDir(host: ReplayHost, id: string): string;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One replayed session: the four comparison families of the P5 double run.
|
|
3
|
+
*
|
|
4
|
+
* 1. **session log JSONL** — replay the golden log through the TS engine's own
|
|
5
|
+
* persistent log and re-serialize it: the bytes must come back identical
|
|
6
|
+
* (codec + replay fidelity), and the file itself must stay untouched;
|
|
7
|
+
* 2. **messages projection** — `GET /api/sessions/{id}/messages` over the REAL
|
|
8
|
+
* host vs the frozen capture (`messages-expected.json`) + the engine's own
|
|
9
|
+
* `derive_messages` vs its stored derivation;
|
|
10
|
+
* 3. **SSE sequence** — the derived transcript vs the stored golden, and the
|
|
11
|
+
* same frames pushed through the real `GET /api/events` endpoint;
|
|
12
|
+
* 4. **real turn + compaction** — a probe turn appended by the engine (byte
|
|
13
|
+
* level), then compaction over HTTP with an independently re-derived plan.
|
|
14
|
+
*/
|
|
15
|
+
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
16
|
+
import { tmpdir } from "node:os";
|
|
17
|
+
import { join } from "node:path";
|
|
18
|
+
import { deriveMessages, deriveSseTranscript, parseSessionJsonl, PersistentSessionLog } from "@celestea/session";
|
|
19
|
+
import { serializeEventLog } from "@celestea/runtime";
|
|
20
|
+
import { compareBytes, compareJson, note, tally } from "./compare.js";
|
|
21
|
+
import { fixtureFiles, readJson, readJsonl, plantFixture, splitSessionId } from "./fixtures.js";
|
|
22
|
+
import { SESSION_LOG_ID, SESSION_LOG_NAME } from "../runtime/engine-session.js";
|
|
23
|
+
import { activate, captureSseWire, compactFindings, runTurn, sseFindings, turnFindings } from "./probes.js";
|
|
24
|
+
/** Replay one fixture session end to end and collect its findings. */
|
|
25
|
+
export async function replaySession(opts) {
|
|
26
|
+
const { host, entry } = opts;
|
|
27
|
+
const files = fixtureFiles(opts.fixturesDir, entry.slug);
|
|
28
|
+
const planted = plantFixture(host.root, files, entry.id);
|
|
29
|
+
const events = parseSessionJsonl(planted.logBytes).events;
|
|
30
|
+
const findings = [...logFindings(entry.id, planted.logBytes)];
|
|
31
|
+
findings.push(...(await messagesFindings(host, entry.id, files.messages, files.derivedMessages, events)));
|
|
32
|
+
findings.push(...(await sseChecks(host, entry.id, files.sse, events)));
|
|
33
|
+
const input = opts.probeInput ?? "P5 重放探针";
|
|
34
|
+
const status = await activate(host, entry.id);
|
|
35
|
+
const turn = await runTurn(host, input);
|
|
36
|
+
const after = readFileSync(join(planted.dir, SESSION_LOG_NAME), "utf8");
|
|
37
|
+
findings.push(...turnFindings(entry.id, planted.logBytes, after, input, turn.frames));
|
|
38
|
+
if (status !== 200)
|
|
39
|
+
findings.push(note(`${entry.id} :: activate`, "golden", `activate returned ${status}`, "diff"));
|
|
40
|
+
findings.push(...(await compactFindings(host, entry.id, planted.dir)));
|
|
41
|
+
const counts = tally(findings);
|
|
42
|
+
return { id: entry.id, slug: entry.slug, roles: entry.roles, events: events.length, turns: entry.turns, findings, verdict: counts.diffed === 0 ? "match" : "diff" };
|
|
43
|
+
}
|
|
44
|
+
/** Family 1: the golden log replayed and re-serialized, byte for byte. */
|
|
45
|
+
export function logFindings(id, goldenText) {
|
|
46
|
+
const dir = mkdtempSync(join(tmpdir(), "replay-log-"));
|
|
47
|
+
try {
|
|
48
|
+
const path = join(dir, SESSION_LOG_NAME);
|
|
49
|
+
writeFileSync(path, goldenText, "utf8");
|
|
50
|
+
const log = PersistentSessionLog.open(dir, SESSION_LOG_ID);
|
|
51
|
+
const findings = [
|
|
52
|
+
compareBytes(`${id} :: session-log-jsonl`, goldenText, serializeEventLog(log.events()), "replay -> re-serialize"),
|
|
53
|
+
compareBytes(`${id} :: session-log-file`, goldenText, readFileSync(path, "utf8"), "replay kept the file untouched"),
|
|
54
|
+
];
|
|
55
|
+
findings.push(log.tornTail === null
|
|
56
|
+
? note(`${id} :: session-log-replay`, "byte-exact", `${log.events().length} event(s), no torn tail, next turn id turn-${log.peekTurnNumber()}`, "match")
|
|
57
|
+
: note(`${id} :: session-log-replay`, "golden", `torn tail at line ${log.tornTail.line}`, "diff"));
|
|
58
|
+
log.close();
|
|
59
|
+
return findings;
|
|
60
|
+
}
|
|
61
|
+
finally {
|
|
62
|
+
rmSync(dir, { recursive: true, force: true });
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/** Family 2: the Studio projection (golden) + the engine projection (self-check). */
|
|
66
|
+
export async function messagesFindings(host, id, messagesFile, derivedFile, events) {
|
|
67
|
+
const res = await host.app.request(`/api/sessions/${encodeURIComponent(id)}/messages`);
|
|
68
|
+
const body = (await res.json());
|
|
69
|
+
const golden = readJson(messagesFile).messages;
|
|
70
|
+
return [
|
|
71
|
+
compareJson(`${id} :: messages-projection`, "golden", golden, body.messages ?? [], `GET /api/sessions/{id}/messages (${res.status})`),
|
|
72
|
+
compareJson(`${id} :: engine-derive-messages`, "self-check", readJson(derivedFile).messages, deriveMessages(events), "engine model-visible history"),
|
|
73
|
+
];
|
|
74
|
+
}
|
|
75
|
+
/** Family 3: derived transcript vs the stored golden, then the SSE transport. */
|
|
76
|
+
export async function sseChecks(host, id, sseFile, events) {
|
|
77
|
+
const derived = deriveSseTranscript(events);
|
|
78
|
+
const golden = sseFile === null ? null : readJsonl(sseFile);
|
|
79
|
+
const expected = derived.map((f) => ({
|
|
80
|
+
event: f.event,
|
|
81
|
+
turn: f.data.turn,
|
|
82
|
+
payload: f.data.payload,
|
|
83
|
+
}));
|
|
84
|
+
const capture = await captureSseWire(host, expected, id);
|
|
85
|
+
return sseFindings(id, derived, golden, capture);
|
|
86
|
+
}
|
|
87
|
+
/** Where a planted session copy lives (report / debugging aid). */
|
|
88
|
+
export function plantedDir(host, id) {
|
|
89
|
+
const { workspace, session } = splitSessionId(id);
|
|
90
|
+
return join(host.root, workspace, session);
|
|
91
|
+
}
|
package/dist/routes.d.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Route table derived from the frozen contract.
|
|
3
|
+
*
|
|
4
|
+
* 64 endpoints (68 method+path combos minus the 4 static routes).
|
|
5
|
+
* The contract path params use `{id}`; Hono uses `:id`, so paths are translated here
|
|
6
|
+
* once and the translation is asserted in tests.
|
|
7
|
+
*
|
|
8
|
+
* W516 added `GET|POST|DELETE /api/sessions/{id}/grants` and
|
|
9
|
+
* `GET /api/sessions/{id}/grants/confirm-token` (39 -> 43); W725 added
|
|
10
|
+
* `GET /api/sessions/{id}/context` (43 -> 44). W767 added Studio's own login
|
|
11
|
+
* cookie gate: `GET /login`, `POST /auth/login`, `GET /auth/check` (44 -> 47;
|
|
12
|
+
* the first two are deliberately NOT under `/api/`). W785 added
|
|
13
|
+
* `GET /api/usage/ledger` (49 -> 50). W791 added
|
|
14
|
+
* `POST /api/sessions/{id}/mode` (50 -> 51; the P1 session working mode). W9
|
|
15
|
+
* added the six permission endpoints (51 -> 57). W860 added
|
|
16
|
+
* `GET|PUT /api/sessions/{id}/tools` and `GET /api/plugins` (57 -> 60). W870
|
|
17
|
+
* added `PUT /api/sessions/{id}/model` (60 -> 61; the statusline picker's
|
|
18
|
+
* session-scoped model switch). G5 added `GET /api/fs/list` (61 -> 62; the
|
|
19
|
+
* Win-style file manager's directory+file listing). G2 added `POST /api/exec`
|
|
20
|
+
* (62 -> 63; immediate shell execution for the UI's /run and !, no model). All of
|
|
21
|
+
* them have NO counterpart in the legacy backend:
|
|
22
|
+
* `contracts/route-table.snapshot.json` keeps the frozen extraction intact and
|
|
23
|
+
* lists the TypeScript-only additions separately.
|
|
24
|
+
*/
|
|
25
|
+
import { type EndpointContract } from "@celestea/core";
|
|
26
|
+
export interface RegisteredRoute {
|
|
27
|
+
id: string;
|
|
28
|
+
method: "GET" | "POST" | "DELETE" | "PUT";
|
|
29
|
+
/** Contract path, e.g. /api/sessions/{id}/messages */
|
|
30
|
+
contractPath: string;
|
|
31
|
+
/** Hono path, e.g. /api/sessions/:id/messages */
|
|
32
|
+
honoPath: string;
|
|
33
|
+
endpoint: EndpointContract;
|
|
34
|
+
}
|
|
35
|
+
export declare function toHonoPath(contractPath: string): string;
|
|
36
|
+
/** Substitute placeholder values so a route can be exercised in tests. */
|
|
37
|
+
export declare function concretePath(contractPath: string, sample?: string): string;
|
|
38
|
+
export declare function studioRoutes(): RegisteredRoute[];
|
|
39
|
+
export declare const API_ENDPOINT_COUNT = 64;
|
|
40
|
+
export declare const STATIC_ROUTE_COUNT = 4;
|
|
41
|
+
/** Id-keyed view of the contract routes: a handler asks for its id, never a path. */
|
|
42
|
+
export interface RouteTable {
|
|
43
|
+
routes: RegisteredRoute[];
|
|
44
|
+
/** Throws when the id is not in the frozen contract (typo guard). */
|
|
45
|
+
get(id: string): RegisteredRoute;
|
|
46
|
+
}
|
|
47
|
+
export declare function routeTable(): RouteTable;
|
package/dist/routes.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Route table derived from the frozen contract.
|
|
3
|
+
*
|
|
4
|
+
* 64 endpoints (68 method+path combos minus the 4 static routes).
|
|
5
|
+
* The contract path params use `{id}`; Hono uses `:id`, so paths are translated here
|
|
6
|
+
* once and the translation is asserted in tests.
|
|
7
|
+
*
|
|
8
|
+
* W516 added `GET|POST|DELETE /api/sessions/{id}/grants` and
|
|
9
|
+
* `GET /api/sessions/{id}/grants/confirm-token` (39 -> 43); W725 added
|
|
10
|
+
* `GET /api/sessions/{id}/context` (43 -> 44). W767 added Studio's own login
|
|
11
|
+
* cookie gate: `GET /login`, `POST /auth/login`, `GET /auth/check` (44 -> 47;
|
|
12
|
+
* the first two are deliberately NOT under `/api/`). W785 added
|
|
13
|
+
* `GET /api/usage/ledger` (49 -> 50). W791 added
|
|
14
|
+
* `POST /api/sessions/{id}/mode` (50 -> 51; the P1 session working mode). W9
|
|
15
|
+
* added the six permission endpoints (51 -> 57). W860 added
|
|
16
|
+
* `GET|PUT /api/sessions/{id}/tools` and `GET /api/plugins` (57 -> 60). W870
|
|
17
|
+
* added `PUT /api/sessions/{id}/model` (60 -> 61; the statusline picker's
|
|
18
|
+
* session-scoped model switch). G5 added `GET /api/fs/list` (61 -> 62; the
|
|
19
|
+
* Win-style file manager's directory+file listing). G2 added `POST /api/exec`
|
|
20
|
+
* (62 -> 63; immediate shell execution for the UI's /run and !, no model). All of
|
|
21
|
+
* them have NO counterpart in the legacy backend:
|
|
22
|
+
* `contracts/route-table.snapshot.json` keeps the frozen extraction intact and
|
|
23
|
+
* lists the TypeScript-only additions separately.
|
|
24
|
+
*/
|
|
25
|
+
import { loadEndpoints } from "@celestea/core";
|
|
26
|
+
export function toHonoPath(contractPath) {
|
|
27
|
+
return contractPath.replace(/\{([A-Za-z_][A-Za-z0-9_]*)\}/g, ":$1").replace(/\{\*([A-Za-z_][A-Za-z0-9_]*)\}/g, "*");
|
|
28
|
+
}
|
|
29
|
+
/** Substitute placeholder values so a route can be exercised in tests. */
|
|
30
|
+
export function concretePath(contractPath, sample = "sample-ws%2Fsample-session") {
|
|
31
|
+
return contractPath
|
|
32
|
+
.replace(/\{\*([A-Za-z_][A-Za-z0-9_]*)\}/g, "sample/asset.js")
|
|
33
|
+
.replace(/\{([A-Za-z_][A-Za-z0-9_]*)\}/g, (_m, name) => (name === "id" ? sample : `sample-${name}`));
|
|
34
|
+
}
|
|
35
|
+
export function studioRoutes() {
|
|
36
|
+
const c = loadEndpoints();
|
|
37
|
+
return c.endpoints.map((e) => ({
|
|
38
|
+
id: e.id,
|
|
39
|
+
method: e.method,
|
|
40
|
+
contractPath: e.path,
|
|
41
|
+
honoPath: toHonoPath(e.path),
|
|
42
|
+
endpoint: e,
|
|
43
|
+
}));
|
|
44
|
+
}
|
|
45
|
+
export const API_ENDPOINT_COUNT = 64;
|
|
46
|
+
export const STATIC_ROUTE_COUNT = 4;
|
|
47
|
+
export function routeTable() {
|
|
48
|
+
const routes = studioRoutes();
|
|
49
|
+
const byId = new Map(routes.map((r) => [r.id, r]));
|
|
50
|
+
return {
|
|
51
|
+
routes,
|
|
52
|
+
get(id) {
|
|
53
|
+
const route = byId.get(id);
|
|
54
|
+
if (route === undefined)
|
|
55
|
+
throw new Error(`unknown contract endpoint id '${id}'`);
|
|
56
|
+
return route;
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* W804 (multimodal P0 section 3.1): resolve content-addressed image references
|
|
3
|
+
* into a REQUEST-scoped `images` table (attachment_id -> data URL) before the
|
|
4
|
+
* wire layer runs.
|
|
5
|
+
*
|
|
6
|
+
* The bytes are read here, on the request path, and are NEVER persisted: the
|
|
7
|
+
* session log keeps only the reference. A reference whose object is missing is a
|
|
8
|
+
* HARD error — a silently dropped image is worse than a failed request.
|
|
9
|
+
*/
|
|
10
|
+
import type { Llm } from "@celestea/core";
|
|
11
|
+
import type { AttachmentStore } from "@celestea/tools";
|
|
12
|
+
/** Wrap one `Llm` so image references become a data-URL table on the request. */
|
|
13
|
+
export declare function withAttachments(inner: Llm, store: AttachmentStore | null): Llm;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* W804 (multimodal P0 section 3.1): resolve content-addressed image references
|
|
3
|
+
* into a REQUEST-scoped `images` table (attachment_id -> data URL) before the
|
|
4
|
+
* wire layer runs.
|
|
5
|
+
*
|
|
6
|
+
* The bytes are read here, on the request path, and are NEVER persisted: the
|
|
7
|
+
* session log keeps only the reference. A reference whose object is missing is a
|
|
8
|
+
* HARD error — a silently dropped image is worse than a failed request.
|
|
9
|
+
*/
|
|
10
|
+
/** Wrap one `Llm` so image references become a data-URL table on the request. */
|
|
11
|
+
export function withAttachments(inner, store) {
|
|
12
|
+
if (store === null)
|
|
13
|
+
return inner;
|
|
14
|
+
return {
|
|
15
|
+
async generate(req) {
|
|
16
|
+
const table = {};
|
|
17
|
+
for (const message of req.messages) {
|
|
18
|
+
for (const part of message.content) {
|
|
19
|
+
if (part.type !== "image")
|
|
20
|
+
continue;
|
|
21
|
+
const id = part.content.attachment_id;
|
|
22
|
+
if (table[id] !== undefined)
|
|
23
|
+
continue;
|
|
24
|
+
const url = await store.readDataUrl(id);
|
|
25
|
+
if (url === null) {
|
|
26
|
+
throw new Error(`W804: attachment ${id} is not present in ${store.dir}; the image cannot be delivered`);
|
|
27
|
+
}
|
|
28
|
+
table[id] = url;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (Object.keys(table).length === 0)
|
|
32
|
+
return inner.generate(req);
|
|
33
|
+
const withImages = { ...req, images: table };
|
|
34
|
+
return inner.generate(withImages);
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Boot recovery of the ACTIVE session (E §1.3 P0 ③, §1.6 apps/studio row).
|
|
3
|
+
*
|
|
4
|
+
* `workspaces.json.active_session` is the one session the host knows was in use
|
|
5
|
+
* when the process stopped, so that is the session it repairs at startup — before
|
|
6
|
+
* any instance exists for it, which matters because a composed instance replays
|
|
7
|
+
* the log and derives its turn counter from it (E §1.3 P0 ④).
|
|
8
|
+
*
|
|
9
|
+
* The heavy lifting (decision table, append-only repair) lives in the engine
|
|
10
|
+
* packages; this module only answers "WHICH directory" and reports the outcome on
|
|
11
|
+
* the P0 observation channel (stderr; the durable record is the sidecar's
|
|
12
|
+
* `repaired[]`). Recovery never throws and never fails the boot: a damaged
|
|
13
|
+
* sidecar or an unknown session id must not stop the studio from starting.
|
|
14
|
+
*
|
|
15
|
+
* W787 (§5.2③): the repair is an AUTOMATIC behaviour, so it now also reaches the
|
|
16
|
+
* audit channel (`recovery-audit.jsonl`) when one is wired — the P0 note that
|
|
17
|
+
* only two channels existed was a deliberate deferral, and P1 is when it closes.
|
|
18
|
+
*/
|
|
19
|
+
import { type BootRecoveryReport } from "@celestea/runtime";
|
|
20
|
+
import type { SessionsStore } from "../store/sessions.js";
|
|
21
|
+
import type { WorkspacesStore } from "../store/workspaces.js";
|
|
22
|
+
import type { RecoveryAuditWriter } from "./recovery-audit.js";
|
|
23
|
+
export interface BootRecoveryInput {
|
|
24
|
+
workspaces: Pick<WorkspacesStore, "activeSession">;
|
|
25
|
+
sessions: Pick<SessionsStore, "resolve">;
|
|
26
|
+
now?: () => number;
|
|
27
|
+
warn?: (message: string) => void;
|
|
28
|
+
/** W787: the durable audit channel (§5.2③); absent = stderr only. */
|
|
29
|
+
audit?: RecoveryAuditWriter | null;
|
|
30
|
+
}
|
|
31
|
+
/** Recover the previously active session; null when there is nothing to look at. */
|
|
32
|
+
export declare function recoverActiveSessionOnBoot(input: BootRecoveryInput): BootRecoveryReport | null;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Boot recovery of the ACTIVE session (E §1.3 P0 ③, §1.6 apps/studio row).
|
|
3
|
+
*
|
|
4
|
+
* `workspaces.json.active_session` is the one session the host knows was in use
|
|
5
|
+
* when the process stopped, so that is the session it repairs at startup — before
|
|
6
|
+
* any instance exists for it, which matters because a composed instance replays
|
|
7
|
+
* the log and derives its turn counter from it (E §1.3 P0 ④).
|
|
8
|
+
*
|
|
9
|
+
* The heavy lifting (decision table, append-only repair) lives in the engine
|
|
10
|
+
* packages; this module only answers "WHICH directory" and reports the outcome on
|
|
11
|
+
* the P0 observation channel (stderr; the durable record is the sidecar's
|
|
12
|
+
* `repaired[]`). Recovery never throws and never fails the boot: a damaged
|
|
13
|
+
* sidecar or an unknown session id must not stop the studio from starting.
|
|
14
|
+
*
|
|
15
|
+
* W787 (§5.2③): the repair is an AUTOMATIC behaviour, so it now also reaches the
|
|
16
|
+
* audit channel (`recovery-audit.jsonl`) when one is wired — the P0 note that
|
|
17
|
+
* only two channels existed was a deliberate deferral, and P1 is when it closes.
|
|
18
|
+
*/
|
|
19
|
+
import { recoverSessionOnBoot } from "@celestea/runtime";
|
|
20
|
+
/** Recover the previously active session; null when there is nothing to look at. */
|
|
21
|
+
export function recoverActiveSessionOnBoot(input) {
|
|
22
|
+
const warn = input.warn ?? defaultWarn;
|
|
23
|
+
const active = input.workspaces.activeSession();
|
|
24
|
+
if (active === null || active.trim() === "")
|
|
25
|
+
return null;
|
|
26
|
+
const resolved = input.sessions.resolve(active);
|
|
27
|
+
if (!resolved.ok) {
|
|
28
|
+
warn(`[celestea-recovery] active session '${active}' does not resolve — nothing to recover`);
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
// The sidecar's self-description must be the SAME string a live instance
|
|
32
|
+
// writes. W878: that is the host's TRUSTED `resolve()` id (the same one
|
|
33
|
+
// grants.json/permission.json/tools.json use), NOT a path inference — the
|
|
34
|
+
// inference breaks the moment a session directory sinks below the workspace
|
|
35
|
+
// root (`<ws>/.celestea/sessions/<dir>`), which would silently skip recovery.
|
|
36
|
+
const report = recoverSessionOnBoot({
|
|
37
|
+
dir: resolved.value.dir,
|
|
38
|
+
session: resolved.value.id,
|
|
39
|
+
...(input.now === undefined ? {} : { now: input.now }),
|
|
40
|
+
warn,
|
|
41
|
+
});
|
|
42
|
+
announce(report, warn);
|
|
43
|
+
auditRepair(input.audit, report);
|
|
44
|
+
return report;
|
|
45
|
+
}
|
|
46
|
+
/** One audit line for the ONE automatic action of this capability (§5.2③). */
|
|
47
|
+
function auditRepair(audit, report) {
|
|
48
|
+
if (audit == null || !report.appended)
|
|
49
|
+
return;
|
|
50
|
+
audit.write({
|
|
51
|
+
event: "session_repaired",
|
|
52
|
+
session: report.session,
|
|
53
|
+
turn_id: report.turn_id,
|
|
54
|
+
count: report.dangling_before.length,
|
|
55
|
+
detail: `appended turn_end{outcome:interrupted} after a crash (dangling before=${report.dangling_before.length})`,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
/** The P0 visibility rule: a repair, a skip that hides a dangling turn, a warning. */
|
|
59
|
+
function announce(report, warn) {
|
|
60
|
+
if (report.appended) {
|
|
61
|
+
warn(`[celestea-recovery] ${report.session}: closed interrupted ${String(report.turn_id)} (crash recovery, ${report.dangling_before.length} dangling turn(s) before)`);
|
|
62
|
+
}
|
|
63
|
+
else if (report.action === "skipped_no_checkpoint" && report.dangling_before.length > 0) {
|
|
64
|
+
warn(`[celestea-recovery] ${report.session}: ${report.dangling_before.length} dangling turn(s) left untouched — no checkpoint (fail-safe)`);
|
|
65
|
+
}
|
|
66
|
+
for (const message of report.warnings)
|
|
67
|
+
warn(`[celestea-recovery] ${message}`);
|
|
68
|
+
}
|
|
69
|
+
function defaultWarn(message) {
|
|
70
|
+
process.stderr.write(`${message}\n`);
|
|
71
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Engine request -> host view (W725): the projection behind
|
|
3
|
+
* `GET /api/sessions/{id}/context`.
|
|
4
|
+
*
|
|
5
|
+
* The projection is deliberately thin and lossless in the direction that
|
|
6
|
+
* matters: everything the model sees comes from the agent loop's own
|
|
7
|
+
* `contextSnapshot()` (system prompt, post-trim derived history, tool schemas).
|
|
8
|
+
* This module never trims, filters or re-derives a single message — it only
|
|
9
|
+
* flattens the engine's `Message[]` into one display row per message, because
|
|
10
|
+
* the wire contract carries `content` as a string.
|
|
11
|
+
*
|
|
12
|
+
* Flattening rules (frozen; the frontend renders them verbatim):
|
|
13
|
+
* - a message's content blocks are joined with "\n";
|
|
14
|
+
* - a `tool_call` block contributes `[tool_call] <name> <compact JSON args>`;
|
|
15
|
+
* - an assistant tool-call row carries the FIRST call's `tool_name` +
|
|
16
|
+
* `tool_call_id` (`tool_name` = what it called, id = the call it made);
|
|
17
|
+
* - a `tool` result row carries its own `tool_call_id`, resolved back to the
|
|
18
|
+
* call's `tool_name` while the messages are walked in order.
|
|
19
|
+
*/
|
|
20
|
+
import type { Content, Message, ToolSpec } from "@celestea/core";
|
|
21
|
+
import type { Runtime } from "@celestea/runtime";
|
|
22
|
+
import type { ContextMessageView, ContextToolView, SessionContextView } from "../runtime-adapter.js";
|
|
23
|
+
/** What to report when the mounted loop has no snapshot capability. */
|
|
24
|
+
export interface ContextFallback {
|
|
25
|
+
model: string;
|
|
26
|
+
system: string;
|
|
27
|
+
tools: readonly ToolSpec[];
|
|
28
|
+
}
|
|
29
|
+
/** One message's content blocks as a single display string. */
|
|
30
|
+
export declare function renderContent(content: readonly Content[]): string;
|
|
31
|
+
/** W804: e.g. `[image] logo.png image/png 512x512 (attachment <sha>)`. */
|
|
32
|
+
export declare function imageText(ref: {
|
|
33
|
+
attachment_id: string;
|
|
34
|
+
media_type: string;
|
|
35
|
+
width: number;
|
|
36
|
+
height: number;
|
|
37
|
+
name?: string;
|
|
38
|
+
}): string;
|
|
39
|
+
/** `Message[]` -> one view row each, resolving tool names in message order. */
|
|
40
|
+
export declare function messageViews(messages: readonly Message[]): ContextMessageView[];
|
|
41
|
+
/**
|
|
42
|
+
* The session's context as the engine assembled it. `runtime.contextSnapshot()`
|
|
43
|
+
* is the ONLY source; the fallback (a loop without the capability) reports the
|
|
44
|
+
* profile's system prompt and the composed tool schemas with no history.
|
|
45
|
+
*/
|
|
46
|
+
export declare function contextViewOf(runtime: Runtime, fallback: ContextFallback): SessionContextView;
|
|
47
|
+
/**
|
|
48
|
+
* W769: the read-only view helpers the adapter's HTTP surface needs, so the
|
|
49
|
+
* adapter stays a router instead of an assembly site (it is at its size budget).
|
|
50
|
+
*
|
|
51
|
+
* `sessionContextOf` is `/api/sessions/{id}/context` for one already-composed
|
|
52
|
+
* runtime: the read-only view of a runtime the caller already resolved.
|
|
53
|
+
*/
|
|
54
|
+
export declare function sessionContextOf(runtime: Runtime, profile: {
|
|
55
|
+
model: string;
|
|
56
|
+
system_prompt: string;
|
|
57
|
+
}): SessionContextView;
|
|
58
|
+
/** Tool schemas pass through verbatim (name / description / parameters). */
|
|
59
|
+
export declare function toolViews(specs: readonly ToolSpec[]): ContextToolView[];
|