@jungjaehoon/mama-os 0.32.3 → 0.34.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 (105) hide show
  1. package/README.md +15 -17
  2. package/dist/agent/os-agent-capabilities.md +2 -2
  3. package/dist/api/graph-api.d.ts +4 -4
  4. package/dist/api/graph-api.d.ts.map +1 -1
  5. package/dist/api/graph-api.js +47 -78
  6. package/dist/api/graph-api.js.map +1 -1
  7. package/dist/api/index.d.ts +10 -0
  8. package/dist/api/index.d.ts.map +1 -1
  9. package/dist/api/index.js +9 -2
  10. package/dist/api/index.js.map +1 -1
  11. package/dist/api/operator-handler.d.ts +1 -1
  12. package/dist/api/operator-handler.js +1 -1
  13. package/dist/api/runtime-status-handler.d.ts +41 -0
  14. package/dist/api/runtime-status-handler.d.ts.map +1 -0
  15. package/dist/api/runtime-status-handler.js +55 -0
  16. package/dist/api/runtime-status-handler.js.map +1 -0
  17. package/dist/cli/commands/start.d.ts.map +1 -1
  18. package/dist/cli/commands/start.js +58 -23
  19. package/dist/cli/commands/start.js.map +1 -1
  20. package/dist/cli/runtime/api-routes-init.d.ts +2 -0
  21. package/dist/cli/runtime/api-routes-init.d.ts.map +1 -1
  22. package/dist/cli/runtime/api-routes-init.js +62 -1
  23. package/dist/cli/runtime/api-routes-init.js.map +1 -1
  24. package/dist/cli/runtime/api-server-init.d.ts +12 -1
  25. package/dist/cli/runtime/api-server-init.d.ts.map +1 -1
  26. package/dist/cli/runtime/api-server-init.js +54 -0
  27. package/dist/cli/runtime/api-server-init.js.map +1 -1
  28. package/dist/cli/runtime/utilities.d.ts +1 -1
  29. package/dist/cli/runtime/utilities.d.ts.map +1 -1
  30. package/dist/cli/runtime/utilities.js +3 -1
  31. package/dist/cli/runtime/utilities.js.map +1 -1
  32. package/dist/gateways/message-router.d.ts +4 -0
  33. package/dist/gateways/message-router.d.ts.map +1 -1
  34. package/dist/gateways/message-router.js +62 -13
  35. package/dist/gateways/message-router.js.map +1 -1
  36. package/dist/gateways/owner-report-inbox.d.ts +40 -0
  37. package/dist/gateways/owner-report-inbox.d.ts.map +1 -0
  38. package/dist/gateways/owner-report-inbox.js +128 -0
  39. package/dist/gateways/owner-report-inbox.js.map +1 -0
  40. package/dist/gateways/report-projection-v1.d.ts +41 -0
  41. package/dist/gateways/report-projection-v1.d.ts.map +1 -0
  42. package/dist/gateways/report-projection-v1.js +160 -0
  43. package/dist/gateways/report-projection-v1.js.map +1 -0
  44. package/dist/gateways/session-store.d.ts +22 -0
  45. package/dist/gateways/session-store.d.ts.map +1 -1
  46. package/dist/gateways/session-store.js +76 -3
  47. package/dist/gateways/session-store.js.map +1 -1
  48. package/dist/gateways/telegram-message-ledger.d.ts +10 -0
  49. package/dist/gateways/telegram-message-ledger.d.ts.map +1 -1
  50. package/dist/gateways/telegram-message-ledger.js +23 -2
  51. package/dist/gateways/telegram-message-ledger.js.map +1 -1
  52. package/dist/gateways/telegram-report-context-store.d.ts +193 -0
  53. package/dist/gateways/telegram-report-context-store.d.ts.map +1 -0
  54. package/dist/gateways/telegram-report-context-store.js +612 -0
  55. package/dist/gateways/telegram-report-context-store.js.map +1 -0
  56. package/dist/gateways/telegram.d.ts +9 -0
  57. package/dist/gateways/telegram.d.ts.map +1 -1
  58. package/dist/gateways/telegram.js +100 -0
  59. package/dist/gateways/telegram.js.map +1 -1
  60. package/dist/gateways/types.d.ts +12 -0
  61. package/dist/gateways/types.d.ts.map +1 -1
  62. package/dist/multi-agent/dashboard-agent-persona.js +2 -2
  63. package/dist/operator/board-slot-instructions.d.ts +1 -1
  64. package/dist/operator/board-slot-instructions.js +1 -1
  65. package/dist/operator/operator-trigger-loop.d.ts +17 -1
  66. package/dist/operator/operator-trigger-loop.d.ts.map +1 -1
  67. package/dist/operator/operator-trigger-loop.js +87 -23
  68. package/dist/operator/operator-trigger-loop.js.map +1 -1
  69. package/dist/operator/report-delivery-coordinator.d.ts +82 -0
  70. package/dist/operator/report-delivery-coordinator.d.ts.map +1 -0
  71. package/dist/operator/report-delivery-coordinator.js +123 -0
  72. package/dist/operator/report-delivery-coordinator.js.map +1 -0
  73. package/dist/operator/situation-report.d.ts +7 -0
  74. package/dist/operator/situation-report.d.ts.map +1 -1
  75. package/dist/operator/situation-report.js +12 -0
  76. package/dist/operator/situation-report.js.map +1 -1
  77. package/package.json +1 -2
  78. package/public/viewer/js/modules/system.js +183 -0
  79. package/public/viewer/js/utils/ui-commands.js +4 -9
  80. package/public/viewer/log-viewer.html +2 -192
  81. package/public/viewer/operator/operator.css +1 -0
  82. package/public/viewer/operator/operator.js +14830 -0
  83. package/public/viewer/src/modules/system.ts +242 -0
  84. package/public/viewer/src/utils/ui-commands.ts +4 -8
  85. package/public/viewer/sw.js +44 -10
  86. package/public/viewer/viewer.css +338 -331
  87. package/public/viewer/viewer.html +388 -1472
  88. package/public/ui/assets/index-BDXDGEV6.js +0 -62
  89. package/public/ui/assets/index-po2tN0xq.css +0 -1
  90. package/public/ui/index.html +0 -14
  91. package/public/ui/theme-init.js +0 -20
  92. package/public/viewer/js/modules/agents.js +0 -1153
  93. package/public/viewer/js/modules/chat.js +0 -1993
  94. package/public/viewer/js/modules/connector-feed.js +0 -352
  95. package/public/viewer/js/modules/dashboard.js +0 -456
  96. package/public/viewer/js/modules/entity-audit.js +0 -106
  97. package/public/viewer/js/modules/entity-review.js +0 -66
  98. package/public/viewer/js/modules/settings.js +0 -1257
  99. package/public/viewer/src/modules/agents.ts +0 -1302
  100. package/public/viewer/src/modules/chat.ts +0 -2426
  101. package/public/viewer/src/modules/connector-feed.ts +0 -391
  102. package/public/viewer/src/modules/dashboard.ts +0 -498
  103. package/public/viewer/src/modules/entity-audit.ts +0 -154
  104. package/public/viewer/src/modules/entity-review.ts +0 -175
  105. package/public/viewer/src/modules/settings.ts +0 -1547
