@portel/photon-core 2.9.2 → 2.9.4
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.
- package/dist/asset-discovery.d.ts.map +1 -1
- package/dist/asset-discovery.js +16 -1
- package/dist/asset-discovery.js.map +1 -1
- package/dist/base.d.ts +4 -2
- package/dist/base.d.ts.map +1 -1
- package/dist/base.js +2 -2
- package/dist/base.js.map +1 -1
- package/dist/compiler.d.ts.map +1 -1
- package/dist/compiler.js +3 -1
- package/dist/compiler.js.map +1 -1
- package/dist/dependency-manager.d.ts +12 -0
- package/dist/dependency-manager.d.ts.map +1 -1
- package/dist/dependency-manager.js +56 -0
- package/dist/dependency-manager.js.map +1 -1
- package/dist/schema-extractor.d.ts +12 -2
- package/dist/schema-extractor.d.ts.map +1 -1
- package/dist/schema-extractor.js +182 -25
- package/dist/schema-extractor.js.map +1 -1
- package/dist/types.d.ts +82 -28
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
- package/src/asset-discovery.ts +16 -1
- package/src/base.ts +3 -3
- package/src/compiler.ts +3 -1
- package/src/dependency-manager.ts +58 -0
- package/src/schema-extractor.ts +200 -27
- package/src/types.ts +100 -29
package/src/schema-extractor.ts
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
import * as fs from 'fs/promises';
|
|
12
12
|
import * as ts from 'typescript';
|
|
13
|
-
import { ExtractedSchema, ConstructorParam, TemplateInfo, StaticInfo, OutputFormat, YieldInfo, MCPDependency, PhotonDependency, CLIDependency, ResolvedInjection, PhotonAssets, UIAsset, PromptAsset, ResourceAsset, ConfigSchema, ConfigParam } from './types.js';
|
|
13
|
+
import { ExtractedSchema, ConstructorParam, TemplateInfo, StaticInfo, OutputFormat, YieldInfo, MCPDependency, PhotonDependency, CLIDependency, ResolvedInjection, PhotonAssets, UIAsset, PromptAsset, ResourceAsset, ConfigSchema, ConfigParam, SettingsSchema, SettingsProperty, NotificationSubscription } from './types.js';
|
|
14
14
|
import { parseDuration, parseRate } from './utils/duration.js';
|
|
15
15
|
import { builtinRegistry, type MiddlewareDeclaration } from './middleware.js';
|
|
16
16
|
|
|
@@ -18,8 +18,12 @@ export interface ExtractedMetadata {
|
|
|
18
18
|
tools: ExtractedSchema[];
|
|
19
19
|
templates: TemplateInfo[];
|
|
20
20
|
statics: StaticInfo[];
|
|
21
|
-
/**
|
|
21
|
+
/** Settings schema from `protected settings = { ... }` property */
|
|
22
|
+
settingsSchema?: SettingsSchema;
|
|
23
|
+
/** @deprecated Configuration schema from configure() method */
|
|
22
24
|
configSchema?: ConfigSchema;
|
|
25
|
+
/** Notification subscription from @notify-on tag */
|
|
26
|
+
notificationSubscriptions?: NotificationSubscription;
|
|
23
27
|
}
|
|
24
28
|
|
|
25
29
|
/**
|
|
@@ -47,13 +51,19 @@ export class SchemaExtractor {
|
|
|
47
51
|
const templates: TemplateInfo[] = [];
|
|
48
52
|
const statics: StaticInfo[] = [];
|
|
49
53
|
|
|
50
|
-
//
|
|
54
|
+
// Settings schema tracking (new property-based approach)
|
|
55
|
+
let settingsSchema: SettingsSchema | undefined;
|
|
56
|
+
|
|
57
|
+
// Configuration schema tracking (deprecated method-based approach)
|
|
51
58
|
let configSchema: ConfigSchema = {
|
|
52
59
|
hasConfigureMethod: false,
|
|
53
60
|
hasGetConfigMethod: false,
|
|
54
61
|
params: [],
|
|
55
62
|
};
|
|
56
63
|
|
|
64
|
+
// Notification subscriptions tracking (from @notify-on tag)
|
|
65
|
+
let notificationSubscriptions: NotificationSubscription | undefined;
|
|
66
|
+
|
|
57
67
|
try {
|
|
58
68
|
// If source doesn't contain a class declaration, wrap it in one
|
|
59
69
|
let sourceToParse = source;
|
|
@@ -70,7 +80,7 @@ export class SchemaExtractor {
|
|
|
70
80
|
);
|
|
71
81
|
|
|
72
82
|
// Helper to process a method declaration
|
|
73
|
-
const processMethod = (member: ts.MethodDeclaration) => {
|
|
83
|
+
const processMethod = (member: ts.MethodDeclaration, isStatefulClass: boolean = false) => {
|
|
74
84
|
const methodName = member.name.getText(sourceFile);
|
|
75
85
|
|
|
76
86
|
// Skip private methods (prefixed with _)
|
|
@@ -78,32 +88,32 @@ export class SchemaExtractor {
|
|
|
78
88
|
return;
|
|
79
89
|
}
|
|
80
90
|
|
|
81
|
-
//
|
|
91
|
+
// Track configure/getConfig for backward compat metadata (deprecated)
|
|
92
|
+
// These are no longer hidden — they become normal visible tools
|
|
82
93
|
if (methodName === 'configure') {
|
|
83
94
|
configSchema.hasConfigureMethod = true;
|
|
84
|
-
const
|
|
85
|
-
configSchema.description = this.extractDescription(
|
|
95
|
+
const jsdocConfig = this.getJSDocComment(member, sourceFile);
|
|
96
|
+
configSchema.description = this.extractDescription(jsdocConfig);
|
|
86
97
|
|
|
87
|
-
// Extract configure() parameters as config schema
|
|
88
98
|
const paramsType = this.getFirstParameterType(member, sourceFile);
|
|
89
99
|
if (paramsType) {
|
|
90
|
-
const { properties, required } = this.buildSchemaFromType(paramsType, sourceFile);
|
|
91
|
-
const paramDocs = this.extractParamDocs(
|
|
100
|
+
const { properties: configProps, required: configRequired } = this.buildSchemaFromType(paramsType, sourceFile);
|
|
101
|
+
const paramDocs = this.extractParamDocs(jsdocConfig);
|
|
92
102
|
|
|
93
|
-
configSchema.params = Object.keys(
|
|
103
|
+
configSchema.params = Object.keys(configProps).map(name => ({
|
|
94
104
|
name,
|
|
95
|
-
type:
|
|
96
|
-
description: paramDocs.get(name) ||
|
|
97
|
-
required:
|
|
98
|
-
defaultValue:
|
|
105
|
+
type: configProps[name].type || 'string',
|
|
106
|
+
description: paramDocs.get(name) || configProps[name].description,
|
|
107
|
+
required: configRequired.includes(name),
|
|
108
|
+
defaultValue: configProps[name].default,
|
|
99
109
|
}));
|
|
100
110
|
}
|
|
101
|
-
|
|
111
|
+
// Fall through — configure() is now a normal tool (not hidden)
|
|
102
112
|
}
|
|
103
113
|
|
|
104
114
|
if (methodName === 'getConfig') {
|
|
105
115
|
configSchema.hasGetConfigMethod = true;
|
|
106
|
-
|
|
116
|
+
// Fall through — getConfig() is now a normal tool (not hidden)
|
|
107
117
|
}
|
|
108
118
|
|
|
109
119
|
const jsdoc = this.getJSDocComment(member, sourceFile);
|
|
@@ -212,6 +222,19 @@ export class SchemaExtractor {
|
|
|
212
222
|
// Check for static keyword on the method
|
|
213
223
|
const isStaticMethod = member.modifiers?.some(m => m.kind === ts.SyntaxKind.StaticKeyword) || false;
|
|
214
224
|
|
|
225
|
+
// Event emission for @stateful classes: all public methods emit events automatically
|
|
226
|
+
const emitsEventData = isStatefulClass ? {
|
|
227
|
+
emitsEvent: true,
|
|
228
|
+
eventName: methodName,
|
|
229
|
+
eventPayload: {
|
|
230
|
+
method: 'string',
|
|
231
|
+
params: 'object',
|
|
232
|
+
result: 'any',
|
|
233
|
+
timestamp: 'string',
|
|
234
|
+
instance: 'string',
|
|
235
|
+
},
|
|
236
|
+
} : {};
|
|
237
|
+
|
|
215
238
|
tools.push({
|
|
216
239
|
name: methodName,
|
|
217
240
|
description,
|
|
@@ -246,15 +269,123 @@ export class SchemaExtractor {
|
|
|
246
269
|
...(deprecated !== undefined ? { deprecated } : {}),
|
|
247
270
|
// Unified middleware declarations (new — runtime uses this)
|
|
248
271
|
...(middleware.length > 0 ? { middleware } : {}),
|
|
272
|
+
// Event emission (for @stateful classes)
|
|
273
|
+
...emitsEventData,
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
// Helper to extract settings from a `protected settings = { ... }` property
|
|
279
|
+
const processSettingsProperty = (member: ts.PropertyDeclaration, classNode: ts.ClassDeclaration) => {
|
|
280
|
+
const name = member.name.getText(sourceFile);
|
|
281
|
+
if (name !== 'settings') return;
|
|
282
|
+
|
|
283
|
+
// Must be protected
|
|
284
|
+
const isProtected = member.modifiers?.some(
|
|
285
|
+
m => m.kind === ts.SyntaxKind.ProtectedKeyword
|
|
286
|
+
);
|
|
287
|
+
if (!isProtected) return;
|
|
288
|
+
|
|
289
|
+
// Must have an object literal initializer
|
|
290
|
+
if (!member.initializer || !ts.isObjectLiteralExpression(member.initializer)) return;
|
|
291
|
+
|
|
292
|
+
// Get class-level JSDoc for @property descriptions
|
|
293
|
+
const classJsdoc = this.getJSDocComment(classNode as any, sourceFile);
|
|
294
|
+
const propertyDocs = new Map<string, string>();
|
|
295
|
+
const propertyRegex = /@property\s+(\w+)\s+(.*?)(?=\n\s*\*\s*@|\n\s*\*\/|\n\s*\*\s*$)/gs;
|
|
296
|
+
let propMatch: RegExpExecArray | null;
|
|
297
|
+
while ((propMatch = propertyRegex.exec(classJsdoc)) !== null) {
|
|
298
|
+
propertyDocs.set(propMatch[1], propMatch[2].trim());
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const properties: SettingsProperty[] = [];
|
|
302
|
+
|
|
303
|
+
for (const prop of member.initializer.properties) {
|
|
304
|
+
if (!ts.isPropertyAssignment(prop)) continue;
|
|
305
|
+
const propName = prop.name.getText(sourceFile);
|
|
306
|
+
|
|
307
|
+
// Determine type and default from the initializer
|
|
308
|
+
let type = 'string';
|
|
309
|
+
let defaultValue: any = undefined;
|
|
310
|
+
let required = false;
|
|
311
|
+
|
|
312
|
+
const init = prop.initializer;
|
|
313
|
+
|
|
314
|
+
if (ts.isNumericLiteral(init)) {
|
|
315
|
+
type = 'number';
|
|
316
|
+
defaultValue = Number(init.text);
|
|
317
|
+
} else if (ts.isStringLiteral(init)) {
|
|
318
|
+
type = 'string';
|
|
319
|
+
defaultValue = init.text;
|
|
320
|
+
} else if (init.kind === ts.SyntaxKind.TrueKeyword) {
|
|
321
|
+
type = 'boolean';
|
|
322
|
+
defaultValue = true;
|
|
323
|
+
} else if (init.kind === ts.SyntaxKind.FalseKeyword) {
|
|
324
|
+
type = 'boolean';
|
|
325
|
+
defaultValue = false;
|
|
326
|
+
} else if (init.kind === ts.SyntaxKind.UndefinedKeyword) {
|
|
327
|
+
// `key: undefined` — required, type inferred from as-expression or defaults to string
|
|
328
|
+
required = true;
|
|
329
|
+
} else if (ts.isAsExpression(init)) {
|
|
330
|
+
// `key: undefined as string | undefined` or `key: 5 as number`
|
|
331
|
+
const innerInit = init.expression;
|
|
332
|
+
const isUndefined = innerInit.kind === ts.SyntaxKind.UndefinedKeyword ||
|
|
333
|
+
(ts.isIdentifier(innerInit) && innerInit.text === 'undefined');
|
|
334
|
+
if (isUndefined) {
|
|
335
|
+
required = true;
|
|
336
|
+
} else if (ts.isNumericLiteral(innerInit)) {
|
|
337
|
+
type = 'number';
|
|
338
|
+
defaultValue = Number(innerInit.text);
|
|
339
|
+
} else if (ts.isStringLiteral(innerInit)) {
|
|
340
|
+
type = 'string';
|
|
341
|
+
defaultValue = innerInit.text;
|
|
342
|
+
} else if (innerInit.kind === ts.SyntaxKind.TrueKeyword || innerInit.kind === ts.SyntaxKind.FalseKeyword) {
|
|
343
|
+
type = 'boolean';
|
|
344
|
+
defaultValue = innerInit.kind === ts.SyntaxKind.TrueKeyword;
|
|
345
|
+
}
|
|
346
|
+
// Try to get type from the as-expression type annotation
|
|
347
|
+
const typeText = init.type.getText(sourceFile).replace(/\s*\|\s*undefined/g, '').trim();
|
|
348
|
+
if (typeText === 'number') type = 'number';
|
|
349
|
+
else if (typeText === 'boolean') type = 'boolean';
|
|
350
|
+
else if (typeText === 'string') type = 'string';
|
|
351
|
+
} else if (ts.isArrayLiteralExpression(init)) {
|
|
352
|
+
type = 'array';
|
|
353
|
+
defaultValue = [];
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
properties.push({
|
|
357
|
+
name: propName,
|
|
358
|
+
type,
|
|
359
|
+
description: propertyDocs.get(propName),
|
|
360
|
+
default: defaultValue,
|
|
361
|
+
required,
|
|
249
362
|
});
|
|
250
363
|
}
|
|
364
|
+
|
|
365
|
+
settingsSchema = {
|
|
366
|
+
hasSettings: true,
|
|
367
|
+
properties,
|
|
368
|
+
description: propertyDocs.size > 0 ? 'Photon settings' : undefined,
|
|
369
|
+
};
|
|
251
370
|
};
|
|
252
371
|
|
|
253
372
|
// Visit all nodes in the AST
|
|
254
373
|
const visit = (node: ts.Node) => {
|
|
255
374
|
// Look for class declarations
|
|
256
375
|
if (ts.isClassDeclaration(node)) {
|
|
376
|
+
// Check if this class has @stateful decorator
|
|
377
|
+
const classJsdoc = this.getJSDocComment(node as any, sourceFile);
|
|
378
|
+
const isStatefulClass = /@stateful\b/i.test(classJsdoc);
|
|
379
|
+
|
|
380
|
+
// Extract notification subscriptions from @notify-on tag
|
|
381
|
+
notificationSubscriptions = this.extractNotifyOn(classJsdoc);
|
|
382
|
+
|
|
257
383
|
node.members.forEach((member) => {
|
|
384
|
+
// Detect `protected settings = { ... }` property
|
|
385
|
+
if (ts.isPropertyDeclaration(member)) {
|
|
386
|
+
processSettingsProperty(member, node);
|
|
387
|
+
}
|
|
388
|
+
|
|
258
389
|
// Process all public methods (sync or async)
|
|
259
390
|
// Skip private/protected — only public methods become tools
|
|
260
391
|
if (ts.isMethodDeclaration(member)) {
|
|
@@ -262,7 +393,7 @@ export class SchemaExtractor {
|
|
|
262
393
|
m => m.kind === ts.SyntaxKind.PrivateKeyword || m.kind === ts.SyntaxKind.ProtectedKeyword
|
|
263
394
|
);
|
|
264
395
|
if (!isPrivate) {
|
|
265
|
-
processMethod(member);
|
|
396
|
+
processMethod(member, isStatefulClass);
|
|
266
397
|
}
|
|
267
398
|
}
|
|
268
399
|
});
|
|
@@ -276,11 +407,23 @@ export class SchemaExtractor {
|
|
|
276
407
|
console.error('Failed to parse TypeScript source:', error.message);
|
|
277
408
|
}
|
|
278
409
|
|
|
279
|
-
// Only include configSchema if there's a configure() method
|
|
280
410
|
const result: ExtractedMetadata = { tools, templates, statics };
|
|
411
|
+
|
|
412
|
+
// Include settingsSchema if detected
|
|
413
|
+
if (settingsSchema) {
|
|
414
|
+
result.settingsSchema = settingsSchema;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// Include configSchema if there's a configure() method (deprecated)
|
|
281
418
|
if (configSchema.hasConfigureMethod) {
|
|
282
419
|
result.configSchema = configSchema;
|
|
283
420
|
}
|
|
421
|
+
|
|
422
|
+
// Include notification subscriptions if detected
|
|
423
|
+
if (notificationSubscriptions) {
|
|
424
|
+
result.notificationSubscriptions = notificationSubscriptions;
|
|
425
|
+
}
|
|
426
|
+
|
|
284
427
|
return result;
|
|
285
428
|
}
|
|
286
429
|
|
|
@@ -1230,9 +1373,6 @@ export class SchemaExtractor {
|
|
|
1230
1373
|
if (constraints.pattern !== undefined) {
|
|
1231
1374
|
s.pattern = constraints.pattern;
|
|
1232
1375
|
}
|
|
1233
|
-
if (constraints.format !== undefined) {
|
|
1234
|
-
s.format = constraints.format;
|
|
1235
|
-
}
|
|
1236
1376
|
} else if (s.type === 'array') {
|
|
1237
1377
|
if (constraints.min !== undefined) {
|
|
1238
1378
|
s.minItems = constraints.min;
|
|
@@ -1246,6 +1386,9 @@ export class SchemaExtractor {
|
|
|
1246
1386
|
}
|
|
1247
1387
|
|
|
1248
1388
|
// Apply type-agnostic constraints
|
|
1389
|
+
if (constraints.format !== undefined) {
|
|
1390
|
+
s.format = constraints.format;
|
|
1391
|
+
}
|
|
1249
1392
|
if (constraints.default !== undefined) {
|
|
1250
1393
|
s.default = constraints.default;
|
|
1251
1394
|
}
|
|
@@ -1352,6 +1495,30 @@ export class SchemaExtractor {
|
|
|
1352
1495
|
return /@async\b/i.test(jsdocContent);
|
|
1353
1496
|
}
|
|
1354
1497
|
|
|
1498
|
+
/**
|
|
1499
|
+
* Extract notification subscriptions from @notify-on tag
|
|
1500
|
+
* Specifies which event types this photon is interested in
|
|
1501
|
+
* Format: @notify-on mentions, deadlines, errors
|
|
1502
|
+
*/
|
|
1503
|
+
private extractNotifyOn(jsdocContent: string): NotificationSubscription | undefined {
|
|
1504
|
+
const notifyMatch = jsdocContent.match(/@notify-on\s+([^\n]+)/i);
|
|
1505
|
+
if (!notifyMatch) {
|
|
1506
|
+
return undefined;
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
// Parse comma-separated event types
|
|
1510
|
+
const watchFor = notifyMatch[1]
|
|
1511
|
+
.split(',')
|
|
1512
|
+
.map(s => s.trim())
|
|
1513
|
+
.filter(s => s.length > 0);
|
|
1514
|
+
|
|
1515
|
+
if (watchFor.length === 0) {
|
|
1516
|
+
return undefined;
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
return { watchFor };
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1355
1522
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
1356
1523
|
// DAEMON FEATURE EXTRACTION
|
|
1357
1524
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
@@ -1815,23 +1982,29 @@ export class SchemaExtractor {
|
|
|
1815
1982
|
extractPhotonDependencies(source: string): PhotonDependency[] {
|
|
1816
1983
|
const dependencies: PhotonDependency[] = [];
|
|
1817
1984
|
|
|
1818
|
-
// Match @photon <name> <source> pattern
|
|
1985
|
+
// Match @photon <name> <source> pattern, where source may include :instanceName suffix
|
|
1986
|
+
// e.g., @photon homeTodos todo:home → source='todo', instanceName='home'
|
|
1819
1987
|
// Source ends at: newline, end of comment (*), or @ (next tag)
|
|
1820
|
-
const photonRegex = /@photon\s+(\w+)\s+([^\s*@\n]+)
|
|
1988
|
+
const photonRegex = /@photon\s+(\w+)\s+([^\s*@\n:]+)(?::(\w[\w-]*))?/g;
|
|
1821
1989
|
|
|
1822
1990
|
let match;
|
|
1823
1991
|
while ((match = photonRegex.exec(source)) !== null) {
|
|
1824
|
-
const [, name, rawSource] = match;
|
|
1992
|
+
const [, name, rawSource, instanceName] = match;
|
|
1825
1993
|
const photonSource = rawSource.trim();
|
|
1826
1994
|
|
|
1827
1995
|
// Determine source type
|
|
1828
1996
|
const sourceType = this.classifyPhotonSource(photonSource);
|
|
1829
1997
|
|
|
1830
|
-
|
|
1998
|
+
const dep: PhotonDependency = {
|
|
1831
1999
|
name,
|
|
1832
2000
|
source: photonSource,
|
|
1833
2001
|
sourceType,
|
|
1834
|
-
}
|
|
2002
|
+
};
|
|
2003
|
+
if (instanceName) {
|
|
2004
|
+
dep.instanceName = instanceName;
|
|
2005
|
+
}
|
|
2006
|
+
|
|
2007
|
+
dependencies.push(dep);
|
|
1835
2008
|
}
|
|
1836
2009
|
|
|
1837
2010
|
return dependencies;
|
package/src/types.ts
CHANGED
|
@@ -191,6 +191,26 @@ export interface ExtractedSchema {
|
|
|
191
191
|
|
|
192
192
|
/** When true, method uses individual params instead of a single params object */
|
|
193
193
|
simpleParams?: boolean;
|
|
194
|
+
|
|
195
|
+
// ═══ EVENT EMISSION ═══
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* True if this method automatically emits an event on execution
|
|
199
|
+
* (for @stateful classes, all public methods emit automatically)
|
|
200
|
+
*/
|
|
201
|
+
emitsEvent?: boolean;
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Event name that will be emitted when this method is called
|
|
205
|
+
* For @stateful classes, defaults to the method name (e.g., 'add', 'done')
|
|
206
|
+
*/
|
|
207
|
+
eventName?: string;
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Event payload structure — what data will be sent with the event
|
|
211
|
+
* Typically mirrors the return type of the method
|
|
212
|
+
*/
|
|
213
|
+
eventPayload?: Record<string, string>;
|
|
194
214
|
}
|
|
195
215
|
|
|
196
216
|
export interface PhotonClass {
|
|
@@ -302,6 +322,8 @@ export interface PhotonDependency {
|
|
|
302
322
|
source: string;
|
|
303
323
|
/** Resolved source type */
|
|
304
324
|
sourceType: 'marketplace' | 'github' | 'npm' | 'local';
|
|
325
|
+
/** Named instance to load (e.g., 'home' from `@photon homeTodos todo:home`) */
|
|
326
|
+
instanceName?: string;
|
|
305
327
|
}
|
|
306
328
|
|
|
307
329
|
/**
|
|
@@ -487,6 +509,8 @@ export interface PhotonClassExtended extends PhotonClass {
|
|
|
487
509
|
assets?: PhotonAssets;
|
|
488
510
|
/** Names of injected @photon dependencies (for client-side event routing) */
|
|
489
511
|
injectedPhotons?: string[];
|
|
512
|
+
/** Settings schema if the photon has `protected settings = { ... }` */
|
|
513
|
+
settingsSchema?: SettingsSchema;
|
|
490
514
|
}
|
|
491
515
|
|
|
492
516
|
/** @deprecated Use PhotonClassExtended instead */
|
|
@@ -642,10 +666,56 @@ export interface WorkflowRun {
|
|
|
642
666
|
}
|
|
643
667
|
|
|
644
668
|
// ══════════════════════════════════════════════════════════════════════════════
|
|
645
|
-
//
|
|
669
|
+
// SETTINGS (first-class property-driven configuration)
|
|
670
|
+
// ══════════════════════════════════════════════════════════════════════════════
|
|
671
|
+
|
|
672
|
+
/**
|
|
673
|
+
* A single property in a photon's settings declaration
|
|
674
|
+
*/
|
|
675
|
+
export interface SettingsProperty {
|
|
676
|
+
/** Property name */
|
|
677
|
+
name: string;
|
|
678
|
+
/** JSON Schema type (string, number, boolean, etc.) */
|
|
679
|
+
type: string;
|
|
680
|
+
/** Description from @property JSDoc tag */
|
|
681
|
+
description?: string;
|
|
682
|
+
/** Default value from the object literal (undefined means elicitation required) */
|
|
683
|
+
default?: any;
|
|
684
|
+
/** True if default is undefined — runtime must elicit this value */
|
|
685
|
+
required: boolean;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
/**
|
|
689
|
+
* Settings schema extracted from a photon's `protected settings = { ... }` property
|
|
690
|
+
*
|
|
691
|
+
* Example:
|
|
692
|
+
* ```typescript
|
|
693
|
+
* /**
|
|
694
|
+
* * @property wipLimit WIP limit for in-progress tasks (1-20)
|
|
695
|
+
* * @property staleTaskDays Days before a task is considered stale
|
|
696
|
+
* *\/
|
|
697
|
+
* protected settings = {
|
|
698
|
+
* wipLimit: 5,
|
|
699
|
+
* staleTaskDays: 7,
|
|
700
|
+
* projectsRoot: undefined as string | undefined,
|
|
701
|
+
* };
|
|
702
|
+
* ```
|
|
703
|
+
*/
|
|
704
|
+
export interface SettingsSchema {
|
|
705
|
+
/** Whether a settings property was detected */
|
|
706
|
+
hasSettings: boolean;
|
|
707
|
+
/** Individual settings properties */
|
|
708
|
+
properties: SettingsProperty[];
|
|
709
|
+
/** Description from class-level JSDoc */
|
|
710
|
+
description?: string;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
// ══════════════════════════════════════════════════════════════════════════════
|
|
714
|
+
// CONFIGURATION CONVENTION (deprecated — use settings property instead)
|
|
646
715
|
// ══════════════════════════════════════════════════════════════════════════════
|
|
647
716
|
|
|
648
717
|
/**
|
|
718
|
+
* @deprecated Use SettingsProperty instead
|
|
649
719
|
* Configuration parameter extracted from configure() method
|
|
650
720
|
*/
|
|
651
721
|
export interface ConfigParam {
|
|
@@ -662,35 +732,8 @@ export interface ConfigParam {
|
|
|
662
732
|
}
|
|
663
733
|
|
|
664
734
|
/**
|
|
735
|
+
* @deprecated Use SettingsSchema instead
|
|
665
736
|
* Configuration schema extracted from a Photon's configure() method
|
|
666
|
-
*
|
|
667
|
-
* The configure() method is a by-convention method for photon configuration.
|
|
668
|
-
* Similar to how main() makes a photon a UI application, configure() makes
|
|
669
|
-
* it a configurable photon.
|
|
670
|
-
*
|
|
671
|
-
* When present, the framework will:
|
|
672
|
-
* 1. Extract parameter schema from the method signature
|
|
673
|
-
* 2. Present a configuration UI during install/setup
|
|
674
|
-
* 3. Store config at ~/.photon/{photonName}/config.json
|
|
675
|
-
* 4. Make config available via getConfig()
|
|
676
|
-
*
|
|
677
|
-
* Example:
|
|
678
|
-
* ```typescript
|
|
679
|
-
* export default class MyPhoton extends Photon {
|
|
680
|
-
* async configure(params: {
|
|
681
|
-
* apiEndpoint: string;
|
|
682
|
-
* maxRetries?: number;
|
|
683
|
-
* }) {
|
|
684
|
-
* // Save config - framework handles storage
|
|
685
|
-
* return { success: true };
|
|
686
|
-
* }
|
|
687
|
-
*
|
|
688
|
-
* async getConfig() {
|
|
689
|
-
* // Read config - framework handles loading
|
|
690
|
-
* return loadPhotonConfig('my-photon');
|
|
691
|
-
* }
|
|
692
|
-
* }
|
|
693
|
-
* ```
|
|
694
737
|
*/
|
|
695
738
|
export interface ConfigSchema {
|
|
696
739
|
/** Whether configure() method exists */
|
|
@@ -702,3 +745,31 @@ export interface ConfigSchema {
|
|
|
702
745
|
/** Description from configure() JSDoc */
|
|
703
746
|
description?: string;
|
|
704
747
|
}
|
|
748
|
+
|
|
749
|
+
// ══════════════════════════════════════════════════════════════════════════════
|
|
750
|
+
// NOTIFICATION SUBSCRIPTIONS (@notify-on tag)
|
|
751
|
+
// ══════════════════════════════════════════════════════════════════════════════
|
|
752
|
+
|
|
753
|
+
/**
|
|
754
|
+
* Notification subscription declaration extracted from @notify-on tag
|
|
755
|
+
* Specifies which event types this photon cares about for notifications
|
|
756
|
+
*/
|
|
757
|
+
export interface NotificationSubscription {
|
|
758
|
+
/** Event types this photon wants to be notified about */
|
|
759
|
+
watchFor: string[];
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
/**
|
|
763
|
+
* Notification metadata attached to method return values via __notification property
|
|
764
|
+
* Specifies priority and type of notification when an event occurs
|
|
765
|
+
*/
|
|
766
|
+
export interface NotificationMetadata {
|
|
767
|
+
/** Type of notification (mentions, deadline, error, etc.) */
|
|
768
|
+
type: string;
|
|
769
|
+
/** Priority level for display and handling */
|
|
770
|
+
priority: 'critical' | 'warning' | 'info';
|
|
771
|
+
/** Optional message to display */
|
|
772
|
+
message?: string;
|
|
773
|
+
/** Optional tags for additional filtering */
|
|
774
|
+
tags?: string[];
|
|
775
|
+
}
|