@celestea/core 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.
Files changed (80) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +95 -0
  3. package/contracts/data-files/checkpoint.schema.json +111 -0
  4. package/contracts/data-files/cli-main-jsonl-precompact.schema.json +27 -0
  5. package/contracts/data-files/cli-main-jsonl.schema.json +22 -0
  6. package/contracts/data-files/fallbacks.schema.json +71 -0
  7. package/contracts/data-files/index.json +124 -0
  8. package/contracts/data-files/pricing.schema.json +65 -0
  9. package/contracts/data-files/prompts.schema.json +130 -0
  10. package/contracts/data-files/providers.schema.json +177 -0
  11. package/contracts/data-files/registry-tsv.schema.json +74 -0
  12. package/contracts/data-files/session.schema.json +51 -0
  13. package/contracts/data-files/usage-ledger.schema.json +112 -0
  14. package/contracts/data-files/workspaces.schema.json +63 -0
  15. package/contracts/endpoints.json +4390 -0
  16. package/contracts/probe-evidence.json +219 -0
  17. package/contracts/route-table.snapshot.json +377 -0
  18. package/contracts/scope-hash-vectors.json +273 -0
  19. package/contracts/session-event.schema.json +441 -0
  20. package/contracts/sse-events.json +202 -0
  21. package/contracts/tools.json +730 -0
  22. package/dist/agent.d.ts +65 -0
  23. package/dist/agent.js +36 -0
  24. package/dist/celestea-home.d.ts +63 -0
  25. package/dist/celestea-home.js +96 -0
  26. package/dist/celestea-sources.d.ts +53 -0
  27. package/dist/celestea-sources.js +61 -0
  28. package/dist/context.d.ts +33 -0
  29. package/dist/context.js +55 -0
  30. package/dist/contracts/index.d.ts +234 -0
  31. package/dist/contracts/index.js +159 -0
  32. package/dist/errors.d.ts +16 -0
  33. package/dist/errors.js +22 -0
  34. package/dist/event-bus.d.ts +60 -0
  35. package/dist/event-bus.js +100 -0
  36. package/dist/index.d.ts +66 -0
  37. package/dist/index.js +66 -0
  38. package/dist/injection.d.ts +61 -0
  39. package/dist/injection.js +27 -0
  40. package/dist/json.d.ts +34 -0
  41. package/dist/json.js +127 -0
  42. package/dist/llm.d.ts +34 -0
  43. package/dist/llm.js +41 -0
  44. package/dist/memory.d.ts +72 -0
  45. package/dist/memory.js +123 -0
  46. package/dist/message.d.ts +189 -0
  47. package/dist/message.js +252 -0
  48. package/dist/plugin.d.ts +38 -0
  49. package/dist/plugin.js +49 -0
  50. package/dist/projection.d.ts +67 -0
  51. package/dist/projection.js +168 -0
  52. package/dist/question.d.ts +154 -0
  53. package/dist/question.js +82 -0
  54. package/dist/redact.d.ts +40 -0
  55. package/dist/redact.js +185 -0
  56. package/dist/repo.d.ts +14 -0
  57. package/dist/repo.js +87 -0
  58. package/dist/sandbox.d.ts +182 -0
  59. package/dist/sandbox.js +78 -0
  60. package/dist/session-event.d.ts +57 -0
  61. package/dist/session-event.js +425 -0
  62. package/dist/session-log.d.ts +71 -0
  63. package/dist/session-log.js +66 -0
  64. package/dist/skill-catalog.d.ts +29 -0
  65. package/dist/skill-catalog.js +52 -0
  66. package/dist/skills.d.ts +116 -0
  67. package/dist/skills.js +273 -0
  68. package/dist/sse-bus.d.ts +40 -0
  69. package/dist/sse-bus.js +105 -0
  70. package/dist/stream.d.ts +115 -0
  71. package/dist/stream.js +52 -0
  72. package/dist/tool-surface.d.ts +45 -0
  73. package/dist/tool-surface.js +98 -0
  74. package/dist/tool.d.ts +77 -0
  75. package/dist/tool.js +15 -0
  76. package/dist/turn-id.d.ts +37 -0
  77. package/dist/turn-id.js +76 -0
  78. package/dist/types.d.ts +396 -0
  79. package/dist/types.js +58 -0
  80. package/package.json +27 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Mcd0LUO
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,95 @@
1
+ # @celestea/core
2
+
3
+ The semantic kernel of the TypeScript rewrite: the frozen contract types, the
4
+ model-visible message model, the serde-exact `SessionEvent` codec, and the
5
+ plugin seams every other package is built on.
6
+
7
+ Ports (1:1, field names are contract):
8
+
9
+ | TS module | Legacy source |
10
+ |---|---|
11
+ | `message.ts` | `crates/core/src/message.rs` (Role / Content / ToolCall / Message / Usage) |
12
+ | `stream.ts` | `crates/core/src/message.rs` + `llm.rs` (ModelRequest / StreamEvent / LlmError) |
13
+ | `session-event.ts` | `crates/core/src/session_log.rs` (SessionEvent serde, TurnOutcome) |
14
+ | `session-log.ts` | `crates/core/src/session_log.rs` (SessionLog trait) |
15
+ | `plugin.ts` | `crates/core/src/plugin.rs` (+ `registry.rs` NamedRegistry) |
16
+ | `context.ts` | `crates/core/src/context.rs` |
17
+ | `event-bus.ts` | `crates/core/src/event_bus.rs` |
18
+ | `llm.ts` | `crates/core/src/llm.rs` |
19
+ | `tool.ts` | `crates/core/src/tool.rs` |
20
+ | `agent.ts` | `crates/core/src/agent.rs` |
21
+ | `types.ts` | P0 frozen contracts (SSE envelope, endpoints, providers, workers) |
22
+
23
+ ## Dependency rule
24
+
25
+ **`core` imports nothing from the other packages.** It declares seams only; the
26
+ concrete implementations live in sibling packages and are plugged in at compose
27
+ time (`examples` below). Consumers import from the package root
28
+ (`@celestea/core`) — never a deep path.
29
+
30
+ ## Public API (via `index.ts` only)
31
+
32
+ - **Data model** — `Role`, `Content`, `ToolCall`, `Message` (+ `Message.user`,
33
+ `Message.assistantText`, `Message.assistantToolCall`, `Message.toolResult`),
34
+ helpers (`messageText`, `messageToolCalls`, `toolCallIds`, `hasToolCalls`),
35
+ `Usage` (`usageAdd`, `usageSum`, `usageIsEmpty`, `cacheHitRatio`),
36
+ `ToolSpec` / `ToolDecision` (in `types.ts`).
37
+ - **SessionEvent** — the union in `types.ts`; `validateSessionEvent`,
38
+ `parseSessionEvent`, `serializeSessionEvent` (serde-exact: tag first,
39
+ declaration order, `parent_id` omitted when absent, `value`/`error`/`outcome`
40
+ always written), `effectiveOutcome`, `outcomePhase`, `outcomeError`.
41
+ - **Seams** — `Plugin`, `Context`, `EventBus`, `SessionLog`, `Llm`, `Tool`,
42
+ `ToolGuard`, `ToolRegistry`, `AgentLoop`, plus the well-known service tokens
43
+ `SESSION_LOG_SERVICE`, `EVENT_BUS_SERVICE`, `LLM_SERVICE`,
44
+ `LLM_REGISTRY_SERVICE`, `TOOL_REGISTRY_SERVICE`, `TOOL_GUARD_SERVICE`,
45
+ `AGENT_LOOP_SERVICE`.
46
+ - **JSON** — `serdeJsonString` (serde_json-compatible text: sorted object keys,
47
+ `None` → `null`), `firstJsonDiff`, `stableStringify`.
48
+ - **SSE bus** — `createSseBus` / `SseBus` (SDK-side broadcast; renamed from the
49
+ P0 `EventBus` so `EventBus` now means the engine seam).
50
+
51
+ ## Extension points (everything is a plugin)
52
+
53
+ ```ts
54
+ import { Context, mountPlugins, SESSION_LOG_SERVICE, definePlugin } from "@celestea/core";
55
+ import { inMemorySessionLogPlugin } from "@celestea/session";
56
+
57
+ const ctx = mountPlugins(Context.root(), [
58
+ definePlugin("my-events", (c) => c.provide(EVENT_BUS_SERVICE, createEventBus())),
59
+ inMemorySessionLogPlugin(),
60
+ ]);
61
+
62
+ const log = ctx.require(SESSION_LOG_SERVICE); // SessionLog seam, not a concrete class
63
+ const scoped = ctx.scoped(); // per-agent scope, falls back to the parent
64
+ ```
65
+
66
+ Registration is append-only with **last-wins** semantics (`Context.provide`,
67
+ `NamedRegistry`, `LlmRegistry`), so a test or a preset can shadow a service
68
+ without touching the composition root. `EventBus` has three independent modes:
69
+ `on`/`emit` (broadcast), `bail`/`runBail` (first non-`undefined` answer
70
+ short-circuits) and `waterfall`/`runWaterfall` (transform chain).
71
+
72
+ ## File layout
73
+
74
+ ```
75
+ src/types.ts 303 P0 frozen contracts (SessionEvent union, SSE, endpoints)
76
+ src/session-event.ts 216 serde-exact SessionEvent codec + TurnOutcome helpers
77
+ src/redact.ts 204 fixture/report secret redaction (P0)
78
+ src/message.ts 188 Role / Content / ToolCall / Message / Usage
79
+ src/contracts/ 163 frozen contract-file loaders
80
+ src/json.ts 132 JSON helpers + serde_json-compatible text
81
+ src/sse-bus.ts 109 SDK-side SSE broadcast bus
82
+ src/event-bus.ts 102 engine EventBus seam
83
+ src/tool.ts 70 Tool / ToolGuard / ToolRegistry seams
84
+ src/context.ts 66 Context service container
85
+ src/plugin.ts 64 Plugin seam + NamedRegistry
86
+ src/stream.ts 56 ModelRequest / StreamEvent / LlmError
87
+ src/agent.ts 50 AgentLoop seam + AgentConfig
88
+ src/llm.ts 43 Llm seam + LlmRegistry
89
+ src/session-log.ts 34 SessionLog seam
90
+ src/index.ts 46 the only public entry point
91
+ ```
92
+
93
+ Every file stays well under 400 lines and every function under 80 lines
94
+ (`createRedactor` in the pre-existing P0 `redact.ts` is the one 94-line
95
+ exception — flagged for a later split, out of the P1 scope).
@@ -0,0 +1,111 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "title": "checkpoint.json (per-session crash / shutdown sidecar)",
4
+ "source": {
5
+ "design": "/src/celestea_studio-ts/docs/iteration-e-capabilities.md §1.2.2/§1.2.3 (E-P0③)",
6
+ "code": "packages/session/src/checkpoint.ts, packages/session/src/checkpoint-recovery.ts, packages/runtime/src/recovery.ts"
7
+ },
8
+ "path": {
9
+ "default": "<session dir>/checkpoint.json (<session dir> = <workspace>/<session>)",
10
+ "mode": "0600",
11
+ "write": "<path>.tmp-<pid> -> rename (atomic), pretty-printed JSON, trailing newline",
12
+ "writeTimings": "turn_start (one row appended), turn_end (one row appended), any lane change (P1: push / drain / restore), clean shutdown"
13
+ },
14
+ "purpose": {
15
+ "kept": "the two facts the log cannot express: WHO was running (pid/boot_id/clean_shutdown) and WHICH turn was open (open_turn)",
16
+ "notKept": "everything derivable from cli-main.jsonl (turn counter, history, last outcome) — the log stays the single source of truth (K4)",
17
+ "neverWritten": "the sidecar never rewrites a log row; the only repair is ONE appended turn_end{outcome:\"interrupted\"}, recorded in repaired[]"
18
+ },
19
+ "failureSemantics": {
20
+ "missing": "no checkpoint: recovery does not touch the log at all (a dangling turn_start may belong to another live process)",
21
+ "unparsable": "the WHOLE file is ignored + observed on stderr; behaviour is exactly 'missing' (never read as a clean exit)",
22
+ "unknownVersion": "ignored + observed (same as unparsable)",
23
+ "sessionMismatch": "ignored + observed: the sidecar was copied/renamed and cannot describe this directory",
24
+ "writeFailure": "reported on stderr and swallowed: checkpointing is observation and can never fail a turn"
25
+ },
26
+ "open_turn": {
27
+ "meaning": "the turn THIS process started and has not closed yet (null = no turn is open)",
28
+ "repairCondition": "open_turn.id has a turn_start row in cli-main.jsonl AND no turn_end row for that id (double signature)",
29
+ "idempotency": "the second boot sees the appended turn_end and only clears open_turn: the log is byte-identical (assertion A2)"
30
+ },
31
+ "clean_shutdown": {
32
+ "true": "the process left through Runtime.shutdown(); the next boot repairs NOTHING, whatever the log looks like",
33
+ "false": "written by every activity (turn_start, turn_end); a crash therefore leaves the last value false"
34
+ },
35
+ "schema": {
36
+ "type": "object",
37
+ "required": ["version", "session", "pid", "boot_id", "updated_at", "clean_shutdown", "open_turn", "last_outcome", "degraded", "lanes", "delivered_ids", "repaired"],
38
+ "properties": {
39
+ "version": { "const": 1, "description": "sidecar schema version (NOT a data-file format version; any other value voids the file)" },
40
+ "session": { "type": "string", "description": "\"<workspace>/<session>\" self-description; a mismatch voids the file" },
41
+ "pid": { "type": "integer", "description": "process that wrote the file" },
42
+ "boot_id": { "type": "string", "pattern": "^b-[0-9a-f]{8}$", "description": "one id per process start (process-wide constant)" },
43
+ "updated_at": { "type": "integer", "description": "unix seconds of the last write" },
44
+ "clean_shutdown": { "type": "boolean" },
45
+ "open_turn": {
46
+ "type": ["object", "null"],
47
+ "required": ["id", "started_at"],
48
+ "properties": {
49
+ "id": { "type": "string", "pattern": "^turn-\\d+$" },
50
+ "started_at": { "type": "integer", "description": "unix seconds" }
51
+ },
52
+ "additionalProperties": false
53
+ },
54
+ "last_outcome": { "enum": ["completed", "cancelled", "error", "step_limit", "interrupted", null], "description": "redundant with the log (operator convenience); the log wins on any conflict" },
55
+ "degraded": {
56
+ "type": "object",
57
+ "required": ["log_write_errors"],
58
+ "properties": { "log_write_errors": { "type": "integer", "minimum": 0, "description": "PersistentSessionLog.writeErrorCount() sampled at every write; non-zero means disk and memory diverged" } },
59
+ "additionalProperties": false
60
+ },
61
+ "lanes": {
62
+ "type": "object",
63
+ "required": ["next_turn", "next_step"],
64
+ "description": "E §1.3 P1 ① (W787): the two injection lanes exactly as the inbox holds them — a message that was ACCEPTED and not yet injected. Written on every lane change (push / drain), so a crash loses at most the newest change (G1-4: a user's message used to vanish silently). Restoring is deliberately silent: a message queued by a dead process was already announced by it.",
65
+ "properties": {
66
+ "next_turn": { "type": "array", "items": { "$ref": "#/schema/$defs/lane_message" } },
67
+ "next_step": { "type": "array", "items": { "$ref": "#/schema/$defs/lane_message" } }
68
+ },
69
+ "additionalProperties": false
70
+ },
71
+ "delivered_ids": {
72
+ "type": "array",
73
+ "items": { "type": "string" },
74
+ "description": "E §1.3 P1 ① (W787): the bounded (512, oldest-first) ledger of injection ids this session already ACCEPTED. It is the cross-process half of the idempotency key (capability 2's `receipt:<wid>:<attempt>`), so a receipt replayed after a restart is still dropped as a duplicate (B3)."
75
+ },
76
+ "repaired": {
77
+ "type": "array",
78
+ "description": "every log row THIS engine appended for recovery; empty on a healthy session (assertion A2: length stays 1 after a second boot)",
79
+ "items": {
80
+ "type": "object",
81
+ "required": ["at", "action", "turn_id"],
82
+ "properties": {
83
+ "at": { "type": "integer" },
84
+ "action": { "const": "synthesize_turn_end" },
85
+ "turn_id": { "type": "string", "pattern": "^turn-\\d+$" }
86
+ },
87
+ "additionalProperties": false
88
+ }
89
+ }
90
+ },
91
+ "additionalProperties": false
92
+ },
93
+ "$defs": {
94
+ "lane_message": {
95
+ "type": "object",
96
+ "required": ["text", "from", "at", "lane", "kind", "id", "source"],
97
+ "properties": {
98
+ "text": { "type": "string" },
99
+ "from": { "type": "string", "description": "attribution label (empty = the user)" },
100
+ "at": { "type": "integer" },
101
+ "lane": { "enum": ["next-turn", "next-step"] },
102
+ "kind": { "type": "string", "description": "InjectionKind (user / receipt / relay / stimulus)" },
103
+ "id": { "type": "string", "description": "idempotency key (empty = never deduplicated)" },
104
+ "source": { "type": "object", "description": "the delivery envelope (kind / form / sender), verbatim" },
105
+ "duplicate": { "type": "boolean" }
106
+ },
107
+ "additionalProperties": false
108
+ }
109
+ },
110
+ "generatedAt": "2026-09-11T00:00:00Z"
111
+ }
@@ -0,0 +1,27 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "title": "<session-dir>/cli-main.jsonl.precompact (single-copy backup)",
4
+ "source": {
5
+ "legacy": "frozen from the retired implementation (original source file deleted 2026-09-15): src/compact.rs (rewrite_atomic)",
6
+ "doc": "docs/data-files.md §4.4"
7
+ },
8
+ "notes": [
9
+ "Single copy, OVERWRITTEN on every /compact. The only manual rollback channel.",
10
+ "Byte format is identical to cli-main.jsonl (JSONL of SessionEvent).",
11
+ "Not written unless a compaction actually happens (needs >=9 complete turns)."
12
+ ],
13
+ "format": {
14
+ "type": "jsonl",
15
+ "eventSchema": {
16
+ "$ref": "./session-event.schema.json"
17
+ }
18
+ },
19
+ "compaction": {
20
+ "threshold": 8,
21
+ "keepTurns": 4,
22
+ "result": "turn-1 = summary round (user_message '【上下文压缩】<summary<=20000 chars>' + assistant_message '上下文已压缩,以上为历史摘要。' + turn_end completed); turn-2..turn-(K+1) = the last K complete turns verbatim with only turn_start/turn_end ids renumbered",
23
+ "dropped": "the unclosed tail turn and any events before the first turn_start",
24
+ "writeGuarantee": "backup -> tmp + sync_all -> rename (fsync'd, unlike the other data files); failure deletes the tmp"
25
+ },
26
+ "generatedAt": "2026-09-10T01:20:00Z"
27
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "title": "<session-dir>/cli-main.jsonl (engine v1 session log)",
4
+ "source": {
5
+ "legacy": "frozen from the retired implementation (original source file deleted 2026-09-15): /src/celestea_harness/crates/core/src/session_log.rs:44-85",
6
+ "doc": "docs/data-files.md §4"
7
+ },
8
+ "path": {
9
+ "default": "<workspace path>/<session-dir>/cli-main.jsonl",
10
+ "envOverride": "CELESTEA_SESSION_DIR points at the session DIR"
11
+ },
12
+ "eventSchema": {
13
+ "$ref": "./session-event.schema.json"
14
+ },
15
+ "parse": {
16
+ "blankLines": "skipped",
17
+ "tornTail": "STOP at the first unparsable line; the rest of the file is ignored (never treated as content)"
18
+ },
19
+ "writer": "engine PersistentSessionLog (Studio never appends); /compact rewrites it atomically after backing up to cli-main.jsonl.precompact",
20
+ "attachments": "W804: a user_message row MAY carry an `attachments` array of content-addressed ImageRefs (see session-event.schema.json $defs/AttachmentRef). The image BYTES are NOT in this file: they live at <session-dir>/attachments/<sha256>.<ext>. No base64 / data: URL is ever written here.",
21
+ "generatedAt": "2026-09-10T01:20:00Z"
22
+ }
@@ -0,0 +1,71 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "title": "fallbacks.json (model fallback chain, sidecar config)",
4
+ "source": {
5
+ "design": "/src/celestea_studio-ts/docs/iteration-e-capabilities.md §4.2.4 (E-P1, W785)",
6
+ "code": "packages/llm/src/fallback-config.ts (parseConfig/loadFallbackConfig), apps/studio/src/runtime/fallback-host.ts"
7
+ },
8
+ "path": {
9
+ "default": "<data dir>/fallbacks.json",
10
+ "alternate": "CELESTEA_LLM_FALLBACKS = the same JSON inline (wins over the file)",
11
+ "switch": "CELESTEA_LLM_FALLBACK=on|1|true|yes; anything else (and absent) = OFF, and the file is not even read",
12
+ "mode": "read-only (never written by the engine; operational input like pricing.json)"
13
+ },
14
+ "purpose": {
15
+ "kept": "the target chain (name/provider/model/base_url/api-key ENV NAME) and the retry/cooldown policy",
16
+ "notKept": "no credential VALUE is ever stored or logged (only `apiKeyEnv` names, §4.5 R4-3); the chain is deliberately NOT part of the frozen 12-key Profile (§4.2.4)",
17
+ "fallback": "when the file is missing/unparsable the engine assembles the chain from the composed profile alone and REPORTS the problem in /api/status.fallback.problems (never silently 'no fallback')"
18
+ },
19
+ "schema": {
20
+ "type": "object",
21
+ "required": ["targets"],
22
+ "additionalProperties": false,
23
+ "properties": {
24
+ "version": { "type": "integer", "const": 1 },
25
+ "enabled": { "type": "boolean" },
26
+ "targets": {
27
+ "type": "array",
28
+ "minItems": 1,
29
+ "items": {
30
+ "type": "object",
31
+ "required": ["name", "model"],
32
+ "additionalProperties": false,
33
+ "properties": {
34
+ "name": { "type": "string", "minLength": 1, "description": "stable target name: ledger `fallback_from`, SSE frame `from`/`to`, audit line" },
35
+ "provider": { "type": "string", "description": "providers.json row id (defaults to `name`)" },
36
+ "model": { "type": "string", "minLength": 1 },
37
+ "baseUrl": { "type": "string", "description": "absent = the composed profile's base_url" },
38
+ "apiKeyEnv": { "type": "string", "description": "ENV VAR NAME of this target's key; absent = the profile's api_key_env" }
39
+ }
40
+ }
41
+ },
42
+ "policy": {
43
+ "type": "object",
44
+ "additionalProperties": false,
45
+ "properties": {
46
+ "maxAttempts": { "type": "integer", "minimum": 1, "default": 2 },
47
+ "cooldownMs": { "type": "integer", "minimum": 0, "default": 60000 },
48
+ "failureThreshold": { "type": "integer", "minimum": 1, "default": 3 },
49
+ "notRetryableStatuses": { "type": "array", "items": { "type": "integer" }, "default": [400, 401, 403, 404, 422] },
50
+ "retryableStatuses": { "type": "array", "items": { "type": "integer" }, "default": [408, 425, 429, 500, 502, 503, 504] },
51
+ "respectRetryAfter": { "type": "boolean", "default": true }
52
+ }
53
+ }
54
+ }
55
+ },
56
+ "example": {
57
+ "version": 1,
58
+ "enabled": true,
59
+ "targets": [
60
+ { "name": "primary", "provider": "celestea", "model": "deepseek-chat" },
61
+ { "name": "backup", "provider": "基元", "model": "gpt-x", "baseUrl": "https://backup.example/v1", "apiKeyEnv": "BACKUP_API_KEY" }
62
+ ],
63
+ "policy": { "maxAttempts": 2, "cooldownMs": 60000, "failureThreshold": 3 }
64
+ },
65
+ "visibility": {
66
+ "sse": "one `status` frame with phase:\"fallback\" (+ from/to/reason/attempt/effective_model); the event NAME set is unchanged (K5)",
67
+ "statusline": "/api/status gains `effective_model` and `fallback{active,chain,effective_model,last_reason,targets,problems}` (optional additive fields)",
68
+ "audit": "<data dir>/fallbacks-audit.jsonl (authoritative, append-only, rotated at 16 MiB) + best-effort POST /api/audit when CELESTEA_AUDIT_URL is set",
69
+ "ledger": "one row per ATTEMPT in usage-ledger.jsonl (`attempt` + `fallback_from`)"
70
+ }
71
+ }
@@ -0,0 +1,124 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "title": "Celestea Studio data-file schemas (format freeze)",
4
+ "generatedAt": "2026-09-10T01:20:00Z",
5
+ "doc": "docs/data-files.md",
6
+ "freezeRule": "Migration period: NO format changes. Do not add a `version` field (it would let the two implementations diverge).",
7
+ "files": [
8
+ {
9
+ "file": "workspaces.json",
10
+ "schema": "workspaces.schema.json",
11
+ "version": "v2 (naming convention; no version field)",
12
+ "mode": "0644"
13
+ },
14
+ {
15
+ "file": "providers.json",
16
+ "schema": "providers.schema.json",
17
+ "version": "none",
18
+ "mode": "0600",
19
+ "secret": true
20
+ },
21
+ {
22
+ "file": "prompts.json",
23
+ "schema": "prompts.schema.json",
24
+ "version": "none",
25
+ "mode": "0644"
26
+ },
27
+ {
28
+ "file": "<CELESTEA_HOME>/workspaces/<ws>/prompts.json",
29
+ "schema": "prompts.schema.json",
30
+ "version": "none",
31
+ "mode": "0644",
32
+ "note": "W880: the workspace-scoped prompt registry. Legacy <ws>/.celestea/prompts.json and <ws>/.celestea-prompts.json stay readable as fallbacks; the canonical file wins on read and is the only write target."
33
+ },
34
+ {
35
+ "file": "session.json",
36
+ "schema": "session.schema.json",
37
+ "version": "none",
38
+ "mode": "0644"
39
+ },
40
+ {
41
+ "file": "cli-main.jsonl",
42
+ "schema": "cli-main-jsonl.schema.json",
43
+ "version": "engine v1 SessionEvent",
44
+ "mode": "0644"
45
+ },
46
+ {
47
+ "file": "cli-main.jsonl.precompact",
48
+ "schema": "cli-main-jsonl-precompact.schema.json",
49
+ "version": "engine v1 SessionEvent",
50
+ "mode": "0644"
51
+ },
52
+ {
53
+ "file": "/tmp/celestea-workers-registry.tsv",
54
+ "schema": "registry-tsv.schema.json",
55
+ "version": "4-column TSV",
56
+ "mode": "0644"
57
+ },
58
+ {
59
+ "file": "<data dir>/worker-registry.tsv",
60
+ "schema": "registry-tsv.schema.json",
61
+ "version": "4-column TSV (same format as /tmp/celestea-workers-registry.tsv)",
62
+ "mode": "0644",
63
+ "note": "W787 (E §2.2.1/§2.3 P0 ①): the STUDIO's own worker table — `<data dir>/worker-registry.tsv`, env `CELESTEA_WORKER_REGISTRY`, empty value = in-memory only (`tsvPath: null`). It is a SEPARATE table from the DSH-side plugin's `workerBase/registry.tsv`; neither side ever writes the other's (R2-1, asserted by B6)."
64
+ },
65
+ {
66
+ "file": "pricing.json",
67
+ "schema": "pricing.schema.json",
68
+ "version": "price snapshot (its own `version` is the price source's, not a data-file format version)",
69
+ "mode": "0644"
70
+ },
71
+ {
72
+ "file": "usage-ledger.jsonl",
73
+ "schema": "usage-ledger.schema.json",
74
+ "version": "ledger v1 (per-row `v`)",
75
+ "mode": "0600"
76
+ },
77
+ {
78
+ "file": "checkpoint.json",
79
+ "schema": "checkpoint.schema.json",
80
+ "version": "sidecar v1 (its own `version: 1` is the SIDECAR schema, not a data-file format version)",
81
+ "mode": "0600"
82
+ },
83
+ {
84
+ "file": "fallbacks.json",
85
+ "schema": "fallbacks.schema.json",
86
+ "version": "sidecar v1 (`version: 1` is the sidecar's own field, not a data-file format version)",
87
+ "mode": "read-only (never written by the engine)"
88
+ }
89
+ ],
90
+ "durability": {
91
+ "workspaces.json": "tmp+rename, no fsync",
92
+ "providers.json": "tmp+rename+0600, fsync (errors ignored)",
93
+ "prompts.json": "tmp+rename, no fsync",
94
+ "cli-main.jsonl (engine)": "engine-owned",
95
+ "cli-main.jsonl (compact rewrite)": "backup -> tmp+sync_all -> rename",
96
+ "session.json": "plain write, NOT atomic",
97
+ "registry.tsv": "tmp+rename (the write MERGES with the rows other session registries already wrote: one table carries every studio session, §2.2.1)",
98
+ "pricing.json": "read-only snapshot (never written by the engine); missing/unparsable -> empty table (every model unpriced)",
99
+ "usage-ledger.jsonl": "append-only, one writeSync per record on an O_APPEND fd (no fsync); rotated at 16 MiB to `<path>.1` (replaced), read() reads the current file only (W785 §3.3 P1 ④)",
100
+ "checkpoint.json": "tmp-<pid> + rename, mode 0600, no fsync (observation only: a lost sidecar reads as 'no checkpoint', which is fail-safe)",
101
+ "fallbacks.json": "read-only sidecar config (never written by the engine); missing/unparsable -> chain assembled from the composed profile + the problem reported in /api/status.fallback.problems"
102
+ },
103
+ "roundTripRequirement": "Every file needs a read -> write -> re-read round-trip test; both implementations must agree byte-for-byte on the written form.",
104
+ "attachments": {
105
+ "location": "<session-dir>/attachments/<sha256>.<ext>",
106
+ "kind": "binary image objects (PNG/JPEG/WebP/GIF), content-addressed; NEVER JSON",
107
+ "introduced": "W804 (multimodal P0 section 5): read_image and POST /api/turn inline uploads; the session log stores only ImageRef references (no base64)",
108
+ "lifecycle": "moves/archives/deletes with the session directory (rename); P0 does no GC",
109
+ "fixtures": "excluded: attachment bytes never enter fixtures/ (scripts/export-golden.ts hard rule)",
110
+ "schema": "none (binary; the JSONL reference shape is frozen by session-event.schema.json $defs/AttachmentRef)"
111
+ },
112
+ "ledger": {
113
+ "implemented": "W728 P0 (iteration E §3): step-granular append-only ledger + pricing snapshot + unpriced marking; W785 P1: GET /api/usage/ledger aggregate view (session|turn|model|day + since/until) + /api/status.cost + 16 MiB rotation",
114
+ "notImplemented": "scripts/sync-pricing.ts (the price snapshot is operator-supplied), SSE status.cost_delta, three-way reconciliation + budget stop-loss (P1/P2)"
115
+ },
116
+ "recovery": {
117
+ "implemented": "E-P0③ (§1.3 P0): checkpoint.json sidecar + idempotent boot synthesis of `turn_end: interrupted` + turnNo restored from the log; E-P1①②③ (§1.3 P1, W787): lane + delivered_ids persistence (the sidecar's `lanes`/`delivered_ids`), `GET /api/status.recovery` (pure addition, still no endpoint) and the degraded-log audit line (`<data dir>/recovery-audit.jsonl`)",
118
+ "notImplemented": "step-level resume and the tool side-effect table (§1.3 P2)"
119
+ },
120
+ "workerRegistry": {
121
+ "implemented": "E §2.3 P0 (W787): `<data dir>/worker-registry.tsv` (`CELESTEA_WORKER_REGISTRY`, `null` = in-memory) + `host=`/`attempt=`/`lease=` tokens + boot observation (`stale[]`/`orphans[]` on GET /api/worker/status, `<data dir>/recovery-audit.jsonl`) with NO re-dispatch; E §2.3 P1: attempt-ized report names (`results/<wid>-<short>-a<attempt>.md`) + `receipt=<wid>:<attempt>` idempotency (registry row and host inbox)",
122
+ "notImplemented": "E §2.3 P2 (auto-adopt / re-dispatch behind `CELESTEA_WORKER_RECOVER=1`, lease heartbeat, `autoDelete` interaction) and the optional read-only view of the DSH-side plugin's table"
123
+ }
124
+ }
@@ -0,0 +1,65 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "title": "pricing.json (model price snapshot for the usage ledger)",
4
+ "source": {
5
+ "design": "/src/celestea_studio-ts/docs/iteration-e-capabilities.md §3.2.2 (W728 P0)",
6
+ "code": "packages/runtime/src/pricing.ts",
7
+ "priceOfRecord": "newapi owns the price of record (LTS biz/newapi.md I1–I3); this snapshot is an engine-side ESTIMATE"
8
+ },
9
+ "path": {
10
+ "default": "<data dir>/pricing.json (<data dir> = dirname(workspacesFile))",
11
+ "envOverride": "CELESTEA_PRICING_FILE"
12
+ },
13
+ "notes": [
14
+ "NO version field of the data-file kind: `version` here is the PRICE SNAPSHOT version (a date/label from the price source), and it is copied into every ledger row that was priced with it — an old row is never re-priced.",
15
+ "A model absent from `models` is NOT priced as 0: the ledger writes priced_by=\"unpriced\" and cost.total=null (never a silent zero).",
16
+ "A missing or unparsable file is an EMPTY table: every model is unpriced; the reason is reported on stderr and never thrown.",
17
+ "The engine only computes tokens/1e6 × unit price. Group multipliers, expressions and cache-discount algebra stay on the platform side (no second billing implementation)."
18
+ ],
19
+ "schema": {
20
+ "type": "object",
21
+ "required": ["version", "currency", "models"],
22
+ "properties": {
23
+ "version": { "type": "string", "description": "price snapshot version/label, e.g. \"2026-09-11\"" },
24
+ "effective_from": { "type": ["integer", "null"], "description": "unix seconds the snapshot is effective from" },
25
+ "currency": { "type": "string", "description": "display currency of every unit price (no conversion is ever done)" },
26
+ "unit": { "const": "per_mtok", "description": "the only unit the engine understands (price per million tokens)" },
27
+ "models": {
28
+ "type": "object",
29
+ "description": "model id -> unit prices",
30
+ "additionalProperties": {
31
+ "type": "object",
32
+ "required": ["in", "out"],
33
+ "properties": {
34
+ "in": { "type": "number", "minimum": 0, "description": "price of one UNCACHED prompt token; the provider's prompt_tokens already contains the cache-hit region, which is priced by cache_read only" },
35
+ "out": { "type": "number", "minimum": 0 },
36
+ "cache_read": { "type": "number", "minimum": 0, "default": 0, "description": "price of one cache-HIT (cache-read) token; charged once, never on top of the `in` price" }
37
+ },
38
+ "additionalProperties": true
39
+ }
40
+ },
41
+ "source": {
42
+ "type": "object",
43
+ "description": "where the snapshot came from (P1 sync script): kind/ref/synced_at",
44
+ "additionalProperties": true
45
+ }
46
+ },
47
+ "additionalProperties": true
48
+ },
49
+ "example": {
50
+ "version": "2026-09-11",
51
+ "effective_from": 1759968000,
52
+ "currency": "CNY",
53
+ "unit": "per_mtok",
54
+ "models": {
55
+ "deepseek-chat": { "in": 1.0, "out": 2.0, "cache_read": 0.1 },
56
+ "deepseek-reasoner": { "in": 2.0, "out": 8.0, "cache_read": 0.2 }
57
+ },
58
+ "source": {
59
+ "kind": "manual",
60
+ "ref": "/src/CelesteaTeamAPI/newapi-ops/PRICING-ARCHITECTURE.md",
61
+ "synced_at": 1760000000
62
+ }
63
+ },
64
+ "generatedAt": "2026-09-11T00:00:00Z"
65
+ }