package/README.md CHANGED
@@ -86,7 +86,7 @@ npx @jungjaehoon/mama-os init --backend cline # or claude / codex / auto
86
86
  mama start
87
87
 
88
88
  # 3. Open the operator board
89
- open http://localhost:3847/ui
89
+ open http://localhost:3847/viewer
90
90
  ```
91
91
 
92
92
  **Prerequisites:** Node.js >= 22.13.0, one authenticated backend CLI (Claude, Codex, or Cline), 500MB disk space.
@@ -157,25 +157,23 @@ Agents delegate via `delegate()` with skill injection and automatic retry. Confi
157
157
 
158
158
  ## Viewer
159
159
 
160
- Web UI at `http://localhost:3847` (redirects to the operator board at `/ui`). PWA-enabled for
161
- mobile (add to home screen).
160
+ One web console at `http://localhost:3847/viewer` (`/` redirects there), opening on the
161
+ operator board. PWA-enabled for mobile (add to home screen).
162
162
 
163
- **Operator board (`/ui`)** — the primary surface: four agent-published report slots (briefing,
164
- action required, decisions, pipeline) rendered live over SSE, plus a Triggers tab showing the
165
- trigger loop's library with an owner veto tray.
163
+ Three groups:
166
164
 
167
- **Legacy viewer (`/viewer`)** tabs:
165
+ | Group | View | What it shows |
166
+ | ------------- | ---------- | ------------------------------------------------------------------------------------------------ |
167
+ | **Operator** | Board | Four agent-published report slots (briefing, action required, decisions, pipeline) live over SSE |
168
+ | **Operator** | Tasks | The task board fed from your channels |
169
+ | **Operator** | Triggers | The trigger loop's library with an owner veto tray |
170
+ | **Knowledge** | Memory | Interactive reasoning graph (1000+ nodes), search, export |
171
+ | **Knowledge** | Wiki | Knowledge base (syncs with Obsidian vault) |
172
+ | **System** | Runtime | Read-only runtime truth: backend, model, gateways, health |
173
+ | **System** | Connectors | Connector status and last poll |
174
+ | **System** | Logs | Daemon logs with filtering, pinning, stats, WebSocket and polling |
168
175
 
