@openchamber/sdk 1.23.2-preview.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 (75) hide show
  1. package/API.md +529 -0
  2. package/DOCUMENTATION.md +81 -0
  3. package/GUEST_SERVICES.md +166 -0
  4. package/LICENSE +21 -0
  5. package/README.md +186 -0
  6. package/dist/api-version.d.ts +6 -0
  7. package/dist/api-version.js +5 -0
  8. package/dist/contract.d.ts +471 -0
  9. package/dist/contract.js +256 -0
  10. package/dist/host-version.d.ts +16 -0
  11. package/dist/host-version.js +50 -0
  12. package/dist/host.d.ts +101 -0
  13. package/dist/host.js +606 -0
  14. package/dist/index.d.ts +12 -0
  15. package/dist/index.js +7 -0
  16. package/dist/manifest.d.ts +298 -0
  17. package/dist/manifest.js +224 -0
  18. package/dist/parse.d.ts +279 -0
  19. package/dist/parse.js +379 -0
  20. package/dist/protocol.d.ts +1092 -0
  21. package/dist/protocol.js +450 -0
  22. package/dist/schemas.d.ts +4 -0
  23. package/dist/schemas.js +6 -0
  24. package/dist/ui/badge.d.ts +10 -0
  25. package/dist/ui/badge.js +26 -0
  26. package/dist/ui/banner.d.ts +13 -0
  27. package/dist/ui/banner.js +48 -0
  28. package/dist/ui/button.d.ts +14 -0
  29. package/dist/ui/button.js +49 -0
  30. package/dist/ui/checkbox.d.ts +12 -0
  31. package/dist/ui/checkbox.js +45 -0
  32. package/dist/ui/dom.d.ts +15 -0
  33. package/dist/ui/dom.js +60 -0
  34. package/dist/ui/empty.d.ts +11 -0
  35. package/dist/ui/empty.js +44 -0
  36. package/dist/ui/field.d.ts +18 -0
  37. package/dist/ui/field.js +49 -0
  38. package/dist/ui/icons.d.ts +10 -0
  39. package/dist/ui/icons.js +23 -0
  40. package/dist/ui/index.d.ts +35 -0
  41. package/dist/ui/index.js +17 -0
  42. package/dist/ui/list.d.ts +26 -0
  43. package/dist/ui/list.js +90 -0
  44. package/dist/ui/menu.d.ts +20 -0
  45. package/dist/ui/menu.js +111 -0
  46. package/dist/ui/navigation.d.ts +18 -0
  47. package/dist/ui/navigation.js +38 -0
  48. package/dist/ui/option.d.ts +16 -0
  49. package/dist/ui/option.js +35 -0
  50. package/dist/ui/popup.d.ts +5 -0
  51. package/dist/ui/popup.js +43 -0
  52. package/dist/ui/progress.d.ts +11 -0
  53. package/dist/ui/progress.js +44 -0
  54. package/dist/ui/search.d.ts +11 -0
  55. package/dist/ui/search.js +62 -0
  56. package/dist/ui/select.d.ts +22 -0
  57. package/dist/ui/select.js +164 -0
  58. package/dist/ui/separator.d.ts +6 -0
  59. package/dist/ui/separator.js +26 -0
  60. package/dist/ui/spinner.d.ts +8 -0
  61. package/dist/ui/spinner.js +29 -0
  62. package/dist/ui/style.d.ts +1 -0
  63. package/dist/ui/style.js +190 -0
  64. package/dist/ui/tabs.d.ts +15 -0
  65. package/dist/ui/tabs.js +61 -0
  66. package/dist/ui/text.d.ts +23 -0
  67. package/dist/ui/text.js +89 -0
  68. package/dist/ui/theme.d.ts +22 -0
  69. package/dist/ui/theme.js +69 -0
  70. package/dist/workspace-schemas.d.ts +136 -0
  71. package/dist/workspace-schemas.js +44 -0
  72. package/dist/workspace.d.ts +109 -0
  73. package/dist/workspace.js +4 -0
  74. package/package.json +55 -0
  75. package/scripts/bundle-guest.ts +44 -0
