@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.
Files changed (240) hide show
  1. package/LICENSE +21 -0
  2. package/dist/app.d.ts +111 -0
  3. package/dist/app.js +219 -0
  4. package/dist/auth/api-token.d.ts +76 -0
  5. package/dist/auth/api-token.js +172 -0
  6. package/dist/auth/htpasswd.d.ts +22 -0
  7. package/dist/auth/htpasswd.js +61 -0
  8. package/dist/auth/index.d.ts +14 -0
  9. package/dist/auth/index.js +14 -0
  10. package/dist/auth/page.d.ts +19 -0
  11. package/dist/auth/page.js +73 -0
  12. package/dist/auth/rate-limit.d.ts +27 -0
  13. package/dist/auth/rate-limit.js +47 -0
  14. package/dist/auth/token.d.ts +49 -0
  15. package/dist/auth/token.js +112 -0
  16. package/dist/config.d.ts +78 -0
  17. package/dist/config.js +72 -0
  18. package/dist/deployment.d.ts +92 -0
  19. package/dist/deployment.js +154 -0
  20. package/dist/fake-runtime-adapter.d.ts +37 -0
  21. package/dist/fake-runtime-adapter.js +276 -0
  22. package/dist/handlers/auth.d.ts +24 -0
  23. package/dist/handlers/auth.js +155 -0
  24. package/dist/handlers/common.d.ts +76 -0
  25. package/dist/handlers/common.js +127 -0
  26. package/dist/handlers/config-shape.d.ts +76 -0
  27. package/dist/handlers/config-shape.js +227 -0
  28. package/dist/handlers/config.d.ts +26 -0
  29. package/dist/handlers/config.js +161 -0
  30. package/dist/handlers/context-shape.d.ts +33 -0
  31. package/dist/handlers/context-shape.js +59 -0
  32. package/dist/handlers/dialog.d.ts +22 -0
  33. package/dist/handlers/dialog.js +292 -0
  34. package/dist/handlers/exec.d.ts +22 -0
  35. package/dist/handlers/exec.js +131 -0
  36. package/dist/handlers/fs-read.d.ts +63 -0
  37. package/dist/handlers/fs-read.js +159 -0
  38. package/dist/handlers/fs.d.ts +57 -0
  39. package/dist/handlers/fs.js +153 -0
  40. package/dist/handlers/grants-shape.d.ts +45 -0
  41. package/dist/handlers/grants-shape.js +130 -0
  42. package/dist/handlers/grants.d.ts +21 -0
  43. package/dist/handlers/grants.js +267 -0
  44. package/dist/handlers/health.d.ts +37 -0
  45. package/dist/handlers/health.js +139 -0
  46. package/dist/handlers/index.d.ts +40 -0
  47. package/dist/handlers/index.js +85 -0
  48. package/dist/handlers/permissions.d.ts +9 -0
  49. package/dist/handlers/permissions.js +183 -0
  50. package/dist/handlers/plugins.d.ts +18 -0
  51. package/dist/handlers/plugins.js +23 -0
  52. package/dist/handlers/prompts.d.ts +12 -0
  53. package/dist/handlers/prompts.js +120 -0
  54. package/dist/handlers/providers.d.ts +12 -0
  55. package/dist/handlers/providers.js +178 -0
  56. package/dist/handlers/questions.d.ts +23 -0
  57. package/dist/handlers/questions.js +115 -0
  58. package/dist/handlers/session-model.d.ts +38 -0
  59. package/dist/handlers/session-model.js +105 -0
  60. package/dist/handlers/session-move.d.ts +22 -0
  61. package/dist/handlers/session-move.js +188 -0
  62. package/dist/handlers/session-tools.d.ts +19 -0
  63. package/dist/handlers/session-tools.js +75 -0
  64. package/dist/handlers/sessions.d.ts +32 -0
  65. package/dist/handlers/sessions.js +242 -0
  66. package/dist/handlers/usage.d.ts +26 -0
  67. package/dist/handlers/usage.js +77 -0
  68. package/dist/handlers/worker.d.ts +12 -0
  69. package/dist/handlers/worker.js +82 -0
  70. package/dist/handlers/workspaces.d.ts +12 -0
  71. package/dist/handlers/workspaces.js +88 -0
  72. package/dist/harness.test-util.d.ts +101 -0
  73. package/dist/harness.test-util.js +176 -0
  74. package/dist/index.d.ts +33 -0
  75. package/dist/index.js +33 -0
  76. package/dist/main.d.ts +13 -0
  77. package/dist/main.js +35 -0
  78. package/dist/plugins.d.ts +105 -0
  79. package/dist/plugins.js +97 -0
  80. package/dist/question-registry.d.ts +124 -0
  81. package/dist/question-registry.js +212 -0
  82. package/dist/question-rows.d.ts +32 -0
  83. package/dist/question-rows.js +43 -0
  84. package/dist/replay/compare.d.ts +47 -0
  85. package/dist/replay/compare.js +89 -0
  86. package/dist/replay/e2e-replay.d.ts +46 -0
  87. package/dist/replay/e2e-replay.js +91 -0
  88. package/dist/replay/expect-compact.d.ts +31 -0
  89. package/dist/replay/expect-compact.js +92 -0
  90. package/dist/replay/fixtures.d.ts +52 -0
  91. package/dist/replay/fixtures.js +53 -0
  92. package/dist/replay/host.d.ts +32 -0
  93. package/dist/replay/host.js +62 -0
  94. package/dist/replay/index.d.ts +16 -0
  95. package/dist/replay/index.js +16 -0
  96. package/dist/replay/probes.d.ts +55 -0
  97. package/dist/replay/probes.js +225 -0
  98. package/dist/replay/report.d.ts +13 -0
  99. package/dist/replay/report.js +103 -0
  100. package/dist/replay/session-e2e.d.ts +44 -0
  101. package/dist/replay/session-e2e.js +91 -0
  102. package/dist/routes.d.ts +47 -0
  103. package/dist/routes.js +59 -0
  104. package/dist/runtime/attachments-llm.d.ts +13 -0
  105. package/dist/runtime/attachments-llm.js +37 -0
  106. package/dist/runtime/boot-recovery.d.ts +32 -0
  107. package/dist/runtime/boot-recovery.js +71 -0
  108. package/dist/runtime/context-snapshot.d.ts +59 -0
  109. package/dist/runtime/context-snapshot.js +113 -0
  110. package/dist/runtime/engine-grants.d.ts +121 -0
  111. package/dist/runtime/engine-grants.js +338 -0
  112. package/dist/runtime/engine-permissions.d.ts +29 -0
  113. package/dist/runtime/engine-permissions.js +92 -0
  114. package/dist/runtime/engine-plugins.d.ts +189 -0
  115. package/dist/runtime/engine-plugins.js +292 -0
  116. package/dist/runtime/engine-profile.d.ts +28 -0
  117. package/dist/runtime/engine-profile.js +88 -0
  118. package/dist/runtime/engine-session.d.ts +9 -0
  119. package/dist/runtime/engine-session.js +9 -0
  120. package/dist/runtime/fallback-contract.d.ts +32 -0
  121. package/dist/runtime/fallback-contract.js +10 -0
  122. package/dist/runtime/fallback-host.d.ts +134 -0
  123. package/dist/runtime/fallback-host.js +299 -0
  124. package/dist/runtime/host-autowake.d.ts +79 -0
  125. package/dist/runtime/host-autowake.js +86 -0
  126. package/dist/runtime/image-downgrade.d.ts +49 -0
  127. package/dist/runtime/image-downgrade.js +90 -0
  128. package/dist/runtime/inbox-message.d.ts +11 -0
  129. package/dist/runtime/inbox-message.js +20 -0
  130. package/dist/runtime/index.d.ts +17 -0
  131. package/dist/runtime/index.js +17 -0
  132. package/dist/runtime/ledger-view.d.ts +40 -0
  133. package/dist/runtime/ledger-view.js +56 -0
  134. package/dist/runtime/llm-assembly.d.ts +66 -0
  135. package/dist/runtime/llm-assembly.js +114 -0
  136. package/dist/runtime/offline-llm.d.ts +71 -0
  137. package/dist/runtime/offline-llm.js +132 -0
  138. package/dist/runtime/provider-target.d.ts +10 -0
  139. package/dist/runtime/provider-target.js +10 -0
  140. package/dist/runtime/question-host.d.ts +33 -0
  141. package/dist/runtime/question-host.js +41 -0
  142. package/dist/runtime/question-view.d.ts +48 -0
  143. package/dist/runtime/question-view.js +76 -0
  144. package/dist/runtime/real-runtime-adapter.d.ts +95 -0
  145. package/dist/runtime/real-runtime-adapter.js +553 -0
  146. package/dist/runtime/recovery-audit.d.ts +81 -0
  147. package/dist/runtime/recovery-audit.js +95 -0
  148. package/dist/runtime/recovery-view.d.ts +36 -0
  149. package/dist/runtime/recovery-view.js +63 -0
  150. package/dist/runtime/session-compose.d.ts +235 -0
  151. package/dist/runtime/session-compose.js +350 -0
  152. package/dist/runtime/session-grants.d.ts +36 -0
  153. package/dist/runtime/session-grants.js +69 -0
  154. package/dist/runtime/session-lifecycle.d.ts +40 -0
  155. package/dist/runtime/session-lifecycle.js +86 -0
  156. package/dist/runtime/session-publisher.d.ts +40 -0
  157. package/dist/runtime/session-publisher.js +48 -0
  158. package/dist/runtime/session-release.d.ts +53 -0
  159. package/dist/runtime/session-release.js +80 -0
  160. package/dist/runtime/test-util.d.ts +78 -0
  161. package/dist/runtime/test-util.js +162 -0
  162. package/dist/runtime/watchdog-view.d.ts +33 -0
  163. package/dist/runtime/watchdog-view.js +45 -0
  164. package/dist/runtime/worker-bridge.d.ts +76 -0
  165. package/dist/runtime/worker-bridge.js +156 -0
  166. package/dist/runtime/worker-live.d.ts +22 -0
  167. package/dist/runtime/worker-live.js +25 -0
  168. package/dist/runtime/worker-recovery.d.ts +35 -0
  169. package/dist/runtime/worker-recovery.js +64 -0
  170. package/dist/runtime/worker-table.d.ts +58 -0
  171. package/dist/runtime/worker-table.js +70 -0
  172. package/dist/runtime-adapter.d.ts +421 -0
  173. package/dist/runtime-adapter.js +76 -0
  174. package/dist/serial-queue.d.ts +16 -0
  175. package/dist/serial-queue.js +22 -0
  176. package/dist/server.d.ts +44 -0
  177. package/dist/server.js +119 -0
  178. package/dist/settings.d.ts +23 -0
  179. package/dist/settings.js +31 -0
  180. package/dist/sse.d.ts +67 -0
  181. package/dist/sse.js +186 -0
  182. package/dist/static.d.ts +20 -0
  183. package/dist/static.js +120 -0
  184. package/dist/store/builtin-sections.d.ts +52 -0
  185. package/dist/store/builtin-sections.js +118 -0
  186. package/dist/store/celestea-home.d.ts +12 -0
  187. package/dist/store/celestea-home.js +11 -0
  188. package/dist/store/fs-json.d.ts +51 -0
  189. package/dist/store/fs-json.js +119 -0
  190. package/dist/store/grants-audit.d.ts +84 -0
  191. package/dist/store/grants-audit.js +109 -0
  192. package/dist/store/grants-service.d.ts +44 -0
  193. package/dist/store/grants-service.js +41 -0
  194. package/dist/store/grants-tokens.d.ts +62 -0
  195. package/dist/store/grants-tokens.js +105 -0
  196. package/dist/store/grants.d.ts +113 -0
  197. package/dist/store/grants.js +238 -0
  198. package/dist/store/index.d.ts +36 -0
  199. package/dist/store/index.js +36 -0
  200. package/dist/store/mode.d.ts +35 -0
  201. package/dist/store/mode.js +41 -0
  202. package/dist/store/permissions.d.ts +61 -0
  203. package/dist/store/permissions.js +139 -0
  204. package/dist/store/prompts-compose.d.ts +56 -0
  205. package/dist/store/prompts-compose.js +62 -0
  206. package/dist/store/prompts-template.d.ts +26 -0
  207. package/dist/store/prompts-template.js +99 -0
  208. package/dist/store/prompts.d.ts +142 -0
  209. package/dist/store/prompts.js +273 -0
  210. package/dist/store/provider-probe.d.ts +81 -0
  211. package/dist/store/provider-probe.js +154 -0
  212. package/dist/store/providers.d.ts +97 -0
  213. package/dist/store/providers.js +219 -0
  214. package/dist/store/result.d.ts +29 -0
  215. package/dist/store/result.js +30 -0
  216. package/dist/store/session-id.d.ts +119 -0
  217. package/dist/store/session-id.js +220 -0
  218. package/dist/store/session-meta.d.ts +41 -0
  219. package/dist/store/session-meta.js +66 -0
  220. package/dist/store/session-ops.d.ts +109 -0
  221. package/dist/store/session-ops.js +264 -0
  222. package/dist/store/session-tools.d.ts +42 -0
  223. package/dist/store/session-tools.js +69 -0
  224. package/dist/store/sessions.d.ts +162 -0
  225. package/dist/store/sessions.js +307 -0
  226. package/dist/store/validate.d.ts +13 -0
  227. package/dist/store/validate.js +38 -0
  228. package/dist/store/workspaces.d.ts +67 -0
  229. package/dist/store/workspaces.js +228 -0
  230. package/dist/user-questions.d.ts +58 -0
  231. package/dist/user-questions.js +157 -0
  232. package/dist/version.d.ts +14 -0
  233. package/dist/version.js +52 -0
  234. package/package.json +38 -0
  235. package/webdist/assets/index-Bngs7gUm.js +98 -0
  236. package/webdist/assets/index-Bu6ci_rN.css +1 -0
  237. package/webdist/assets/katex-CCbS2qAY.js +257 -0
  238. package/webdist/assets/mhchem-CnUN8HwY.js +1 -0
  239. package/webdist/build-meta.json +7 -0
  240. package/webdist/index.html +261 -0
