@go-go-golems/os-scripting 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 (119) hide show
  1. package/README.md +147 -0
  2. package/app/createAppStore.d.ts +89 -0
  3. package/app/createAppStore.js +90 -0
  4. package/app/index.d.ts +2 -0
  5. package/app/index.js +2 -0
  6. package/app/runtimeSessionLifecycleMiddleware.d.ts +6 -0
  7. package/app/runtimeSessionLifecycleMiddleware.js +42 -0
  8. package/features/runtimeSessions/capabilityPolicy.d.ts +12 -0
  9. package/features/runtimeSessions/capabilityPolicy.js +36 -0
  10. package/features/runtimeSessions/index.d.ts +3 -0
  11. package/features/runtimeSessions/index.js +3 -0
  12. package/features/runtimeSessions/runtimeSessionsSlice.d.ts +90 -0
  13. package/features/runtimeSessions/runtimeSessionsSlice.js +210 -0
  14. package/features/runtimeSessions/selectors.d.ts +16 -0
  15. package/features/runtimeSessions/selectors.js +40 -0
  16. package/hypercard/artifacts/artifactProjectionMiddleware.d.ts +1 -0
  17. package/hypercard/artifacts/artifactProjectionMiddleware.js +66 -0
  18. package/hypercard/artifacts/artifactRuntime.d.ts +22 -0
  19. package/hypercard/artifacts/artifactRuntime.js +137 -0
  20. package/hypercard/artifacts/artifactsSelectors.d.ts +10 -0
  21. package/hypercard/artifacts/artifactsSelectors.js +2 -0
  22. package/hypercard/artifacts/artifactsSlice.d.ts +38 -0
  23. package/hypercard/artifacts/artifactsSlice.js +94 -0
  24. package/hypercard/debug/RuntimeSurfaceDebugWindow.d.ts +7 -0
  25. package/hypercard/debug/RuntimeSurfaceDebugWindow.js +181 -0
  26. package/hypercard/debug/jsSessionDebugRegistry.d.ts +11 -0
  27. package/hypercard/debug/jsSessionDebugRegistry.js +43 -0
  28. package/hypercard/debug/runtimeDebugApp.d.ts +19 -0
  29. package/hypercard/debug/runtimeDebugApp.js +25 -0
  30. package/hypercard/debug/runtimeDebugRegistry.d.ts +6 -0
  31. package/hypercard/debug/runtimeDebugRegistry.js +49 -0
  32. package/hypercard/editor/CodeEditorWindow.d.ts +7 -0
  33. package/hypercard/editor/CodeEditorWindow.js +128 -0
  34. package/hypercard/editor/editorLaunch.d.ts +16 -0
  35. package/hypercard/editor/editorLaunch.js +58 -0
  36. package/hypercard/editor/runtimeSurfaceRef.d.ts +8 -0
  37. package/hypercard/editor/runtimeSurfaceRef.js +60 -0
  38. package/hypercard/index.d.ts +12 -0
  39. package/hypercard/index.js +12 -0
  40. package/hypercard/task-manager/TaskManagerWindow.d.ts +1 -0
  41. package/hypercard/task-manager/TaskManagerWindow.js +102 -0
  42. package/hypercard/task-manager/index.d.ts +6 -0
  43. package/hypercard/task-manager/index.js +6 -0
  44. package/hypercard/task-manager/jsSessionSource.d.ts +10 -0
  45. package/hypercard/task-manager/jsSessionSource.js +49 -0
  46. package/hypercard/task-manager/runtimeSessionSource.d.ts +26 -0
  47. package/hypercard/task-manager/runtimeSessionSource.js +123 -0
  48. package/hypercard/task-manager/taskManagerApp.d.ts +16 -0
  49. package/hypercard/task-manager/taskManagerApp.js +25 -0
  50. package/hypercard/task-manager/taskManagerRegistry.d.ts +10 -0
  51. package/hypercard/task-manager/taskManagerRegistry.js +75 -0
  52. package/hypercard/task-manager/types.d.ts +25 -0
  53. package/hypercard/task-manager/types.js +1 -0
  54. package/hypercard/timeline/hypercardCard.d.ts +5 -0
  55. package/hypercard/timeline/hypercardCard.js +104 -0
  56. package/index.d.ts +18 -0
  57. package/index.js +18 -0
  58. package/package.json +72 -0
  59. package/plugin-runtime/contracts.d.ts +116 -0
  60. package/plugin-runtime/contracts.js +32 -0
  61. package/plugin-runtime/fixtures/column-stack.vm.js +19 -0
  62. package/plugin-runtime/fixtures/dynamic-card.vm.js +13 -0
  63. package/plugin-runtime/fixtures/inventory-stack.vm.js +29 -0
  64. package/plugin-runtime/fixtures/kanban-card.vm.js +55 -0
  65. package/plugin-runtime/fixtures/loop-stack.vm.js +16 -0
  66. package/plugin-runtime/fixtures/patched-low-stock-handler.vm.js +3 -0
  67. package/plugin-runtime/fixtures/patched-low-stock-render.vm.js +5 -0
  68. package/plugin-runtime/index.d.ts +6 -0
  69. package/plugin-runtime/index.js +6 -0
  70. package/plugin-runtime/intentSchema.d.ts +3 -0
  71. package/plugin-runtime/intentSchema.js +25 -0
  72. package/plugin-runtime/jsEvalSupport.d.ts +6 -0
  73. package/plugin-runtime/jsEvalSupport.js +93 -0
  74. package/plugin-runtime/jsSessionService.d.ts +55 -0
  75. package/plugin-runtime/jsSessionService.js +136 -0
  76. package/plugin-runtime/quickJsSessionCore.d.ts +24 -0
  77. package/plugin-runtime/quickJsSessionCore.js +92 -0
  78. package/plugin-runtime/runtimeService.d.ts +34 -0
  79. package/plugin-runtime/runtimeService.js +248 -0
  80. package/plugin-runtime/runtimeSurfaceRegistry.d.ts +45 -0
  81. package/plugin-runtime/runtimeSurfaceRegistry.js +73 -0
  82. package/plugin-runtime/stack-bootstrap.vm.js +236 -0
  83. package/repl/attachedJsSessionRegistry.d.ts +25 -0
  84. package/repl/attachedJsSessionRegistry.js +81 -0
  85. package/repl/attachedRuntimeSessionRegistry.d.ts +11 -0
  86. package/repl/attachedRuntimeSessionRegistry.js +107 -0
  87. package/repl/hypercardReplDriver.d.ts +54 -0
  88. package/repl/hypercardReplDriver.js +600 -0
  89. package/repl/jsReplDriver.d.ts +8 -0
  90. package/repl/jsReplDriver.js +348 -0
  91. package/repl/jsSessionBroker.d.ts +21 -0
  92. package/repl/jsSessionBroker.js +75 -0
  93. package/repl/runtimeBroker.d.ts +43 -0
  94. package/repl/runtimeBroker.js +117 -0
  95. package/runtime-host/RuntimeSurfaceSessionHost.d.ts +8 -0
  96. package/runtime-host/RuntimeSurfaceSessionHost.js +384 -0
  97. package/runtime-host/fixtures/CardSessionHost.chat.vm.js +61 -0
  98. package/runtime-host/fixtures/CardSessionHost.list.vm.js +29 -0
  99. package/runtime-host/fixtures/CardSessionHost.nav.vm.js +101 -0
  100. package/runtime-host/fixtures/CardSessionHost.report.vm.js +34 -0
  101. package/runtime-host/pluginIntentRouting.d.ts +13 -0
  102. package/runtime-host/pluginIntentRouting.js +83 -0
  103. package/runtime-packages/index.d.ts +1 -0
  104. package/runtime-packages/index.js +1 -0
  105. package/runtime-packages/runtimePackageRegistry.d.ts +14 -0
  106. package/runtime-packages/runtimePackageRegistry.js +42 -0
  107. package/runtime-packages/ui.package.vm.js +84 -0
  108. package/runtime-packs/index.d.ts +1 -0
  109. package/runtime-packs/index.js +1 -0
  110. package/runtime-packs/runtimeSurfaceTypeRegistry.d.ts +20 -0
  111. package/runtime-packs/runtimeSurfaceTypeRegistry.js +37 -0
  112. package/runtime-session-manager/index.d.ts +2 -0
  113. package/runtime-session-manager/index.js +2 -0
  114. package/runtime-session-manager/runtimeOwnership.d.ts +10 -0
  115. package/runtime-session-manager/runtimeOwnership.js +19 -0
  116. package/runtime-session-manager/runtimeSessionManager.d.ts +47 -0
  117. package/runtime-session-manager/runtimeSessionManager.js +214 -0
  118. package/testRuntimeUi.d.ts +4 -0
  119. package/testRuntimeUi.js +39 -0
