@portel/photon 1.7.0 → 1.8.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 (94) hide show
  1. package/README.md +23 -24
  2. package/dist/auto-ui/beam.d.ts.map +1 -1
  3. package/dist/auto-ui/beam.js +117 -42
  4. package/dist/auto-ui/beam.js.map +1 -1
  5. package/dist/auto-ui/design-system/tokens.d.ts +1 -1
  6. package/dist/auto-ui/design-system/tokens.d.ts.map +1 -1
  7. package/dist/auto-ui/design-system/tokens.js +1 -1
  8. package/dist/auto-ui/design-system/tokens.js.map +1 -1
  9. package/dist/auto-ui/frontend/index.html +1 -1
  10. package/dist/auto-ui/rendering/components.d.ts.map +1 -1
  11. package/dist/auto-ui/rendering/components.js +568 -0
  12. package/dist/auto-ui/rendering/components.js.map +1 -1
  13. package/dist/auto-ui/rendering/field-analyzer.d.ts +56 -0
  14. package/dist/auto-ui/rendering/field-analyzer.d.ts.map +1 -1
  15. package/dist/auto-ui/rendering/field-analyzer.js +177 -0
  16. package/dist/auto-ui/rendering/field-analyzer.js.map +1 -1
  17. package/dist/auto-ui/rendering/layout-selector.d.ts +14 -2
  18. package/dist/auto-ui/rendering/layout-selector.d.ts.map +1 -1
  19. package/dist/auto-ui/rendering/layout-selector.js +125 -1
  20. package/dist/auto-ui/rendering/layout-selector.js.map +1 -1
  21. package/dist/auto-ui/streamable-http-transport.d.ts +1 -1
  22. package/dist/auto-ui/streamable-http-transport.d.ts.map +1 -1
  23. package/dist/auto-ui/streamable-http-transport.js +353 -19
  24. package/dist/auto-ui/streamable-http-transport.js.map +1 -1
  25. package/dist/auto-ui/types.d.ts +7 -1
  26. package/dist/auto-ui/types.d.ts.map +1 -1
  27. package/dist/auto-ui/types.js.map +1 -1
  28. package/dist/beam.bundle.js +22441 -4216
  29. package/dist/beam.bundle.js.map +4 -4
  30. package/dist/cli/commands/info.d.ts.map +1 -1
  31. package/dist/cli/commands/info.js +37 -0
  32. package/dist/cli/commands/info.js.map +1 -1
  33. package/dist/cli/commands/package.d.ts.map +1 -1
  34. package/dist/cli/commands/package.js +16 -0
  35. package/dist/cli/commands/package.js.map +1 -1
  36. package/dist/cli.d.ts.map +1 -1
  37. package/dist/cli.js +628 -14
  38. package/dist/cli.js.map +1 -1
  39. package/dist/context-store.d.ts +79 -0
  40. package/dist/context-store.d.ts.map +1 -0
  41. package/dist/context-store.js +210 -0
  42. package/dist/context-store.js.map +1 -0
  43. package/dist/daemon/client.d.ts +13 -4
  44. package/dist/daemon/client.d.ts.map +1 -1
  45. package/dist/daemon/client.js +138 -77
  46. package/dist/daemon/client.js.map +1 -1
  47. package/dist/daemon/manager.d.ts +0 -25
  48. package/dist/daemon/manager.d.ts.map +1 -1
  49. package/dist/daemon/manager.js +10 -38
  50. package/dist/daemon/manager.js.map +1 -1
  51. package/dist/daemon/protocol.d.ts +7 -2
  52. package/dist/daemon/protocol.d.ts.map +1 -1
  53. package/dist/daemon/protocol.js.map +1 -1
  54. package/dist/daemon/server.js +257 -35
  55. package/dist/daemon/server.js.map +1 -1
  56. package/dist/daemon/session-manager.d.ts +24 -4
  57. package/dist/daemon/session-manager.d.ts.map +1 -1
  58. package/dist/daemon/session-manager.js +62 -12
  59. package/dist/daemon/session-manager.js.map +1 -1
  60. package/dist/index.d.ts +0 -1
  61. package/dist/index.d.ts.map +1 -1
  62. package/dist/index.js +0 -3
  63. package/dist/index.js.map +1 -1
  64. package/dist/loader.d.ts +3 -20
  65. package/dist/loader.d.ts.map +1 -1
  66. package/dist/loader.js +53 -75
  67. package/dist/loader.js.map +1 -1
  68. package/dist/photon-cli-runner.d.ts.map +1 -1
  69. package/dist/photon-cli-runner.js +258 -218
  70. package/dist/photon-cli-runner.js.map +1 -1
  71. package/dist/photon-doc-extractor.d.ts +2 -0
  72. package/dist/photon-doc-extractor.d.ts.map +1 -1
  73. package/dist/photon-doc-extractor.js +42 -6
  74. package/dist/photon-doc-extractor.js.map +1 -1
  75. package/dist/photons/maker.photon.d.ts.map +1 -1
  76. package/dist/photons/maker.photon.js +3 -1
  77. package/dist/photons/maker.photon.js.map +1 -1
  78. package/dist/photons/maker.photon.ts +3 -1
  79. package/dist/serv/index.d.ts.map +1 -1
  80. package/dist/serv/index.js.map +1 -1
  81. package/dist/server.d.ts +32 -15
  82. package/dist/server.d.ts.map +1 -1
  83. package/dist/server.js +468 -469
  84. package/dist/server.js.map +1 -1
  85. package/dist/shared/security.d.ts.map +1 -1
  86. package/dist/shared/security.js +4 -8
  87. package/dist/shared/security.js.map +1 -1
  88. package/dist/shell-completions.d.ts +21 -0
  89. package/dist/shell-completions.d.ts.map +1 -0
  90. package/dist/shell-completions.js +102 -0
  91. package/dist/shell-completions.js.map +1 -0
  92. package/dist/template-manager.d.ts.map +1 -1
  93. package/dist/template-manager.js.map +1 -1
  94. package/package.json +10 -6