@@ -0,0 +1,421 @@
1
+ /**
2
+ * `RuntimeAdapter` — the ONE seam between the Studio host and the engine.
3
+ *
4
+ * P4 ships the Hono layer plus the data stores; the real runtime
5
+ * (`packages/runtime` compose + agent-loop + llm + tools) lands on a separate
6
+ * workstream. Everything the engine owns is therefore expressed here as an
7
+ * injected interface, and P4 verifies the contract against a fake adapter:
8
+ *
9
+ * POST /api/turn -> startTurn() / inject() (W513: a busy
10
+ * session takes an interjection instead
11
+ * of a 409; the turn is never restarted)
12
+ * GET /api/events -> attach(bus) (the adapter emits,
13
+ * one envelope per session)
14
+ * POST /api/cancel -> cancel(session)
15
+ * POST /api/clear -> clear(session)
16
+ * POST /api/sessions/{id}/activate -> ensureSession(id) (W513: never 409)
17
+ * POST /api/sessions/{id}/compact -> compact(session)
18
+ * GET /api/status -> statusline(session?) + isBusy(session?)
19
+ * GET /api/tools -> tools()
20
+ * GET /api/sessions/{id}/context -> sessionContext(session) (W725)
21
+ * GET+POST /api/config -> profile() / configure(patch)
22
+ * POST /api/worker/{spawn,send} -> workerSpawn() / workerSend()
23
+ * GET /api/worker/status -> workerStatus(wid?)
24
+ * GET /api/sessions (worker rows) -> workerSessions()
25
+ * GET /api/sessions/worker:<sid>/… -> workerMessages(sid)
26
+ *
27
+ * W513 (session independence): busy, turn numbering, status/usage trackers and
28
+ * the session inbox are PER SESSION. `isBusy()` with no argument keeps the
29
+ * legacy "is anything running" reading for the handlers that guard process-wide
30
+ * operations; every session-scoped handler passes the target session id.
31
+ *
32
+ * Replacing the fake with the real runtime is a one-line change in
33
+ * `createStudioApp({ runtime })` — no handler changes, no route changes.
34
+ */
35
+ import type { AskUserQuestionAnswerItem, AskUserQuestionItem, ImageRef, InjectionPlacement, Statusline } from "@celestea/core";
36
+ /**
37
+ * W737: the busy-slot error is part of the ENGINE contract, so it has exactly
38
+ * one definition — `packages/runtime/src/errors.ts`. It is imported (never
39
+ * redefined) here and re-exported, so every studio-side import of
40
+ * `TurnBusyError` resolves to the very class object the real engine throws.
41
+ */
42
+ import { TurnBusyError } from "@celestea/runtime";
43
+ /**
44
+ * W785 (E-P1, capability 3): the aggregate views of the usage ledger. Types only
45
+ * — the value (`queryLedger`/`ledgerCostBlock`) is called by the REAL adapter.
46
+ */
47
+ import type { LedgerCostBlock, LedgerQuery, LedgerQueryResult } from "@celestea/runtime";
48
+ import type { FallbackStatusView } from "./runtime/fallback-contract.js";
49
+ import type { StudioBus } from "./sse.js";
50
+ /** Verbatim engine error text (`{e}` placeholders). */
51
+ export declare class EngineError extends Error {
52
+ readonly kind = "engine";
53
+ constructor(message: string);
54
+ }
55
+ /**
56
+ * Thrown by `startTurn` when the single-concurrency slot is occupied, and by
57
+ * `clear` while the target session's turn is in flight (409).
58
+ *
59
+ * W737: SINGLE SOURCE — `@celestea/runtime`'s `errors.ts` (`StudioError`
60
+ * subclass: `status: 409`, `kind: "turn_busy"`). This file used to declare a
61
+ * second, `extends Error` copy; the handlers branched on that copy, so the real
62
+ * engine's error failed `instanceof` and the busy race surfaced as a 500
63
+ * instead of a 409 / an interjection. Only the fake adapter threw the copy,
64
+ * which is what kept the contract tests green. Do not redeclare it here.
65
+ */
66
+ export { TurnBusyError };
67
+ /** Thrown when the live-session / concurrent-turn cap is reached (503). */
68
+ export declare class CapacityError extends Error {
69
+ readonly kind = "capacity";
70
+ /** Seconds the client should wait before retrying (Retry-After). */
71
+ readonly retryAfterSeconds: number;
72
+ constructor(message: string, retryAfterSeconds?: number);
73
+ }
74
+ export interface EngineProfile {
75
+ model: string;
76
+ base_url: string;
77
+ reasoning_effort: string | null;
78
+ max_steps: number;
79
+ max_parallel_tool_calls: number;
80
+ max_output_tokens: number | null;
81
+ context_window: number;
82
+ api_key_env: string;
83
+ /** Registry-assembled (or overridden) system prompt. */
84
+ system_prompt: string;
85
+ }
86
+ /** `POST /api/config` accepted patch: the host validates, the engine applies. */
87
+ export interface ProfilePatch {
88
+ model?: string;
89
+ reasoning_effort?: string | null;
90
+ base_url?: string;
91
+ /** Goes into the process env only: never persisted, echoed or logged. */
92
+ api_key?: string;
93
+ max_steps?: number;
94
+ max_output_tokens?: number | null;
95
+ context_window?: number;
96
+ system_prompt?: string;
97
+ }
98
+ export interface ToolInfo {
99
+ name: string;
100
+ description: string;
101
+ }
102
+ /** One model-visible message, flattened for the context viewer (W725). */
103
+ export interface ContextMessageView {
104
+ role: string;
105
+ content: string;
106
+ /** Name of the tool this message calls (assistant) or answers (tool). */
107
+ tool_name?: string;
108
+ /** Provider call id, set on a `tool` result (and on the call it answers). */
109
+ tool_call_id?: string;
110
+ }
111
+ /** `registry.schemas()` row -> the two-field view the host exposes (W729). */
112
+ export declare function toolSpecView(spec: {
113
+ name: string;
114
+ description: string;
115
+ }): ToolInfo;
116
+ /** One tool schema the model is offered (W725) — `registry.schemas()` verbatim. */
117
+ export interface ContextToolView {
118
+ name: string;
119
+ description: string;
120
+ parameters: Record<string, unknown>;
121
+ }
122
+ /**
123
+ * W725: one session's model-visible context, as the ENGINE assembles it —
124
+ * system prompt, the messages the next step would send (already trimmed by the
125
+ * loop) and the tool schemas. Read-only: taking a snapshot never drives a turn.
126
+ */
127
+ export interface SessionContextView {
128
+ model: string;
129
+ system: string;
130
+ tools: ContextToolView[];
131
+ messages: ContextMessageView[];
132
+ }
133
+ /**
134
+ * W847: requested delivery lane for a BUSY session (POST /api/turn body
135
+ * `mode`). `steer` (default) = inject into the RUNNING turn at its next step
136
+ * boundary; `queue` = park on the next-turn lane for the NEXT turn start. On an
137
+ * IDLE session the field is ignored (the input IS the new turn).
138
+ */
139
+ export type TurnDeliveryMode = "steer" | "queue";
140
+ export interface TurnRequest {
141
+ input: string;
142
+ /** Active session id, or null when nothing is activated. */
143
+ session: string | null;
144
+ /**
145
+ * W847: optional lane request. Omitted behaves exactly like `steer` (the
146
+ * pre-W847 request byte for byte). Only the host's busy path reads it.
147
+ */
148
+ mode?: TurnDeliveryMode;
149
+ /**
150
+ * W804: content-addressed image references for THIS turn's user message
151
+ * (already stored by the host). Omitted = the pre-W804 request byte for byte.
152
+ */
153
+ attachments?: readonly ImageRef[];
154
+ }
155
+ export interface TurnStart {
156
+ turn: number;
157
+ /** W515 §2: the placement of this turn's own input (`context` = it IS the turn). */
158
+ placement?: InjectionPlacement;
159
+ }
160
+ /**
161
+ * Result of delivering a message into a session (W513 interjection).
162
+ *
163
+ * W515 §2: `placement` is the client-visible landing state —
164
+ * `steering` = will be injected into the RUNNING turn at its next step
165
+ * boundary, `queued` = accepted and waiting for the next turn start,
166
+ * `context` = already appended to the model-visible log.
167
+ */
168
+ export interface InjectOutcome {
169
+ /** Session-local turn number the message was (or will be) injected into. */
170
+ turn: number;
171
+ /** True = delivered into a RUNNING turn; false = queued for the next one. */
172
+ injected: boolean;
173
+ /** Messages still waiting on the target lane after this delivery. */
174
+ pending: number;
175
+ placement: InjectionPlacement;
176
+ /** True when the idempotency key was already accepted (nothing was queued). */
177
+ duplicate: boolean;
178
+ }
179
+ /** `POST /api/sessions/{id}/activate` — "open the view + ensure the runtime". */
180
+ export interface SessionRuntimeInfo {
181
+ /** `created` = this call composed the instance, `reused` = it already existed. */
182
+ runtime: "created" | "reused";
183
+ /** Whether the session has an in-flight turn right now. */
184
+ busy: boolean;
185
+ /** True when the instance was recomposed (profile epoch had moved on). */
186
+ rebuilt: boolean;
187
+ }
188
+ export interface ClearOutcome {
189
+ cleared: boolean;
190
+ }
191
+ export interface CompactOutcome {
192
+ compacted: boolean;
193
+ /** Present only when `compacted === true`. */
194
+ kept_turns?: number;
195
+ note: string;
196
+ /** Canonical session id the compact ran against. */
197
+ session: string;
198
+ rebound: boolean;
199
+ }
200
+ export interface WorkerSpawnRequest {
201
+ wid: string;
202
+ brief: string;
203
+ title?: string;
204
+ model?: string;
205
+ report_to?: string;
206
+ /** Host session whose registry spawns the worker (default: active session). */
207
+ session?: string | null;
208
+ }
209
+ export interface WorkerSpawnOutcome {
210
+ ok: boolean;
211
+ sessionId?: string;
212
+ title?: string;
213
+ wid?: string;
214
+ error?: string;
215
+ /** Tool envelope passthrough (`{ok:false, value:…}`). */
216
+ value?: unknown;
217
+ }
218
+ export interface WorkerSendRequest {
219
+ target: string;
220
+ content: string;
221
+ }
222
+ import type { RecoveryView } from "./runtime/recovery-view.js";
223
+ /** E §1.3 P1 ②: the `/api/status.recovery` block (see `runtime/recovery-view.ts`). */
224
+ export type { RecoveryView };
225
+ export interface WorkerStatusReport {
226
+ ok: boolean;
227
+ total: number;
228
+ by_status: Record<string, number>;
229
+ by_state?: Record<string, number>;
230
+ workers: unknown[];
231
+ wid?: string;
232
+ error?: string;
233
+ /**
234
+ * E §2.3 P0 ③ (W787): RUNNING rows of the PERSISTED table whose owning process
235
+ * is gone, and RUNNING rows whose `host=` session no longer exists. Observation
236
+ * only — the studio never re-dispatches at boot (P2, `CELESTEA_WORKER_RECOVER`).
237
+ */
238
+ stale?: unknown[];
239
+ orphans?: unknown[];
240
+ /**
241
+ * W740: how many live instances are sweeping their worker rows (the count of
242
+ * RUNNING watchdog timers). Absent from an engine that mounts no watchdog.
243
+ */
244
+ watchdogs?: number;
245
+ }
246
+ /** Engine-memory worker session row (`kind: "worker"`, workspace "engine"). */
247
+ export interface WorkerSessionRow {
248
+ id: string;
249
+ workspace: string;
250
+ kind: "worker";
251
+ title: string;
252
+ model: string | null;
253
+ size: number;
254
+ modified: number;
255
+ active: boolean;
256
+ /** W729: the mode the worker inherited (or was spawned with). */
257
+ mode: string;
258
+ /** Worker id (`W513`) — the same `wid` the registry row carries. */
259
+ wid?: string;
260
+ /** Registry status: `RUNNING` / `DONE` / `FAILED`. */
261
+ status?: string;
262
+ /** Driver state: `idle` / `in-turn`. */
263
+ state?: string;
264
+ /** Host session that owns this worker's registry. */
265
+ host_session?: string | null;
266
+ /** E §2.3 P1 ③ (W787): which try this row is (first spawn = 1, re-dispatch +1). */
267
+ attempt?: number;
268
+ /** E §2.3 P1 ③ (W787): idempotency key of the delivered receipt (`wid:attempt`). */
269
+ last_receipt?: string | null;
270
+ /** Whether the OWNING host session has an in-flight turn. */
271
+ busy?: boolean;
272
+ }
273
+ export interface RuntimeAdapter {
274
+ /** Diagnostic name, surfaced by tests and logs (never by the HTTP API). */
275
+ readonly name: string;
276
+ /** Hand the adapter the bus it emits engine frames into. */
277
+ attach(bus: StudioBus): void;
278
+ /**
279
+ * Optional host hook: hand the engine the system prompt the HOST assembled
280
+ * (prompt registry + settings override). The real adapter applies it to the
281
+ * next composed generation; an adapter without a prompt registry ignores it.
282
+ */
283
+ primeSystemPrompt?(prompt: string): void;
284
+ /**
285
+ * Busy probe. No argument = "is ANY session running" (legacy reading, used by
286
+ * the process-wide guards); with a session id = that session's own slot.
287
+ */
288
+ isBusy(session?: string | null): boolean;
289
+ /** Grab the session's slot, emit `status:start`, return; rest goes over SSE. */
290
+ startTurn(req: TurnRequest): Promise<TurnStart>;
291
+ /**
292
+ * Deliver `input` into the session's RUNNING turn: it is appended as a
293
+ * `user_message` at the next step boundary (no new turn, no interruption).
294
+ */
295
+ inject(req: TurnRequest): InjectOutcome;
296
+ /** Ensure the session has a runtime instance (activate; never fails on busy). */
297
+ ensureSession(session: string | null): SessionRuntimeInfo;
298
+ /**
299
+ * W516: this session's security boundary changed (its `grants.json` was
300
+ * written) — drop its instance so the next turn recomposes. The turn in
301
+ * flight keeps the boundary it started with; returns false when the session
302
+ * has no live instance (nothing to invalidate).
303
+ */
304
+ invalidateSession?(session: string | null): boolean;
305
+ /**
306
+ * W794: the session's DIRECTORY is going away (delete / archive) — cut the
307
+ * model response it may be streaming right now and hand back the engine
308
+ * instance it owned.
309
+ *
310
+ * Order is the contract: the in-flight turn is aborted through the SAME
311
+ * cooperative path `POST /api/cancel` uses, the host then waits (bounded) for
312
+ * it to settle, and only then is THAT instance disposed and forgotten (never
313
+ * the process-wide generation, never a neighbour's instance). `true` = an
314
+ * instance was released; `false` = this session had none (nothing to do).
315
+ *
316
+ * Optional: an adapter with no per-session registry has nothing to release.
317
+ */
318
+ releaseSession?(session: string | null): Promise<boolean>;
319
+ /** Session ids with a live runtime instance. */
320
+ liveSessions(): string[];
321
+ /** Session ids with an in-flight turn. */
322
+ busySessions(): string[];
323
+ /** Cooperative cancel of the target session's turn: true = signal sent. */
324
+ cancel(session?: string | null): boolean;
325
+ /** Truncate the active session log + reset the turn counter. */
326
+ clear(session: string | null): Promise<ClearOutcome>;
327
+ compact(session: string): Promise<CompactOutcome>;
328
+ profile(): EngineProfile;
329
+ /** Apply an accepted patch (hot compose); throws EngineError on failure. */
330
+ configure(patch: ProfilePatch): Promise<EngineProfile>;
331
+ statusline(session?: string | null): Statusline;
332
+ /**
333
+ * E §4.2.3 #4 (W785): the model-fallback view of one session — which target
334
+ * chain is armed, which model is ACTUALLY serving and what went wrong.
335
+ * `null`/absent = the capability is off for this process, and the handler then
336
+ * reports `effective_model = model` with `fallback.active = false`.
337
+ */
338
+ fallbackView?(session: string | null): FallbackStatusView | null;
339
+ /**
340
+ * E-P1 (capability 3, W785): the aggregate view behind `GET /api/usage/ledger`
341
+ * — the ONE append-only ledger of the process, filtered and folded by
342
+ * `session`/`turn`/`model`/`day`. Optional: an adapter without a ledger answers
343
+ * `{ok:false, error:"usage ledger unavailable"}` through the handler, and a
344
+ * ledger that is switched OFF reports `{ok:false, error:"usage ledger disabled"}`
345
+ * (both HTTP 200 — the key is "no ledger here", not a client error).
346
+ */
347
+ usageLedger?(q: LedgerQuery): LedgerQueryResult | {
348
+ ok: false;
349
+ error: string;
350
+ };
351
+ /**
352
+ * E-P1 (capability 3, W785): this session's cost block for `/api/status`
353
+ * (`{session_total, turn_total, attempts, currency, priced_by, …}`). `null` =
354
+ * no ledger configured; an absent method means the same, and the handler then
355
+ * omits the `cost` key entirely (the field is a pure addition, §3.2.4).
356
+ */
357
+ costBlock?(session: string | null): LedgerCostBlock | null;
358
+ /**
359
+ * E §1.3 P1 ② (W787): the session's `recovery` block for `GET /api/status`
360
+ * (checkpoint repairs, dangling turns, log degradation, last outcome).
361
+ * Optional: an adapter without checkpointing answers the empty block.
362
+ */
363
+ recoveryView?(session: string | null): RecoveryView;
364
+ tools(): ToolInfo[];
365
+ /**
366
+ * W729 (S2): the tool face of ONE session, used to render the `{{tools}}`
367
+ * variable of that session's own system prompt. Optional: an adapter without
368
+ * per-session generations falls back to [tools]. The real adapter answers from
369
+ * the session's LIVE instance and never composes one (the composer calls this
370
+ * while composing that very session — peeking keeps that non-recursive).
371
+ */
372
+ sessionTools?(session: string | null): ToolInfo[];
373
+ /**
374
+ * W725: the session's OWN model-visible context (`GET /api/sessions/{id}/
375
+ * context`). The instance is composed on demand, exactly like activate does;
376
+ * an unknown session is the handler's 404, never this seam's guess.
377
+ */
378
+ sessionContext(session: string | null): SessionContextView;
379
+ workerSpawn(req: WorkerSpawnRequest): Promise<WorkerSpawnOutcome>;
380
+ workerSend(req: WorkerSendRequest): Promise<Record<string, unknown>>;
381
+ workerStatus(wid?: string): WorkerStatusReport;
382
+ workerSessions(): WorkerSessionRow[];
383
+ /** Transcript of an engine-memory worker session, or null when unknown. */
384
+ workerMessages(sessionId: string): unknown[] | null;
385
+ /**
386
+ * W783: answer one pending user question (`POST /api/questions/{id}/answer`).
387
+ * Optional — an embedded adapter without the user-question feature simply does
388
+ * not implement it, and the handler then reports the question as unknown.
389
+ */
390
+ answerQuestion?(requestId: string, answers: AskUserQuestionAnswerItem[], sessionId?: string): QuestionAnswerOutcome;
391
+ /** W783: every question still answerable (`GET /api/questions`, §7 recovery). */
392
+ pendingQuestions?(sessionId?: string | null): PendingQuestionView[];
393
+ }
394
+ /** W783: why an answer was refused (`ok:false`), never a silent no-op. */
395
+ export type QuestionAnswerRefusal = "unknown" | "settled" | "timed_out" | "mismatch";
396
+ /**
397
+ * W783: the outcome of one answer attempt. The accepted case echoes the asking
398
+ * session, because the pending entry is gone by the time the handler answers the
399
+ * HTTP request (reading it back from the table would always find nothing).
400
+ */
401
+ export type QuestionAnswerOutcome = {
402
+ ok: true;
403
+ session: string | null;
404
+ } | {
405
+ ok: false;
406
+ reason: QuestionAnswerRefusal;
407
+ };
408
+ /**
409
+ * W783: one still-answerable question as the recovery endpoint reports it.
410
+ * `expired`/`remaining_ms` are computed at READ time from `expires_at`, so the
411
+ * client never has to trust its own clock (§6.1).
412
+ */
413
+ export interface PendingQuestionView {
414
+ id: string;
415
+ session: string | null;
416
+ questions: readonly AskUserQuestionItem[];
417
+ expires_at: number;
418
+ timeout_ms: number;
419
+ remaining_ms: number;
420
+ expired: boolean;
421
+ }
@@ -0,0 +1,76 @@
1
+ /**
2
+ * `RuntimeAdapter` — the ONE seam between the Studio host and the engine.
3
+ *
4
+ * P4 ships the Hono layer plus the data stores; the real runtime
5
+ * (`packages/runtime` compose + agent-loop + llm + tools) lands on a separate
6
+ * workstream. Everything the engine owns is therefore expressed here as an
7
+ * injected interface, and P4 verifies the contract against a fake adapter:
8
+ *
9
+ * POST /api/turn -> startTurn() / inject() (W513: a busy
10
+ * session takes an interjection instead
11
+ * of a 409; the turn is never restarted)
12
+ * GET /api/events -> attach(bus) (the adapter emits,
13
+ * one envelope per session)
14
+ * POST /api/cancel -> cancel(session)
15
+ * POST /api/clear -> clear(session)
16
+ * POST /api/sessions/{id}/activate -> ensureSession(id) (W513: never 409)
17
+ * POST /api/sessions/{id}/compact -> compact(session)
18
+ * GET /api/status -> statusline(session?) + isBusy(session?)
19
+ * GET /api/tools -> tools()
20
+ * GET /api/sessions/{id}/context -> sessionContext(session) (W725)
21
+ * GET+POST /api/config -> profile() / configure(patch)
22
+ * POST /api/worker/{spawn,send} -> workerSpawn() / workerSend()
23
+ * GET /api/worker/status -> workerStatus(wid?)
24
+ * GET /api/sessions (worker rows) -> workerSessions()
25
+ * GET /api/sessions/worker:<sid>/… -> workerMessages(sid)
26
+ *
27
+ * W513 (session independence): busy, turn numbering, status/usage trackers and
28
+ * the session inbox are PER SESSION. `isBusy()` with no argument keeps the
29
+ * legacy "is anything running" reading for the handlers that guard process-wide
30
+ * operations; every session-scoped handler passes the target session id.
31
+ *
32
+ * Replacing the fake with the real runtime is a one-line change in
33
+ * `createStudioApp({ runtime })` — no handler changes, no route changes.
34
+ */
35
+ /**
36
+ * W737: the busy-slot error is part of the ENGINE contract, so it has exactly
37
+ * one definition — `packages/runtime/src/errors.ts`. It is imported (never
38
+ * redefined) here and re-exported, so every studio-side import of
39
+ * `TurnBusyError` resolves to the very class object the real engine throws.
40
+ */
41
+ import { TurnBusyError } from "@celestea/runtime";
42
+ /** Verbatim engine error text (`{e}` placeholders). */
43
+ export class EngineError extends Error {
44
+ kind = "engine";
45
+ constructor(message) {
46
+ super(message);
47
+ this.name = "EngineError";
48
+ }
49
+ }
50
+ /**
51
+ * Thrown by `startTurn` when the single-concurrency slot is occupied, and by
52
+ * `clear` while the target session's turn is in flight (409).
53
+ *
54
+ * W737: SINGLE SOURCE — `@celestea/runtime`'s `errors.ts` (`StudioError`
55
+ * subclass: `status: 409`, `kind: "turn_busy"`). This file used to declare a
56
+ * second, `extends Error` copy; the handlers branched on that copy, so the real
57
+ * engine's error failed `instanceof` and the busy race surfaced as a 500
58
+ * instead of a 409 / an interjection. Only the fake adapter threw the copy,
59
+ * which is what kept the contract tests green. Do not redeclare it here.
60
+ */
61
+ export { TurnBusyError };
62
+ /** Thrown when the live-session / concurrent-turn cap is reached (503). */
63
+ export class CapacityError extends Error {
64
+ kind = "capacity";
65
+ /** Seconds the client should wait before retrying (Retry-After). */
66
+ retryAfterSeconds;
67
+ constructor(message, retryAfterSeconds = 1) {
68
+ super(message);
69
+ this.name = "CapacityError";
70
+ this.retryAfterSeconds = retryAfterSeconds;
71
+ }
72
+ }
73
+ /** `registry.schemas()` row -> the two-field view the host exposes (W729). */
74
+ export function toolSpecView(spec) {
75
+ return { name: spec.name, description: spec.description };
76
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * W815-N2: a minimal promise-chain mutex for the Studio's HOT-APPLY writes.
3
+ *
4
+ * Both `POST /api/prompts` (registry persist -> engine configure -> rollback)
5
+ * and `POST /api/config` (host overrides -> engine configure) read a snapshot,
6
+ * mutate shared state and `await` the engine; without a lock two concurrent
7
+ * writers interleave their snapshot/rollback windows and the earlier failure can
8
+ * erase the later success. Every task runs strictly after the previous one
9
+ * settled (resolved OR rejected), so a caller still observes its own outcome
10
+ * while the chain stays alive.
11
+ */
12
+ export declare class SerialQueue {
13
+ private tail;
14
+ /** Run `task` after every previously enqueued task has settled. */
15
+ run<T>(task: () => Promise<T>): Promise<T>;
16
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * W815-N2: a minimal promise-chain mutex for the Studio's HOT-APPLY writes.
3
+ *
4
+ * Both `POST /api/prompts` (registry persist -> engine configure -> rollback)
5
+ * and `POST /api/config` (host overrides -> engine configure) read a snapshot,
6
+ * mutate shared state and `await` the engine; without a lock two concurrent
7
+ * writers interleave their snapshot/rollback windows and the earlier failure can
8
+ * erase the later success. Every task runs strictly after the previous one
9
+ * settled (resolved OR rejected), so a caller still observes its own outcome
10
+ * while the chain stays alive.
11
+ */
12
+ export class SerialQueue {
13
+ tail = Promise.resolve();
14
+ /** Run `task` after every previously enqueued task has settled. */
15
+ run(task) {
16
+ const result = this.tail.then(task, task);
17
+ // A rejected task must not poison the chain: swallow it on the tail only —
18
+ // the caller gets the real rejection through `result`.
19
+ this.tail = result.then(() => undefined, () => undefined);
20
+ return result;
21
+ }
22
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * `startStudioServer` (H): the ONE reusable server bootstrap.
3
+ *
4
+ * `apps/studio/src/main.ts` (source checkout) and the `celestea` CLI
5
+ * (`packages/cli`) both call this, so the startup log lines and the W742
6
+ * graceful-teardown order exist in exactly one place. `main.ts` stays a thin
7
+ * wrapper that pins the historical source defaults; the CLI passes its own
8
+ * flags.
9
+ *
10
+ * Teardown order is the W742 contract (do not reorder):
11
+ * signal -> stop accepting traffic (bounded grace) -> flush grants audit ->
12
+ * engine down (workers settled, logs closed) -> let the loop drain.
13
+ */
14
+ import { type StudioAppOptions } from "./app.js";
15
+ /** Env knob: drain window before leftover sockets are cut. */
16
+ export declare const ENV_DRAIN_MS = "CELESTEA_SHUTDOWN_DRAIN_MS";
17
+ /** Env knob: ceiling for the whole teardown. */
18
+ export declare const ENV_TEARDOWN_MS = "CELESTEA_SHUTDOWN_TIMEOUT_MS";
19
+ export interface StudioServerOptions extends StudioAppOptions {
20
+ port: number;
21
+ hostname: string;
22
+ /** Emit the startup banner (default true; tests may silence it). */
23
+ log?: boolean;
24
+ /** Called once the listener is up, with the ACTUAL bound port. */
25
+ onListening?: (info: {
26
+ port: number;
27
+ hostname: string;
28
+ endpointCount: number;
29
+ }) => void;
30
+ }
31
+ export interface StudioServerHandle {
32
+ port: number;
33
+ hostname: string;
34
+ endpointCount: number;
35
+ /** Resolves once the socket is bound (the ACTUAL address is then known). */
36
+ listening: Promise<{
37
+ port: number;
38
+ hostname: string;
39
+ }>;
40
+ /** Graceful stop (idempotent); resolves once the loop may drain. */
41
+ stop(signal: string): Promise<void>;
42
+ }
43
+ /** Boot the studio HTTP server; returns a handle whose `stop` is the teardown. */
44
+ export declare function startStudioServer(options: StudioServerOptions): StudioServerHandle;