@portel/photon-core 2.8.0 → 2.8.2

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 (66) hide show
  1. package/dist/base.js +1 -1
  2. package/dist/base.js.map +1 -1
  3. package/dist/channels/registry.js +7 -7
  4. package/dist/channels/registry.js.map +1 -1
  5. package/dist/collections/Collection.js +1 -1
  6. package/dist/collections/Collection.js.map +1 -1
  7. package/dist/collections/ReactiveArray.d.ts +7 -2
  8. package/dist/collections/ReactiveArray.d.ts.map +1 -1
  9. package/dist/collections/ReactiveArray.js +21 -0
  10. package/dist/collections/ReactiveArray.js.map +1 -1
  11. package/dist/decorators.js +1 -1
  12. package/dist/decorators.js.map +1 -1
  13. package/dist/design-system/index.js +1 -1
  14. package/dist/design-system/index.js.map +1 -1
  15. package/dist/design-system/oklch.d.ts +119 -0
  16. package/dist/design-system/oklch.d.ts.map +1 -0
  17. package/dist/design-system/oklch.js +354 -0
  18. package/dist/design-system/oklch.js.map +1 -0
  19. package/dist/design-system/tokens.d.ts +5 -4
  20. package/dist/design-system/tokens.d.ts.map +1 -1
  21. package/dist/design-system/tokens.js +16 -14
  22. package/dist/design-system/tokens.js.map +1 -1
  23. package/dist/generator.d.ts +0 -48
  24. package/dist/generator.d.ts.map +1 -1
  25. package/dist/generator.js +0 -26
  26. package/dist/generator.js.map +1 -1
  27. package/dist/index.d.ts +1 -1
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +1 -3
  30. package/dist/index.js.map +1 -1
  31. package/dist/mcp-apps.d.ts.map +1 -1
  32. package/dist/mcp-apps.js +1 -2
  33. package/dist/mcp-apps.js.map +1 -1
  34. package/dist/memory.d.ts +4 -1
  35. package/dist/memory.d.ts.map +1 -1
  36. package/dist/memory.js +49 -25
  37. package/dist/memory.js.map +1 -1
  38. package/dist/rendering/layout-selector.js +2 -2
  39. package/dist/rendering/layout-selector.js.map +1 -1
  40. package/dist/schema-extractor.d.ts.map +1 -1
  41. package/dist/schema-extractor.js +13 -1
  42. package/dist/schema-extractor.js.map +1 -1
  43. package/dist/stateful.d.ts +1 -2
  44. package/dist/stateful.d.ts.map +1 -1
  45. package/dist/stateful.js +30 -13
  46. package/dist/stateful.js.map +1 -1
  47. package/dist/types.d.ts +7 -3
  48. package/dist/types.d.ts.map +1 -1
  49. package/dist/types.js.map +1 -1
  50. package/package.json +1 -1
  51. package/src/base.ts +1 -1
  52. package/src/channels/registry.ts +7 -7
  53. package/src/collections/Collection.ts +4 -4
  54. package/src/collections/ReactiveArray.ts +24 -2
  55. package/src/decorators.ts +1 -1
  56. package/src/design-system/index.ts +1 -1
  57. package/src/design-system/oklch.ts +493 -0
  58. package/src/design-system/tokens.ts +17 -14
  59. package/src/generator.ts +0 -69
  60. package/src/index.ts +0 -14
  61. package/src/mcp-apps.ts +2 -3
  62. package/src/memory.ts +43 -24
  63. package/src/rendering/layout-selector.ts +5 -5
  64. package/src/schema-extractor.ts +16 -1
  65. package/src/stateful.ts +28 -13
  66. package/src/types.ts +9 -3
package/src/generator.ts CHANGED
@@ -1220,72 +1220,3 @@ export async function* wrapAsGenerator<T>(
1220
1220
  return await asyncFn();
1221
1221
  }