@@ -0,0 +1,210 @@
1
+ import { createSlice, nanoid } from '@reduxjs/toolkit';
2
+ import { authorizeDomainIntent, authorizeSystemIntent, resolveCapabilityPolicy, } from './capabilityPolicy';
3
+ import { getRuntimeActionDomain, getRuntimeActionKind, getRuntimeActionOperation } from '../../plugin-runtime/contracts';
4
+ const MAX_TIMELINE_ENTRIES = 300;
5
+ const initialState = {
6
+ sessions: {},
7
+ timeline: [],
8
+ pendingDomainIntents: [],
9
+ pendingSystemIntents: [],
10
+ pendingNavIntents: [],
11
+ };
12
+ function isRecord(value) {
13
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
14
+ }
15
+ function deepSet(target, path, value) {
16
+ const keys = path.split('.').filter(Boolean);
17
+ if (keys.length === 0)
18
+ return;
19
+ let current = target;
20
+ for (let index = 0; index < keys.length - 1; index += 1) {
21
+ const key = keys[index];
22
+ const nextValue = current[key];
23
+ if (!isRecord(nextValue)) {
24
+ current[key] = {};
25
+ }
26
+ current = current[key];
27
+ }
28
+ current[keys[keys.length - 1]] = value;
29
+ }
30
+ function clearObject(target) {
31
+ for (const key of Object.keys(target)) {
32
+ delete target[key];
33
+ }
34
+ }
35
+ function applyLocalStateAction(target, operation, payload) {
36
+ if (operation === 'patch') {
37
+ if (!isRecord(payload)) {
38
+ return { outcome: 'ignored', reason: 'patch_requires_object_payload' };
39
+ }
40
+ Object.assign(target, payload);
41
+ return { outcome: 'applied', reason: null };
42
+ }
43
+ if (operation === 'set') {
44
+ if (!isRecord(payload) || typeof payload.path !== 'string') {
45
+ return { outcome: 'ignored', reason: 'set_requires_{path,value}_payload' };
46
+ }
47
+ deepSet(target, payload.path, payload.value);
48
+ return { outcome: 'applied', reason: null };
49
+ }
50
+ if (operation === 'reset') {
51
+ clearObject(target);
52
+ return { outcome: 'applied', reason: null };
53
+ }
54
+ return { outcome: 'ignored', reason: `unsupported_local_action:${operation}` };
55
+ }
56
+ function appendTimeline(state, payload, kind, outcome, reason) {
57
+ state.timeline.push({
58
+ id: payload.id,
59
+ timestamp: payload.timestamp,
60
+ sessionId: payload.sessionId,
61
+ surfaceId: payload.surfaceId,
62
+ kind,
63
+ actionType: payload.action.type,
64
+ payload: payload.action.payload,
65
+ outcome,
66
+ reason,
67
+ });
68
+ if (state.timeline.length > MAX_TIMELINE_ENTRIES) {
69
+ state.timeline.splice(0, state.timeline.length - MAX_TIMELINE_ENTRIES);
70
+ }
71
+ }
72
+ const runtimeSessionsSlice = createSlice({
73
+ name: 'runtimeSessions',
74
+ initialState,
75
+ reducers: {
76
+ registerRuntimeSession(state, action) {
77
+ const payload = action.payload;
78
+ state.sessions[payload.sessionId] = {
79
+ bundleId: payload.bundleId,
80
+ status: payload.status ?? 'loading',
81
+ error: null,
82
+ sessionState: payload.initialSessionState ? { ...payload.initialSessionState } : {},
83
+ surfaceState: payload.initialSurfaceState ? { ...payload.initialSurfaceState } : {},
84
+ capabilities: resolveCapabilityPolicy(payload.capabilities),
85
+ };
86
+ },
87
+ removeRuntimeSession(state, action) {
88
+ const { sessionId } = action.payload;
89
+ delete state.sessions[sessionId];
90
+ state.pendingDomainIntents = state.pendingDomainIntents.filter((intent) => intent.sessionId !== sessionId);
91
+ state.pendingSystemIntents = state.pendingSystemIntents.filter((intent) => intent.sessionId !== sessionId);
92
+ state.pendingNavIntents = state.pendingNavIntents.filter((intent) => intent.sessionId !== sessionId);
93
+ },
94
+ setRuntimeSessionStatus(state, action) {
95
+ const session = state.sessions[action.payload.sessionId];
96
+ if (!session) {
97
+ return;
98
+ }
99
+ session.status = action.payload.status;
100
+ session.error = action.payload.error ?? null;
101
+ },
102
+ ingestRuntimeAction: {
103
+ reducer(state, action) {
104
+ const payload = action.payload;
105
+ const session = state.sessions[payload.sessionId];
106
+ if (!session) {
107
+ appendTimeline(state, payload, 'unknown', 'denied', `missing_session:${payload.sessionId}`);
108
+ return;
109
+ }
110
+ const kind = getRuntimeActionKind(payload.action.type);
111
+ if (kind === 'draft') {
112
+ if (!session.surfaceState[payload.surfaceId]) {
113
+ session.surfaceState[payload.surfaceId] = {};
114
+ }
115
+ const result = applyLocalStateAction(session.surfaceState[payload.surfaceId], getRuntimeActionOperation(payload.action.type), payload.action.payload);
116
+ appendTimeline(state, payload, kind, result.outcome, result.reason);
117
+ return;
118
+ }
119
+ if (kind === 'filters') {
120
+ const result = applyLocalStateAction(session.sessionState, getRuntimeActionOperation(payload.action.type), payload.action.payload);
121
+ appendTimeline(state, payload, kind, result.outcome, result.reason);
122
+ return;
123
+ }
124
+ if (kind === 'domain') {
125
+ const domain = getRuntimeActionDomain(payload.action.type);
126
+ if (!domain) {
127
+ appendTimeline(state, payload, kind, 'ignored', `missing_domain_prefix:${payload.action.type}`);
128
+ return;
129
+ }
130
+ const decision = authorizeDomainIntent(session.capabilities, domain);
131
+ if (!decision.allowed) {
132
+ appendTimeline(state, payload, kind, 'denied', decision.reason);
133
+ return;
134
+ }
135
+ state.pendingDomainIntents.push({
136
+ id: payload.id,
137
+ timestamp: payload.timestamp,
138
+ sessionId: payload.sessionId,
139
+ surfaceId: payload.surfaceId,
140
+ domain,
141
+ type: payload.action.type,
142
+ payload: payload.action.payload,
143
+ });
144
+ appendTimeline(state, payload, kind, 'applied', null);
145
+ return;
146
+ }
147
+ if (kind === 'system') {
148
+ const decision = authorizeSystemIntent(session.capabilities, payload.action.type);
149
+ if (!decision.allowed) {
150
+ appendTimeline(state, payload, kind, 'denied', decision.reason);
151
+ return;
152
+ }
153
+ const queued = {
154
+ id: payload.id,
155
+ timestamp: payload.timestamp,
156
+ sessionId: payload.sessionId,
157
+ surfaceId: payload.surfaceId,
158
+ type: payload.action.type,
159
+ payload: payload.action.payload,
160
+ };
161
+ state.pendingSystemIntents.push(queued);
162
+ if (payload.action.type.startsWith('nav.')) {
163
+ state.pendingNavIntents.push(queued);
164
+ }
165
+ appendTimeline(state, payload, kind, 'applied', null);
166
+ return;
167
+ }
168
+ appendTimeline(state, payload, kind, 'ignored', `unsupported_action_type:${payload.action.type}`);
169
+ },
170
+ prepare(payload) {
171
+ return {
172
+ payload: {
173
+ ...payload,
174
+ id: nanoid(),
175
+ timestamp: payload.timestamp ?? new Date().toISOString(),
176
+ },
177
+ };
178
+ },
179
+ },
180
+ dequeuePendingDomainIntent(state, action) {
181
+ const id = action.payload?.id;
182
+ if (!id) {
183
+ state.pendingDomainIntents.shift();
184
+ return;
185
+ }
186
+ state.pendingDomainIntents = state.pendingDomainIntents.filter((intent) => intent.id !== id);
187
+ },
188
+ dequeuePendingSystemIntent(state, action) {
189
+ const id = action.payload?.id;
190
+ if (!id) {
191
+ state.pendingSystemIntents.shift();
192
+ return;
193
+ }
194
+ state.pendingSystemIntents = state.pendingSystemIntents.filter((intent) => intent.id !== id);
195
+ },
196
+ dequeuePendingNavIntent(state, action) {
197
+ const id = action.payload?.id;
198
+ if (!id) {
199
+ state.pendingNavIntents.shift();
200
+ return;
201
+ }
202
+ state.pendingNavIntents = state.pendingNavIntents.filter((intent) => intent.id !== id);
203
+ },
204
+ clearRuntimeTimeline(state) {
205
+ state.timeline = [];
206
+ },
207
+ },
208
+ });
209
+ export const { clearRuntimeTimeline, dequeuePendingDomainIntent, dequeuePendingNavIntent, dequeuePendingSystemIntent, ingestRuntimeAction, registerRuntimeSession, removeRuntimeSession, setRuntimeSessionStatus, } = runtimeSessionsSlice.actions;
210
+ export const runtimeSessionsReducer = runtimeSessionsSlice.reducer;
@@ -0,0 +1,16 @@
1
+ import type { CapabilitySet } from './capabilityPolicy';
2
+ import type { RuntimeSessionsState, RuntimeSessionsStateSlice, RuntimeSessionRecord } from './runtimeSessionsSlice';
3
+ export declare const selectRuntimeSessionsState: (state: RuntimeSessionsStateSlice) => RuntimeSessionsState;
4
+ export declare const selectRuntimeSession: (state: RuntimeSessionsStateSlice, sessionId: string) => RuntimeSessionRecord | undefined;
5
+ export declare const selectRuntimeSessionState: (state: RuntimeSessionsStateSlice, sessionId: string) => Record<string, unknown>;
6
+ export declare const selectRuntimeSurfaceState: (state: RuntimeSessionsStateSlice, sessionId: string, surfaceId: string) => Record<string, unknown>;
7
+ export declare const selectRuntimeTimeline: (state: RuntimeSessionsStateSlice) => import("./runtimeSessionsSlice").RuntimeTimelineEntry[];
8
+ export declare const selectPendingDomainIntents: (state: RuntimeSessionsStateSlice) => import("./runtimeSessionsSlice").DomainIntentEnvelope[];
9
+ export declare const selectPendingSystemIntents: (state: RuntimeSessionsStateSlice) => import("./runtimeSessionsSlice").SystemIntentEnvelope[];
10
+ export declare const selectPendingNavIntents: (state: RuntimeSessionsStateSlice) => import("./runtimeSessionsSlice").SystemIntentEnvelope[];
11
+ /**
12
+ * Returns the app slices that the runtime host currently projects into VM-facing state.
13
+ * The result is intended to be consumed with `useSelector(..., shallowEqual)` so callers
14
+ * rerender only when relevant slice references change.
15
+ */
16
+ export declare const selectProjectedRuntimeDomains: (state: unknown, allowedSlices?: CapabilitySet) => Record<string, unknown>;
@@ -0,0 +1,40 @@
1
+ const EMPTY_RUNTIME_OBJECT = Object.freeze({});
2
+ const projectedDomainsCache = new WeakMap();
3
+ const ALL_PROJECTED_DOMAINS_CACHE_KEY = '__all__';
4
+ function isRecord(value) {
5
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
6
+ }
7
+ export const selectRuntimeSessionsState = (state) => state.runtimeSessions;
8
+ export const selectRuntimeSession = (state, sessionId) => state.runtimeSessions.sessions[sessionId];
9
+ export const selectRuntimeSessionState = (state, sessionId) => state.runtimeSessions.sessions[sessionId]?.sessionState ?? EMPTY_RUNTIME_OBJECT;
10
+ export const selectRuntimeSurfaceState = (state, sessionId, surfaceId) => state.runtimeSessions.sessions[sessionId]?.surfaceState[surfaceId] ?? EMPTY_RUNTIME_OBJECT;
11
+ export const selectRuntimeTimeline = (state) => state.runtimeSessions.timeline;
12
+ export const selectPendingDomainIntents = (state) => state.runtimeSessions.pendingDomainIntents;
13
+ export const selectPendingSystemIntents = (state) => state.runtimeSessions.pendingSystemIntents;
14
+ export const selectPendingNavIntents = (state) => state.runtimeSessions.pendingNavIntents;
15
+ /**
16
+ * Returns the app slices that the runtime host currently projects into VM-facing state.
17
+ * The result is intended to be consumed with `useSelector(..., shallowEqual)` so callers
18
+ * rerender only when relevant slice references change.
19
+ */
20
+ export const selectProjectedRuntimeDomains = (state, allowedSlices = []) => {
21
+ if (!isRecord(state)) {
22
+ return EMPTY_RUNTIME_OBJECT;
23
+ }
24
+ if (Array.isArray(allowedSlices) && allowedSlices.length === 0) {
25
+ return EMPTY_RUNTIME_OBJECT;
26
+ }
27
+ const cacheKey = allowedSlices === 'all' ? ALL_PROJECTED_DOMAINS_CACHE_KEY : allowedSlices.join('\u0000');
28
+ const cachedByState = projectedDomainsCache.get(state);
29
+ const cached = cachedByState?.get(cacheKey);
30
+ if (cached) {
31
+ return cached;
32
+ }
33
+ const projected = Object.fromEntries((allowedSlices === 'all' ? Object.keys(state) : allowedSlices)
34
+ .filter((key) => isRecord(state[key]))
35
+ .map((key) => [key, state[key]]));
36
+ const nextCache = cachedByState ?? new Map();
37
+ nextCache.set(cacheKey, projected);
38
+ projectedDomainsCache.set(state, nextCache);
39
+ return projected;
40
+ };
@@ -0,0 +1 @@
1
+ export declare function createArtifactProjectionMiddleware(): import("@reduxjs/toolkit").ListenerMiddlewareInstance<unknown, import("@reduxjs/toolkit").ThunkDispatch<unknown, unknown, import("@reduxjs/toolkit").UnknownAction>, unknown>;
@@ -0,0 +1,66 @@
1
+ import { createListenerMiddleware } from '@reduxjs/toolkit';
2
+ import { timelineSlice } from '@go-go-golems/os-chat';
3
+ import { registerRuntimeSurface } from '../../plugin-runtime';
4
+ import { extractArtifactUpsertFromTimelineEntity } from './artifactRuntime';
5
+ import { upsertArtifact } from './artifactsSlice';
6
+ function runtimeSurfaceEntityIsFinal(entity) {
7
+ const rawStatus = entity.props && typeof entity.props === 'object'
8
+ ? entity.props.status
9
+ : undefined;
10
+ if (typeof rawStatus !== 'string') {
11
+ return true;
12
+ }
13
+ const status = rawStatus.trim().toLowerCase();
14
+ return status !== 'streaming' && status !== 'pending';
15
+ }
16
+ function projectArtifactFromEntity(dispatch, entity) {
17
+ if (!entity) {
18
+ return;
19
+ }
20
+ const upsert = extractArtifactUpsertFromTimelineEntity(entity.kind, entity.props);
21
+ if (!upsert) {
22
+ return;
23
+ }
24
+ const queueForInjection = runtimeSurfaceEntityIsFinal(entity) &&
25
+ Boolean(upsert.runtimeSurfaceId && upsert.runtimeSurfaceCode && upsert.packId);
26
+ dispatch(upsertArtifact({
27
+ ...upsert,
28
+ updatedAt: Date.now(),
29
+ queueForInjection,
30
+ }));
31
+ if (queueForInjection && upsert.runtimeSurfaceId && upsert.runtimeSurfaceCode && upsert.packId) {
32
+ registerRuntimeSurface(upsert.runtimeSurfaceId, upsert.runtimeSurfaceCode, upsert.packId);
33
+ }
34
+ }
35
+ export function createArtifactProjectionMiddleware() {
36
+ const listener = createListenerMiddleware();
37
+ listener.startListening({
38
+ actionCreator: timelineSlice.actions.addEntity,
39
+ effect: (action, api) => {
40
+ projectArtifactFromEntity(api.dispatch, action.payload.entity);
41
+ },
42
+ });
43
+ listener.startListening({
44
+ actionCreator: timelineSlice.actions.upsertEntity,
45
+ effect: (action, api) => {
46
+ projectArtifactFromEntity(api.dispatch, action.payload.entity);
47
+ },
48
+ });
49
+ listener.startListening({
50
+ actionCreator: timelineSlice.actions.applySnapshot,
51
+ effect: (action, api) => {
52
+ for (const entity of action.payload.entities) {
53
+ projectArtifactFromEntity(api.dispatch, entity);
54
+ }
55
+ },
56
+ });
57
+ listener.startListening({
58
+ actionCreator: timelineSlice.actions.mergeSnapshot,
59
+ effect: (action, api) => {
60
+ for (const entity of action.payload.entities) {
61
+ projectArtifactFromEntity(api.dispatch, entity);
62
+ }
63
+ },
64
+ });
65
+ return listener;
66
+ }
@@ -0,0 +1,22 @@
1
+ import type { OpenWindowPayload } from '@go-go-golems/os-core/desktop-core';
2
+ import type { ArtifactSource } from './artifactsSlice';
3
+ export interface ArtifactUpsert {
4
+ id: string;
5
+ title?: string;
6
+ template?: string;
7
+ data?: Record<string, unknown>;
8
+ source: ArtifactSource;
9
+ runtimeSurfaceId?: string;
10
+ runtimeSurfaceCode?: string;
11
+ packId?: string;
12
+ }
13
+ export declare function normalizeArtifactId(raw: unknown): string | undefined;
14
+ export declare function extractArtifactUpsertFromSem(type: string | undefined, data: Record<string, unknown>): ArtifactUpsert | undefined;
15
+ export declare function extractArtifactUpsertFromTimelineEntity(entityKind: string | undefined, props: unknown): ArtifactUpsert | undefined;
16
+ export declare function buildArtifactOpenWindowPayload(input: {
17
+ artifactId: string;
18
+ template?: string;
19
+ title?: string;
20
+ bundleId?: string;
21
+ runtimeSurfaceId?: string;
22
+ }): OpenWindowPayload | undefined;
@@ -0,0 +1,137 @@
1
+ import { recordField, stringField, structuredRecordFromUnknown } from '@go-go-golems/os-chat';
2
+ export function normalizeArtifactId(raw) {
3
+ if (typeof raw !== 'string') {
4
+ return undefined;
5
+ }
6
+ let value = raw.trim();
7
+ if (!value) {
8
+ return undefined;
9
+ }
10
+ for (let i = 0; i < 2; i += 1) {
11
+ const isDoubleQuoted = value.startsWith('"') && value.endsWith('"');
12
+ const isSingleQuoted = value.startsWith("'") && value.endsWith("'");
13
+ if (value.length > 1 && (isDoubleQuoted || isSingleQuoted)) {
14
+ value = value.slice(1, -1).trim();
15
+ continue;
16
+ }
17
+ break;
18
+ }
19
+ return value || undefined;
20
+ }
21
+ function artifactFromStructured(record) {
22
+ const payload = recordField(record, 'data');
23
+ const artifact = payload ? recordField(payload, 'artifact') : undefined;
24
+ const artifactId = normalizeArtifactId(artifact ? stringField(artifact, 'id') : undefined);
25
+ if (!artifactId) {
26
+ return undefined;
27
+ }
28
+ return {
29
+ id: artifactId,
30
+ title: stringField(record, 'title'),
31
+ data: artifact ? recordField(artifact, 'data') : undefined,
32
+ source: 'card',
33
+ };
34
+ }
35
+ function cardFieldsFromStructured(record) {
36
+ const payload = recordField(record, 'data');
37
+ const card = payload ? recordField(payload, 'card') : undefined;
38
+ const runtime = payload ? recordField(payload, 'runtime') : undefined;
39
+ if (!card) {
40
+ return {
41
+ packId: runtime ? stringField(runtime, 'pack') : undefined,
42
+ };
43
+ }
44
+ return {
45
+ runtimeSurfaceId: stringField(card, 'id'),
46
+ runtimeSurfaceCode: stringField(card, 'code'),
47
+ packId: runtime ? stringField(runtime, 'pack') : undefined,
48
+ };
49
+ }
50
+ function artifactFromCardResult(resultRecord) {
51
+ const upsert = artifactFromStructured(resultRecord);
52
+ if (!upsert) {
53
+ return undefined;
54
+ }
55
+ return {
56
+ ...upsert,
57
+ ...cardFieldsFromStructured(resultRecord),
58
+ };
59
+ }
60
+ function artifactFromTimelineCardProps(props) {
61
+ const resultRecord = structuredRecordFromUnknown(props.result) ??
62
+ structuredRecordFromUnknown(props.resultRaw) ??
63
+ props;
64
+ return artifactFromCardResult(resultRecord);
65
+ }
66
+ export function extractArtifactUpsertFromSem(type, data) {
67
+ if (!type) {
68
+ return undefined;
69
+ }
70
+ if (type === 'hypercard.card.v2') {
71
+ return artifactFromCardResult(data);
72
+ }
73
+ if (type !== 'timeline.upsert') {
74
+ return undefined;
75
+ }
76
+ const entity = recordField(data, 'entity');
77
+ if (!entity) {
78
+ return undefined;
79
+ }
80
+ const entityKind = stringField(entity, 'kind');
81
+ if (entityKind !== 'hypercard.card.v2') {
82
+ return undefined;
83
+ }
84
+ const props = recordField(entity, 'props');
85
+ if (!props) {
86
+ return undefined;
87
+ }
88
+ return artifactFromTimelineCardProps(props);
89
+ }
90
+ export function extractArtifactUpsertFromTimelineEntity(entityKind, props) {
91
+ if (String(entityKind || '') !== 'hypercard.card.v2') {
92
+ return undefined;
93
+ }
94
+ if (!props || typeof props !== 'object' || Array.isArray(props)) {
95
+ return undefined;
96
+ }
97
+ return artifactFromTimelineCardProps(props);
98
+ }
99
+ function sanitizeArtifactKey(value) {
100
+ return value.trim().toLowerCase().replace(/[^a-z0-9_-]+/g, '-');
101
+ }
102
+ function cleanString(value) {
103
+ if (typeof value !== 'string') {
104
+ return undefined;
105
+ }
106
+ const trimmed = value.trim();
107
+ return trimmed.length > 0 ? trimmed : undefined;
108
+ }
109
+ export function buildArtifactOpenWindowPayload(input) {
110
+ const artifactId = normalizeArtifactId(input.artifactId);
111
+ if (!artifactId) {
112
+ return undefined;
113
+ }
114
+ const runtimeSurfaceId = cleanString(input.runtimeSurfaceId);
115
+ if (!runtimeSurfaceId) {
116
+ return undefined;
117
+ }
118
+ const safeKey = sanitizeArtifactKey(artifactId);
119
+ const title = input.title?.trim() || `Artifact ${artifactId}`;
120
+ const bundleId = cleanString(input.bundleId) ?? 'inventory';
121
+ return {
122
+ id: `window:artifact:${safeKey}`,
123
+ title,
124
+ icon: '🃏',
125
+ bounds: { x: 220, y: 50, w: 520, h: 420 },
126
+ content: {
127
+ kind: 'surface',
128
+ surface: {
129
+ bundleId,
130
+ surfaceId: runtimeSurfaceId,
131
+ surfaceSessionId: `artifact-session:${safeKey}`,
132
+ param: artifactId,
133
+ },
134
+ },
135
+ dedupeKey: `artifact:${artifactId}`,
136
+ };
137
+ }
@@ -0,0 +1,10 @@
1
+ import type { ArtifactRecord } from './artifactsSlice';
2
+ interface ArtifactsStateShape {
3
+ byId: Record<string, ArtifactRecord>;
4
+ }
5
+ interface ArtifactsStateSlice {
6
+ hypercardArtifacts: ArtifactsStateShape;
7
+ }
8
+ export declare const selectArtifactsById: (state: ArtifactsStateSlice) => Record<string, ArtifactRecord>;
9
+ export declare const selectArtifactById: (state: ArtifactsStateSlice, artifactId: string) => ArtifactRecord | undefined;
10
+ export {};
@@ -0,0 +1,2 @@
1
+ export const selectArtifactsById = (state) => state.hypercardArtifacts.byId;
2
+ export const selectArtifactById = (state, artifactId) => state.hypercardArtifacts.byId[artifactId];
@@ -0,0 +1,38 @@
1
+ export type ArtifactSource = 'widget' | 'card';
2
+ export interface ArtifactRecord {
3
+ id: string;
4
+ title: string;
5
+ template: string;
6
+ data: Record<string, unknown>;
7
+ source: ArtifactSource;
8
+ updatedAt: number;
9
+ runtimeSurfaceId?: string;
10
+ runtimeSurfaceCode?: string;
11
+ packId?: string;
12
+ injectionStatus?: 'pending' | 'injected' | 'failed';
13
+ injectionError?: string;
14
+ }
15
+ export interface ArtifactsState {
16
+ byId: Record<string, ArtifactRecord>;
17
+ }
18
+ export declare const upsertArtifact: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
19
+ id: string;
20
+ title?: string;
21
+ template?: string;
22
+ data?: Record<string, unknown>;
23
+ source: ArtifactSource;
24
+ updatedAt?: number;
25
+ runtimeSurfaceId?: string;
26
+ runtimeSurfaceCode?: string;
27
+ packId?: string;
28
+ queueForInjection?: boolean;
29
+ }, "artifacts/upsertArtifact">, markRuntimeSurfaceInjectionResults: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
30
+ injectedSurfaceIds?: string[];
31
+ failed?: Array<{
32
+ surfaceId: string;
33
+ error?: string;
34
+ }>;
35
+ updatedAt?: number;
36
+ }, "artifacts/markRuntimeSurfaceInjectionResults">, clearArtifacts: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<"artifacts/clearArtifacts">;
37
+ export declare const artifactsReducer: import("@reduxjs/toolkit").Reducer<ArtifactsState>;
38
+ export declare const hypercardArtifactsReducer: import("@reduxjs/toolkit").Reducer<ArtifactsState>;
@@ -0,0 +1,94 @@
1
+ import { createSlice } from '@reduxjs/toolkit';
2
+ const initialState = {
3
+ byId: {},
4
+ };
5
+ function cleanString(value) {
6
+ if (typeof value !== 'string') {
7
+ return undefined;
8
+ }
9
+ const trimmed = value.trim();
10
+ return trimmed.length > 0 ? trimmed : undefined;
11
+ }
12
+ function cleanObject(value) {
13
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
14
+ return {};
15
+ }
16
+ return value;
17
+ }
18
+ const artifactsSlice = createSlice({
19
+ name: 'artifacts',
20
+ initialState,
21
+ reducers: {
22
+ upsertArtifact(state, action) {
23
+ const id = cleanString(action.payload.id);
24
+ if (!id) {
25
+ return;
26
+ }
27
+ const existing = state.byId[id];
28
+ const title = cleanString(action.payload.title) ?? existing?.title ?? id;
29
+ const template = cleanString(action.payload.template) ?? existing?.template ?? '';
30
+ const data = action.payload.data !== undefined
31
+ ? cleanObject(action.payload.data)
32
+ : existing?.data ?? {};
33
+ const source = action.payload.source ?? existing?.source ?? 'widget';
34
+ const updatedAt = action.payload.updatedAt ?? Date.now();
35
+ const runtimeSurfaceId = cleanString(action.payload.runtimeSurfaceId) ?? existing?.runtimeSurfaceId;
36
+ const runtimeSurfaceCode = cleanString(action.payload.runtimeSurfaceCode) ?? existing?.runtimeSurfaceCode;
37
+ const packId = cleanString(action.payload.packId) ?? existing?.packId;
38
+ const queueForInjection = action.payload.queueForInjection === true;
39
+ state.byId[id] = {
40
+ id,
41
+ title,
42
+ template,
43
+ data,
44
+ source,
45
+ updatedAt,
46
+ runtimeSurfaceId,
47
+ runtimeSurfaceCode,
48
+ packId,
49
+ injectionStatus: runtimeSurfaceCode && queueForInjection
50
+ ? (existing?.injectionStatus ?? 'pending')
51
+ : existing?.injectionStatus,
52
+ injectionError: existing?.injectionError,
53
+ };
54
+ },
55
+ markRuntimeSurfaceInjectionResults(state, action) {
56
+ const injected = new Set((action.payload.injectedSurfaceIds ?? [])
57
+ .map((id) => cleanString(id))
58
+ .filter((id) => typeof id === 'string'));
59
+ const failed = new Map();
60
+ for (const item of action.payload.failed ?? []) {
61
+ const surfaceId = cleanString(item.surfaceId);
62
+ if (!surfaceId)
63
+ continue;
64
+ failed.set(surfaceId, cleanString(item.error));
65
+ }
66
+ if (injected.size === 0 && failed.size === 0) {
67
+ return;
68
+ }
69
+ const updatedAt = action.payload.updatedAt ?? Date.now();
70
+ for (const artifact of Object.values(state.byId)) {
71
+ const runtimeSurfaceId = cleanString(artifact.runtimeSurfaceId);
72
+ if (!runtimeSurfaceId)
73
+ continue;
74
+ if (injected.has(runtimeSurfaceId)) {
75
+ artifact.injectionStatus = 'injected';
76
+ artifact.injectionError = undefined;
77
+ artifact.updatedAt = updatedAt;
78
+ continue;
79
+ }
80
+ if (failed.has(runtimeSurfaceId)) {
81
+ artifact.injectionStatus = 'failed';
82
+ artifact.injectionError = failed.get(runtimeSurfaceId);
83
+ artifact.updatedAt = updatedAt;
84
+ }
85
+ }
86
+ },
87
+ clearArtifacts(state) {
88
+ state.byId = {};
89
+ },
90
+ },
91
+ });
92
+ export const { upsertArtifact, markRuntimeSurfaceInjectionResults, clearArtifacts } = artifactsSlice.actions;
93
+ export const artifactsReducer = artifactsSlice.reducer;
94
+ export const hypercardArtifactsReducer = artifactsSlice.reducer;
@@ -0,0 +1,7 @@
1
+ import { type RuntimeBundleDefinition } from '@go-go-golems/os-core';
2
+ export interface RuntimeSurfaceDebugWindowProps {
3
+ ownerAppId: string;
4
+ bundles?: RuntimeBundleDefinition[];
5
+ initialStackId?: string;
6
+ }
7
+ export declare function RuntimeSurfaceDebugWindow({ ownerAppId, bundles, initialStackId, }: RuntimeSurfaceDebugWindowProps): import("react/jsx-runtime").JSX.Element;