@@ -16,6 +16,8 @@ export interface DaemonRequest {
16
16
  photonPath?: string;
17
17
  sessionId?: string;
18
18
  clientType?: 'cli' | 'mcp' | 'code-mode' | 'beam';
19
+ /** Instance name hint for auto-recovery from session drift */
20
+ instanceName?: string;
19
21
  method?: string;
20
22
  args?: Record<string, unknown>;
21
23
  /** Response to a prompt request */
@@ -32,7 +34,7 @@ export interface DaemonRequest {
32
34
  jobId?: string;
33
35
  /** Cron expression for scheduled jobs */
34
36
  cron?: string;
35
- /** Last event ID received by client (for replay on reconnect) */
37
+ /** Last event timestamp received by client (for delta sync on reconnect) */
36
38
  lastEventId?: string;
37
39
  }
38
40
  /**
@@ -44,6 +46,8 @@ export interface DaemonResponse {
44
46
  success?: boolean;
45
47
  data?: unknown;
46
48
  error?: string;
49
+ /** Actionable hint for the caller when type === 'error' */
50
+ suggestion?: string;
47
51
  /** Prompt request details (when type === 'prompt') */
48
52
  prompt?: {
49
53
  type: 'text' | 'password' | 'confirm' | 'select';
@@ -58,7 +62,7 @@ export interface DaemonResponse {
58
62
  channel?: string;
59
63
  /** Message payload for channel_message type */
60
64
  message?: unknown;
61
- /** Event ID for tracking (for replay support) */
65
+ /** Event timestamp for tracking (for delta sync support) */
62
66
  eventId?: string;
63
67
  }
64
68
  /**
@@ -78,6 +82,7 @@ export interface DaemonStatus {
78
82
  export interface PhotonSession {
79
83
  id: string;
80
84
  instance: PhotonMCPClass;
85
+ instanceName: string;
81
86
  createdAt: number;
82
87
  lastActivity: number;
83
88
  clientType?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../../src/daemon/protocol.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1D;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,IAAI,EACA,SAAS,GACT,MAAM,GACN,UAAU,GACV,QAAQ,GACR,iBAAiB,GACjB,WAAW,GACX,aAAa,GACb,SAAS,GACT,MAAM,GACN,QAAQ,GACR,UAAU,GACV,YAAY,GACZ,WAAW,GACX,YAAY,GACZ,kBAAkB,CAAC;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,2FAA2F;IAC3F,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,qEAAqE;IACrE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,KAAK,GAAG,KAAK,GAAG,WAAW,GAAG,MAAM,CAAC;IAClD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,mCAAmC;IACnC,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC;IACtC,0CAA0C;IAC1C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,6CAA6C;IAC7C,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,2CAA2C;IAC3C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,0CAA0C;IAC1C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,qCAAqC;IACrC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,yCAAyC;IACzC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,iEAAiE;IACjE,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,QAAQ,GAAG,iBAAiB,GAAG,gBAAgB,CAAC;IACpF,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sDAAsD;IACtD,MAAM,CAAC,EAAE;QACP,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,GAAG,QAAQ,CAAC;QACjD,OAAO,EAAE,MAAM,CAAC;QAChB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,OAAO,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG;YAAE,KAAK,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;KAC5D,CAAC;IACF,4CAA4C;IAC5C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,+CAA+C;IAC/C,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,iDAAiD;IACjD,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,OAAO,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,cAAc,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,aAAa,CAyDvE;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,cAAc,CAazE"}
1
+ {"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../../src/daemon/protocol.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"protocol.js","sourceRoot":"","sources":["../../src/daemon/protocol.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AA2HH;;GAEG;AACH,MAAM,UAAU,oBAAoB,CAAC,GAAY;IAC/C,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IAC1D,MAAM,GAAG,GAAG,GAA6B,CAAC;IAE1C,IAAI,OAAO,GAAG,CAAC,EAAE,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAE7C,MAAM,UAAU,GAAG;QACjB,SAAS;QACT,MAAM;QACN,UAAU;QACV,QAAQ;QACR,iBAAiB;QACjB,WAAW;QACX,aAAa;QACb,SAAS;QACT,MAAM;QACN,QAAQ;QACR,UAAU;QACV,YAAY;QACZ,WAAW;QACX,YAAY;QACZ,kBAAkB;KACnB,CAAC;IACF,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAc,CAAC;QAAE,OAAO,KAAK,CAAC;IAE3D,IAAI,GAAG,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QAC3B,IAAI,OAAO,GAAG,CAAC,MAAM,KAAK,QAAQ;YAAE,OAAO,KAAK,CAAC;IACnD,CAAC;IAED,4CAA4C;IAC5C,IAAI,CAAC,WAAW,EAAE,aAAa,EAAE,SAAS,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAc,CAAC,EAAE,CAAC;QACzE,IAAI,OAAO,GAAG,CAAC,OAAO,KAAK,QAAQ;YAAE,OAAO,KAAK,CAAC;IACpD,CAAC;IAED,sCAAsC;IACtC,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAc,CAAC,EAAE,CAAC;QACpD,IAAI,OAAO,GAAG,CAAC,QAAQ,KAAK,QAAQ;YAAE,OAAO,KAAK,CAAC;IACrD,CAAC;IAED,sDAAsD;IACtD,IAAI,GAAG,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;QAC5B,IAAI,OAAO,GAAG,CAAC,KAAK,KAAK,QAAQ;YAAE,OAAO,KAAK,CAAC;QAChD,IAAI,OAAO,GAAG,CAAC,MAAM,KAAK,QAAQ;YAAE,OAAO,KAAK,CAAC;QACjD,IAAI,OAAO,GAAG,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO,KAAK,CAAC;IACjD,CAAC;IAED,4BAA4B;IAC5B,IAAI,GAAG,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;QAC9B,IAAI,OAAO,GAAG,CAAC,KAAK,KAAK,QAAQ;YAAE,OAAO,KAAK,CAAC;IAClD,CAAC;IAED,6BAA6B;IAC7B,IAAI,GAAG,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC1B,IAAI,OAAO,GAAG,CAAC,UAAU,KAAK,QAAQ;YAAE,OAAO,KAAK,CAAC;IACvD,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,qBAAqB,CAAC,GAAY;IAChD,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IAC1D,MAAM,GAAG,GAAG,GAA8B,CAAC;IAE3C,IAAI,OAAO,GAAG,CAAC,EAAE,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC7C,IACE,CAAC,CAAC,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,iBAAiB,EAAE,gBAAgB,CAAC,CAAC,QAAQ,CAClF,GAAG,CAAC,IAAc,CACnB;QAED,OAAO,KAAK,CAAC;IAEf,OAAO,IAAI,CAAC;AACd,CAAC"}
1
+ {"version":3,"file":"protocol.js","sourceRoot":"","sources":["../../src/daemon/protocol.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAgIH;;GAEG;AACH,MAAM,UAAU,oBAAoB,CAAC,GAAY;IAC/C,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IAC1D,MAAM,GAAG,GAAG,GAA6B,CAAC;IAE1C,IAAI,OAAO,GAAG,CAAC,EAAE,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAE7C,MAAM,UAAU,GAAG;QACjB,SAAS;QACT,MAAM;QACN,UAAU;QACV,QAAQ;QACR,iBAAiB;QACjB,WAAW;QACX,aAAa;QACb,SAAS;QACT,MAAM;QACN,QAAQ;QACR,UAAU;QACV,YAAY;QACZ,WAAW;QACX,YAAY;QACZ,kBAAkB;KACnB,CAAC;IACF,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAc,CAAC;QAAE,OAAO,KAAK,CAAC;IAE3D,IAAI,GAAG,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QAC3B,IAAI,OAAO,GAAG,CAAC,MAAM,KAAK,QAAQ;YAAE,OAAO,KAAK,CAAC;IACnD,CAAC;IAED,4CAA4C;IAC5C,IAAI,CAAC,WAAW,EAAE,aAAa,EAAE,SAAS,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAc,CAAC,EAAE,CAAC;QACzE,IAAI,OAAO,GAAG,CAAC,OAAO,KAAK,QAAQ;YAAE,OAAO,KAAK,CAAC;IACpD,CAAC;IAED,sCAAsC;IACtC,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAc,CAAC,EAAE,CAAC;QACpD,IAAI,OAAO,GAAG,CAAC,QAAQ,KAAK,QAAQ;YAAE,OAAO,KAAK,CAAC;IACrD,CAAC;IAED,sDAAsD;IACtD,IAAI,GAAG,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;QAC5B,IAAI,OAAO,GAAG,CAAC,KAAK,KAAK,QAAQ;YAAE,OAAO,KAAK,CAAC;QAChD,IAAI,OAAO,GAAG,CAAC,MAAM,KAAK,QAAQ;YAAE,OAAO,KAAK,CAAC;QACjD,IAAI,OAAO,GAAG,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO,KAAK,CAAC;IACjD,CAAC;IAED,4BAA4B;IAC5B,IAAI,GAAG,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;QAC9B,IAAI,OAAO,GAAG,CAAC,KAAK,KAAK,QAAQ;YAAE,OAAO,KAAK,CAAC;IAClD,CAAC;IAED,6BAA6B;IAC7B,IAAI,GAAG,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC1B,IAAI,OAAO,GAAG,CAAC,UAAU,KAAK,QAAQ;YAAE,OAAO,KAAK,CAAC;IACvD,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,qBAAqB,CAAC,GAAY;IAChD,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IAC1D,MAAM,GAAG,GAAG,GAA8B,CAAC;IAE3C,IAAI,OAAO,GAAG,CAAC,EAAE,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC7C,IACE,CAAC,CAAC,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,iBAAiB,EAAE,gBAAgB,CAAC,CAAC,QAAQ,CAClF,GAAG,CAAC,IAAc,CACnB;QAED,OAAO,KAAK,CAAC;IAEf,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -31,46 +31,52 @@ if (!socketPath) {
31
31
  // Map of photonName -> SessionManager (lazy initialized)
32
32
  const sessionManagers = new Map();
33
33
  const photonPaths = new Map(); // photonName -> photonPath
34
- let idleTimeout = 600000; // 10 minutes default
34
+ const fileWatchers = new Map();
35
+ const watchDebounce = new Map();
36
+ let idleTimeout = 0; // Daemon stays alive — it manages persistent stateful data
35
37
  let idleTimer = null;
36
38
  // Track pending prompts waiting for user input
37
39
  const pendingPrompts = new Map();
38
40
  // Channel subscriptions for pub/sub
39
41
  const channelSubscriptions = new Map();
40
- const EVENT_BUFFER_SIZE = 30;
42
+ /** Buffer retention window — events older than this are purged */
43
+ const EVENT_BUFFER_DURATION_MS = 5 * 60 * 1000; // 5 minutes
41
44
  const channelEventBuffers = new Map();
42
45
  function bufferEvent(channel, message) {
43
46
  let buffer = channelEventBuffers.get(channel);
44
47
  if (!buffer) {
45
- buffer = { events: [], nextId: 1 };
48
+ buffer = { events: [] };
46
49
  channelEventBuffers.set(channel, buffer);
47
50
  }
48
- const eventId = buffer.nextId++;
51
+ const now = Date.now();
49
52
  const event = {
50
- id: eventId,
53
+ id: now,
51
54
  channel,
52
55
  message,
53
- timestamp: Date.now(),
56
+ timestamp: now,
54
57
  };
55
58
  buffer.events.push(event);
56
- // Keep only last N events (circular buffer)
57
- if (buffer.events.length > EVENT_BUFFER_SIZE) {
59
+ // Purge events older than retention window
60
+ const cutoff = now - EVENT_BUFFER_DURATION_MS;
61
+ while (buffer.events.length > 0 && buffer.events[0].timestamp < cutoff) {
58
62
  buffer.events.shift();
59
63
  }
60
- return eventId;
64
+ return now;
61
65
  }
62
- function getEventsSince(channel, lastEventId) {
66
+ function getEventsSince(channel, lastTimestamp) {
63
67
  const buffer = channelEventBuffers.get(channel);
64
68
  if (!buffer || buffer.events.length === 0) {
65
- return { events: [], refreshNeeded: false };
69
+ // If client has a lastEventId but buffer is empty (e.g. daemon restarted),
70
+ // signal that a full refresh is needed — events were lost.
71
+ return { events: [], refreshNeeded: lastTimestamp > 0 };
66
72
  }
67
73
  const oldestEvent = buffer.events[0];
68
- // If lastEventId is older than our oldest buffered event, refresh needed
69
- if (lastEventId < oldestEvent.id) {
74
+ // Client's last timestamp is older than our oldest buffered event → stale, full sync needed
75
+ if (lastTimestamp < oldestEvent.timestamp) {
70
76
  return { events: [], refreshNeeded: true };
71
77
  }
72
- // Find events to replay
73
- const events = buffer.events.filter((e) => e.id > lastEventId);
78
+ // Delta sync: return events after the client's last timestamp
79
+ const events = buffer.events.filter((e) => e.timestamp > lastTimestamp);
74
80
  return { events, refreshNeeded: false };
75
81
  }
76
82
  // ════════════════════════════════════════════════════════════════════════════════
@@ -268,7 +274,9 @@ function startWebhookServer(port) {
268
274
  const expectedSecret = process.env.PHOTON_WEBHOOK_SECRET;
269
275
  if (expectedSecret) {
270
276
  const providedSecret = req.headers['x-webhook-secret'];
271
- if (!providedSecret || typeof providedSecret !== 'string' || !timingSafeEqual(providedSecret, expectedSecret)) {
277
+ if (!providedSecret ||
278
+ typeof providedSecret !== 'string' ||
279
+ !timingSafeEqual(providedSecret, expectedSecret)) {
272
280
  res.writeHead(401, { 'Content-Type': 'application/json' });
273
281
  res.end(JSON.stringify({ error: 'Invalid webhook secret' }));
274
282
  return;
@@ -277,7 +285,9 @@ function startWebhookServer(port) {
277
285
  else if (!process.env.PHOTON_WEBHOOK_ALLOW_UNAUTHENTICATED) {
278
286
  // Security: require explicit opt-in for unauthenticated webhooks
279
287
  res.writeHead(403, { 'Content-Type': 'application/json' });
280
- res.end(JSON.stringify({ error: 'Webhook secret not configured. Set PHOTON_WEBHOOK_SECRET or PHOTON_WEBHOOK_ALLOW_UNAUTHENTICATED=true' }));
288
+ res.end(JSON.stringify({
289
+ error: 'Webhook secret not configured. Set PHOTON_WEBHOOK_SECRET or PHOTON_WEBHOOK_ALLOW_UNAUTHENTICATED=true',
290
+ }));
281
291
  return;
282
292
  }
283
293
  let args = {};
@@ -366,7 +376,8 @@ function publishToChannel(channel, message, excludeSocket) {
366
376
  sentSockets.add(socket);
367
377
  }
368
378
  catch {
369
- // Socket write failed
379
+ // Dead socket — remove from subscribers
380
+ exactSubscribers.delete(socket);
370
381
  }
371
382
  }
372
383
  }
@@ -384,7 +395,8 @@ function publishToChannel(channel, message, excludeSocket) {
384
395
  sentSockets.add(socket);
385
396
  }
386
397
  catch {
387
- // Socket write failed
398
+ // Dead socket — remove from subscribers
399
+ wildcardSubscribers.delete(socket);
388
400
  }
389
401
  }
390
402
  }
@@ -418,6 +430,7 @@ async function getOrCreateSessionManager(photonName, photonPath) {
418
430
  manager = new SessionManager(pathToUse, photonName, idleTimeout, logger.child({ scope: photonName }));
419
431
  sessionManagers.set(photonName, manager);
420
432
  photonPaths.set(photonName, pathToUse);
433
+ watchPhotonFile(photonName, pathToUse);
421
434
  logger.info('Session manager initialized', { photonName });
422
435
  return manager;
423
436
  }
@@ -505,32 +518,32 @@ async function handleRequest(request, socket) {
505
518
  }
506
519
  subs.add(socket);
507
520
  logger.info('Client subscribed to channel', { channel, subscribers: subs.size });
508
- // Replay missed events if lastEventId provided
521
+ // Replay missed events if lastEventId (timestamp) provided
509
522
  if (lastEventId !== undefined) {
510
- const parsedLastEventId = parseInt(String(lastEventId), 10) || 0;
511
- const { events, refreshNeeded } = getEventsSince(channel, parsedLastEventId);
523
+ const lastTimestamp = parseInt(String(lastEventId), 10) || 0;
524
+ const { events, refreshNeeded } = getEventsSince(channel, lastTimestamp);
512
525
  if (refreshNeeded) {
513
- // Send refresh-needed signal
526
+ // Stale: client's timestamp is older than buffer window → full sync needed
514
527
  socket.write(JSON.stringify({
515
528
  type: 'refresh_needed',
516
529
  id: request.id,
517
530
  channel,
518
531
  }) + '\n');
519
- logger.info('Replay: refresh needed', { channel, lastEventId });
532
+ logger.info('Stale client, full sync needed', { channel, lastTimestamp });
520
533
  }
521
534
  else if (events.length > 0) {
522
- // Replay events
535
+ // Delta sync: replay missed events
523
536
  for (const event of events) {
524
537
  socket.write(JSON.stringify({
525
538
  type: 'channel_message',
526
- id: `replay_${event.id}`,
527
- eventId: event.id,
539
+ id: `replay_${event.timestamp}`,
540
+ eventId: event.timestamp,
528
541
  channel: event.channel,
529
542
  message: event.message,
530
543
  replay: true,
531
544
  }) + '\n');
532
545
  }
533
- logger.info('Replayed events', { channel, count: events.length });
546
+ logger.info('Delta sync: replayed events', { channel, count: events.length });
534
547
  }
535
548
  }
536
549
  return {
@@ -565,11 +578,11 @@ async function handleRequest(request, socket) {
565
578
  data: { published: true, channel, eventId },
566
579
  };
567
580
  }
568
- // Handle get_events_since (for event replay)
581
+ // Handle get_events_since (for delta sync / full sync detection)
569
582
  if (request.type === 'get_events_since') {
570
583
  const channel = request.channel;
571
- const parsedLastEventId = parseInt(String(request.lastEventId || '0'), 10) || 0;
572
- const { events, refreshNeeded } = getEventsSince(channel, parsedLastEventId);
584
+ const lastTimestamp = parseInt(String(request.lastEventId || '0'), 10) || 0;
585
+ const { events, refreshNeeded } = getEventsSince(channel, lastTimestamp);
573
586
  return {
574
587
  type: 'result',
575
588
  id: request.id,
@@ -620,7 +633,12 @@ async function handleRequest(request, socket) {
620
633
  if (request.type === 'schedule') {
621
634
  const photonName = request.photonName;
622
635
  if (!photonName) {
623
- return { type: 'error', id: request.id, error: 'photonName required for scheduling' };
636
+ return {
637
+ type: 'error',
638
+ id: request.id,
639
+ error: 'photonName required for scheduling',
640
+ suggestion: 'Include photonName in the request payload',
641
+ };
624
642
  }
625
643
  const job = {
626
644
  id: request.jobId,
@@ -656,11 +674,21 @@ async function handleRequest(request, socket) {
656
674
  // Handle command execution
657
675
  if (request.type === 'command') {
658
676
  if (!request.method) {
659
- return { type: 'error', id: request.id, error: 'Method name required' };
677
+ return {
678
+ type: 'error',
679
+ id: request.id,
680
+ error: 'Method name required',
681
+ suggestion: 'Specify the method to call: { method: "methodName" }',
682
+ };
660
683
  }
661
684
  const photonName = request.photonName;
662
685
  if (!photonName) {
663
- return { type: 'error', id: request.id, error: 'photonName required for commands' };
686
+ return {
687
+ type: 'error',
688
+ id: request.id,
689
+ error: 'photonName required for commands',
690
+ suggestion: 'Include photonName in the request payload',
691
+ };
664
692
  }
665
693
  const sessionManager = await getOrCreateSessionManager(photonName, request.photonPath);
666
694
  if (!sessionManager) {
@@ -672,10 +700,53 @@ async function handleRequest(request, socket) {
672
700
  }
673
701
  try {
674
702
  const session = await sessionManager.getOrCreateSession(request.sessionId, request.clientType);
703
+ // ── Auto-recover from instance drift ─────────────────────────
704
+ // If the client tells us which instance it expects but the daemon
705
+ // session has drifted (e.g. session expired and was recreated as
706
+ // "default"), silently switch back to the correct instance.
707
+ if (request.instanceName && request.instanceName !== session.instanceName) {
708
+ logger.info('Instance drift detected, auto-switching', {
709
+ from: session.instanceName || 'default',
710
+ to: request.instanceName,
711
+ sessionId: session.id,
712
+ });
713
+ await sessionManager.switchInstance(session.id, request.instanceName);
714
+ }
715
+ // ── Runtime-injected instance tools ──────────────────────────
716
+ if (request.method === '_use') {
717
+ const instanceName = String(request.args?.name ?? '');
718
+ await sessionManager.switchInstance(session.id, instanceName);
719
+ const label = instanceName || 'default';
720
+ return {
721
+ type: 'result',
722
+ id: request.id,
723
+ success: true,
724
+ data: { instance: label, message: `Switched to instance: ${label}` },
725
+ };
726
+ }
727
+ if (request.method === '_instances') {
728
+ const { InstanceStore } = await import('../context-store.js');
729
+ const store = new InstanceStore();
730
+ const instances = store.listInstances(photonName);
731
+ const current = session.instanceName || 'default';
732
+ // Ensure current instance is always in the list (may not have a state file yet)
733
+ if (!instances.includes(current)) {
734
+ instances.push(current);
735
+ instances.sort();
736
+ }
737
+ return {
738
+ type: 'result',
739
+ id: request.id,
740
+ success: true,
741
+ data: { instances, current },
742
+ };
743
+ }
744
+ // ─────────────────────────────────────────────────────────────
675
745
  logger.info('Executing request', {
676
746
  method: request.method,
677
747
  photon: photonName,
678
748
  sessionId: session.id,
749
+ instance: session.instanceName || 'default',
679
750
  });
680
751
  setPromptHandler(createSocketPromptHandler(socket, request.id));
681
752
  const outputHandler = (emit) => {
@@ -686,6 +757,14 @@ async function handleRequest(request, socket) {
686
757
  };
687
758
  const result = await sessionManager.loader.executeTool(session.instance, request.method, request.args || {}, { outputHandler });
688
759
  setPromptHandler(null);
760
+ // Persist reactive state after each tool call
761
+ await persistInstanceState(session.instance, photonName, session.instanceName);
762
+ // Notify subscribers that state may have changed
763
+ publishToChannel(`${photonName}:state-changed`, {
764
+ event: 'state-changed',
765
+ method: request.method,
766
+ data: result,
767
+ }, socket);
689
768
  return { type: 'result', id: request.id, success: true, data: result };
690
769
  }
691
770
  catch (error) {
@@ -700,6 +779,144 @@ async function handleRequest(request, socket) {
700
779
  return { type: 'error', id: request.id, error: `Unknown request type: ${request.type}` };
701
780
  }
702
781
  // ════════════════════════════════════════════════════════════════════════════════
782
+ // STATE PERSISTENCE
783
+ // ════════════════════════════════════════════════════════════════════════════════
784
+ /**
785
+ * Persist reactive collection state to disk after each tool call.
786
+ * Scans the instance for properties with _propertyName (ReactiveArray/Map/Set markers)
787
+ * and serializes them to the instance-specific state file.
788
+ */
789
+ /** Cache of state keys per photon (extracted once from source) */
790
+ const stateKeysCache = new Map();
791
+ /**
792
+ * Get the state property keys for a photon by extracting constructor params.
793
+ * State params: non-primitive with default on @stateful photon.
794
+ */
795
+ async function getStateKeys(photonName, photonPath) {
796
+ if (stateKeysCache.has(photonName)) {
797
+ return stateKeysCache.get(photonName);
798
+ }
799
+ try {
800
+ const { SchemaExtractor } = await import('@portel/photon-core');
801
+ const fsPromises = await import('fs/promises');
802
+ const source = await fsPromises.readFile(photonPath, 'utf-8');
803
+ const extractor = new SchemaExtractor();
804
+ const injections = extractor.resolveInjections(source, photonName);
805
+ const keys = injections
806
+ .filter((inj) => inj.injectionType === 'state')
807
+ .map((inj) => inj.stateKey);
808
+ stateKeysCache.set(photonName, keys);
809
+ logger.debug('State keys extracted', { photon: photonName, keys });
810
+ return keys;
811
+ }
812
+ catch (error) {
813
+ logger.error('Failed to extract state keys', {
814
+ photon: photonName,
815
+ error: getErrorMessage(error),
816
+ });
817
+ return [];
818
+ }
819
+ }
820
+ /**
821
+ * Persist reactive collection state to disk after each tool call.
822
+ * Only persists properties identified as 'state' injection type
823
+ * (non-primitive constructor params with defaults on @stateful photons).
824
+ */
825
+ async function persistInstanceState(instance, photonName, instanceName) {
826
+ try {
827
+ const photonPath = photonPaths.get(photonName);
828
+ if (!photonPath)
829
+ return;
830
+ const keys = await getStateKeys(photonName, photonPath);
831
+ if (keys.length === 0)
832
+ return;
833
+ // instance is PhotonMCPClass wrapper — actual user class is instance.instance
834
+ const target = instance?.instance ?? instance;
835
+ const snapshot = {};
836
+ for (const key of keys) {
837
+ const value = target[key];
838
+ if (value === undefined)
839
+ continue;
840
+ if (value && typeof value === 'object' && value._propertyName) {
841
+ // ReactiveArray/Map/Set — serialize underlying data
842
+ snapshot[key] = value.toJSON ? value.toJSON() : globalThis.Array.from(value);
843
+ }
844
+ else {
845
+ // Plain value (array, object, etc.)
846
+ snapshot[key] = value;
847
+ }
848
+ }
849
+ if (Object.keys(snapshot).length > 0) {
850
+ const { getInstanceStatePath } = await import('../context-store.js');
851
+ const statePath = getInstanceStatePath(photonName, instanceName);
852
+ const fsPromises = await import('fs/promises');
853
+ const path = await import('path');
854
+ await fsPromises.mkdir(path.dirname(statePath), { recursive: true });
855
+ await fsPromises.writeFile(statePath, JSON.stringify(snapshot, null, 2));
856
+ logger.debug('Persisted state', { photon: photonName, instance: instanceName || 'default' });
857
+ }
858
+ }
859
+ catch (error) {
860
+ logger.error('Failed to persist state', {
861
+ photon: photonName,
862
+ error: getErrorMessage(error),
863
+ });
864
+ }
865
+ }
866
+ // ════════════════════════════════════════════════════════════════════════════════
867
+ // FILE WATCHING (Auto Hot-Reload)
868
+ // ════════════════════════════════════════════════════════════════════════════════
869
+ function watchPhotonFile(photonName, photonPath) {
870
+ if (fileWatchers.has(photonPath))
871
+ return;
872
+ try {
873
+ const watcher = fs.watch(photonPath, (eventType) => {
874
+ // Debounce: 100ms (same as Beam)
875
+ const existing = watchDebounce.get(photonPath);
876
+ if (existing)
877
+ clearTimeout(existing);
878
+ watchDebounce.set(photonPath, setTimeout(async () => {
879
+ watchDebounce.delete(photonPath);
880
+ // On macOS, editors like sed -i and some IDEs replace the file (new inode),
881
+ // which kills the watcher. Re-watch if file still exists.
882
+ if (eventType === 'rename') {
883
+ unwatchPhotonFile(photonPath);
884
+ if (fs.existsSync(photonPath)) {
885
+ watchPhotonFile(photonName, photonPath);
886
+ }
887
+ }
888
+ if (!fs.existsSync(photonPath))
889
+ return;
890
+ logger.info('File changed, auto-reloading', { photonName, path: photonPath });
891
+ // Invalidate cached state keys so they're re-extracted from fresh source
892
+ stateKeysCache.delete(photonName);
893
+ await reloadPhoton(photonName, photonPath);
894
+ }, 100));
895
+ });
896
+ watcher.on('error', (err) => {
897
+ logger.warn('File watcher error', { photonName, error: getErrorMessage(err) });
898
+ unwatchPhotonFile(photonPath);
899
+ });
900
+ fileWatchers.set(photonPath, watcher);
901
+ logger.info('Watching photon file', { photonName, path: photonPath });
902
+ }
903
+ catch (err) {
904
+ logger.warn('Failed to watch photon file', { photonName, error: getErrorMessage(err) });
905
+ }
906
+ }
907
+ function unwatchPhotonFile(photonPath) {
908
+ const watcher = fileWatchers.get(photonPath);
909
+ if (watcher) {
910
+ watcher.close();
911
+ fileWatchers.delete(photonPath);
912
+ }
913
+ const timer = watchDebounce.get(photonPath);
914
+ if (timer) {
915
+ clearTimeout(timer);
916
+ watchDebounce.delete(photonPath);
917
+ }
918
+ }
919
+ // ════════════════════════════════════════════════════════════════════════════════
703
920
  // HOT RELOAD
704
921
  // ════════════════════════════════════════════════════════════════════════════════
705
922
  async function reloadPhoton(photonName, newPhotonPath) {
@@ -707,8 +924,9 @@ async function reloadPhoton(photonName, newPhotonPath) {
707
924
  logger.info('Hot-reloading photon', { photonName, path: newPhotonPath });
708
925
  const sessionManager = sessionManagers.get(photonName);
709
926
  if (!sessionManager) {
710
- // First time - just register the path
927
+ // First time - just register the path and start watching
711
928
  photonPaths.set(photonName, newPhotonPath);
929
+ watchPhotonFile(photonName, newPhotonPath);
712
930
  return { success: true, sessionsUpdated: 0 };
713
931
  }
714
932
  await sessionManager.loader.reloadFile(newPhotonPath);
@@ -862,6 +1080,10 @@ function shutdown() {
862
1080
  jobTimers.clear();
863
1081
  scheduledJobs.clear();
864
1082
  activeLocks.clear();
1083
+ // Close file watchers and debounce timers
1084
+ for (const photonPath of fileWatchers.keys()) {
1085
+ unwatchPhotonFile(photonPath);
1086
+ }
865
1087
  for (const manager of sessionManagers.values()) {
866
1088
  manager.destroy();
867
1089
  }