@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/dist/stream.js ADDED
@@ -0,0 +1,52 @@
1
+ /**
2
+ * The LLM request/stream seam payloads — ports of
3
+ * `crates/core/src/message.rs:65-153` (ModelRequest / StreamEvent / LlmError).
4
+ *
5
+ * `StreamEvent` is what a provider adapter yields: incremental deltas for the
6
+ * UI, then exactly one authoritative terminal event. Terminal variants never
7
+ * follow each other (`Done` after `Failed`/`Interrupted` is a contract
8
+ * violation), because the engine's five TurnOutcome states are derived from
9
+ * which terminal event arrived.
10
+ *
11
+ * A1 (W746): this module is the SINGLE source of the LLM vocabulary. Provider
12
+ * packages (`@celestea/llm`) re-export these symbols instead of redeclaring
13
+ * them, so `instanceof LlmError` and every structural type agree across
14
+ * packages. `LlmError` here carries the structured classification TS needs
15
+ * (see the class doc).
16
+ */
17
+ export const STREAM_EVENT_KINDS = ["text", "thinking", "usage", "done", "failed", "interrupted"];
18
+ /**
19
+ * `LlmError` — the provider-facing failure (`LlmError(String)` in the legacy engine).
20
+ *
21
+ * The semantics ride the canonical `llm timeout: …` message prefix; TS adds the
22
+ * machine-readable fields on top of the unchanged text:
23
+ * `kind` (the turn-outcome kind a caller should report), `isTimeout` +
24
+ * `timeoutStage`, `httpStatus` and `retryable` (the conservative default pair
25
+ * is `(null, false)`: a failure with no evidence of being transient is a
26
+ * local/configuration problem, not something to retry).
27
+ */
28
+ export class LlmError extends Error {
29
+ /** Turn-outcome kind this failure maps to. */
30
+ kind;
31
+ /** True for any timeout; the message then carries the canonical prefix. */
32
+ isTimeout;
33
+ /** Which guard tripped, when the failure was a timeout. */
34
+ timeoutStage;
35
+ /** HTTP status of the failing response; `null` when none was received. */
36
+ httpStatus;
37
+ /** True for transient causes (timeouts, transport, 408/425/429/5xx). */
38
+ retryable;
39
+ constructor(message, kind = "generate", options) {
40
+ super(message);
41
+ this.name = "LlmError";
42
+ this.kind = kind;
43
+ this.isTimeout = options?.isTimeout ?? false;
44
+ this.timeoutStage = options?.timeoutStage ?? null;
45
+ this.httpStatus = options?.httpStatus ?? null;
46
+ this.retryable = options?.retryable ?? false;
47
+ }
48
+ }
49
+ /** True for the two terminal variants (no further event may follow). */
50
+ export function isTerminalStreamEvent(ev) {
51
+ return ev.kind === "done" || ev.kind === "failed" || ev.kind === "interrupted";
52
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * W855 (B6): the MODEL-VISIBLE FACE of a tool result, derived at READ time.
3
+ *
4
+ * The session log stores the ORIGINAL `tool_result.value`; the face (a bounded
5
+ * head/tail window + locator, or a tool-authored truncation note) is produced
6
+ * HERE by the projection. Keeping the pure rendering in core is what lets
7
+ * `projection.ts` render it without importing the L1 retention policy.
8
+ *
9
+ * The primitives were moved out of `packages/agent-loop/src/retention.ts`
10
+ * (W846/W855) unchanged, so the live and replayed faces stay byte-identical.
11
+ */
12
+ import type { ToolResultSurface } from "./types.js";
13
+ /** Longest prefix of text within maxBytes, never splitting a code point. */
14
+ export declare function cutPrefixCodePoints(text: string, maxBytes: number): string;
15
+ /** Longest suffix of text within maxBytes, never splitting a code point. */
16
+ export declare function cutSuffixCodePoints(text: string, maxBytes: number): string;
17
+ export interface RetainedText {
18
+ head: string;
19
+ tail: string;
20
+ keptBytes: number;
21
+ omittedBytes: number;
22
+ totalBytes: number;
23
+ }
24
+ /** Head+tail window over text (code-point safe) with the EXACT omitted count. */
25
+ export declare function retainHeadTail(text: string, headBytes: number, tailBytes: number): RetainedText;
26
+ /** The standardized omission clause + the tool-shaped retrieval instruction. */
27
+ export declare function formatOmissionNotice(surface: Extract<ToolResultSurface, {
28
+ kind: "omitted";
29
+ }>): string;
30
+ /** Bounded head/tail window + notice (the model-visible face of an omission). */
31
+ export declare function renderOmittedText(text: string, surface: Extract<ToolResultSurface, {
32
+ kind: "omitted";
33
+ }>): string;
34
+ /**
35
+ * The RAW face of one tool value: the bounded/annotated string when a surface is
36
+ * present, else the value untouched. SSE/replay frames and the Studio transcript
37
+ * use exactly this, so a live frame and its replay agree.
38
+ */
39
+ export declare function toolSurfaceValue(value: unknown, surface?: ToolResultSurface): unknown;
40
+ /**
41
+ * The model-PROJECTED TEXT of one tool value with its surface applied. The
42
+ * projection has ALWAYS JSON-encoded the value (`serdeJsonString`), so a
43
+ * surface-less string stays `"hello"`; a surfaced one encodes its RAW face.
44
+ */
45
+ export declare function toolSurfaceText(value: unknown, surface?: ToolResultSurface): string;
@@ -0,0 +1,98 @@
1
+ /**
2
+ * W855 (B6): the MODEL-VISIBLE FACE of a tool result, derived at READ time.
3
+ *
4
+ * The session log stores the ORIGINAL `tool_result.value`; the face (a bounded
5
+ * head/tail window + locator, or a tool-authored truncation note) is produced
6
+ * HERE by the projection. Keeping the pure rendering in core is what lets
7
+ * `projection.ts` render it without importing the L1 retention policy.
8
+ *
9
+ * The primitives were moved out of `packages/agent-loop/src/retention.ts`
10
+ * (W846/W855) unchanged, so the live and replayed faces stay byte-identical.
11
+ */
12
+ import { serdeJsonString } from "./json.js";
13
+ /** Longest prefix of text within maxBytes, never splitting a code point. */
14
+ export function cutPrefixCodePoints(text, maxBytes) {
15
+ if (maxBytes <= 0)
16
+ return "";
17
+ let used = 0;
18
+ let out = "";
19
+ for (const ch of text) {
20
+ const n = Buffer.byteLength(ch, "utf8");
21
+ if (used + n > maxBytes)
22
+ break;
23
+ used += n;
24
+ out += ch;
25
+ }
26
+ return out;
27
+ }
28
+ /** Longest suffix of text within maxBytes, never splitting a code point. */
29
+ export function cutSuffixCodePoints(text, maxBytes) {
30
+ if (maxBytes <= 0)
31
+ return "";
32
+ let used = 0;
33
+ let out = "";
34
+ const chars = Array.from(text);
35
+ for (let i = chars.length - 1; i >= 0; i -= 1) {
36
+ const ch = chars[i] ?? "";
37
+ const n = Buffer.byteLength(ch, "utf8");
38
+ if (used + n > maxBytes)
39
+ break;
40
+ used += n;
41
+ out = ch + out;
42
+ }
43
+ return out;
44
+ }
45
+ /** Head+tail window over text (code-point safe) with the EXACT omitted count. */
46
+ export function retainHeadTail(text, headBytes, tailBytes) {
47
+ const totalBytes = Buffer.byteLength(text, "utf8");
48
+ if (totalBytes <= headBytes + tailBytes) {
49
+ return { head: text, tail: "", keptBytes: totalBytes, omittedBytes: 0, totalBytes };
50
+ }
51
+ const head = cutPrefixCodePoints(text, headBytes);
52
+ const tail = cutSuffixCodePoints(text, tailBytes);
53
+ const keptBytes = Buffer.byteLength(head, "utf8") + Buffer.byteLength(tail, "utf8");
54
+ return { head, tail, keptBytes, omittedBytes: totalBytes - keptBytes, totalBytes };
55
+ }
56
+ /** The standardized omission clause + the tool-shaped retrieval instruction. */
57
+ export function formatOmissionNotice(surface) {
58
+ return ("[omitted] " +
59
+ String(surface.omitted_bytes) +
60
+ " of " +
61
+ String(surface.total_bytes) +
62
+ " bytes kept out of the model context by the tool-result budget; full text: " +
63
+ surface.locator +
64
+ " (" +
65
+ surface.retrieval_hint +
66
+ ")");
67
+ }
68
+ /** Bounded head/tail window + notice (the model-visible face of an omission). */
69
+ export function renderOmittedText(text, surface) {
70
+ const window = retainHeadTail(text, surface.head_bytes, surface.tail_bytes);
71
+ const notice = formatOmissionNotice(surface);
72
+ if (window.tail === "")
73
+ return window.head + "\n" + notice;
74
+ return window.head + "\n...\n" + window.tail + "\n" + notice;
75
+ }
76
+ /**
77
+ * The RAW face of one tool value: the bounded/annotated string when a surface is
78
+ * present, else the value untouched. SSE/replay frames and the Studio transcript
79
+ * use exactly this, so a live frame and its replay agree.
80
+ */
81
+ export function toolSurfaceValue(value, surface) {
82
+ if (surface === undefined)
83
+ return value;
84
+ const text = typeof value === "string" ? value : serdeJsonString(value === undefined ? null : value);
85
+ if (surface.kind === "omitted")
86
+ return renderOmittedText(text, surface);
87
+ return text === "" ? surface.note : text + "\n" + surface.note;
88
+ }
89
+ /**
90
+ * The model-PROJECTED TEXT of one tool value with its surface applied. The
91
+ * projection has ALWAYS JSON-encoded the value (`serdeJsonString`), so a
92
+ * surface-less string stays `"hello"`; a surfaced one encodes its RAW face.
93
+ */
94
+ export function toolSurfaceText(value, surface) {
95
+ if (surface === undefined)
96
+ return serdeJsonString(value === undefined ? null : value);
97
+ return serdeJsonString(toolSurfaceValue(value, surface));
98
+ }
package/dist/tool.d.ts ADDED
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Tool + ToolGuard seams — port of `crates/core/src/tool.rs`.
3
+ *
4
+ * A guard is the "waterfall" step of dispatch: it may Allow, Deny or Ask, and
5
+ * the first non-Allow decision short-circuits the chain. The registry runs the
6
+ * guard chain and then the tool, capturing errors instead of throwing.
7
+ *
8
+ * `ToolDecision` keeps the P0 TS shape (`{kind:"allow"}` / `{kind:"deny",reason}`
9
+ * / `{kind:"ask",reason}`) declared in `./types.ts` — it is the contract the
10
+ * tools package and the API surface already use, and it is the same three
11
+ * variants as the engine's enum.
12
+ */
13
+ import type { ToolDecision, ToolResultSurface, ToolSpec } from "./types.js";
14
+ export interface ToolInput {
15
+ call_id: string;
16
+ name: string;
17
+ args: unknown;
18
+ }
19
+ /** `Tool::execute_with` result: canonical value + optional authored rendering. */
20
+ export interface ToolExecOutcome {
21
+ value: unknown;
22
+ render: string | null;
23
+ /**
24
+ * W855 (B6): a tool-authored MODEL-FACE descriptor (e.g. `read_file`'s
25
+ * truncation note). Unlike `render` (display-only, never projected), this is
26
+ * persisted on the `tool_result` log row and applied by the projection.
27
+ */
28
+ surface?: ToolResultSurface;
29
+ }
30
+ export interface ToolOutput {
31
+ call_id: string;
32
+ /** Canonical, machine-readable result value. Never a display rendering. */
33
+ value: unknown;
34
+ /** Human-readable rendering, decoupled from the canonical value. */
35
+ render: string | null;
36
+ error: string | null;
37
+ /** The guard verdict for this dispatch (null when no guard ran). */
38
+ decision: ToolDecision | null;
39
+ /** W855 (B6): the model-face descriptor carried through dispatch (optional). */
40
+ surface?: ToolResultSurface;
41
+ }
42
+ export interface Tool {
43
+ spec(): ToolSpec;
44
+ execute(args: unknown): Promise<unknown>;
45
+ /**
46
+ * W255 run_code: tools that need the caller-assigned `call_id` (run_code
47
+ * embeds it in `<parent>:c<n>` sub-call ids) or that author their own
48
+ * `render` override this; the default delegates to `execute`.
49
+ */
50
+ executeWith?(input: ToolInput): Promise<ToolExecOutcome>;
51
+ }
52
+ export interface ToolGuard {
53
+ /**
54
+ * `ToolGuard::check`. Note the guard chain collects the FIRST
55
+ * non-Allow verdict, so a later Allow never un-denies an earlier Deny.
56
+ */
57
+ check(input: ToolInput): Promise<ToolDecision>;
58
+ }
59
+ export interface ToolRegistry {
60
+ register(tool: Tool): void;
61
+ addGuard(guard: ToolGuard): void;
62
+ get(name: string): Tool | undefined;
63
+ /**
64
+ * The model-facing specs. The registry's own order is by name, exactly like
65
+ * `ToolRegistry::schemas` (crates/tools/src/registry.rs); a disclosure
66
+ * decorator may re-project it into a **stable disclosure order** (baseline
67
+ * first, newly disclosed names appended) so the prompt prefix stays
68
+ * append-only across a session. The order is NOT part of the contract (only
69
+ * the name set is), so a decorator is free to choose it.
70
+ */
71
+ schemas(): ToolSpec[];
72
+ /** Run the guard chain, then the tool. Errors are captured, not thrown. */
73
+ dispatch(input: ToolInput): Promise<ToolOutput>;
74
+ }
75
+ /** Well-known tokens for the tool services in a Context. */
76
+ export declare const TOOL_REGISTRY_SERVICE = "celestea.core.ToolRegistry";
77
+ export declare const TOOL_GUARD_SERVICE = "celestea.core.ToolGuard";
package/dist/tool.js ADDED
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Tool + ToolGuard seams — port of `crates/core/src/tool.rs`.
3
+ *
4
+ * A guard is the "waterfall" step of dispatch: it may Allow, Deny or Ask, and
5
+ * the first non-Allow decision short-circuits the chain. The registry runs the
6
+ * guard chain and then the tool, capturing errors instead of throwing.
7
+ *
8
+ * `ToolDecision` keeps the P0 TS shape (`{kind:"allow"}` / `{kind:"deny",reason}`
9
+ * / `{kind:"ask",reason}`) declared in `./types.ts` — it is the contract the
10
+ * tools package and the API surface already use, and it is the same three
11
+ * variants as the engine's enum.
12
+ */
13
+ /** Well-known tokens for the tool services in a Context. */
14
+ export const TOOL_REGISTRY_SERVICE = "celestea.core.ToolRegistry";
15
+ export const TOOL_GUARD_SERVICE = "celestea.core.ToolGuard";
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Turn id ownership and arithmetic (session_log.rs:94-98).
3
+ *
4
+ * A2 (W746): the turn-id math lives in CORE, next to the `SessionLog` seam that
5
+ * promises it. `SessionLog.nextTurnId()` is a seam method, so `formatTurnId` /
6
+ * `nextTurnNumber` cannot be private to one implementation package: every
7
+ * backend must mint the same ids, and `packages/session` re-exports these.
8
+ *
9
+ * The LOG owns the counter, not the loop: next_turn_number only recognises the
10
+ * `turn-<n>` prefix and restores its counter from the max replayed id, so ids
11
+ * are monotonic and never reused after a restart.
12
+ */
13
+ import type { SessionEvent } from "./types.js";
14
+ export declare function parseTurnNumber(id: string): number | null;
15
+ export declare function formatTurnId(n: number): string;
16
+ /** Max turn-<n> seen in the log; -1 when none. */
17
+ export declare function maxTurnNumber(events: readonly SessionEvent[]): number;
18
+ /** The next id the log would allocate. */
19
+ export declare function nextTurnId(events: readonly SessionEvent[]): string;
20
+ /**
21
+ * The engine's `next_turn_number` (persistent.rs:391-402): max `turn-<n>` in the
22
+ * log plus one, or 0 when the log holds no such id. Legacy ids (e.g. `"t1"`) are
23
+ * ignored, so a replayed counter never collides with an id already on disk.
24
+ */
25
+ export declare function nextTurnNumber(events: readonly SessionEvent[]): number;
26
+ export interface TurnIdAudit {
27
+ ids: string[];
28
+ nonMonotonic: Array<{
29
+ index: number;
30
+ previous: string;
31
+ current: string;
32
+ }>;
33
+ duplicates: string[];
34
+ malformed: string[];
35
+ }
36
+ /** Verify the monotonicity + uniqueness contract of turn_start ids. */
37
+ export declare function auditTurnIds(events: readonly SessionEvent[]): TurnIdAudit;
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Turn id ownership and arithmetic (session_log.rs:94-98).
3
+ *
4
+ * A2 (W746): the turn-id math lives in CORE, next to the `SessionLog` seam that
5
+ * promises it. `SessionLog.nextTurnId()` is a seam method, so `formatTurnId` /
6
+ * `nextTurnNumber` cannot be private to one implementation package: every
7
+ * backend must mint the same ids, and `packages/session` re-exports these.
8
+ *
9
+ * The LOG owns the counter, not the loop: next_turn_number only recognises the
10
+ * `turn-<n>` prefix and restores its counter from the max replayed id, so ids
11
+ * are monotonic and never reused after a restart.
12
+ */
13
+ const TURN_ID = /^turn-(\d+)$/;
14
+ export function parseTurnNumber(id) {
15
+ const m = TURN_ID.exec(id);
16
+ if (!m || m[1] === undefined)
17
+ return null;
18
+ const n = Number.parseInt(m[1], 10);
19
+ return Number.isSafeInteger(n) ? n : null;
20
+ }
21
+ export function formatTurnId(n) {
22
+ return `turn-${n}`;
23
+ }
24
+ /** Max turn-<n> seen in the log; -1 when none. */
25
+ export function maxTurnNumber(events) {
26
+ let max = -1;
27
+ for (const ev of events) {
28
+ if (ev.type !== "turn_start" && ev.type !== "turn_end")
29
+ continue;
30
+ const n = parseTurnNumber(ev.id);
31
+ if (n !== null && n > max)
32
+ max = n;
33
+ }
34
+ return max;
35
+ }
36
+ /** The next id the log would allocate. */
37
+ export function nextTurnId(events) {
38
+ return formatTurnId(nextTurnNumber(events));
39
+ }
40
+ /**
41
+ * The engine's `next_turn_number` (persistent.rs:391-402): max `turn-<n>` in the
42
+ * log plus one, or 0 when the log holds no such id. Legacy ids (e.g. `"t1"`) are
43
+ * ignored, so a replayed counter never collides with an id already on disk.
44
+ */
45
+ export function nextTurnNumber(events) {
46
+ return maxTurnNumber(events) + 1;
47
+ }
48
+ /** Verify the monotonicity + uniqueness contract of turn_start ids. */
49
+ export function auditTurnIds(events) {
50
+ const ids = [];
51
+ const seen = new Set();
52
+ const duplicates = [];
53
+ const malformed = [];
54
+ const nonMonotonic = [];
55
+ let previous = -1;
56
+ let previousId = "";
57
+ for (const ev of events) {
58
+ if (ev.type !== "turn_start")
59
+ continue;
60
+ ids.push(ev.id);
61
+ if (seen.has(ev.id))
62
+ duplicates.push(ev.id);
63
+ seen.add(ev.id);
64
+ const n = parseTurnNumber(ev.id);
65
+ if (n === null) {
66
+ malformed.push(ev.id);
67
+ continue;
68
+ }
69
+ if (n <= previous) {
70
+ nonMonotonic.push({ index: ids.length - 1, previous: previousId, current: ev.id });
71
+ }
72
+ previous = n;
73
+ previousId = ev.id;
74
+ }
75
+ return { ids, nonMonotonic, duplicates, malformed };
76
+ }