@sumeru/server 0.1.0

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 (95) hide show
  1. package/LICENSE +18 -0
  2. package/dist/.build-fingerprint +1 -0
  3. package/dist/config.d.ts +14 -0
  4. package/dist/config.d.ts.map +1 -0
  5. package/dist/config.js +142 -0
  6. package/dist/config.js.map +1 -0
  7. package/dist/envelope.d.ts +28 -0
  8. package/dist/envelope.d.ts.map +1 -0
  9. package/dist/envelope.js +43 -0
  10. package/dist/envelope.js.map +1 -0
  11. package/dist/export/bundle.d.ts +28 -0
  12. package/dist/export/bundle.d.ts.map +1 -0
  13. package/dist/export/bundle.js +78 -0
  14. package/dist/export/bundle.js.map +1 -0
  15. package/dist/export/handler.d.ts +24 -0
  16. package/dist/export/handler.d.ts.map +1 -0
  17. package/dist/export/handler.js +102 -0
  18. package/dist/export/handler.js.map +1 -0
  19. package/dist/export/index.d.ts +3 -0
  20. package/dist/export/index.d.ts.map +1 -0
  21. package/dist/export/index.js +3 -0
  22. package/dist/export/index.js.map +1 -0
  23. package/dist/handler.d.ts +24 -0
  24. package/dist/handler.d.ts.map +1 -0
  25. package/dist/handler.js +622 -0
  26. package/dist/handler.js.map +1 -0
  27. package/dist/index.d.ts +12 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +10 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/ocas/index.d.ts +3 -0
  32. package/dist/ocas/index.d.ts.map +1 -0
  33. package/dist/ocas/index.js +3 -0
  34. package/dist/ocas/index.js.map +1 -0
  35. package/dist/ocas/schemas.d.ts +41 -0
  36. package/dist/ocas/schemas.d.ts.map +1 -0
  37. package/dist/ocas/schemas.js +108 -0
  38. package/dist/ocas/schemas.js.map +1 -0
  39. package/dist/ocas/store.d.ts +58 -0
  40. package/dist/ocas/store.d.ts.map +1 -0
  41. package/dist/ocas/store.js +139 -0
  42. package/dist/ocas/store.js.map +1 -0
  43. package/dist/search/handler.d.ts +54 -0
  44. package/dist/search/handler.d.ts.map +1 -0
  45. package/dist/search/handler.js +178 -0
  46. package/dist/search/handler.js.map +1 -0
  47. package/dist/search/index.d.ts +4 -0
  48. package/dist/search/index.d.ts.map +1 -0
  49. package/dist/search/index.js +3 -0
  50. package/dist/search/index.js.map +1 -0
  51. package/dist/search/sqlite-index.d.ts +49 -0
  52. package/dist/search/sqlite-index.d.ts.map +1 -0
  53. package/dist/search/sqlite-index.js +508 -0
  54. package/dist/search/sqlite-index.js.map +1 -0
  55. package/dist/search/types.d.ts +143 -0
  56. package/dist/search/types.d.ts.map +1 -0
  57. package/dist/search/types.js +10 -0
  58. package/dist/search/types.js.map +1 -0
  59. package/dist/session/cwd.d.ts +31 -0
  60. package/dist/session/cwd.d.ts.map +1 -0
  61. package/dist/session/cwd.js +54 -0
  62. package/dist/session/cwd.js.map +1 -0
  63. package/dist/session/id.d.ts +12 -0
  64. package/dist/session/id.d.ts.map +1 -0
  65. package/dist/session/id.js +76 -0
  66. package/dist/session/id.js.map +1 -0
  67. package/dist/session/index.d.ts +5 -0
  68. package/dist/session/index.d.ts.map +1 -0
  69. package/dist/session/index.js +4 -0
  70. package/dist/session/index.js.map +1 -0
  71. package/dist/session/store.d.ts +89 -0
  72. package/dist/session/store.d.ts.map +1 -0
  73. package/dist/session/store.js +258 -0
  74. package/dist/session/store.js.map +1 -0
  75. package/dist/sse/buffer.d.ts +53 -0
  76. package/dist/sse/buffer.d.ts.map +1 -0
  77. package/dist/sse/buffer.js +119 -0
  78. package/dist/sse/buffer.js.map +1 -0
  79. package/dist/sse/index.d.ts +3 -0
  80. package/dist/sse/index.d.ts.map +1 -0
  81. package/dist/sse/index.js +3 -0
  82. package/dist/sse/index.js.map +1 -0
  83. package/dist/sse/messages.d.ts +30 -0
  84. package/dist/sse/messages.d.ts.map +1 -0
  85. package/dist/sse/messages.js +489 -0
  86. package/dist/sse/messages.js.map +1 -0
  87. package/dist/start.d.ts +22 -0
  88. package/dist/start.d.ts.map +1 -0
  89. package/dist/start.js +86 -0
  90. package/dist/start.js.map +1 -0
  91. package/dist/types.d.ts +252 -0
  92. package/dist/types.d.ts.map +1 -0
  93. package/dist/types.js +10 -0
  94. package/dist/types.js.map +1 -0
  95. package/package.json +31 -0
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Phase 5 — search index types.
3
+ *
4
+ * These types are shared by the index module and the HTTP endpoints. The
5
+ * `SearchIndex` is a closure carrying the SQLite handle; consumers obtain
6
+ * one from `openSumeruOcas` via the new `searchIndex` slice on the result
7
+ * (mirrored on `OcasConfig`).
8
+ */
9
+ export {};
10
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/search/types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Resolve a per-session `config.cwd` against the instance `workspaceRoot`.
3
+ *
4
+ * Single source of truth for the cwd-resolution policy used by
5
+ * `POST /gateways/:name/sessions`. Both the HTTP handler and tests import
6
+ * this helper.
7
+ *
8
+ * Rules:
9
+ *
10
+ * - `rawCwd` is `undefined` / `null` → `{ ok: true, cwd: null }`
11
+ * - `rawCwd` is the empty string → `{ ok: true, cwd: null }`
12
+ * - `rawCwd` is not a string (and not absent) → `{ ok: false, … }`
13
+ * - non-empty string AND workspaceRoot set → `path.resolve(root, raw)`,
14
+ * rejected if it escapes root
15
+ * - non-empty string AND workspaceRoot null → must be absolute (verbatim);
16
+ * relative is rejected
17
+ *
18
+ * The resolved value is the absolute path the adapter should be told to use
19
+ * (replacing the user-supplied `cwd` in the opaque config blob). The wire
20
+ * envelope returned to the client is left untouched — only the adapter sees
21
+ * the resolved form.
22
+ */
23
+ export type ResolveCwdResult = {
24
+ ok: true;
25
+ cwd: string | null;
26
+ } | {
27
+ ok: false;
28
+ message: string;
29
+ };
30
+ export declare function resolveSessionCwd(workspaceRoot: string | null, rawCwd: unknown): ResolveCwdResult;
31
+ //# sourceMappingURL=cwd.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cwd.d.ts","sourceRoot":"","sources":["../../src/session/cwd.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAIH,MAAM,MAAM,gBAAgB,GACzB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAChC;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAElC,wBAAgB,iBAAiB,CAChC,aAAa,EAAE,MAAM,GAAG,IAAI,EAC5B,MAAM,EAAE,OAAO,GACb,gBAAgB,CA+BlB"}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Resolve a per-session `config.cwd` against the instance `workspaceRoot`.
3
+ *
4
+ * Single source of truth for the cwd-resolution policy used by
5
+ * `POST /gateways/:name/sessions`. Both the HTTP handler and tests import
6
+ * this helper.
7
+ *
8
+ * Rules:
9
+ *
10
+ * - `rawCwd` is `undefined` / `null` → `{ ok: true, cwd: null }`
11
+ * - `rawCwd` is the empty string → `{ ok: true, cwd: null }`
12
+ * - `rawCwd` is not a string (and not absent) → `{ ok: false, … }`
13
+ * - non-empty string AND workspaceRoot set → `path.resolve(root, raw)`,
14
+ * rejected if it escapes root
15
+ * - non-empty string AND workspaceRoot null → must be absolute (verbatim);
16
+ * relative is rejected
17
+ *
18
+ * The resolved value is the absolute path the adapter should be told to use
19
+ * (replacing the user-supplied `cwd` in the opaque config blob). The wire
20
+ * envelope returned to the client is left untouched — only the adapter sees
21
+ * the resolved form.
22
+ */
23
+ import { isAbsolute, resolve as pathResolve, sep } from "node:path";
24
+ export function resolveSessionCwd(workspaceRoot, rawCwd) {
25
+ if (rawCwd === undefined || rawCwd === null) {
26
+ return { ok: true, cwd: null };
27
+ }
28
+ if (typeof rawCwd !== "string") {
29
+ return { ok: false, message: "config.cwd must be a string" };
30
+ }
31
+ if (rawCwd.length === 0) {
32
+ return { ok: true, cwd: null };
33
+ }
34
+ if (workspaceRoot !== null) {
35
+ const root = pathResolve(workspaceRoot);
36
+ const resolved = pathResolve(root, rawCwd);
37
+ if (resolved !== root && !resolved.startsWith(root + sep)) {
38
+ return {
39
+ ok: false,
40
+ message: `config.cwd '${rawCwd}' resolves outside workspaceRoot '${workspaceRoot}'`,
41
+ };
42
+ }
43
+ return { ok: true, cwd: resolved };
44
+ }
45
+ // No workspaceRoot configured — only absolute paths are accepted.
46
+ if (!isAbsolute(rawCwd)) {
47
+ return {
48
+ ok: false,
49
+ message: `config.cwd '${rawCwd}' must be absolute when no workspaceRoot is configured`,
50
+ };
51
+ }
52
+ return { ok: true, cwd: rawCwd };
53
+ }
54
+ //# sourceMappingURL=cwd.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cwd.js","sourceRoot":"","sources":["../../src/session/cwd.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,UAAU,EAAE,OAAO,IAAI,WAAW,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAMpE,MAAM,UAAU,iBAAiB,CAChC,aAA4B,EAC5B,MAAe;IAEf,IAAI,MAAM,KAAK,SAAS,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;QAC7C,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC;IAChC,CAAC;IACD,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;QAChC,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,6BAA6B,EAAE,CAAC;IAC9D,CAAC;IACD,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC;IAChC,CAAC;IAED,IAAI,aAAa,KAAK,IAAI,EAAE,CAAC;QAC5B,MAAM,IAAI,GAAG,WAAW,CAAC,aAAa,CAAC,CAAC;QACxC,MAAM,QAAQ,GAAG,WAAW,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QAC3C,IAAI,QAAQ,KAAK,IAAI,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,GAAG,GAAG,CAAC,EAAE,CAAC;YAC3D,OAAO;gBACN,EAAE,EAAE,KAAK;gBACT,OAAO,EAAE,eAAe,MAAM,qCAAqC,aAAa,GAAG;aACnF,CAAC;QACH,CAAC;QACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,QAAQ,EAAE,CAAC;IACpC,CAAC;IAED,kEAAkE;IAClE,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;QACzB,OAAO;YACN,EAAE,EAAE,KAAK;YACT,OAAO,EAAE,eAAe,MAAM,wDAAwD;SACtF,CAAC;IACH,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC;AAClC,CAAC"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Generate a Sumeru session ID: `ses_` followed by a 26-character ULID
3
+ * (Crockford Base32, uppercase).
4
+ *
5
+ * - Total length: 30 (4 prefix + 26 body).
6
+ * - Body matches `^[0-9A-HJKMNP-TV-Z]{26}$`.
7
+ * - First 10 body characters encode milliseconds since the Unix epoch.
8
+ * - Subsequent calls within the same millisecond emit a strictly increasing
9
+ * randomness component (monotonic ULID), guaranteeing distinct ids.
10
+ */
11
+ export declare function generateSessionId(): string;
12
+ //# sourceMappingURL=id.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"id.d.ts","sourceRoot":"","sources":["../../src/session/id.ts"],"names":[],"mappings":"AAkBA;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C"}
@@ -0,0 +1,76 @@
1
+ import { randomBytes } from "node:crypto";
2
+ /**
3
+ * Crockford Base32 alphabet (excludes I, L, O, U).
4
+ * Used by ULID for both timestamp and randomness components.
5
+ */
6
+ const CROCKFORD = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
7
+ /** Length of the timestamp portion of a ULID (in Crockford chars). */
8
+ const TIME_LEN = 10;
9
+ /** Length of the randomness portion of a ULID. */
10
+ const RAND_LEN = 16;
11
+ /** State for monotonic ULID generation when timestamps collide. */
12
+ let lastTime = 0;
13
+ let lastRandom = new Array(RAND_LEN).fill(0);
14
+ /**
15
+ * Generate a Sumeru session ID: `ses_` followed by a 26-character ULID
16
+ * (Crockford Base32, uppercase).
17
+ *
18
+ * - Total length: 30 (4 prefix + 26 body).
19
+ * - Body matches `^[0-9A-HJKMNP-TV-Z]{26}$`.
20
+ * - First 10 body characters encode milliseconds since the Unix epoch.
21
+ * - Subsequent calls within the same millisecond emit a strictly increasing
22
+ * randomness component (monotonic ULID), guaranteeing distinct ids.
23
+ */
24
+ export function generateSessionId() {
25
+ return `ses_${generateUlid()}`;
26
+ }
27
+ function generateUlid() {
28
+ const now = Date.now();
29
+ const random = now === lastTime ? incrementRandom(lastRandom) : freshRandom();
30
+ lastTime = now;
31
+ lastRandom = random;
32
+ return `${encodeTime(now)}${encodeRandom(random)}`;
33
+ }
34
+ function encodeTime(ms) {
35
+ const out = new Array(TIME_LEN).fill("0");
36
+ let value = ms;
37
+ for (let i = TIME_LEN - 1; i >= 0; i -= 1) {
38
+ const mod = value % 32;
39
+ // Crockford alphabet has length 32, so mod is always in range.
40
+ out[i] = CROCKFORD.charAt(mod);
41
+ value = Math.floor(value / 32);
42
+ }
43
+ return out.join("");
44
+ }
45
+ function freshRandom() {
46
+ const buf = randomBytes(RAND_LEN);
47
+ const out = new Array(RAND_LEN).fill(0);
48
+ for (let i = 0; i < RAND_LEN; i += 1) {
49
+ // 0–31 (5-bit) values for Crockford Base32.
50
+ out[i] = (buf[i] ?? 0) & 0x1f;
51
+ }
52
+ return out;
53
+ }
54
+ function incrementRandom(prev) {
55
+ const out = prev.slice();
56
+ for (let i = RAND_LEN - 1; i >= 0; i -= 1) {
57
+ const v = out[i] ?? 0;
58
+ if (v < 31) {
59
+ out[i] = v + 1;
60
+ return out;
61
+ }
62
+ out[i] = 0;
63
+ }
64
+ // Overflow within a single millisecond is astronomically unlikely; fall back
65
+ // to a fresh random vector rather than failing the call.
66
+ return freshRandom();
67
+ }
68
+ function encodeRandom(values) {
69
+ const out = new Array(RAND_LEN).fill("0");
70
+ for (let i = 0; i < RAND_LEN; i += 1) {
71
+ const v = values[i] ?? 0;
72
+ out[i] = CROCKFORD.charAt(v);
73
+ }
74
+ return out.join("");
75
+ }
76
+ //# sourceMappingURL=id.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"id.js","sourceRoot":"","sources":["../../src/session/id.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ export { type ResolveCwdResult, resolveSessionCwd } from "./cwd.js";
2
+ export { generateSessionId } from "./id.js";
3
+ export type { SessionStore, TransitionResult } from "./store.js";
4
+ export { createSessionStore } from "./store.js";
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/session/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AACpE,OAAO,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAC5C,YAAY,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AACjE,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1,4 @@
1
+ export { resolveSessionCwd } from "./cwd.js";
2
+ export { generateSessionId } from "./id.js";
3
+ export { createSessionStore } from "./store.js";
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/session/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAyB,iBAAiB,EAAE,MAAM,UAAU,CAAC;AACpE,OAAO,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAE5C,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1,89 @@
1
+ import type { Hash } from "@ocas/core";
2
+ import type { NativeSessionRef } from "@sumeru/core";
3
+ import type { OcasConfig, Session, SessionWire, UserSessionConfig } from "../types.js";
4
+ /**
5
+ * In-memory session store.
6
+ *
7
+ * Phase 2: persistence is per-process and lost on restart. Adapter-level
8
+ * persistence lands in a later phase when the message endpoint is wired up.
9
+ *
10
+ * Phase 3: each session also tracks an internal `NativeSessionRef` produced
11
+ * by the adapter when the session was created. The `NativeSessionRef` is
12
+ * NEVER exposed in HTTP envelopes — only `getNativeRef` makes it visible
13
+ * to internal callers (the message handler, the close path).
14
+ *
15
+ * Phase 4: each session carries a `metaHash` (set on create) and a
16
+ * `turnHashes` array that grows as turns are recorded. Both are internal
17
+ * — they are NEVER serialized into HTTP envelopes; `toWire` strips them.
18
+ *
19
+ * Sessions are scoped to their gateway: lookups always include the gateway
20
+ * name, and a session created on `hermes` is invisible from `claude-code`.
21
+ *
22
+ * The store is a closure over a `Map<gateway, Map<id, Session>>`. Insertion
23
+ * order is preserved on the inner maps, which makes listings deterministic
24
+ * (chronological by createdAt ascending).
25
+ */
26
+ export type SessionStore = {
27
+ /**
28
+ * Create a new session on `gateway`. The id is generated server-side.
29
+ * Writes the corresponding `@sumeru/session-meta` node to ocas BEFORE
30
+ * the in-memory session is registered. Throws if the meta-write fails.
31
+ *
32
+ * `resolvedCwd` is the absolute path produced by `resolveSessionCwd`
33
+ * (issue #27) — the value the server already forwarded to the adapter
34
+ * under `config.cwd`. `null` means no cwd hint was supplied. The opaque
35
+ * `config` blob is preserved verbatim and is NOT mutated here.
36
+ */
37
+ create: (gateway: string, adapter: string, config: UserSessionConfig, nativeRef: NativeSessionRef | null, resolvedCwd: string | null) => Session;
38
+ /** List sessions on a gateway in insertion order (chronological). */
39
+ list: (gateway: string) => Session[];
40
+ /** Look up a single session, scoped by gateway. */
41
+ get: (gateway: string, id: string) => Session | null;
42
+ /** Internal-only: retrieve the NativeSessionRef recorded at create time. */
43
+ getNativeRef: (gateway: string, id: string) => NativeSessionRef | null;
44
+ /**
45
+ * Append a turn hash to a session's turn list. Used by the message
46
+ * endpoint as turns are recorded in ocas. No-op if the session is gone.
47
+ */
48
+ appendTurnHash: (gateway: string, id: string, hash: Hash) => void;
49
+ /**
50
+ * Mark a session closed. Returns:
51
+ * - `closed` if the status flipped from idle/active to closed
52
+ * - `already_closed` if it was already closed (idempotent no-op)
53
+ * - `not_found` if no such session exists on the gateway
54
+ */
55
+ close: (gateway: string, id: string) => "closed" | "already_closed" | "not_found";
56
+ /** Number of non-closed sessions on a gateway. */
57
+ activeCount: (gateway: string) => number;
58
+ /**
59
+ * Try to mark a session active (idle → active). Reserved for the future
60
+ * message endpoint; exposed now so the 409 contract is testable.
61
+ */
62
+ tryActivate: (gateway: string, id: string) => TransitionResult<"busy" | "closed" | "not_found">;
63
+ /**
64
+ * Mark a session idle (active → idle). Used at the end of a message
65
+ * exchange. Forward-compat for the message endpoint.
66
+ */
67
+ markIdle: (gateway: string, id: string) => TransitionResult<"not_active" | "not_found">;
68
+ };
69
+ export type TransitionResult<R extends string> = {
70
+ ok: true;
71
+ session: Session;
72
+ } | {
73
+ ok: false;
74
+ reason: R;
75
+ };
76
+ /**
77
+ * Strip internal-only fields (`metaHash`, `turnHashes`) from a `Session` so
78
+ * the result matches the HTTP wire envelope.
79
+ */
80
+ export declare function toWire(session: Session): SessionWire;
81
+ /**
82
+ * Build a fresh, empty session store.
83
+ *
84
+ * The store needs `ocas` so it can write `@sumeru/session-meta` nodes on
85
+ * `create`. The hash is recorded on the in-memory session for later
86
+ * cross-reference by the `/ocas/:hash` endpoint.
87
+ */
88
+ export declare function createSessionStore(ocas: OcasConfig): SessionStore;
89
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/session/store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAErD,OAAO,KAAK,EACX,UAAU,EACV,OAAO,EAEP,WAAW,EACX,iBAAiB,EACjB,MAAM,aAAa,CAAC;AAGrB;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,MAAM,MAAM,YAAY,GAAG;IAC1B;;;;;;;;;OASG;IACH,MAAM,EAAE,CACP,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,iBAAiB,EACzB,SAAS,EAAE,gBAAgB,GAAG,IAAI,EAClC,WAAW,EAAE,MAAM,GAAG,IAAI,KACtB,OAAO,CAAC;IACb,qEAAqE;IACrE,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,EAAE,CAAC;IACrC,mDAAmD;IACnD,GAAG,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,KAAK,OAAO,GAAG,IAAI,CAAC;IACrD,4EAA4E;IAC5E,YAAY,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,KAAK,gBAAgB,GAAG,IAAI,CAAC;IACvE;;;OAGG;IACH,cAAc,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC;IAClE;;;;;OAKG;IACH,KAAK,EAAE,CACN,OAAO,EAAE,MAAM,EACf,EAAE,EAAE,MAAM,KACN,QAAQ,GAAG,gBAAgB,GAAG,WAAW,CAAC;IAC/C,kDAAkD;IAClD,WAAW,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,CAAC;IACzC;;;OAGG;IACH,WAAW,EAAE,CACZ,OAAO,EAAE,MAAM,EACf,EAAE,EAAE,MAAM,KACN,gBAAgB,CAAC,MAAM,GAAG,QAAQ,GAAG,WAAW,CAAC,CAAC;IACvD;;;OAGG;IACH,QAAQ,EAAE,CACT,OAAO,EAAE,MAAM,EACf,EAAE,EAAE,MAAM,KACN,gBAAgB,CAAC,YAAY,GAAG,WAAW,CAAC,CAAC;CAClD,CAAC;AAEF,MAAM,MAAM,gBAAgB,CAAC,CAAC,SAAS,MAAM,IAC1C;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,GAC9B;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,CAAC,CAAA;CAAE,CAAC;AAE5B;;;GAGG;AACH,wBAAgB,MAAM,CAAC,OAAO,EAAE,OAAO,GAAG,WAAW,CAQpD;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,UAAU,GAAG,YAAY,CAwPjE"}
@@ -0,0 +1,258 @@
1
+ import { recordPayload } from "../ocas/index.js";
2
+ import { generateSessionId } from "./id.js";
3
+ /**
4
+ * Strip internal-only fields (`metaHash`, `turnHashes`) from a `Session` so
5
+ * the result matches the HTTP wire envelope.
6
+ */
7
+ export function toWire(session) {
8
+ return {
9
+ id: session.id,
10
+ gateway: session.gateway,
11
+ status: session.status,
12
+ createdAt: session.createdAt,
13
+ config: session.config,
14
+ };
15
+ }
16
+ /**
17
+ * Build a fresh, empty session store.
18
+ *
19
+ * The store needs `ocas` so it can write `@sumeru/session-meta` nodes on
20
+ * `create`. The hash is recorded on the in-memory session for later
21
+ * cross-reference by the `/ocas/:hash` endpoint.
22
+ */
23
+ export function createSessionStore(ocas) {
24
+ const byGateway = new Map();
25
+ const nativeRefs = new Map();
26
+ function ensureGatewayMap(gateway) {
27
+ let inner = byGateway.get(gateway);
28
+ if (inner === undefined) {
29
+ inner = new Map();
30
+ byGateway.set(gateway, inner);
31
+ }
32
+ return inner;
33
+ }
34
+ function nowIso() {
35
+ return new Date().toISOString();
36
+ }
37
+ function refKey(gateway, id) {
38
+ return `${gateway}\u0000${id}`;
39
+ }
40
+ /**
41
+ * Phase 6 (Refs #399): rebuild `byGateway` from disk BEFORE serving any
42
+ * request, so a restarted process recovers previously-recorded sessions
43
+ * and their ordered turn-list pointers. Reads:
44
+ * 1. every `sumeru_session_index` row (ordered by `created_at ASC`), and
45
+ * 2. the per-session turn hashes in one bulk query.
46
+ * `config` is recovered from the immutable `@sumeru/session-meta` node at
47
+ * `metaHash`; a missing/unreadable meta node falls back to `config: {}`
48
+ * with a structured warning. `nativeRef` is intentionally NOT restored —
49
+ * a rehydrated session is read-complete but not resumable for new sends.
50
+ */
51
+ function rehydrate() {
52
+ let sessionCount = 0;
53
+ let turnCount = 0;
54
+ let rows;
55
+ let turnsBySession;
56
+ try {
57
+ rows = ocas.searchIndex.loadSessionRows();
58
+ turnsBySession = ocas.searchIndex.loadSessionTurnsBulk();
59
+ }
60
+ catch (err) {
61
+ const cause = err instanceof Error ? err.message : String(err);
62
+ console.warn(`[sumeru] session rehydrate failed: ${cause}`);
63
+ return;
64
+ }
65
+ for (const row of rows) {
66
+ const inner = ensureGatewayMap(row.gateway);
67
+ const config = recoverConfig(row.sessionId, row.metaHash);
68
+ const turnHashes = turnsBySession.get(row.sessionId) ?? [];
69
+ const session = {
70
+ id: row.sessionId,
71
+ gateway: row.gateway,
72
+ status: row.status,
73
+ createdAt: row.createdAt,
74
+ config,
75
+ metaHash: row.metaHash ?? "",
76
+ turnHashes: [...turnHashes],
77
+ };
78
+ inner.set(session.id, session);
79
+ sessionCount += 1;
80
+ turnCount += turnHashes.length;
81
+ }
82
+ if (sessionCount > 0) {
83
+ console.log(`[sumeru] rehydrated ${sessionCount} sessions, ${turnCount} turns`);
84
+ }
85
+ }
86
+ /**
87
+ * Read a session's opaque `config` from its immutable `@sumeru/session-meta`
88
+ * node. Falls back to `{}` (with a warning) when `metaHash` is `null` or the
89
+ * node is missing/malformed — turn history is the priority, not config.
90
+ */
91
+ function recoverConfig(sessionId, metaHash) {
92
+ if (metaHash === null) {
93
+ console.warn(`[sumeru] session ${sessionId} has no meta_hash; config falls back to {}`);
94
+ return {};
95
+ }
96
+ const node = ocas.store.cas.get(metaHash);
97
+ if (node === null) {
98
+ console.warn(`[sumeru] session ${sessionId} meta node ${metaHash} missing; config falls back to {}`);
99
+ return {};
100
+ }
101
+ const payload = node.payload;
102
+ const config = payload.config;
103
+ if (config === undefined || config === null || typeof config !== "object") {
104
+ return {};
105
+ }
106
+ return config;
107
+ }
108
+ function create(gateway, adapter, config, nativeRef, resolvedCwd) {
109
+ const inner = ensureGatewayMap(gateway);
110
+ const id = generateSessionId();
111
+ const createdAt = nowIso();
112
+ // Record session-meta to ocas FIRST. If validation/IO fails, the
113
+ // in-memory session is never created — the caller propagates a 500.
114
+ const metaHash = recordPayload(ocas.store, ocas.sessionMetaSchemaHash, {
115
+ id,
116
+ gateway,
117
+ adapter,
118
+ createdAt,
119
+ config,
120
+ resolvedCwd,
121
+ });
122
+ // Phase 5: seed the search index. Failures here do NOT roll back the
123
+ // ocas write — the index can always be rebuilt from the meta node.
124
+ // Phase 6 (Refs #399): persist `metaHash` so a restart can re-read
125
+ // `config` from the immutable `@sumeru/session-meta` node.
126
+ try {
127
+ ocas.searchIndex.indexSessionMeta({
128
+ sessionId: id,
129
+ gateway,
130
+ adapter,
131
+ createdAt,
132
+ metaHash,
133
+ });
134
+ }
135
+ catch (err) {
136
+ const cause = err instanceof Error ? err.message : String(err);
137
+ console.warn(`[sumeru] search index seed failed: ${cause}`);
138
+ }
139
+ const session = {
140
+ id,
141
+ gateway,
142
+ status: "idle",
143
+ createdAt,
144
+ config,
145
+ metaHash,
146
+ turnHashes: [],
147
+ };
148
+ inner.set(session.id, session);
149
+ if (nativeRef !== null) {
150
+ nativeRefs.set(refKey(gateway, session.id), nativeRef);
151
+ }
152
+ return session;
153
+ }
154
+ function list(gateway) {
155
+ const inner = byGateway.get(gateway);
156
+ if (inner === undefined)
157
+ return [];
158
+ return Array.from(inner.values());
159
+ }
160
+ function get(gateway, id) {
161
+ const inner = byGateway.get(gateway);
162
+ if (inner === undefined)
163
+ return null;
164
+ return inner.get(id) ?? null;
165
+ }
166
+ function getNativeRef(gateway, id) {
167
+ return nativeRefs.get(refKey(gateway, id)) ?? null;
168
+ }
169
+ function appendTurnHash(gateway, id, hash) {
170
+ const session = get(gateway, id);
171
+ if (session === null)
172
+ return;
173
+ // Phase 6 (Refs #399): persist the list pointer BEFORE mutating memory
174
+ // so disk never lags behind memory. `turn_index` is the 0-based append
175
+ // position (the array length before the push). The write is idempotent
176
+ // on (session_id, turn_index); a persistence failure propagates so the
177
+ // caller's existing error handling (sse/messages.ts) can react — we do
178
+ // NOT silently diverge memory from disk.
179
+ const turnIndex = session.turnHashes.length;
180
+ ocas.searchIndex.appendSessionTurn(session.id, turnIndex, hash);
181
+ session.turnHashes.push(hash);
182
+ }
183
+ function close(gateway, id) {
184
+ const session = get(gateway, id);
185
+ if (session === null)
186
+ return "not_found";
187
+ if (session.status === "closed")
188
+ return "already_closed";
189
+ session.status = "closed";
190
+ // Best-effort: mark the search-index row closed too. Failures are
191
+ // logged inside markSessionClosed; the in-memory flip is the source
192
+ // of truth on the wire.
193
+ ocas.searchIndex.markSessionClosed(id);
194
+ return "closed";
195
+ }
196
+ function activeCount(gateway) {
197
+ const inner = byGateway.get(gateway);
198
+ if (inner === undefined)
199
+ return 0;
200
+ let n = 0;
201
+ for (const s of inner.values()) {
202
+ if (s.status !== "closed")
203
+ n += 1;
204
+ }
205
+ return n;
206
+ }
207
+ function tryActivate(gateway, id) {
208
+ const session = get(gateway, id);
209
+ if (session === null)
210
+ return { ok: false, reason: "not_found" };
211
+ const next = transitionTo(session.status, "active");
212
+ if (next === "busy" || next === "closed") {
213
+ return { ok: false, reason: next };
214
+ }
215
+ session.status = "active";
216
+ return { ok: true, session };
217
+ }
218
+ function markIdle(gateway, id) {
219
+ const session = get(gateway, id);
220
+ if (session === null)
221
+ return { ok: false, reason: "not_found" };
222
+ if (session.status !== "active") {
223
+ return { ok: false, reason: "not_active" };
224
+ }
225
+ session.status = "idle";
226
+ return { ok: true, session };
227
+ }
228
+ // Phase 6 (Refs #399): recover persisted sessions + turn-list pointers from
229
+ // disk before the store serves any request.
230
+ rehydrate();
231
+ return {
232
+ create,
233
+ list,
234
+ get,
235
+ getNativeRef,
236
+ appendTurnHash,
237
+ close,
238
+ activeCount,
239
+ tryActivate,
240
+ markIdle,
241
+ };
242
+ }
243
+ /**
244
+ * Transition table for `tryActivate`-style edges. Returns the literal `"ok"`
245
+ * if the transition is allowed, or a string reason if it is not.
246
+ */
247
+ function transitionTo(from, to) {
248
+ if (to === "active") {
249
+ if (from === "idle")
250
+ return "ok";
251
+ if (from === "active")
252
+ return "busy";
253
+ return "closed"; // from === "closed"
254
+ }
255
+ // Exhaustive: only "active" is a valid `to` here.
256
+ return "ok";
257
+ }
258
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/session/store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Per-send in-memory ring buffer for SSE resume support.
3
+ *
4
+ * Each `POST /gateways/:name/sessions/:id/messages` call creates a fresh
5
+ * buffer keyed by `(gateway, sessionId, sendNonce)`. Events are appended in
6
+ * id order; the most-recent N events are retained (N = `sseBufferSize`,
7
+ * default 1024). After `event: done` the buffer is held for `retentionMs`
8
+ * (default 30_000) to allow late clients to resume.
9
+ *
10
+ * Resume is a pure replay — `adapter.send` is NOT called twice. The buffer
11
+ * is the canonical record of what the original send produced.
12
+ */
13
+ export type SseEvent = {
14
+ id: number;
15
+ event: string;
16
+ /** Stringified data line — already JSON-serialized envelope. */
17
+ data: string;
18
+ };
19
+ export type SseBuffer = {
20
+ gateway: string;
21
+ sessionId: string;
22
+ nonce: string;
23
+ events: SseEvent[];
24
+ maxId: number;
25
+ doneAt: number | null;
26
+ maxSize: number;
27
+ /** Resolved when the underlying send is finished (success or failure). */
28
+ finished: boolean;
29
+ };
30
+ export type SseBufferStore = {
31
+ create: (gateway: string, sessionId: string) => SseBuffer;
32
+ getLatestForSession: (gateway: string, sessionId: string) => SseBuffer | null;
33
+ finish: (buf: SseBuffer) => void;
34
+ purgeExpired: (now: number) => void;
35
+ wasRecentlyExpired: (gateway: string, sessionId: string) => boolean;
36
+ };
37
+ export type SseBufferOptions = {
38
+ maxSize: number;
39
+ retentionMs: number;
40
+ };
41
+ export declare function createSseBufferStore(options: SseBufferOptions): SseBufferStore;
42
+ /** Append an event to a buffer, maintaining `maxSize` ring semantics. */
43
+ export declare function appendEvent(buf: SseBuffer, event: string, data: string): SseEvent;
44
+ /** Wire-format an event: `id: ...\nevent: ...\ndata: ...\n\n`. */
45
+ export declare function formatEvent(evt: SseEvent): string;
46
+ /** Range-replay events whose id is strictly greater than `since`. */
47
+ export declare function eventsAfter(buf: SseBuffer, since: number): SseEvent[];
48
+ /**
49
+ * Lowest still-buffered event id, or 0 when empty. Useful when computing
50
+ * whether a Last-Event-ID predates the ring's current window.
51
+ */
52
+ export declare function lowestId(buf: SseBuffer): number;
53
+ //# sourceMappingURL=buffer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"buffer.d.ts","sourceRoot":"","sources":["../../src/sse/buffer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,MAAM,MAAM,QAAQ,GAAG;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,gEAAgE;IAChE,IAAI,EAAE,MAAM,CAAC;CACb,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,QAAQ,EAAE,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,0EAA0E;IAC1E,QAAQ,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC5B,MAAM,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,SAAS,CAAC;IAC1D,mBAAmB,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,SAAS,GAAG,IAAI,CAAC;IAC9E,MAAM,EAAE,CAAC,GAAG,EAAE,SAAS,KAAK,IAAI,CAAC;IACjC,YAAY,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,kBAAkB,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC;CACpE,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;CACpB,CAAC;AASF,wBAAgB,oBAAoB,CACnC,OAAO,EAAE,gBAAgB,GACvB,cAAc,CAiFhB;AAED,yEAAyE;AACzE,wBAAgB,WAAW,CAC1B,GAAG,EAAE,SAAS,EACd,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,GACV,QAAQ,CASV;AAED,kEAAkE;AAClE,wBAAgB,WAAW,CAAC,GAAG,EAAE,QAAQ,GAAG,MAAM,CAEjD;AAED,qEAAqE;AACrE,wBAAgB,WAAW,CAAC,GAAG,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,QAAQ,EAAE,CAGrE;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,SAAS,GAAG,MAAM,CAG/C"}