1222
1222
 
1223
- // ══════════════════════════════════════════════════════════════════════════════
1224
- // LEGACY COMPATIBILITY - Map old format to new
1225
- // ══════════════════════════════════════════════════════════════════════════════
1226
-
1227
- /**
1228
- * @deprecated Use AskYield instead
1229
- */
1230
- export type PromptYield = AskText | AskPassword;
1231
-
1232
- /**
1233
- * @deprecated Use AskConfirm instead
1234
- */
1235
- export type ConfirmYield = AskConfirm;
1236
-
1237
- /**
1238
- * @deprecated Use AskSelect instead
1239
- */
1240
- export type SelectYield = AskSelect;
1241
-
1242
- /**
1243
- * @deprecated Use EmitProgress instead
1244
- */
1245
- export type ProgressYield = EmitProgress;
1246
-
1247
- /**
1248
- * @deprecated Use EmitStream instead
1249
- */
1250
- export type StreamYield = EmitStream;
1251
-
1252
- /**
1253
- * @deprecated Use EmitLog instead
1254
- */
1255
- export type LogYield = EmitLog;
1256
-
1257
- /**
1258
- * @deprecated Use isAskYield instead
1259
- */
1260
- export const isInputYield = isAskYield;
1261
-
1262
- /**
1263
- * @deprecated Use isEmitYield instead
1264
- */
1265
- export function isProgressYield(y: PhotonYield): y is EmitProgress {
1266
- return isEmitYield(y) && y.emit === 'progress';
1267
- }
1268
-
1269
- /**
1270
- * @deprecated Use isEmitYield instead
1271
- */
1272
- export function isStreamYield(y: PhotonYield): y is EmitStream {
1273
- return isEmitYield(y) && y.emit === 'stream';
1274
- }
1275
-
1276
- /**
1277
- * @deprecated Use isEmitYield instead
1278
- */
1279
- export function isLogYield(y: PhotonYield): y is EmitLog {
1280
- return isEmitYield(y) && y.emit === 'log';
1281
- }
1282
-
1283
- /**
1284
- * @deprecated Use extractAsks instead
1285
- */
1286
- export const extractYields = extractAsks;
1287
-
1288
- /**
1289
- * @deprecated Use ExtractedAsk instead
1290
- */
1291
- export type ExtractedYield = ExtractedAsk;
package/src/index.ts CHANGED
@@ -249,20 +249,6 @@ export {
249
249
  type OutputHandler,
250
250
  type GeneratorExecutorConfig,
251
251
  type ExtractedAsk,
252
-
253
- // Legacy compatibility
254
- isInputYield,
255
- isProgressYield,
256
- isStreamYield,
257
- isLogYield,
258
- extractYields,
259
- type PromptYield,
260
- type ConfirmYield,
261
- type SelectYield,
262
- type ProgressYield,
263
- type StreamYield,
264
- type LogYield,
265
- type ExtractedYield,
266
252
  } from './generator.js';
267
253
 
268
254
  // Stateful Workflow Execution
package/src/mcp-apps.ts CHANGED
@@ -39,7 +39,7 @@ export interface McpAppsInitialize {
39
39
  width?: number;
40
40
  height?: number;
41
41
  };
42
- // Legacy flat theme tokens (kept for backward compat with Photon apps)
42
+ // Flat theme tokens for Photon Bridge apps
43
43
  theme: Record<string, string>;
44
44
  safeAreaInsets?: { top: number; bottom: number; left: number; right: number };
45
45
  };
@@ -154,7 +154,6 @@ export function createMcpAppsInitialize(
154
154
  width: dimensions.width,
155
155
  height: dimensions.height,
156
156
  },
157
- // Legacy flat theme tokens for backward compat
158
157
  theme: themeTokens,
159
158
  ...(context.safeAreaInsets ? { safeAreaInsets: context.safeAreaInsets } : {}),
160
159
  },