169
- | Tab | What it shows |
170
- | ------------- | ---------------------------------------------------------- |
171
- | **Dashboard** | Agent activity, memory stats, system health |
172
- | **Feed** | Real-time stream from all connected sources |
173
- | **Wiki** | Knowledge base (syncs with Obsidian vault) |
174
- | **Memory** | Interactive reasoning graph (1000+ nodes), search, export |
175
- | **Logs** | Daemon logs with filtering, pinning, stats, WebSocket mode |
176
- | **Settings** | Connectors, gateways, agents, cron, token budget |
177
-
178
- Floating chat panel on every tab — voice input, TTS, slash commands.
176
+ Each view has its own hash route, e.g. `/viewer#operator/tasks?task=42`.
179
177
 
180
178
  ## Gateway Integrations
181
179
 
@@ -355,7 +355,7 @@ Restart unnecessary. Continue anyway?
355
355
 
356
356
  **DO NOT confuse contexts**:
357
357
 
358
- ❌ **Wrong** (in Mobile Chat):
358
+ ❌ **Wrong** (in a chat gateway conversation):
359
359
 
360
360
  ```text
361
361
 
@@ -365,7 +365,7 @@ Want me to check the system status?" ← Mentioning OS Agent role in normal Chat
365
365
 
366
366
  ```
367
367
 
368
- ✅ **Correct** (in Mobile Chat):
368
+ ✅ **Correct** (in a chat gateway conversation):
369
369
 
