@abstractframework/continuum 0.2.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.
- package/LICENSE +21 -0
- package/README.md +112 -0
- package/bin/cli.js +206 -0
- package/bin/hub_proxy.js +549 -0
- package/dist/assets/abnfDiagram-VRR7QNED-DHZzhKO_.js +2 -0
- package/dist/assets/arc-9iPl5OLd.js +2 -0
- package/dist/assets/architectureDiagram-ZJ3FMSHR-BVRc0NRl.js +37 -0
- package/dist/assets/blockDiagram-677ZJIJ3-BTIe50OR.js +133 -0
- package/dist/assets/c4Diagram-LMCZKHZV-D5P-4VLr.js +11 -0
- package/dist/assets/channel-CEGeSBNz.js +2 -0
- package/dist/assets/chunk-2Q5K7J3B-WcmxYPWP.js +2 -0
- package/dist/assets/chunk-32BRIVSS-BPArWk_S.js +2 -0
- package/dist/assets/chunk-5VM5RSS4-DplW9bbf.js +16 -0
- package/dist/assets/chunk-EX3LRPZG-B8O0P99j.js +232 -0
- package/dist/assets/chunk-JWPE2WC7-DZoiE5m4.js +2 -0
- package/dist/assets/chunk-MOJQB5TN-FqMZZAe1.js +89 -0
- package/dist/assets/chunk-RYQCIY6F-D4I_kub_.js +2 -0
- package/dist/assets/chunk-V7JOEXUC-CwJZa6s3.js +207 -0
- package/dist/assets/chunk-VR4S4FIN-CdF7hCRc.js +2 -0
- package/dist/assets/chunk-XXDRQBXY-C2vm2DXm.js +2 -0
- package/dist/assets/classDiagram-OUVF2IWQ-D11Ru5dS.js +2 -0
- package/dist/assets/classDiagram-v2-EOCWNBFH-D11Ru5dS.js +2 -0
- package/dist/assets/cose-bilkent-JH36ORCC-BEoqSVoj.js +2 -0
- package/dist/assets/cynefin-VYW2F7L2-DzsDaKjB.js +179 -0
- package/dist/assets/cynefinDiagram-TSTJHNR4-qVt2ETEm.js +63 -0
- package/dist/assets/cytoscape.esm-DTSO7Bv0.js +332 -0
- package/dist/assets/dagre-VKFMJZFB-BszpuHqM.js +5 -0
- package/dist/assets/defaultLocale-DX6XiGOO.js +2 -0
- package/dist/assets/diagram-FQU43EPY-DkSwN897.js +4 -0
- package/dist/assets/diagram-G47NLZAW-DmmkniMR.js +25 -0
- package/dist/assets/diagram-NH7WQ7WH-PkbaZ3fb.js +25 -0
- package/dist/assets/diagram-OA4YK3LP-D5O-VGkx.js +31 -0
- package/dist/assets/diagram-WEI45ONY-CtS1f8zZ.js +42 -0
- package/dist/assets/ebnfDiagram-CCIWWBDH-2ydDo0Sq.js +2 -0
- package/dist/assets/erDiagram-Q63AITRT-Dc-2Dmcc.js +86 -0
- package/dist/assets/flowDiagram-23GEKE2U-DKKOHSrp.js +157 -0
- package/dist/assets/ganttDiagram-NO4QXBWP-B9Zi-Zd_.js +293 -0
- package/dist/assets/gitGraphDiagram-IHSO6WYX-lXzgHn-s.js +107 -0
- package/dist/assets/graph-C9eacEi8.js +2 -0
- package/dist/assets/index-CCcufdYK.css +1 -0
- package/dist/assets/index-DmnZnFTc.js +124 -0
- package/dist/assets/infoDiagram-FWYZ7A6U-BHV_H3bL.js +3 -0
- package/dist/assets/init-Gi6I4Gst.js +2 -0
- package/dist/assets/ishikawaDiagram-FXEZZL3T-BX74rKth.js +71 -0
- package/dist/assets/journeyDiagram-5HDEW3XC-BByysGFJ.js +140 -0
- package/dist/assets/kanban-definition-HUTT4EX6-DatfLmCp.js +90 -0
- package/dist/assets/katex-C5jXJg4s.js +258 -0
- package/dist/assets/layout-DEXfKzaS.js +2 -0
- package/dist/assets/linear-BgTPNh4i.js +2 -0
- package/dist/assets/map-Czzmt4hB.js +2 -0
- package/dist/assets/mermaid.core-CrABX5SH.js +315 -0
- package/dist/assets/mindmap-definition-LN4V7U3C-Bz1XgdXA.js +97 -0
- package/dist/assets/ordinal-Cboi1Yqb.js +2 -0
- package/dist/assets/pegDiagram-2B236MQR-D5B8Egll.js +2 -0
- package/dist/assets/pieDiagram-ENE6RG2P-BaDg4f41.js +40 -0
- package/dist/assets/quadrantDiagram-ABIIQ3AL-jr1V5VXJ.js +8 -0
- package/dist/assets/railroadDiagram-RFXS5EU6-F13Ho8ra.js +2 -0
- package/dist/assets/requirementDiagram-TGXJPOKE-DXb_w8Ww.js +85 -0
- package/dist/assets/sankeyDiagram-HTMAVEWB-BN42kL5b.js +41 -0
- package/dist/assets/sequenceDiagram-DBY2YBRQ-DI7-dEyi.js +163 -0
- package/dist/assets/sizeCapture-X5ZJPWSS-Dv-15ctr.js +2 -0
- package/dist/assets/stateDiagram-2N3HPSRC-De5ObM42.js +2 -0
- package/dist/assets/stateDiagram-v2-6OUMAXLB-D9AwhP3S.js +2 -0
- package/dist/assets/swimlanes-5IMT3BWC-DzZwgXNi.js +3 -0
- package/dist/assets/swimlanesDiagram-G3AALYLV-DyzT80_E.js +9 -0
- package/dist/assets/timeline-definition-FHXFAJF6-BO8KkL_w.js +121 -0
- package/dist/assets/vennDiagram-L72KCM5P-D6LUv_CX.js +35 -0
- package/dist/assets/wardleyDiagram-EHGQE667-DWJp0Lmi.js +79 -0
- package/dist/assets/xychartDiagram-FW5EYKEG-B5IA4jNf.js +8 -0
- package/dist/index.html +14 -0
- package/package.json +68 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Laurent-Philippe Albou
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# AbstractContinuum
|
|
2
|
+
|
|
3
|
+
Continuous iterative development and deployment console for AbstractFramework.
|
|
4
|
+
|
|
5
|
+
Connect it to a Run Gateway and it becomes the framework's development
|
|
6
|
+
cockpit (board-first, kanban/scrum-inspired — see
|
|
7
|
+
[docs/design/redesign_2026_07.md](docs/design/redesign_2026_07.md)):
|
|
8
|
+
|
|
9
|
+
- **Board** (landing page) — the work flow as a kanban: Triage → Ready →
|
|
10
|
+
In Progress → In Review → Done, with a Failed history lane. Cards carry
|
|
11
|
+
type / priority (P0–P3) / labels (sprints ride the `sprint-N` label
|
|
12
|
+
convention) / package chips and a Definition-of-Ready dot; the card
|
|
13
|
+
drawer holds the spec (view/edit + metadata), the item's runs, and the
|
|
14
|
+
QA review (acceptance-criteria checklist → promote / iterate / UAT).
|
|
15
|
+
- **Executions** — the live ops view: queued / running / awaiting-QA
|
|
16
|
+
requests with live log tails (cursor-follow), one-click QA actions, and
|
|
17
|
+
a recently-finished strip.
|
|
18
|
+
- **Backlog** — the full catalog power view (all kinds incl. recurrent /
|
|
19
|
+
deprecated / trash, search, batch execute, merge, AI assist + advisor).
|
|
20
|
+
- **Agents & Entities** — the workforce: the configured execution agent
|
|
21
|
+
(executor is a pluggable seam — Codex CLI today), the advisor agent, a
|
|
22
|
+
track record from execution history, and gateway entities with their
|
|
23
|
+
skills.
|
|
24
|
+
- **Team** — an [agora](docs/configuration.md#team-page-agora-hub) hub
|
|
25
|
+
client for human + agent collaboration: channels and DMs with threaded
|
|
26
|
+
messages, a unified inbox of open asks, hub-wide search, channel files,
|
|
27
|
+
work claims, and live updates over WebSocket. It posts as one configured
|
|
28
|
+
operator seat through a server-side proxy (the seat key never reaches the
|
|
29
|
+
browser).
|
|
30
|
+
- **Inbox** — triage bug reports, feature requests, and email; run triage
|
|
31
|
+
and apply decisions.
|
|
32
|
+
- **Services** — control managed prod/UAT processes (start / stop /
|
|
33
|
+
restart / redeploy) and write-only environment variables. This surface is
|
|
34
|
+
high trust: see [SECURITY.md](SECURITY.md) before exposing it.
|
|
35
|
+
- **Settings** — gateway session sign-in and AI preferences.
|
|
36
|
+
|
|
37
|
+
Executing a Ready item is gated by an advisory **Definition of Ready**
|
|
38
|
+
checklist (type, real summary, acceptance criteria, test commands — parsed
|
|
39
|
+
from the spec, override always available and labeled); promoting from
|
|
40
|
+
review shows the acceptance criteria as a **Definition of Done** checklist.
|
|
41
|
+
|
|
42
|
+
The observer app ([AbstractObserver](https://github.com/lpalbou/AbstractObserver))
|
|
43
|
+
watches and discusses the running system; this app develops and deploys it.
|
|
44
|
+
The pipeline is executor-agnostic by design: the gateway currently runs
|
|
45
|
+
executions through the Codex CLI, but the executing agent is a pluggable
|
|
46
|
+
seam and the UI reads the executor identity from the gateway's exec config
|
|
47
|
+
(see [docs/architecture.md](docs/architecture.md)).
|
|
48
|
+
|
|
49
|
+
## Install and run
|
|
50
|
+
|
|
51
|
+
Requires Node.js ≥ 18 and a running Run Gateway
|
|
52
|
+
([AbstractGateway](https://github.com/lpalbou/AbstractGateway), default
|
|
53
|
+
`http://127.0.0.1:8080`).
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
npx @abstractframework/continuum
|
|
57
|
+
# or install the CLI globally
|
|
58
|
+
npm install -g @abstractframework/continuum
|
|
59
|
+
abstractcontinuum
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Open `http://localhost:3002` and sign in with a gateway user and token (the
|
|
63
|
+
connection badge at the bottom of the sidebar). The token is exchanged
|
|
64
|
+
server-side for an HttpOnly session cookie and never stored in the browser.
|
|
65
|
+
|
|
66
|
+
Common environment variables (full list in
|
|
67
|
+
[docs/configuration.md](docs/configuration.md), or `abstractcontinuum --help`):
|
|
68
|
+
|
|
69
|
+
- `PORT` (default `3002`), `HOST` (default `127.0.0.1`)
|
|
70
|
+
- `ABSTRACTCONTINUUM_GATEWAY_URL` (or `ABSTRACTGATEWAY_URL`) — the gateway
|
|
71
|
+
this deployment talks to (default `http://127.0.0.1:8080`)
|
|
72
|
+
- `ABSTRACTCONTINUUM_HUB_URL` / `ABSTRACTCONTINUUM_HUB_SEAT` — the agora hub
|
|
73
|
+
and operator seat for the Team page
|
|
74
|
+
|
|
75
|
+
Before exposing the console beyond localhost, read
|
|
76
|
+
[docs/security.md](docs/security.md): a signed-in user can redeploy services.
|
|
77
|
+
|
|
78
|
+
## Develop from source
|
|
79
|
+
|
|
80
|
+
The source build resolves the shared UI kit (`@abstractframework/ui-kit`,
|
|
81
|
+
`@abstractframework/panel-chat`) and the session proxy
|
|
82
|
+
(`@abstractframework/app-server`) from a sibling
|
|
83
|
+
[AbstractUIC](https://github.com/lpalbou/AbstractUIC) checkout:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
git clone https://github.com/lpalbou/AbstractUIC.git abstractuic
|
|
87
|
+
git clone https://github.com/lpalbou/AbstractContinuum.git abstractcontinuum
|
|
88
|
+
cd abstractcontinuum
|
|
89
|
+
npm install
|
|
90
|
+
npm run dev # vite dev server on :3003 (proxies /api to :8080)
|
|
91
|
+
npm test # vitest (unit + jsdom component tests)
|
|
92
|
+
npm run build # production build into dist/
|
|
93
|
+
npm start # serve dist/ + the app-origin gateway session proxy on :3002
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for the development workflow.
|
|
97
|
+
|
|
98
|
+
## Documentation
|
|
99
|
+
|
|
100
|
+
- [docs/README.md](docs/README.md) — documentation index
|
|
101
|
+
- [docs/getting-started.md](docs/getting-started.md) — first run and first execution
|
|
102
|
+
- [docs/architecture.md](docs/architecture.md) — components, data flow, design boundaries
|
|
103
|
+
- [docs/api.md](docs/api.md) — the gateway API families this app consumes
|
|
104
|
+
- [docs/configuration.md](docs/configuration.md) — environment variables and settings
|
|
105
|
+
- [docs/security.md](docs/security.md) — trust model (read before deploying)
|
|
106
|
+
- [docs/faq.md](docs/faq.md) / [docs/troubleshooting.md](docs/troubleshooting.md)
|
|
107
|
+
- [CHANGELOG.md](CHANGELOG.md) — release history
|
|
108
|
+
- [CONTRIBUTING.md](CONTRIBUTING.md) — development workflow
|
|
109
|
+
|
|
110
|
+
## License
|
|
111
|
+
|
|
112
|
+
MIT — see [LICENSE](LICENSE).
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* CLI entry point for AbstractContinuum
|
|
5
|
+
* Serves the built console and proxies /api/* to the gateway through the
|
|
6
|
+
* shared app-origin session proxy (first-party cookies + CSRF; tokens never
|
|
7
|
+
* in URLs) — the same @abstractframework/app-server module the observer,
|
|
8
|
+
* flow, and code apps converge on.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import * as http from 'http';
|
|
12
|
+
import { readFileSync, existsSync, statSync } from 'fs';
|
|
13
|
+
import { homedir } from 'os';
|
|
14
|
+
import { join, extname, dirname } from 'path';
|
|
15
|
+
import { fileURLToPath } from 'url';
|
|
16
|
+
import { createGatewaySessionProxy } from '@abstractframework/app-server';
|
|
17
|
+
import { createHubProxy } from './hub_proxy.js';
|
|
18
|
+
|
|
19
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
20
|
+
const DIST_DIR = join(__dirname, '..', 'dist');
|
|
21
|
+
|
|
22
|
+
const argv = process.argv.slice(2);
|
|
23
|
+
if (argv.includes('--version') || argv.includes('-v')) {
|
|
24
|
+
const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf8'));
|
|
25
|
+
console.log(pkg.version);
|
|
26
|
+
process.exit(0);
|
|
27
|
+
}
|
|
28
|
+
if (argv.includes('--help') || argv.includes('-h')) {
|
|
29
|
+
console.log(`Usage: abstractcontinuum [--help] [--version]
|
|
30
|
+
|
|
31
|
+
Serves the AbstractContinuum console and proxies /api/* to a Run Gateway
|
|
32
|
+
through a same-origin session proxy. Configuration is environment-driven:
|
|
33
|
+
|
|
34
|
+
PORT HTTP port (default 3002)
|
|
35
|
+
HOST bind address (default 127.0.0.1)
|
|
36
|
+
ABSTRACTCONTINUUM_GATEWAY_URL gateway URL (fallback ABSTRACTGATEWAY_URL,
|
|
37
|
+
default http://127.0.0.1:8080)
|
|
38
|
+
ABSTRACTCONTINUUM_HUB_URL agora hub for the Team page (fallback
|
|
39
|
+
AGORA_HUB_URL, default http://127.0.0.1:8765)
|
|
40
|
+
ABSTRACTCONTINUUM_HUB_SEAT hub seat the Team page acts as (default laurent)
|
|
41
|
+
ABSTRACTCONTINUUM_HUB_KEYS hub key store (default ~/.agora/keys.json)
|
|
42
|
+
ABSTRACTCONTINUUM_HUB_KEY hub API key (overrides the key store)
|
|
43
|
+
|
|
44
|
+
Documentation: https://github.com/lpalbou/AbstractContinuum#readme`);
|
|
45
|
+
process.exit(0);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const PORT = process.env.PORT || 3002;
|
|
49
|
+
// Default bind is LOOPBACK (entity's c1768 SSRF finding against the shared
|
|
50
|
+
// session proxy, plus a continuum-specific amplifier: this server also
|
|
51
|
+
// mounts the hub proxy carrying the OPERATOR's seat key — a LAN peer
|
|
52
|
+
// reaching the port could author hub messages as the operator). Wider
|
|
53
|
+
// binds are an explicit deployment choice via HOST.
|
|
54
|
+
const HOST = process.env.HOST || '127.0.0.1';
|
|
55
|
+
const DEFAULT_GATEWAY_URL = String(process.env.ABSTRACTCONTINUUM_GATEWAY_URL || process.env.ABSTRACTGATEWAY_URL || 'http://127.0.0.1:8080').trim().replace(/\/+$/, '') || 'http://127.0.0.1:8080';
|
|
56
|
+
const HUB_URL = String(process.env.ABSTRACTCONTINUUM_HUB_URL || process.env.AGORA_HUB_URL || 'http://127.0.0.1:8765').trim().replace(/\/+$/, '');
|
|
57
|
+
const HUB_SEAT = String(process.env.ABSTRACTCONTINUUM_HUB_SEAT || 'laurent').trim();
|
|
58
|
+
const HUB_KEYS_PATH = String(process.env.ABSTRACTCONTINUUM_HUB_KEYS || join(homedir(), '.agora', 'keys.json'));
|
|
59
|
+
|
|
60
|
+
const MIME_TYPES = {
|
|
61
|
+
'.html': 'text/html',
|
|
62
|
+
'.js': 'application/javascript',
|
|
63
|
+
'.css': 'text/css',
|
|
64
|
+
'.json': 'application/json',
|
|
65
|
+
'.png': 'image/png',
|
|
66
|
+
'.jpg': 'image/jpeg',
|
|
67
|
+
'.jpeg': 'image/jpeg',
|
|
68
|
+
'.gif': 'image/gif',
|
|
69
|
+
'.svg': 'image/svg+xml',
|
|
70
|
+
'.ico': 'image/x-icon',
|
|
71
|
+
'.webmanifest': 'application/manifest+json',
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
function getMimeType(filePath) {
|
|
75
|
+
const ext = extname(filePath).toLowerCase();
|
|
76
|
+
return MIME_TYPES[ext] || 'application/octet-stream';
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Content-Security-Policy for the app document (security adversary P1
|
|
80
|
+
// 2026-07-16): untrusted markdown (channel files, messages, attachments)
|
|
81
|
+
// renders in this app, and standard markdown allows remote images — a
|
|
82
|
+
// crafted  would beacon the operator's IP
|
|
83
|
+
// and read-timing to an external host the moment a preview opens. img-src
|
|
84
|
+
// 'self' data: kills that class app-wide. script-src 'self' is a backstop
|
|
85
|
+
// belt (the renderer already cannot emit script). ws:/wss: are needed
|
|
86
|
+
// because 'self' does not reliably cover scheme-different WebSocket dials;
|
|
87
|
+
// only our own bundle can open sockets (script-src), so this stays tight.
|
|
88
|
+
const CSP = [
|
|
89
|
+
"default-src 'self'",
|
|
90
|
+
"script-src 'self'",
|
|
91
|
+
"style-src 'self' 'unsafe-inline'", // React style={} attributes
|
|
92
|
+
"img-src 'self' data:",
|
|
93
|
+
"font-src 'self' data:",
|
|
94
|
+
"connect-src 'self' ws: wss:",
|
|
95
|
+
"object-src 'none'",
|
|
96
|
+
"base-uri 'self'",
|
|
97
|
+
"form-action 'self'",
|
|
98
|
+
"frame-ancestors 'none'",
|
|
99
|
+
].join('; ');
|
|
100
|
+
|
|
101
|
+
function serveFile(res, filePath) {
|
|
102
|
+
try {
|
|
103
|
+
if (!existsSync(filePath) || !statSync(filePath).isFile()) {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
const content = readFileSync(filePath);
|
|
107
|
+
const mime = getMimeType(filePath);
|
|
108
|
+
const headers = {
|
|
109
|
+
'Content-Type': mime,
|
|
110
|
+
'Cache-Control': 'no-cache',
|
|
111
|
+
'X-Content-Type-Options': 'nosniff',
|
|
112
|
+
};
|
|
113
|
+
// CSP is a document policy — attach it where a document loads.
|
|
114
|
+
if (mime === 'text/html') headers['Content-Security-Policy'] = CSP;
|
|
115
|
+
res.writeHead(200, headers);
|
|
116
|
+
res.end(content);
|
|
117
|
+
return true;
|
|
118
|
+
} catch {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Cookies abstractcontinuum_gateway_*, header x-abstractcontinuum-csrf, and
|
|
124
|
+
// the ABSTRACTCONTINUUM_* / ABSTRACTGATEWAY_* env gates all derive from appId.
|
|
125
|
+
const gatewaySessionProxy = createGatewaySessionProxy({
|
|
126
|
+
appId: 'abstractcontinuum',
|
|
127
|
+
defaultGatewayUrl: DEFAULT_GATEWAY_URL,
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// Team page transport: /api/hub/* forwards an allowlisted hub API subset
|
|
131
|
+
// with the OPERATOR seat's key attached server-side (proposal c1692,
|
|
132
|
+
// agency contract c1696 — authorship stays the operator's own).
|
|
133
|
+
const hubProxy = createHubProxy({ hubUrl: HUB_URL, seat: HUB_SEAT, keysPath: HUB_KEYS_PATH });
|
|
134
|
+
|
|
135
|
+
const server = http.createServer((req, res) => {
|
|
136
|
+
// Parse against a FIXED base: a malformed Host header (e.g. "a b") makes
|
|
137
|
+
// new URL throw synchronously, which would kill the whole process from
|
|
138
|
+
// one bad request (adversarial P1 2026-07-13). The host plays no role in
|
|
139
|
+
// routing here — only the pathname does.
|
|
140
|
+
let pathname = '/';
|
|
141
|
+
let search = '';
|
|
142
|
+
try {
|
|
143
|
+
const u = new URL(req.url, 'http://local');
|
|
144
|
+
pathname = u.pathname;
|
|
145
|
+
search = u.search;
|
|
146
|
+
} catch {
|
|
147
|
+
res.writeHead(400);
|
|
148
|
+
res.end('Bad Request');
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (hubProxy.handle(req, res, pathname, search)) {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (gatewaySessionProxy.handle(req, res, pathname)) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (pathname.includes('..')) {
|
|
161
|
+
res.writeHead(400);
|
|
162
|
+
res.end('Bad Request');
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (pathname === '/' || pathname === '') {
|
|
167
|
+
pathname = '/index.html';
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const filePath = join(DIST_DIR, pathname);
|
|
171
|
+
if (serveFile(res, filePath)) return;
|
|
172
|
+
if (serveFile(res, filePath + '.html')) return;
|
|
173
|
+
|
|
174
|
+
// SPA fallback
|
|
175
|
+
if (serveFile(res, join(DIST_DIR, 'index.html'))) return;
|
|
176
|
+
|
|
177
|
+
res.writeHead(404);
|
|
178
|
+
res.end('Not Found');
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// Live hub updates: relay browser WebSockets to the hub with the seat key
|
|
182
|
+
// attached server-side (operator c2240 — realtime, not 5s polling).
|
|
183
|
+
server.on('upgrade', (req, socket, head) => {
|
|
184
|
+
// F7 (dm-99 tail): a raw upgrade socket with no 'error' listener is a
|
|
185
|
+
// process-killer (browser aborts mid-handshake raise uncaught 'error');
|
|
186
|
+
// and a rejected relay promise without .catch is an unhandled rejection.
|
|
187
|
+
socket.on('error', () => {
|
|
188
|
+
try { socket.destroy(); } catch { /* already gone */ }
|
|
189
|
+
});
|
|
190
|
+
void hubProxy.handleUpgrade(req, socket, head).then((handled) => {
|
|
191
|
+
if (!handled) socket.destroy();
|
|
192
|
+
}).catch(() => {
|
|
193
|
+
try { socket.destroy(); } catch { /* already gone */ }
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
server.listen(PORT, HOST, () => {
|
|
198
|
+
console.log(`
|
|
199
|
+
AbstractContinuum is running.
|
|
200
|
+
Local: http://localhost:${PORT}
|
|
201
|
+
Gateway: ${DEFAULT_GATEWAY_URL}
|
|
202
|
+
`);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
process.on('SIGINT', () => process.exit(0));
|
|
206
|
+
process.on('SIGTERM', () => process.exit(0));
|