@@ -178,7 +177,7 @@ export function createThemeChangeMessages(theme: 'light' | 'dark'): unknown[] {
178
177
  styles: { variables: themeTokens },
179
178
  },
180
179
  },
181
- // MCP Apps Extension (legacy name for backward compat)
180
+ // MCP Apps Extension (older spec draft name, still used by some clients)
182
181
  {
183
182
  jsonrpc: '2.0',
184
183
  method: 'ui/notifications/context',
package/src/memory.ts CHANGED
@@ -24,7 +24,7 @@
24
24
  * ```
25
25
  */
26
26
 
27
- import * as fs from 'fs';
27
+ import * as fs from 'fs/promises';
28
28
  import * as path from 'path';
29
29
  import * as os from 'os';
30
30
 
@@ -74,6 +74,18 @@ function keyPath(dir: string, key: string): string {
74
74
  return path.join(dir, `${safeKey}.json`);
75
75
  }
76
76
 
77
+ /**
78
+ * Check if a path exists (async)
79
+ */
80
+ async function pathExists(p: string): Promise<boolean> {
81
+ try {
82
+ await fs.access(p);
83
+ return true;
84
+ } catch {
85
+ return false;
86
+ }
87
+ }
88
+
77
89
  /**
78
90
  * Scoped Memory Provider
79
91
  *
@@ -112,11 +124,11 @@ export class MemoryProvider {
112
124
  const filePath = keyPath(dir, key);
113
125
 
114
126
  try {
115
- if (!fs.existsSync(filePath)) return null;
116
- const content = fs.readFileSync(filePath, 'utf-8');
127
+ const content = await fs.readFile(filePath, 'utf-8');
117
128
  return JSON.parse(content) as T;
118
- } catch {
119
- return null;
129
+ } catch (error: any) {
130
+ if (error.code === 'ENOENT') return null;
131
+ throw error;
120
132
  }
121
133
  }
122
134
 
@@ -130,12 +142,12 @@ export class MemoryProvider {
130
142
  async set<T = any>(key: string, value: T, scope: MemoryScope = 'photon'): Promise<void> {
131
143
  const dir = resolveDir(this._photonId, scope, this._sessionId);
132
144
 
133
- if (!fs.existsSync(dir)) {
134
- fs.mkdirSync(dir, { recursive: true });
145
+ if (!await pathExists(dir)) {
146
+ await fs.mkdir(dir, { recursive: true });
135
147
  }
136
148
 
137
149
  const filePath = keyPath(dir, key);
138
- fs.writeFileSync(filePath, JSON.stringify(value, null, 2));
150
+ await fs.writeFile(filePath, JSON.stringify(value, null, 2));
139
151
  }
140
152
 
141
153
  /**
@@ -149,11 +161,13 @@ export class MemoryProvider {
149
161
  const dir = resolveDir(this._photonId, scope, this._sessionId);
150
162
  const filePath = keyPath(dir, key);
151
163
 
152
- if (fs.existsSync(filePath)) {
153
- fs.unlinkSync(filePath);
164
+ try {
165
+ await fs.unlink(filePath);
154
166
  return true;
167
+ } catch (error: any) {
168
+ if (error.code === 'ENOENT') return false;
169
+ throw error;
155
170
  }
156
- return false;
157
171
  }
158
172
 
159
173
  /**
@@ -164,7 +178,7 @@ export class MemoryProvider {
164
178
  */
165
179
  async has(key: string, scope: MemoryScope = 'photon'): Promise<boolean> {
166
180
  const dir = resolveDir(this._photonId, scope, this._sessionId);
167
- return fs.existsSync(keyPath(dir, key));
181
+ return pathExists(keyPath(dir, key));
168
182
  }
169
183
 
170
184
  /**
@@ -175,14 +189,14 @@ export class MemoryProvider {
175
189
  async keys(scope: MemoryScope = 'photon'): Promise<string[]> {
176
190
  const dir = resolveDir(this._photonId, scope, this._sessionId);
177
191
 
178
- if (!fs.existsSync(dir)) return [];
179
-
180
192
  try {
181
- return fs.readdirSync(dir)
193
+ const files = await fs.readdir(dir);
194
+ return files
182
195
  .filter(f => f.endsWith('.json'))
183
- .map(f => f.slice(0, -5)); // Remove .json extension
184
- } catch {
185
- return [];
196
+ .map(f => f.slice(0, -5));
197
+ } catch (error: any) {
198
+ if (error.code === 'ENOENT') return [];
199
+ throw error;
186
200
  }
187
201
  }
188
202
 
@@ -194,11 +208,13 @@ export class MemoryProvider {
194
208
  async clear(scope: MemoryScope = 'photon'): Promise<void> {
195
209
  const dir = resolveDir(this._photonId, scope, this._sessionId);
196
210
 
197
- if (fs.existsSync(dir)) {
198
- const files = fs.readdirSync(dir).filter(f => f.endsWith('.json'));
199
- for (const file of files) {
200
- fs.unlinkSync(path.join(dir, file));
201
- }
211
+ try {
212
+ const files = await fs.readdir(dir);
213
+ const jsonFiles = files.filter(f => f.endsWith('.json'));
214
+ await Promise.all(jsonFiles.map(file => fs.unlink(path.join(dir, file))));
215
+ } catch (error: any) {
216
+ if (error.code === 'ENOENT') return;
217
+ throw error;
202
218
  }
203
219
  }
204
220
 
@@ -222,7 +238,10 @@ export class MemoryProvider {
222
238
  }
223
239
 
224
240
  /**
225
- * Update a value atomically (read-modify-write)
241
+ * Update a value with read-modify-write
242
+ *
243
+ * Note: Not truly atomic under concurrent access. For concurrent
244
+ * writes, use distributed locking via `withLock()`.
226
245
  *
227
246
  * @param key The key to update
228
247
  * @param updater Function that receives current value and returns new value
@@ -15,9 +15,9 @@ export type LayoutType =
15
15
  | 'tree' // Nested object as collapsible tree
16
16
  | 'kv' // Flat object as key-value table
17
17
  | 'chips' // Array of strings as chips/tags
18
- | 'table' // Legacy: grid table (backward compat)
19
- | 'markdown' // Legacy: markdown rendering
20
- | 'mermaid' // Legacy: mermaid diagrams
18
+ | 'table' // Grid table display
19
+ | 'markdown' // Markdown rendering
20
+ | 'mermaid' // Mermaid diagrams
21
21
  | 'code' // Code block with syntax highlighting
22
22
  | 'json' // Raw JSON display
23
23
  | 'html'; // Raw HTML (for custom UIs)
@@ -230,7 +230,7 @@ export function parseLayoutHints(hintsString: string): LayoutHints {
230
230
  // Match @key value or @key value:renderer
231
231
  const match = part.match(/@(\w+)\s+([^:]+)(?::(\w+))?/);
232
232
  if (match) {
233
- const [, key, value, renderer] = match;
233
+ const [, key, value] = match;
234
234
  const cleanValue = value.trim();
235
235
 
236
236
  switch (key) {
@@ -368,7 +368,7 @@ function parseLayoutHints(hintsString) {
368
368
  for (const part of parts) {
369
369
  const match = part.match(/@(\\w+)\\s+([^:]+)(?::(\\w+))?/);
370
370
  if (match) {
371
- const [, key, value, renderer] = match;
371
+ const [, key, value] = match;
372
372
  const cleanValue = value.trim();
373
373
  switch (key) {
374
374
  case 'title': hints.title = cleanValue; break;
@@ -1258,6 +1258,21 @@ export class SchemaExtractor {
1258
1258
  return subtype ? `code:${subtype}` as OutputFormat : 'code';
1259
1259
  }
1260
1260
 
1261
+ // Match chart format (with optional chart type: bar, pie, line, donut, area)
1262
+ if (format === 'chart') {
1263
+ return subtype ? `chart:${subtype}` as OutputFormat : 'chart';
1264
+ }
1265
+
1266
+ // Match visualization formats
1267
+ if (['metric', 'gauge', 'timeline', 'dashboard', 'cart'].includes(format)) {
1268
+ return format as OutputFormat;
1269
+ }
1270
+
1271
+ // Match container formats
1272
+ if (['panels', 'tabs', 'accordion', 'stack', 'columns'].includes(format)) {
1273
+ return format as OutputFormat;
1274
+ }
1275
+
1261
1276
  return undefined;
1262
1277
  }
1263
1278
 
@@ -1541,7 +1556,7 @@ export class SchemaExtractor {
1541
1556
  const params = this.extractConstructorParams(source);
1542
1557
  const mcpDeps = this.extractMCPDependencies(source);
1543
1558
  const photonDeps = this.extractPhotonDependencies(source);
1544
- const isStateful = /@stateful\s+true/.test(source);
1559
+ const isStateful = /@stateful\b/.test(source);
1545
1560
 
1546
1561
  // Build lookup maps
1547
1562
  const mcpMap = new Map(mcpDeps.map(d => [d.name, d]));
package/src/stateful.ts CHANGED
@@ -130,11 +130,9 @@ export function isCheckpointYield(y: StatefulYield): y is CheckpointYield {
130
130
  * State log writer for a single workflow run
131
131
  */
132
132
  export class StateLog {
133
- private runId: string;
134
133
  private logPath: string;
135
134
 
136
135
  constructor(runId: string, runsDir?: string) {
137
- this.runId = runId;
138
136
  this.logPath = path.join(runsDir || RUNS_DIR, `${runId}.jsonl`);
139
137
  }
140
138
 
@@ -156,8 +154,8 @@ export class StateLog {
156
154
  /**
157
155
  * Write start entry
158
156
  */
159
- async writeStart(tool: string, params: Record<string, any>): Promise<void> {
160
- await this.append({ t: 'start', tool, params } as StateLogStart);
157
+ async writeStart(tool: string, params: Record<string, any>, photon?: string): Promise<void> {
158
+ await this.append({ t: 'start', tool, params, ...(photon ? { photon } : {}) } as StateLogStart);
161
159
  }
162
160
 
163
161
  /**
@@ -225,7 +223,26 @@ export class StateLog {
225
223
  * Stream entries from the log (memory efficient for large logs)
226
224
  */
227
225
  async *stream(): AsyncGenerator<StateLogEntry> {
228
- const fileStream = createReadStream(this.logPath);
226
+ let fileStream;
227
+ try {
228
+ fileStream = createReadStream(this.logPath);
229
+ } catch (error: any) {
230
+ if (error.code === 'ENOENT') return;
231
+ throw error;
232
+ }
233
+
234
+ // Handle ENOENT that surfaces as a stream error
235
+ const streamReady = new Promise<void>((resolve, reject) => {
236
+ fileStream.once('ready', resolve);
237
+ fileStream.once('error', (err: any) => {
238
+ if (err.code === 'ENOENT') resolve();
239
+ else reject(err);
240
+ });
241
+ });
242
+ await streamReady;
243
+
244
+ if (fileStream.destroyed) return;
245
+
229
246
  const rl = createInterface({ input: fileStream });
230
247
 
231
248
  for await (const line of rl) {
@@ -418,7 +435,7 @@ export async function executeStatefulGenerator<T>(
418
435
 
419
436
  // Write start entry (only if not resuming)
420
437
  if (!resumed) {
421
- await log.writeStart(config.tool, config.params);
438
+ await log.writeStart(config.tool, config.params, config.photon);
422
439
  }
423
440
 
424
441
  try {
@@ -512,7 +529,7 @@ export async function executeStatefulGenerator<T>(
512
529
  result = await generator.next(input);
513
530
  } else if (isEmitYield(yielded as PhotonYield)) {
514
531
  const emitYield = yielded as EmitYield;
515
- await log.writeEmit(emitYield.emit, (emitYield as any).message, emitYield);
532
+ await log.writeEmit(emitYield.emit, 'message' in emitYield ? (emitYield as { message?: string }).message : undefined, emitYield);
516
533
 
517
534
  if (config.outputHandler) {
518
535
  await config.outputHandler(emitYield);
@@ -520,8 +537,7 @@ export async function executeStatefulGenerator<T>(
520
537
 
521
538
  result = await generator.next();
522
539
  } else {
523
- // Unknown yield, skip
524
- result = await generator.next();
540
+ throw new Error(`Unknown yield type: ${JSON.stringify(yielded)}`);
525
541
  }
526
542
  }
527
543
 
@@ -603,7 +619,7 @@ export async function getRunInfo(runId: string, runsDir?: string): Promise<Workf
603
619
 
604
620
  return {
605
621
  runId,
606
- photon: '', // Would need to be stored in start entry
622
+ photon: (firstEntry as StateLogStart).photon || state.tool.split('.')[0] || 'unknown',
607
623
  tool: state.tool,
608
624
  params: state.params,
609
625
  status,
@@ -834,7 +850,7 @@ export async function maybeStatefulExecute<T>(
834
850
  await log.init();
835
851
 
836
852
  // Write start entry
837
- await log.writeStart(config.tool, config.params);
853
+ await log.writeStart(config.tool, config.params, config.photon);
838
854
 
839
855
  // Replay buffered events to log
840
856
  for (const event of bufferedEvents) {
@@ -906,8 +922,7 @@ export async function maybeStatefulExecute<T>(
906
922
 
907
923
  result = await generator.next();
908
924
  } else {
909
- // Unknown yield, skip
910
- result = await generator.next();
925
+ throw new Error(`Unknown yield type: ${JSON.stringify(yielded)}`);
911
926
  }
912
927
  }
913
928
 
package/src/types.ts CHANGED
@@ -4,13 +4,17 @@
4
4
 
5
5
  /**
6
6
  * Output format types
7
- * - Structural: primitive, table, tree, list, none
8
- * - Content: json, markdown, yaml, xml, html, code, code:<lang>
7
+ * - Structural: primitive, table, tree, list, none, card, grid, chips, kv
8
+ * - Content: json, markdown, yaml, xml, html, mermaid, code, code:<lang>
9
+ * - Visualization: chart, chart:<type>, metric, gauge, timeline, dashboard, cart
10
+ * - Container: panels, tabs, accordion, stack, columns
9
11
  */
10
12
  export type OutputFormat =
11
13
  | 'primitive' | 'table' | 'tree' | 'list' | 'none'
12
14
  | 'json' | 'markdown' | 'yaml' | 'xml' | 'html' | 'mermaid'
13
- | 'card' | 'grid' | 'chips' | 'kv' | 'tabs' | 'accordion'
15
+ | 'card' | 'grid' | 'chips' | 'kv'
16
+ | 'chart' | `chart:${string}` | 'metric' | 'gauge' | 'timeline' | 'dashboard' | 'cart'
17
+ | 'panels' | 'tabs' | 'accordion' | 'stack' | 'columns'
14
18
  | `code` | `code:${string}`;
15
19
 
16
20
  export interface PhotonTool {
@@ -409,6 +413,8 @@ export interface StateLogStart extends StateLogBase {
409
413
  tool: string;
410
414
  /** Input parameters */
411
415
  params: Record<string, any>;
416
+ /** Photon name that started this run */
417
+ photon?: string;
412
418
  }
413
419
 
414
420
  /**