370
370
  ```text
371
371
 
@@ -1,13 +1,13 @@
1
1
  /**
2
2
  * MAMA Graph API
3
3
  *
4
- * HTTP API endpoints for Graph Viewer.
4
+ * HTTP API endpoints for the Viewer.
5
5
  * Provides /graph endpoint for fetching decisions and edges data.
6
6
  * Provides /viewer endpoint for serving HTML viewer.
7
7
  */
8
8
  import type { GraphNode, GraphEdge, CheckpointData, GraphHandlerOptions, GraphHandlerFn } from './graph-api-types.js';
9
- declare const VIEWER_HTML_PATH: string;
10
- declare const VIEWER_CSS_PATH: string;
9
+ declare const viewerHtmlPath: () => string;
10
+ declare const viewerCssPath: () => string;
11
11
  declare const DEFAULT_GRAPH_LIMIT = 300;
12
12
  declare function migrateLegacyManagedBackends<T extends Record<string, unknown>>(config: T): T;
13
13
  type GraphDecisionRow = {
@@ -37,6 +37,6 @@ declare function filterNodesByTopic(nodes: GraphNode[], topic: string): GraphNod
37
37
  declare function filterEdgesByNodes(edges: GraphEdge[], nodes: GraphNode[]): GraphEdge[];
38
38
  declare function createGraphHandler(options?: GraphHandlerOptions): GraphHandlerFn;
39
39
  declare function validateConfigUpdate(config: Record<string, any>): string[];
40
- export { createGraphHandler, DEFAULT_GRAPH_LIMIT, mapDecisionRowToGraphNode, parseGraphLimit, buildGraphMeta, validateConfigUpdate, migrateLegacyManagedBackends, getAllNodes, getAllEdges, getAllCheckpoints, getUniqueTopics, filterNodesByTopic, filterEdgesByNodes, VIEWER_HTML_PATH, VIEWER_CSS_PATH, };
40
+ export { createGraphHandler, DEFAULT_GRAPH_LIMIT, mapDecisionRowToGraphNode, parseGraphLimit, buildGraphMeta, validateConfigUpdate, migrateLegacyManagedBackends, getAllNodes, getAllEdges, getAllCheckpoints, getUniqueTopics, filterNodesByTopic, filterEdgesByNodes, viewerHtmlPath, viewerCssPath, };
41
41
  export type { GraphNode, GraphEdge, SimilarityEdge, CheckpointData, GraphHandlerOptions, } from './graph-api-types.js';
42
42
  //# sourceMappingURL=graph-api.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"graph-api.d.ts","sourceRoot":"","sources":["../../src/api/graph-api.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AA6CH,OAAO,KAAK,EACV,SAAS,EACT,SAAS,EAET,cAAc,EACd,mBAAmB,EAGnB,cAAc,EACf,MAAM,sBAAsB,CAAC;AAwC9B,QAAA,MAAM,gBAAgB,QAAuC,CAAC;AAC9D,QAAA,MAAM,eAAe,QAAsC,CAAC;AA+C5D,QAAA,MAAM,mBAAmB,MAAM,CAAC;AA8DhC,iBAAS,4BAA4B,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,CA4ErF;AAqFD,KAAK,gBAAgB,GAAG;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAUF,iBAAS,yBAAyB,CAAC,GAAG,EAAE,gBAAgB,GAAG,SAAS,CASnE;AAED,iBAAS,eAAe,CAAC,MAAM,EAAE,eAAe,GAAG,MAAM,GAAG,IAAI,CAgB/D;AAED,iBAAS,cAAc,CAAC,KAAK,EAAE;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;IACxB,SAAS,EAAE,OAAO,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;CACvB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,CASnC;AAED,iBAAe,WAAW,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC,CAmBtE;AAqDD,iBAAe,WAAW,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC,CA0BjD;AAWD,iBAAe,iBAAiB,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC,CAiC5D;AAED,iBAAS,eAAe,CAAC,KAAK,EAAE,SAAS,EAAE,GAAG,MAAM,EAAE,CAGrD;AAED,iBAAS,kBAAkB,CAAC,KAAK,EAAE,SAAS,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,CAE1E;AAED,iBAAS,kBAAkB,CAAC,KAAK,EAAE,SAAS,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,SAAS,EAAE,CAG/E;AAorBD,iBAAS,kBAAkB,CAAC,OAAO,GAAE,mBAAwB,GAAG,cAAc,CA4xC7E;AAmpBD,iBAAS,oBAAoB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,EAAE,CAiGnE;AAm3BD,OAAO,EACL,kBAAkB,EAClB,mBAAmB,EACnB,yBAAyB,EACzB,eAAe,EACf,cAAc,EACd,oBAAoB,EACpB,4BAA4B,EAC5B,WAAW,EACX,WAAW,EACX,iBAAiB,EACjB,eAAe,EACf,kBAAkB,EAClB,kBAAkB,EAClB,gBAAgB,EAChB,eAAe,GAChB,CAAC;AAEF,YAAY,EACV,SAAS,EACT,SAAS,EACT,cAAc,EACd,cAAc,EACd,mBAAmB,GACpB,MAAM,sBAAsB,CAAC"}
1
+ {"version":3,"file":"graph-api.d.ts","sourceRoot":"","sources":["../../src/api/graph-api.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AA6CH,OAAO,KAAK,EACV,SAAS,EACT,SAAS,EAET,cAAc,EACd,mBAAmB,EAGnB,cAAc,EACf,MAAM,sBAAsB,CAAC;AA8C9B,QAAA,MAAM,cAAc,QAAO,MAAwD,CAAC;AACpF,QAAA,MAAM,aAAa,QAAO,MAAuD,CAAC;AAclF,QAAA,MAAM,mBAAmB,MAAM,CAAC;AA8DhC,iBAAS,4BAA4B,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,CA4ErF;AAqFD,KAAK,gBAAgB,GAAG;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAUF,iBAAS,yBAAyB,CAAC,GAAG,EAAE,gBAAgB,GAAG,SAAS,CASnE;AAED,iBAAS,eAAe,CAAC,MAAM,EAAE,eAAe,GAAG,MAAM,GAAG,IAAI,CAgB/D;AAED,iBAAS,cAAc,CAAC,KAAK,EAAE;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;IACxB,SAAS,EAAE,OAAO,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;CACvB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,CASnC;AAED,iBAAe,WAAW,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC,CAmBtE;AAqDD,iBAAe,WAAW,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC,CA0BjD;AAWD,iBAAe,iBAAiB,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC,CAiC5D;AAED,iBAAS,eAAe,CAAC,KAAK,EAAE,SAAS,EAAE,GAAG,MAAM,EAAE,CAGrD;AAED,iBAAS,kBAAkB,CAAC,KAAK,EAAE,SAAS,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,CAE1E;AAED,iBAAS,kBAAkB,CAAC,KAAK,EAAE,SAAS,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,SAAS,EAAE,CAG/E;AAorBD,iBAAS,kBAAkB,CAAC,OAAO,GAAE,mBAAwB,GAAG,cAAc,CAmxC7E;AAmpBD,iBAAS,oBAAoB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,EAAE,CAiGnE;AAm3BD,OAAO,EACL,kBAAkB,EAClB,mBAAmB,EACnB,yBAAyB,EACzB,eAAe,EACf,cAAc,EACd,oBAAoB,EACpB,4BAA4B,EAC5B,WAAW,EACX,WAAW,EACX,iBAAiB,EACjB,eAAe,EACf,kBAAkB,EAClB,kBAAkB,EAClB,cAAc,EACd,aAAa,GACd,CAAC;AAEF,YAAY,EACV,SAAS,EACT,SAAS,EACT,cAAc,EACd,cAAc,EACd,mBAAmB,GACpB,MAAM,sBAAsB,CAAC"}
@@ -2,7 +2,7 @@
2
2
  /**
3
3
  * MAMA Graph API
4
4
  *
5
- * HTTP API endpoints for Graph Viewer.
5
+ * HTTP API endpoints for the Viewer.
6
6
  * Provides /graph endpoint for fetching decisions and edges data.
7
7
  * Provides /viewer endpoint for serving HTML viewer.
8
8
  */
@@ -10,7 +10,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
10
10
  return (mod && mod.__esModule) ? mod : { "default": mod };
11
11
  };
12
12
  Object.defineProperty(exports, "__esModule", { value: true });
13
- exports.VIEWER_CSS_PATH = exports.VIEWER_HTML_PATH = exports.DEFAULT_GRAPH_LIMIT = void 0;
13
+ exports.viewerCssPath = exports.viewerHtmlPath = exports.DEFAULT_GRAPH_LIMIT = void 0;
14
14
  exports.createGraphHandler = createGraphHandler;
15
15
  exports.mapDecisionRowToGraphNode = mapDecisionRowToGraphNode;
16
16
  exports.parseGraphLimit = parseGraphLimit;
@@ -52,8 +52,15 @@ const mama = require('@jungjaehoon/mama-core/mama-api');
52
52
  // Config paths
53
53
  const MAMA_CONFIG_PATH = path_1.default.join(os_1.default.homedir(), '.mama', 'config.yaml');
54
54
  const PACKAGE_ROOT_DIR = path_1.default.resolve(__dirname, '../..');
55
- // Paths to viewer files (now in public/viewer/)
55
+ // Paths to viewer files (now in public/viewer/). Resolved per request so the
56
+ // MAMA_VIEWER_DIR override works for tests without module-load ordering games.
56
57
  function getViewerDirectory() {
58
+ if (process.env.MAMA_VIEWER_DIR) {
59
+ // resolve() drops trailing slashes; without it the `operatorRoot + path.sep`
60
+ // traversal guard in the /viewer/operator route would 404 every request for
61
+ // '/foo/bar/'-style values.
62
+ return path_1.default.resolve(process.env.MAMA_VIEWER_DIR);
63
+ }
57
64
  const packagePublicViewer = path_1.default.join(PACKAGE_ROOT_DIR, 'public', 'viewer');
58
65
  const candidateDirs = [
59
66
  path_1.default.join(process.cwd(), 'public', 'viewer'),
@@ -70,52 +77,21 @@ function getViewerDirectory() {
70
77
  // Fallback for unusual launch locations.
71
78
  return path_1.default.join(process.cwd(), 'public', 'viewer');
72
79
  }
73
- const VIEWER_DIR = getViewerDirectory();
74
- const VIEWER_HTML_PATH = path_1.default.join(VIEWER_DIR, 'viewer.html');
75
- exports.VIEWER_HTML_PATH = VIEWER_HTML_PATH;
76
- const VIEWER_CSS_PATH = path_1.default.join(VIEWER_DIR, 'viewer.css');
77
- exports.VIEWER_CSS_PATH = VIEWER_CSS_PATH;
78
- const SW_JS_PATH = path_1.default.join(VIEWER_DIR, 'sw.js');
79
- const MANIFEST_JSON_PATH = path_1.default.join(VIEWER_DIR, 'manifest.json');
80
- const VIEWER_ICON_DIR = path_1.default.join(VIEWER_DIR, 'icons');
81
- const VIEWER_FAVICON_PATH = path_1.default.join(VIEWER_DIR, '..', 'favicon.ico');
82
- // Operator viewer SPA (built from ui/ into public/ui/). Resolved per request so
83
- // the MAMA_UI_DIR override works for tests without module-load ordering games.
84
- function getUiDirectory() {
85
- if (process.env.MAMA_UI_DIR) {
86
- // resolve() drops trailing slashes; without it the `uiRoot + path.sep`
87
- // traversal guard below would 404 every request for '/foo/bar/'-style values.
88
- return path_1.default.resolve(process.env.MAMA_UI_DIR);
89
- }
90
- const candidateDirs = [
91
- path_1.default.join(process.cwd(), 'public', 'ui'),
92
- path_1.default.join(PACKAGE_ROOT_DIR, 'public', 'ui'),
93
- path_1.default.join(__dirname, '../../public/ui'),
94
- path_1.default.join(__dirname, '../../../public/ui'),
95
- path_1.default.join(process.cwd(), 'packages', 'standalone', 'public', 'ui'),
96
- ];
97
- for (const candidate of candidateDirs) {
98
- if (fs_1.default.existsSync(candidate) && fs_1.default.statSync(candidate).isDirectory()) {
99
- return candidate;
100
- }
101
- }
102
- return path_1.default.join(process.cwd(), 'public', 'ui');
103
- }
104
- // serveStaticFile requires an explicit content type and only reads bytes (not
105
- // utf-8) for image/* or octet-stream -- fonts map to octet-stream for integrity.
106
- const UI_STATIC_MIME = {
107
- '.html': 'text/html',
108
- '.js': 'text/javascript',
80
+ const viewerHtmlPath = () => path_1.default.join(getViewerDirectory(), 'viewer.html');
81
+ exports.viewerHtmlPath = viewerHtmlPath;
82
+ const viewerCssPath = () => path_1.default.join(getViewerDirectory(), 'viewer.css');
83
+ exports.viewerCssPath = viewerCssPath;
84
+ const swJsPath = () => path_1.default.join(getViewerDirectory(), 'sw.js');
85
+ const manifestJsonPath = () => path_1.default.join(getViewerDirectory(), 'manifest.json');
86
+ const viewerIconDir = () => path_1.default.join(getViewerDirectory(), 'icons');
87
+ const viewerFaviconPath = () => path_1.default.join(getViewerDirectory(), '..', 'favicon.ico');
88
+ const viewerOperatorDir = () => path_1.default.join(getViewerDirectory(), 'operator');
89
+ // Operator bundle assets live under public/viewer/operator/ and are the only
90
+ // extensions that route serves.
91
+ const VIEWER_OPERATOR_MIME = {
92
+ '.js': 'application/javascript',
109
93
  '.css': 'text/css',
110
- '.json': 'application/json',
111
- '.svg': 'image/svg+xml',
112
- '.png': 'image/png',
113
- '.ico': 'image/x-icon',
114
- '.woff2': 'application/octet-stream',
115
94
  };
116
- // Agent-authored slot HTML renders inside this SPA. Two independent XSS layers:
117
- // DOMPurify at render (ui/src/api/report.ts) + this CSP on the document.
118
- const UI_CSP = "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'";
119
95
  const DEFAULT_GRAPH_LIMIT = 300;
120
96
  exports.DEFAULT_GRAPH_LIMIT = DEFAULT_GRAPH_LIMIT;
121
97
  const MAX_GRAPH_LIMIT = 2000;
@@ -494,10 +470,10 @@ function serveStaticFile(res, filePath, contentType, extraHeaders = {}) {
494
470
  }
495
471
  }
496
472
  function handleViewerRequest(_req, res) {
497
- serveStaticFile(res, VIEWER_HTML_PATH, 'text/html');
473
+ serveStaticFile(res, viewerHtmlPath(), 'text/html');
498
474
  }
499
475
  function handleCssRequest(_req, res) {
500
- serveStaticFile(res, VIEWER_CSS_PATH, 'text/css');
476
+ serveStaticFile(res, viewerCssPath(), 'text/css');
501
477
  }
502
478
  async function handleGraphRequest(_req, res, params) {
503
479
  const startTime = Date.now();
@@ -1047,11 +1023,11 @@ function createGraphHandler(options = {}) {
1047
1023
  res.end();
1048
1024
  return true;
1049
1025
  }
1050
- // Route: GET / - redirect to the operator board (M7 phase 2: /ui is the
1051
- // default face; the legacy viewer stays reachable at /viewer)
1026
+ // Route: GET / - redirect to /viewer, the ONE public shell. The temporary
1027
+ // /ui SPA route was retired; there is no second face to fall back to.
1052
1028
  if (pathname === '/' && req.method === 'GET') {
1053
- console.log('[GraphHandler] Redirecting / to /ui');
1054
- res.writeHead(302, { Location: '/ui' });
1029
+ console.log('[GraphHandler] Redirecting / to /viewer');
1030
+ res.writeHead(302, { Location: '/viewer' });
1055
1031
  res.end();
1056
1032
  return true;
1057
1033
  }
@@ -1069,45 +1045,38 @@ function createGraphHandler(options = {}) {
1069
1045
  }
1070
1046
  // Route: GET/HEAD /sw.js - serve Service Worker
1071
1047
  if (pathname === '/sw.js' && (req.method === 'GET' || req.method === 'HEAD')) {
1072
- serveStaticFile(res, SW_JS_PATH, 'application/javascript');
1048
+ serveStaticFile(res, swJsPath(), 'application/javascript');
1073
1049
  return true;
1074
1050
  }
1075
1051
  // Route: GET/HEAD /viewer/sw.js - serve Service Worker (alternative path)
1076
1052
  if (pathname === '/viewer/sw.js' && (req.method === 'GET' || req.method === 'HEAD')) {
1077
- serveStaticFile(res, SW_JS_PATH, 'application/javascript');
1053
+ serveStaticFile(res, swJsPath(), 'application/javascript');
1078
1054
  return true;
1079
1055
  }
1080
1056
  // Route: GET/HEAD /viewer/manifest.json - serve PWA manifest
1081
1057
  if (pathname === '/viewer/manifest.json' && (req.method === 'GET' || req.method === 'HEAD')) {
1082
- serveStaticFile(res, MANIFEST_JSON_PATH, 'application/json');
1058
+ serveStaticFile(res, manifestJsonPath(), 'application/json');
1083
1059
  return true;
1084
1060
  }
1085
1061
  // Route: GET/HEAD /favicon.ico - serve favicon
1086
1062
  if (pathname === '/favicon.ico' && (req.method === 'GET' || req.method === 'HEAD')) {
1087
- serveStaticFile(res, VIEWER_FAVICON_PATH, 'image/x-icon');
1063
+ serveStaticFile(res, viewerFaviconPath(), 'image/x-icon');
1088
1064
  return true;
1089
1065
  }
1090
- // Route: GET/HEAD /ui/* - operator viewer SPA (public/ui, Vite build)
1091
- if ((pathname === '/ui' || pathname.startsWith('/ui/')) &&
1066
+ // Route: GET/HEAD /viewer/operator/*.{js,css} - operator bundle assets.
1067
+ // Traversal-safe and extension-allowlisted: this route must never become a
1068
+ // generic file server rooted at the viewer directory.
1069
+ if (pathname.startsWith('/viewer/operator/') &&
1092
1070
  (req.method === 'GET' || req.method === 'HEAD')) {
1093
- const uiRoot = getUiDirectory();
1094
- // '/ui' and '/ui/' both mean the SPA shell; slicing '/ui/' yields '' which
1095
- // resolves to uiRoot itself and would fail the traversal guard below.
1096
- const rel = pathname === '/ui' || pathname === '/ui/' ? 'index.html' : pathname.slice('/ui/'.length);
1097
- const resolved = path_1.default.resolve(uiRoot, rel);
1098
- if (!resolved.startsWith(uiRoot + path_1.default.sep)) {
1071
+ const operatorRoot = viewerOperatorDir();
1072
+ const resolved = path_1.default.resolve(operatorRoot, pathname.slice('/viewer/operator/'.length));
1073
+ const contentType = VIEWER_OPERATOR_MIME[path_1.default.extname(resolved)];
1074
+ if (!resolved.startsWith(operatorRoot + path_1.default.sep) || !contentType) {
1099
1075
  res.writeHead(404, { 'Content-Type': 'text/plain' });
1100
1076
  res.end('Not found');
1101
1077
  return true;
1102
1078
  }
1103
- const isAsset = rel.startsWith('assets/');
1104
- const exists = fs_1.default.existsSync(resolved) && fs_1.default.statSync(resolved).isFile();
1105
- // Missing hashed assets must 404 (via serveStaticFile's ENOENT path) -- an
1106
- // index.html fallback would mask them as 200 text/html.
1107
- const target = exists || isAsset ? resolved : path_1.default.join(uiRoot, 'index.html');
1108
- const contentType = UI_STATIC_MIME[path_1.default.extname(target)] ?? 'application/octet-stream';
1109
- const extraHeaders = contentType === 'text/html' ? { 'Content-Security-Policy': UI_CSP } : {};
1110
- serveStaticFile(res, target, contentType, extraHeaders);
1079
+ serveStaticFile(res, resolved, contentType);
1111
1080
  return true;
1112
1081
  }
1113
1082
  // Route: GET/HEAD /viewer/icons/*.png - serve PWA icons
@@ -1115,7 +1084,7 @@ function createGraphHandler(options = {}) {
1115
1084
  pathname.endsWith('.png') &&
1116
1085
  (req.method === 'GET' || req.method === 'HEAD')) {
1117
1086
  const fileName = path_1.default.basename(pathname.split('/').pop() || '');
1118
- const filePath = path_1.default.join(VIEWER_ICON_DIR, fileName);
1087
+ const filePath = path_1.default.join(viewerIconDir(), fileName);
1119
1088
  serveStaticFile(res, filePath, 'image/png');
1120
1089
  return true;
1121
1090
  }
@@ -1124,7 +1093,7 @@ function createGraphHandler(options = {}) {
1124
1093
  pathname.endsWith('.svg') &&
1125
1094
  (req.method === 'GET' || req.method === 'HEAD')) {
1126
1095
  const fileName = path_1.default.basename(pathname.split('/').pop() || '');
1127
- const filePath = path_1.default.join(VIEWER_ICON_DIR, fileName);
1096
+ const filePath = path_1.default.join(viewerIconDir(), fileName);
1128
1097
  serveStaticFile(res, filePath, 'image/svg+xml');
1129
1098
  return true;
1130
1099
  }
@@ -1133,7 +1102,7 @@ function createGraphHandler(options = {}) {
1133
1102
  pathname.endsWith('.js') &&
1134
1103
  (req.method === 'GET' || req.method === 'HEAD')) {
1135
1104
  const fileName = pathname.split('/').pop();
1136
- const filePath = path_1.default.join(VIEWER_DIR, 'js', 'utils', fileName);
1105
+ const filePath = path_1.default.join(getViewerDirectory(), 'js', 'utils', fileName);
1137
1106
  serveStaticFile(res, filePath, 'application/javascript');
1138
1107
  return true;
1139
1108
  }
@@ -1144,7 +1113,7 @@ function createGraphHandler(options = {}) {
1144
1113
  pathname.endsWith('.js') &&
1145
1114
  (req.method === 'GET' || req.method === 'HEAD')) {
1146
1115
  const fileName = pathname.split('/').pop();
1147
- const filePath = path_1.default.join(VIEWER_DIR, 'js', fileName);
1116
+ const filePath = path_1.default.join(getViewerDirectory(), 'js', fileName);
1148
1117
  serveStaticFile(res, filePath, 'application/javascript');
1149
1118
  return true;
1150
1119
  }
@@ -1153,7 +1122,7 @@ function createGraphHandler(options = {}) {
1153
1122
  pathname.endsWith('.js') &&
1154
1123
  (req.method === 'GET' || req.method === 'HEAD')) {
1155
1124
  const fileName = pathname.split('/').pop();
1156
- const filePath = path_1.default.join(VIEWER_DIR, 'js', 'modules', fileName);
1125
+ const filePath = path_1.default.join(getViewerDirectory(), 'js', 'modules', fileName);
1157
1126
  serveStaticFile(res, filePath, 'application/javascript');
1158
1127
  return true;
1159
1128
  }