@@ -0,0 +1,61 @@
1
+ import { button, clearNode, el, ensureStyle } from "./dom.js";
2
+ import { moveListSelection, navigationKey } from "./navigation.js";
3
+ import { UI_CSS } from "./style.js";
4
+ export const mountTabs = (root, initial) => {
5
+ ensureStyle(UI_CSS);
6
+ let props = initial;
7
+ const track = el('div', 'oc-sdk oc-sdk-tabs');
8
+ track.setAttribute('role', 'tablist');
9
+ root.append(track);
10
+ const paint = () => {
11
+ clearNode(track);
12
+ track.dataset.track = props.trackBackground ? 'true' : 'false';
13
+ for (const item of props.items) {
14
+ const tab = button('oc-sdk-tab');
15
+ tab.setAttribute('role', 'tab');
16
+ const active = item.id === props.activeId;
17
+ tab.setAttribute('aria-selected', active ? 'true' : 'false');
18
+ tab.tabIndex = active ? 0 : -1;
19
+ tab.dataset.id = item.id;
20
+ const label = el('span');
21
+ label.textContent = item.label;
22
+ tab.append(label);
23
+ if (item.count !== undefined) {
24
+ const count = el('span', 'oc-sdk-tab-count');
25
+ count.textContent = String(item.count);
26
+ tab.append(count);
27
+ }
28
+ tab.addEventListener('click', () => {
29
+ if (item.id !== props.activeId)
30
+ props.onChange(item.id);
31
+ });
32
+ track.append(tab);
33
+ }
34
+ };
35
+ const onKeyDown = (event) => {
36
+ const step = navigationKey(event, 'horizontal');
37
+ if (!step) {
38
+ return;
39
+ }
40
+ const next = moveListSelection(props.items, props.activeId, step);
41
+ if (next && next !== props.activeId) {
42
+ event.preventDefault();
43
+ props.onChange(next);
44
+ const tab = track.querySelector(`[data-id="${CSS.escape(next)}"]`);
45
+ if (tab instanceof HTMLElement)
46
+ tab.focus();
47
+ }
48
+ };
49
+ track.addEventListener('keydown', onKeyDown);
50
+ paint();
51
+ return {
52
+ update: (next) => {
53
+ props = { ...props, ...next };
54
+ paint();
55
+ },
56
+ dispose: () => {
57
+ track.removeEventListener('keydown', onKeyDown);
58
+ track.remove();
59
+ },
60
+ };
61
+ };
@@ -0,0 +1,23 @@
1
+ import { type Handle } from './dom.ts';
2
+ export type TextPart = {
3
+ kind: 'text';
4
+ text: string;
5
+ } | {
6
+ kind: 'image';
7
+ src: string;
8
+ alt: string;
9
+ } | {
10
+ kind: 'link';
11
+ href: string;
12
+ label: string;
13
+ };
14
+ export type TextProps = {
15
+ text: string;
16
+ /** Called with the href of a clicked link. A sandboxed iframe cannot open it alone. */
17
+ onOpenUrl?: (url: string) => void;
18
+ };
19
+ export type TextHandle = Handle<TextProps>;
20
+ export declare const isHttpUrl: (value: string) => boolean;
21
+ /** Splits text into plain runs, `![alt](https://…)` images, and `[label](https://…)` links. Anything else stays text. */
22
+ export declare const splitTextMedia: (text: string) => TextPart[];
23
+ export declare const mountText: (root: Element, initial: TextProps) => TextHandle;
@@ -0,0 +1,89 @@
1
+ import { clearNode, el, ensureStyle } from "./dom.js";
2
+ import { UI_CSS } from "./style.js";
3
+ const MARKDOWN_TOKEN = /(!?)\[([^\]]*)\]\((https?:\/\/[^)\s]+)\)/g;
4
+ export const isHttpUrl = (value) => {
5
+ try {
6
+ const url = new URL(value);
7
+ return url.protocol === 'http:' || url.protocol === 'https:';
8
+ }
9
+ catch {
10
+ return false;
11
+ }
12
+ };
13
+ /** Splits text into plain runs, `![alt](https://…)` images, and `[label](https://…)` links. Anything else stays text. */
14
+ export const splitTextMedia = (text) => {
15
+ const parts = [];
16
+ let last = 0;
17
+ for (const match of text.matchAll(MARKDOWN_TOKEN)) {
18
+ const index = match.index ?? 0;
19
+ if (index > last) {
20
+ parts.push({ kind: 'text', text: text.slice(last, index) });
21
+ }
22
+ const marker = match[1] ?? '';
23
+ const label = (match[2] ?? '').trim();
24
+ const href = match[3] ?? '';
25
+ if (!isHttpUrl(href)) {
26
+ parts.push({ kind: 'text', text: match[0] });
27
+ }
28
+ else if (marker === '!') {
29
+ parts.push({ kind: 'image', src: href, alt: label });
30
+ }
31
+ else {
32
+ parts.push({ kind: 'link', href, label: label || href });
33
+ }
34
+ last = index + match[0].length;
35
+ }
36
+ if (last < text.length) {
37
+ parts.push({ kind: 'text', text: text.slice(last) });
38
+ }
39
+ return parts;
40
+ };
41
+ export const mountText = (root, initial) => {
42
+ ensureStyle(UI_CSS);
43
+ let props = initial;
44
+ const node = el('div', 'oc-sdk oc-sdk-text');
45
+ root.append(node);
46
+ const onClick = (event) => {
47
+ if (!(event.target instanceof HTMLAnchorElement) || !props.onOpenUrl) {
48
+ return;
49
+ }
50
+ event.preventDefault();
51
+ props.onOpenUrl(event.target.href);
52
+ };
53
+ const paint = () => {
54
+ clearNode(node);
55
+ for (const part of splitTextMedia(props.text)) {
56
+ if (part.kind === 'text') {
57
+ node.append(document.createTextNode(part.text));
58
+ }
59
+ else if (part.kind === 'link') {
60
+ const link = el('a');
61
+ link.href = part.href;
62
+ link.rel = 'noopener noreferrer';
63
+ link.target = '_blank';
64
+ link.textContent = part.label;
65
+ node.append(link);
66
+ }
67
+ else {
68
+ const img = el('img');
69
+ img.src = part.src;
70
+ img.alt = part.alt;
71
+ img.loading = 'lazy';
72
+ img.referrerPolicy = 'no-referrer';
73
+ node.append(img);
74
+ }
75
+ }
76
+ };
77
+ node.addEventListener('click', onClick);
78
+ paint();
79
+ return {
80
+ update: (next) => {
81
+ props = { ...props, ...next };
82
+ paint();
83
+ },
84
+ dispose: () => {
85
+ node.removeEventListener('click', onClick);
86
+ node.remove();
87
+ },
88
+ };
89
+ };
@@ -0,0 +1,22 @@
1
+ import type { GuestHostSurface, HostTheme } from '../contract.ts';
2
+ export type ThemeRoot = {
3
+ style: {
4
+ colorScheme: string;
5
+ setProperty: (name: string, value: string) => void;
6
+ };
7
+ dataset?: {
8
+ ocSurface?: string;
9
+ ocTheme?: string;
10
+ };
11
+ };
12
+ /**
13
+ * Paint the host theme onto the iframe root. Guest chrome reads these
14
+ * variables, and the root itself gets the host font and text colour so plain
15
+ * DOM the guest draws outside the kit (a `<pre>`, a `<p>`) inherits them
16
+ * instead of the browser's serif default.
17
+ */
18
+ export declare const applyHostTheme: (theme: HostTheme, root: ThemeRoot) => void;
19
+ export declare const applyHostReady: (ctx: {
20
+ theme: HostTheme;
21
+ surface: GuestHostSurface;
22
+ }, root: ThemeRoot) => void;
@@ -0,0 +1,69 @@
1
+ const TOKEN_VARS = [
2
+ ['--oc-bg', 'background'],
3
+ ['--oc-elevated', 'elevated'],
4
+ ['--oc-fg', 'foreground'],
5
+ ['--oc-muted', 'muted'],
6
+ ['--oc-subtle', 'subtle'],
7
+ ['--oc-border', 'border'],
8
+ ['--oc-hover', 'hover'],
9
+ ['--oc-selection', 'selection'],
10
+ ['--oc-focus', 'focus'],
11
+ ['--oc-primary', 'primary'],
12
+ ['--oc-muted-surface', 'mutedSurface'],
13
+ ['--oc-elevated-fg', 'elevatedForeground'],
14
+ ['--oc-active', 'active'],
15
+ ['--oc-selection-fg', 'selectionForeground'],
16
+ ['--oc-primary-fg', 'primaryForeground'],
17
+ ['--oc-success', 'success'],
18
+ ['--oc-warning', 'warning'],
19
+ ['--oc-error', 'error'],
20
+ ['--oc-info', 'info'],
21
+ ['--oc-font', 'font'],
22
+ ['--oc-mono', 'mono'],
23
+ ['--oc-radius', 'radius'],
24
+ ['--surface-background', 'background'],
25
+ ['--surface-elevated', 'elevated'],
26
+ ['--surface-foreground', 'foreground'],
27
+ ['--surface-muted-foreground', 'muted'],
28
+ ['--surface-subtle', 'subtle'],
29
+ ['--interactive-border', 'border'],
30
+ ['--interactive-hover', 'hover'],
31
+ ['--interactive-selection', 'selection'],
32
+ ['--interactive-focus-ring', 'focus'],
33
+ ['--primary', 'primary'],
34
+ ['--surface-muted', 'mutedSurface'],
35
+ ['--surface-elevated-foreground', 'elevatedForeground'],
36
+ ['--interactive-active', 'active'],
37
+ ['--interactive-selection-foreground', 'selectionForeground'],
38
+ ['--primary-foreground', 'primaryForeground'],
39
+ ['--status-success', 'success'],
40
+ ['--status-warning', 'warning'],
41
+ ['--status-error', 'error'],
42
+ ['--status-info', 'info'],
43
+ ['--font-sans', 'font'],
44
+ ['--font-mono', 'mono'],
45
+ ['--radius', 'radius'],
46
+ ];
47
+ /**
48
+ * Paint the host theme onto the iframe root. Guest chrome reads these
49
+ * variables, and the root itself gets the host font and text colour so plain
50
+ * DOM the guest draws outside the kit (a `<pre>`, a `<p>`) inherits them
51
+ * instead of the browser's serif default.
52
+ */
53
+ export const applyHostTheme = (theme, root) => {
54
+ root.style.colorScheme = theme.mode;
55
+ for (const [name, key] of TOKEN_VARS) {
56
+ root.style.setProperty(name, theme.tokens[key]);
57
+ }
58
+ root.style.setProperty('font-family', theme.tokens.font);
59
+ root.style.setProperty('font-size', '0.875rem');
60
+ root.style.setProperty('line-height', '1.45');
61
+ root.style.setProperty('color', theme.tokens.foreground);
62
+ };
63
+ export const applyHostReady = (ctx, root) => {
64
+ applyHostTheme(ctx.theme, root);
65
+ if (root.dataset) {
66
+ root.dataset.ocSurface = ctx.surface;
67
+ root.dataset.ocTheme = ctx.theme.mode;
68
+ }
69
+ };
@@ -0,0 +1,136 @@
1
+ import { z } from 'zod';
2
+ export declare const guestWorkspaceQuerySchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
3
+ kind: z.ZodLiteral<"projects">;
4
+ }, z.core.$strict>, z.ZodObject<{
5
+ kind: z.ZodLiteral<"worktrees">;
6
+ projectId: z.ZodString;
7
+ }, z.core.$strict>, z.ZodObject<{
8
+ kind: z.ZodLiteral<"sessions">;
9
+ projectId: z.ZodString;
10
+ }, z.core.$strict>], "kind">;
11
+ export declare const guestWorkspaceSnapshotSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
12
+ kind: z.ZodLiteral<"projects">;
13
+ state: z.ZodEnum<{
14
+ loading: "loading";
15
+ ready: "ready";
16
+ error: "error";
17
+ }>;
18
+ projects: z.ZodArray<z.ZodObject<{
19
+ id: z.ZodString;
20
+ name: z.ZodString;
21
+ directory: z.ZodString;
22
+ }, z.core.$strip>>;
23
+ }, z.core.$strip>, z.ZodObject<{
24
+ kind: z.ZodLiteral<"worktrees">;
25
+ projectId: z.ZodString;
26
+ state: z.ZodEnum<{
27
+ loading: "loading";
28
+ ready: "ready";
29
+ error: "error";
30
+ }>;
31
+ worktrees: z.ZodArray<z.ZodObject<{
32
+ directory: z.ZodString;
33
+ name: z.ZodString;
34
+ branch: z.ZodString;
35
+ status: z.ZodEnum<{
36
+ ready: "ready";
37
+ pending: "pending";
38
+ invalid: "invalid";
39
+ missing: "missing";
40
+ }>;
41
+ }, z.core.$strip>>;
42
+ }, z.core.$strip>, z.ZodObject<{
43
+ kind: z.ZodLiteral<"sessions">;
44
+ projectId: z.ZodString;
45
+ state: z.ZodEnum<{
46
+ loading: "loading";
47
+ ready: "ready";
48
+ error: "error";
49
+ }>;
50
+ coverage: z.ZodArray<z.ZodObject<{
51
+ directory: z.ZodString;
52
+ state: z.ZodEnum<{
53
+ loading: "loading";
54
+ ready: "ready";
55
+ error: "error";
56
+ }>;
57
+ }, z.core.$strip>>;
58
+ sessions: z.ZodArray<z.ZodObject<{
59
+ id: z.ZodString;
60
+ title: z.ZodString;
61
+ projectId: z.ZodString;
62
+ directory: z.ZodString;
63
+ parentId: z.ZodNullable<z.ZodString>;
64
+ createdAt: z.ZodNumber;
65
+ updatedAt: z.ZodNumber;
66
+ archivedAt: z.ZodNullable<z.ZodNumber>;
67
+ worktree: z.ZodNullable<z.ZodObject<{
68
+ directory: z.ZodString;
69
+ name: z.ZodString;
70
+ branch: z.ZodString;
71
+ status: z.ZodEnum<{
72
+ ready: "ready";
73
+ pending: "pending";
74
+ invalid: "invalid";
75
+ missing: "missing";
76
+ }>;
77
+ }, z.core.$strip>>;
78
+ activity: z.ZodEnum<{
79
+ unknown: "unknown";
80
+ idle: "idle";
81
+ running: "running";
82
+ retrying: "retrying";
83
+ "waiting-permission": "waiting-permission";
84
+ "waiting-question": "waiting-question";
85
+ }>;
86
+ outcome: z.ZodNullable<z.ZodEnum<{
87
+ completed: "completed";
88
+ failed: "failed";
89
+ }>>;
90
+ items: z.ZodArray<z.ZodObject<{
91
+ id: z.ZodString;
92
+ data: z.ZodOptional<z.ZodJSONSchema>;
93
+ }, z.core.$strip>>;
94
+ }, z.core.$strip>>;
95
+ }, z.core.$strip>], "kind">;
96
+ export declare const guestSessionWorktreeSchema: z.ZodUnion<readonly [z.ZodBoolean, z.ZodObject<{
97
+ kind: z.ZodLiteral<"existing">;
98
+ directory: z.ZodString;
99
+ }, z.core.$strict>, z.ZodObject<{
100
+ kind: z.ZodLiteral<"new">;
101
+ name: z.ZodOptional<z.ZodString>;
102
+ baseBranch: z.ZodOptional<z.ZodString>;
103
+ }, z.core.$strict>]>;
104
+ export declare const guestStorageRequestSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
105
+ op: z.ZodLiteral<"get">;
106
+ key: z.ZodString;
107
+ }, z.core.$strict>, z.ZodObject<{
108
+ op: z.ZodLiteral<"delete">;
109
+ key: z.ZodString;
110
+ }, z.core.$strict>, z.ZodObject<{
111
+ op: z.ZodLiteral<"set">;
112
+ key: z.ZodString;
113
+ value: z.ZodJSONSchema;
114
+ }, z.core.$strict>, z.ZodObject<{
115
+ op: z.ZodLiteral<"keys">;
116
+ }, z.core.$strict>], "op">;
117
+ export declare const guestStorageResultSchema: z.ZodUnion<readonly [z.ZodObject<{
118
+ storage: z.ZodLiteral<true>;
119
+ op: z.ZodLiteral<"get">;
120
+ found: z.ZodLiteral<false>;
121
+ }, z.core.$strip>, z.ZodObject<{
122
+ storage: z.ZodLiteral<true>;
123
+ op: z.ZodLiteral<"get">;
124
+ found: z.ZodLiteral<true>;
125
+ value: z.ZodJSONSchema;
126
+ }, z.core.$strip>, z.ZodObject<{
127
+ storage: z.ZodLiteral<true>;
128
+ op: z.ZodEnum<{
129
+ delete: "delete";
130
+ set: "set";
131
+ }>;
132
+ }, z.core.$strip>, z.ZodObject<{
133
+ storage: z.ZodLiteral<true>;
134
+ op: z.ZodLiteral<"keys">;
135
+ keys: z.ZodArray<z.ZodString>;
136
+ }, z.core.$strip>]>;
@@ -0,0 +1,44 @@
1
+ import { z } from 'zod';
2
+ import { GUEST_STORAGE_KEY_MAX, GUEST_STORAGE_VALUE_BYTES, GUEST_STORAGE_KEYS_MAX } from "./workspace.js";
3
+ const identity = z.string().trim().min(1).max(1024);
4
+ const state = z.enum(['loading', 'ready', 'error']);
5
+ const worktree = z.object({
6
+ directory: identity, name: z.string(), branch: z.string(),
7
+ status: z.enum(['ready', 'pending', 'invalid', 'missing']),
8
+ });
9
+ export const guestWorkspaceQuerySchema = z.discriminatedUnion('kind', [
10
+ z.object({ kind: z.literal('projects') }).strict(),
11
+ z.object({ kind: z.literal('worktrees'), projectId: identity }).strict(),
12
+ z.object({ kind: z.literal('sessions'), projectId: identity }).strict(),
13
+ ]);
14
+ export const guestWorkspaceSnapshotSchema = z.discriminatedUnion('kind', [
15
+ z.object({ kind: z.literal('projects'), state, projects: z.array(z.object({ id: identity, name: z.string(), directory: identity })) }),
16
+ z.object({ kind: z.literal('worktrees'), projectId: identity, state, worktrees: z.array(worktree) }),
17
+ z.object({ kind: z.literal('sessions'), projectId: identity, state,
18
+ coverage: z.array(z.object({ directory: identity, state })),
19
+ sessions: z.array(z.object({
20
+ id: identity, title: z.string(), projectId: identity, directory: identity, parentId: identity.nullable(),
21
+ createdAt: z.number(), updatedAt: z.number(), archivedAt: z.number().nullable(), worktree: worktree.nullable(),
22
+ activity: z.enum(['unknown', 'idle', 'running', 'retrying', 'waiting-permission', 'waiting-question']),
23
+ outcome: z.enum(['completed', 'failed']).nullable(), items: z.array(z.object({ id: identity, data: z.json().optional() })),
24
+ })),
25
+ }),
26
+ ]);
27
+ export const guestSessionWorktreeSchema = z.union([
28
+ z.boolean(),
29
+ z.object({ kind: z.literal('existing'), directory: identity }).strict(),
30
+ z.object({ kind: z.literal('new'), name: z.string().trim().min(1).max(200).optional(), baseBranch: z.string().trim().min(1).max(200).optional() }).strict(),
31
+ ]);
32
+ const storageKey = z.string().min(1).max(GUEST_STORAGE_KEY_MAX);
33
+ export const guestStorageRequestSchema = z.discriminatedUnion('op', [
34
+ z.object({ op: z.literal('get'), key: storageKey }).strict(),
35
+ z.object({ op: z.literal('delete'), key: storageKey }).strict(),
36
+ z.object({ op: z.literal('set'), key: storageKey, value: z.json().refine((value) => new TextEncoder().encode(JSON.stringify(value)).length <= GUEST_STORAGE_VALUE_BYTES) }).strict(),
37
+ z.object({ op: z.literal('keys') }).strict(),
38
+ ]);
39
+ export const guestStorageResultSchema = z.union([
40
+ z.object({ storage: z.literal(true), op: z.literal('get'), found: z.literal(false) }),
41
+ z.object({ storage: z.literal(true), op: z.literal('get'), found: z.literal(true), value: z.json() }),
42
+ z.object({ storage: z.literal(true), op: z.enum(['set', 'delete']) }),
43
+ z.object({ storage: z.literal(true), op: z.literal('keys'), keys: z.array(storageKey).max(GUEST_STORAGE_KEYS_MAX) }),
44
+ ]);
@@ -0,0 +1,109 @@
1
+ import type { JsonValue } from './contract.ts';
2
+ export type GuestLoadState = 'loading' | 'ready' | 'error';
3
+ export type GuestProject = {
4
+ id: string;
5
+ name: string;
6
+ directory: string;
7
+ };
8
+ export type GuestWorktree = {
9
+ directory: string;
10
+ name: string;
11
+ branch: string;
12
+ status: 'ready' | 'pending' | 'invalid' | 'missing';
13
+ };
14
+ export type GuestSessionActivity = 'unknown' | 'idle' | 'running' | 'retrying' | 'waiting-permission' | 'waiting-question';
15
+ export type GuestSessionRecord = {
16
+ id: string;
17
+ title: string;
18
+ projectId: string;
19
+ directory: string;
20
+ parentId: string | null;
21
+ createdAt: number;
22
+ updatedAt: number;
23
+ archivedAt: number | null;
24
+ worktree: GuestWorktree | null;
25
+ activity: GuestSessionActivity;
26
+ /** Observed turn outcome, never a task status. Unknown history stays null. */
27
+ outcome: 'completed' | 'failed' | null;
28
+ items: {
29
+ id: string;
30
+ data?: JsonValue;
31
+ }[];
32
+ };
33
+ export type GuestDirectoryCoverage = {
34
+ directory: string;
35
+ state: GuestLoadState;
36
+ };
37
+ export type GuestProjectsSnapshot = {
38
+ kind: 'projects';
39
+ state: GuestLoadState;
40
+ projects: GuestProject[];
41
+ };
42
+ export type GuestWorktreesSnapshot = {
43
+ kind: 'worktrees';
44
+ projectId: string;
45
+ state: GuestLoadState;
46
+ worktrees: GuestWorktree[];
47
+ };
48
+ export type GuestSessionsSnapshot = {
49
+ kind: 'sessions';
50
+ projectId: string;
51
+ state: GuestLoadState;
52
+ coverage: GuestDirectoryCoverage[];
53
+ sessions: GuestSessionRecord[];
54
+ };
55
+ export type GuestWorkspaceSnapshot = GuestProjectsSnapshot | GuestWorktreesSnapshot | GuestSessionsSnapshot;
56
+ export type GuestWorkspaceQuery = {
57
+ kind: 'projects';
58
+ } | {
59
+ kind: 'worktrees';
60
+ projectId: string;
61
+ } | {
62
+ kind: 'sessions';
63
+ projectId: string;
64
+ };
65
+ export type GuestWorkspaceSubscription = {
66
+ subscriptionId: string;
67
+ query: GuestWorkspaceQuery;
68
+ };
69
+ export type GuestWorkspaceUpdate = {
70
+ subscriptionId: string;
71
+ snapshot: GuestWorkspaceSnapshot;
72
+ };
73
+ export declare const GUEST_STORAGE_KEY_MAX = 128;
74
+ export declare const GUEST_STORAGE_VALUE_BYTES = 65536;
75
+ export declare const GUEST_STORAGE_TOTAL_BYTES = 2097152;
76
+ export declare const GUEST_STORAGE_KEYS_MAX = 2000;
77
+ export type GuestStorageRequest = {
78
+ op: 'get' | 'delete';
79
+ key: string;
80
+ } | {
81
+ op: 'set';
82
+ key: string;
83
+ value: JsonValue;
84
+ } | {
85
+ op: 'keys';
86
+ };
87
+ export type GuestStorageResult = {
88
+ storage: true;
89
+ } & ({
90
+ op: 'get';
91
+ found: false;
92
+ } | {
93
+ op: 'get';
94
+ found: true;
95
+ value: JsonValue;
96
+ } | {
97
+ op: 'set' | 'delete';
98
+ } | {
99
+ op: 'keys';
100
+ keys: string[];
101
+ });
102
+ export type GuestSessionWorktree = boolean | {
103
+ kind: 'existing';
104
+ directory: string;
105
+ } | {
106
+ kind: 'new';
107
+ name?: string;
108
+ baseBranch?: string;
109
+ };
@@ -0,0 +1,4 @@
1
+ export const GUEST_STORAGE_KEY_MAX = 128;
2
+ export const GUEST_STORAGE_VALUE_BYTES = 65_536;
3
+ export const GUEST_STORAGE_TOTAL_BYTES = 2_097_152;
4
+ export const GUEST_STORAGE_KEYS_MAX = 2_000;
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "@openchamber/sdk",
3
+ "version": "1.23.2-preview.1",
4
+ "description": "Contract for OpenChamber guests: manifest parse and iframe host bridge.",
5
+ "private": false,
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/openchamber/openchamber.git",
10
+ "directory": "packages/sdk"
11
+ },
12
+ "type": "module",
13
+ "main": "dist/index.js",
14
+ "types": "dist/index.d.ts",
15
+ "exports": {
16
+ ".": {
17
+ "types": "./dist/index.d.ts",
18
+ "default": "./dist/index.js"
19
+ },
20
+ "./ui": {
21
+ "types": "./dist/ui/index.d.ts",
22
+ "default": "./dist/ui/index.js"
23
+ },
24
+ "./schemas": {
25
+ "types": "./dist/schemas.d.ts",
26
+ "default": "./dist/schemas.js"
27
+ }
28
+ },
29
+ "bin": {
30
+ "openchamber-guest-bundle": "./scripts/bundle-guest.ts"
31
+ },
32
+ "publishConfig": {
33
+ "access": "public"
34
+ },
35
+ "scripts": {
36
+ "build": "tsc -p tsconfig.build.json",
37
+ "type-check": "tsc --noEmit",
38
+ "lint": "eslint \"./src/**/*.ts\" --config ../../eslint.config.js",
39
+ "bundle": "bun scripts/bundle-guest.ts",
40
+ "test": "node ../../scripts/run-isolated-tests.mjs src scripts",
41
+ "clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\""
42
+ },
43
+ "files": [
44
+ "dist",
45
+ "scripts/bundle-guest.ts",
46
+ "README.md",
47
+ "API.md",
48
+ "DOCUMENTATION.md",
49
+ "GUEST_SERVICES.md",
50
+ "LICENSE"
51
+ ],
52
+ "dependencies": {
53
+ "zod": "^4.3.6"
54
+ }
55
+ }
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env bun
2
+ import { writeFile } from 'node:fs/promises';
3
+ import { resolve } from 'node:path';
4
+
5
+ const usage = 'Usage: openchamber-guest-bundle [--node] <entry.ts> <outfile.js>';
6
+
7
+ const args = process.argv.slice(2);
8
+ const nodeTarget = args.includes('--node');
9
+ const [entry, outfile] = args.filter((arg) => arg !== '--node');
10
+ if (!entry || !outfile) {
11
+ console.error(usage);
12
+ process.exit(1);
13
+ }
14
+
15
+ const bun = globalThis.Bun;
16
+ if (!bun?.build) {
17
+ console.error('This bundle command needs Bun.');
18
+ process.exit(1);
19
+ }
20
+
21
+ // A panel runs in a sandboxed iframe that cannot load ESM, so it gets a
22
+ // browser IIFE. A local service runs under Node, so `--node` keeps ESM and
23
+ // leaves the Node built-ins external.
24
+ const result = await bun.build({
25
+ entrypoints: [resolve(entry)],
26
+ format: nodeTarget ? 'esm' : 'iife',
27
+ target: nodeTarget ? 'node' : 'browser',
28
+ minify: !nodeTarget,
29
+ write: false,
30
+ });
31
+
32
+ if (!result.success) {
33
+ const message = result.logs.map((log) => log.message).join('\n');
34
+ console.error(message || 'Guest script build failed');
35
+ process.exit(1);
36
+ }
37
+
38
+ const artifact = result.outputs[0];
39
+ if (!artifact) {
40
+ console.error('Guest script build produced no output');
41
+ process.exit(1);
42
+ }
43
+
44
+ await writeFile(resolve(outfile), await artifact.text());