@mquan/portal 0.0.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 (116) hide show
  1. package/README.md +168 -0
  2. package/dist/src/api.d.ts +5 -0
  3. package/dist/src/api.js +357 -0
  4. package/dist/src/api.js.map +1 -0
  5. package/dist/src/cli.d.ts +17 -0
  6. package/dist/src/cli.js +618 -0
  7. package/dist/src/cli.js.map +1 -0
  8. package/dist/src/daemon-lifecycle.d.ts +24 -0
  9. package/dist/src/daemon-lifecycle.js +107 -0
  10. package/dist/src/daemon-lifecycle.js.map +1 -0
  11. package/dist/src/daemon.d.ts +17 -0
  12. package/dist/src/daemon.js +90 -0
  13. package/dist/src/daemon.js.map +1 -0
  14. package/dist/src/doctor.d.ts +14 -0
  15. package/dist/src/doctor.js +51 -0
  16. package/dist/src/doctor.js.map +1 -0
  17. package/dist/src/entrypoint.d.ts +2 -0
  18. package/dist/src/entrypoint.js +14 -0
  19. package/dist/src/entrypoint.js.map +1 -0
  20. package/dist/src/index.d.ts +5 -0
  21. package/dist/src/index.js +5 -0
  22. package/dist/src/index.js.map +1 -0
  23. package/dist/src/launch.d.ts +41 -0
  24. package/dist/src/launch.js +186 -0
  25. package/dist/src/launch.js.map +1 -0
  26. package/dist/src/mcp.d.ts +9 -0
  27. package/dist/src/mcp.js +132 -0
  28. package/dist/src/mcp.js.map +1 -0
  29. package/dist/src/network/client.d.ts +67 -0
  30. package/dist/src/network/client.js +540 -0
  31. package/dist/src/network/client.js.map +1 -0
  32. package/dist/src/network/coordinator.d.ts +83 -0
  33. package/dist/src/network/coordinator.js +225 -0
  34. package/dist/src/network/coordinator.js.map +1 -0
  35. package/dist/src/network/protocol.d.ts +1352 -0
  36. package/dist/src/network/protocol.js +360 -0
  37. package/dist/src/network/protocol.js.map +1 -0
  38. package/dist/src/network/server.d.ts +28 -0
  39. package/dist/src/network/server.js +474 -0
  40. package/dist/src/network/server.js.map +1 -0
  41. package/dist/src/network/tasks.d.ts +65 -0
  42. package/dist/src/network/tasks.js +276 -0
  43. package/dist/src/network/tasks.js.map +1 -0
  44. package/dist/src/portal-index.d.ts +21 -0
  45. package/dist/src/portal-index.js +140 -0
  46. package/dist/src/portal-index.js.map +1 -0
  47. package/dist/src/provider/claude-managed.d.ts +34 -0
  48. package/dist/src/provider/claude-managed.js +125 -0
  49. package/dist/src/provider/claude-managed.js.map +1 -0
  50. package/dist/src/provider/claude.d.ts +24 -0
  51. package/dist/src/provider/claude.js +114 -0
  52. package/dist/src/provider/claude.js.map +1 -0
  53. package/dist/src/provider/codex/app-server-client.d.ts +43 -0
  54. package/dist/src/provider/codex/app-server-client.js +143 -0
  55. package/dist/src/provider/codex/app-server-client.js.map +1 -0
  56. package/dist/src/provider/codex/index.d.ts +3 -0
  57. package/dist/src/provider/codex/index.js +4 -0
  58. package/dist/src/provider/codex/index.js.map +1 -0
  59. package/dist/src/provider/codex/managed-runner.d.ts +12 -0
  60. package/dist/src/provider/codex/managed-runner.js +162 -0
  61. package/dist/src/provider/codex/managed-runner.js.map +1 -0
  62. package/dist/src/provider/codex/probe.d.ts +2 -0
  63. package/dist/src/provider/codex/probe.js +33 -0
  64. package/dist/src/provider/codex/probe.js.map +1 -0
  65. package/dist/src/provider/codex/protocol.d.ts +193 -0
  66. package/dist/src/provider/codex/protocol.js +10 -0
  67. package/dist/src/provider/codex/protocol.js.map +1 -0
  68. package/dist/src/provider/codex/provider.d.ts +35 -0
  69. package/dist/src/provider/codex/provider.js +289 -0
  70. package/dist/src/provider/codex/provider.js.map +1 -0
  71. package/dist/src/provider/codex/remote-app-server.d.ts +16 -0
  72. package/dist/src/provider/codex/remote-app-server.js +80 -0
  73. package/dist/src/provider/codex/remote-app-server.js.map +1 -0
  74. package/dist/src/provider/codex/websocket-client.d.ts +40 -0
  75. package/dist/src/provider/codex/websocket-client.js +174 -0
  76. package/dist/src/provider/codex/websocket-client.js.map +1 -0
  77. package/dist/src/provider/types.d.ts +35 -0
  78. package/dist/src/provider/types.js +2 -0
  79. package/dist/src/provider/types.js.map +1 -0
  80. package/dist/src/recall/embedding.d.ts +28 -0
  81. package/dist/src/recall/embedding.js +142 -0
  82. package/dist/src/recall/embedding.js.map +1 -0
  83. package/dist/src/recall/types.d.ts +35 -0
  84. package/dist/src/recall/types.js +2 -0
  85. package/dist/src/recall/types.js.map +1 -0
  86. package/dist/src/rpc/client.d.ts +6 -0
  87. package/dist/src/rpc/client.js +58 -0
  88. package/dist/src/rpc/client.js.map +1 -0
  89. package/dist/src/rpc/protocol.d.ts +21 -0
  90. package/dist/src/rpc/protocol.js +11 -0
  91. package/dist/src/rpc/protocol.js.map +1 -0
  92. package/dist/src/rpc/server.d.ts +12 -0
  93. package/dist/src/rpc/server.js +154 -0
  94. package/dist/src/rpc/server.js.map +1 -0
  95. package/dist/src/runtime/paths.d.ts +8 -0
  96. package/dist/src/runtime/paths.js +18 -0
  97. package/dist/src/runtime/paths.js.map +1 -0
  98. package/dist/src/service.d.ts +127 -0
  99. package/dist/src/service.js +637 -0
  100. package/dist/src/service.js.map +1 -0
  101. package/dist/src/session-profile.d.ts +39 -0
  102. package/dist/src/session-profile.js +186 -0
  103. package/dist/src/session-profile.js.map +1 -0
  104. package/dist/src/storage/database.d.ts +9 -0
  105. package/dist/src/storage/database.js +354 -0
  106. package/dist/src/storage/database.js.map +1 -0
  107. package/dist/src/storage/repository.d.ts +275 -0
  108. package/dist/src/storage/repository.js +1291 -0
  109. package/dist/src/storage/repository.js.map +1 -0
  110. package/dist/src/version.d.ts +1 -0
  111. package/dist/src/version.js +2 -0
  112. package/dist/src/version.js.map +1 -0
  113. package/docs/architecture-plan.md +191 -0
  114. package/docs/mvp-plan.md +111 -0
  115. package/docs/remote-demo.md +346 -0
  116. package/package.json +47 -0
@@ -0,0 +1,346 @@
1
+ # Remote agent collaboration demo
2
+
3
+ Status: implemented locally; public packaging and tunnel validation deferred
4
+ Updated: 2026-09-05
5
+
6
+ ## Goal
7
+
8
+ Demonstrate two people on different computers starting Portal-managed Codex
9
+ agents, discovering one another through a temporary public endpoint, agreeing
10
+ on a task, working independently in parallel, and exchanging progress and
11
+ results without either person copying messages between sessions.
12
+
13
+ Target host experience after distribution and tunnel setup:
14
+
15
+ ```bash
16
+ npx -y @mquan/portal@demo serve --listen 127.0.0.1:8787
17
+ ngrok http 8787
18
+
19
+ npx -y @mquan/portal@demo codex \
20
+ --agent kai \
21
+ --server wss://abc.ngrok.app/ws
22
+ ```
23
+
24
+ Friend:
25
+
26
+ ```bash
27
+ npx -y @mquan/portal@demo codex \
28
+ --agent leo \
29
+ --server wss://abc.ngrok.app/ws
30
+ ```
31
+
32
+ The demo package is published under the `demo` dist-tag so it cannot replace a
33
+ future stable `latest` release. Tunnel-provider automation remains deferred.
34
+
35
+ ## Demo contract
36
+
37
+ 1. `portal serve` runs an in-memory registry and WebSocket message relay on the
38
+ host computer. It binds to loopback and is exposed through an HTTPS tunnel
39
+ that supports WebSocket upgrades.
40
+ 2. `portal codex --agent <name> --server <url>` lazily initializes Portal,
41
+ starts or connects to the local daemon, launches a Portal-managed Codex
42
+ session, and advertises only that session to the specified server.
43
+ 3. An agent can list or search live agents connected to the same server.
44
+ Discovery is server-wide for this demo. Names are not reserved or verified;
45
+ ambiguous names return candidates and perform no action.
46
+ 4. An agent can send a non-blocking task offer to a discovered agent. The
47
+ target sees a new attributed Codex turn automatically and can accept or
48
+ decline it.
49
+ 5. An accepted task runs under the target Codex session's existing sandbox and
50
+ approval policy. Both agents can continue working independently and send
51
+ task-scoped updates.
52
+ 6. Completion, failure, questions, and progress are routed back into the peer
53
+ session as new turns. No inbox polling or human message relay is required.
54
+
55
+ This demo does not require Git or a shared filesystem. Tasks may produce a
56
+ textual answer, tool result, file summary, URL, patch, commit, or any other
57
+ artifact reference the agents agree to exchange.
58
+
59
+ ## Architecture
60
+
61
+ ```text
62
+ host Codex friend Codex
63
+ | |
64
+ managed runner managed runner
65
+ | |
66
+ host portald friend portald
67
+ | |
68
+ +------ outbound WebSocket ----+---- WebSocket ---+
69
+ |
70
+ ngrok HTTPS tunnel
71
+ |
72
+ local `portal serve` process
73
+ registry | presence | relay
74
+ ```
75
+
76
+ Both daemons initiate outbound connections, so the friend does not expose an
77
+ inbound port. Messages remain relayed through the server for this phase. P2P,
78
+ STUN, TURN, and direct connection negotiation add no value to the demo and are
79
+ deferred.
80
+
81
+ The daemon owns remote connections and multiplexes its managed sessions over
82
+ them. Connections are keyed by normalized server URL so sessions may join
83
+ different demo servers without interfering with one another.
84
+
85
+ ## Server behavior
86
+
87
+ `portal serve` provides:
88
+
89
+ - `GET /health` for tunnel and operator diagnostics;
90
+ - `WS /ws` for presence, discovery, task events, and acknowledgements;
91
+ - an in-memory connection table;
92
+ - an in-memory live-agent registry with renewable leases; and
93
+ - envelope routing between currently connected clients.
94
+
95
+ Initial protocol operations:
96
+
97
+ ```text
98
+ client.hello
99
+ agent.register
100
+ agent.renew
101
+ agent.unregister
102
+ agent.discover
103
+ envelope.send
104
+ envelope.acknowledge
105
+ ```
106
+
107
+ An advertised agent record contains only bounded metadata:
108
+
109
+ ```json
110
+ {
111
+ "routeId": "route_a81d",
112
+ "sessionId": "ps_56d0",
113
+ "agentName": "leo",
114
+ "provider": "codex",
115
+ "description": "Implementation coding agent",
116
+ "capabilities": ["message", "task"],
117
+ "state": "available",
118
+ "expiresAt": "2026-09-05T20:01:00Z"
119
+ }
120
+ ```
121
+
122
+ The server never receives transcripts, absolute filesystem paths, environment
123
+ variables, process details, or historical session inventory. A disconnected
124
+ agent disappears after its presence lease expires. Server restart clears all
125
+ connections and registrations.
126
+
127
+ ## Collaboration flow
128
+
129
+ ```text
130
+ discover -> offer -> accept/decline -> progress/questions -> complete/fail
131
+ ```
132
+
133
+ The initiator uses a non-blocking delegation tool. This is separate from the
134
+ current blocking `portal.send` behavior so the initiating agent can keep
135
+ working while its peer runs.
136
+
137
+ A task has a globally unique ID, sender and target routes, objective, optional
138
+ context and expected result, timestamps, expiry, and a correlated event
139
+ stream. The minimum state machine is:
140
+
141
+ ```text
142
+ offered ----> accepted ----> running ----> completed
143
+ | |
144
+ +-------> declined +-------> failed
145
+ ```
146
+
147
+ Starting a remote task creates a visible turn in the target Codex session. The
148
+ target calls an MCP action to accept or decline. Acceptance is immediately
149
+ sent back to the initiator; it does not wait for the target turn to finish.
150
+ Progress, questions, and final results are durable in each local Portal
151
+ database and are delivered to a managed session when it is ready for another
152
+ turn. Portal never starts two concurrent turns in the same Codex session.
153
+
154
+ ## Trust boundary for the demo
155
+
156
+ There are no accounts, verified identities, signing keys, relationship records,
157
+ or authorization grants. Agent names and installation identifiers are
158
+ self-asserted. The tunnel URL is an ephemeral demo-room address, not a security
159
+ credential.
160
+
161
+ Every remote agent and event must therefore be labeled `unauthenticated` in
162
+ tool results and injected turns. Only sessions explicitly launched with both
163
+ `--agent` and `--server` are remotely advertised. Vendor sandbox and approval
164
+ settings remain unchanged, and Portal never auto-approves a tool action.
165
+
166
+ Anyone who learns the tunnel URL can discover agents, impersonate a name, and
167
+ send task offers. The server must not be used with sensitive sessions or left
168
+ running after the demonstration. These constraints are acceptable only for a
169
+ controlled validation and must block production positioning.
170
+
171
+ ## Milestones
172
+
173
+ ### M0: Protocol and process contract
174
+
175
+ Status: complete.
176
+
177
+ Define versioned schemas for presence, discovery, envelopes, task events, and
178
+ acknowledgements. Define limits for names, descriptions, message size, task
179
+ expiry, heartbeat interval, and protocol mismatch errors. Add a transport
180
+ interface so the local message service does not depend directly on WebSocket
181
+ implementation details.
182
+
183
+ Checkpoint:
184
+
185
+ - protocol fixtures round-trip through validation;
186
+ - unknown versions and malformed or oversized messages are rejected;
187
+ - a documented compatibility rule determines whether two versions may talk;
188
+ - existing local messaging tests remain unchanged and pass.
189
+
190
+ ### M1: Local registry and WebSocket relay
191
+
192
+ Status: complete.
193
+
194
+ Add `portal serve --listen <address>`. Implement the health endpoint,
195
+ WebSocket lifecycle, live presence leases, discovery queries, envelope
196
+ forwarding, and delivery acknowledgements. Keep all network state in memory.
197
+
198
+ Checkpoint:
199
+
200
+ - two test clients connect to the local server;
201
+ - one registers `leo`, the other discovers it;
202
+ - disconnect or missed heartbeats removes `leo` within the lease window;
203
+ - duplicate names return both candidates;
204
+ - an envelope is delivered once and acknowledged;
205
+ - the server binds to `127.0.0.1` by default.
206
+
207
+ ### M2: Daemon remote presence and discovery
208
+
209
+ Status: complete for explicitly advertised managed Codex sessions.
210
+
211
+ Teach `portald` to open and reuse connections keyed by server URL. Associate a
212
+ managed session with its selected server, publish it after the real Codex
213
+ session ID is known, renew its lease, and unregister it on exit. Merge remote
214
+ results into a new MCP discovery tool without treating them as local indexed
215
+ sessions.
216
+
217
+ Checkpoint:
218
+
219
+ - two daemons connected to one local server discover each other's advertised
220
+ sessions;
221
+ - unadvertised observed and historical sessions never leave the machine;
222
+ - exact unique names resolve and ambiguous names do not send;
223
+ - tunnel interruption produces a visible unavailable state and reconnects;
224
+ - sessions connected to different server URLs remain isolated.
225
+
226
+ ### M3: Remote invitation and visible delivery
227
+
228
+ Status: complete.
229
+
230
+ Route a remote task offer through the server and import it into the target's
231
+ existing managed-runner queue. Represent the sender as an explicit remote,
232
+ unauthenticated peer rather than a locally observed vendor session. Preserve
233
+ event IDs, attribution, correlation, expiry, and idempotency across the relay.
234
+
235
+ Checkpoint:
236
+
237
+ - agent A sends an offer to agent B on another daemon;
238
+ - B receives a visible attributed Codex turn without checking an inbox;
239
+ - replaying an event does not create a second turn;
240
+ - invalid, expired, unknown-target, and disconnected-target deliveries fail
241
+ clearly;
242
+ - the target transcript and MCP result both say the sender is unauthenticated.
243
+
244
+ ### M4: Asynchronous task agreement and collaboration
245
+
246
+ Status: complete for managed Codex sessions.
247
+
248
+ Add non-blocking task delegation and task-scoped event tools. Support offer,
249
+ accept, decline, progress, question, completion, and failure. Update managed
250
+ agent instructions so a target explicitly accepts or declines before acting,
251
+ and so results automatically return to the initiating session.
252
+
253
+ Checkpoint:
254
+
255
+ - A delegates to B and continues its own turn without waiting for B;
256
+ - B accepts and A automatically sees the acceptance;
257
+ - both agents execute work concurrently;
258
+ - B sends a question or progress update and receives A's response;
259
+ - B completes and A automatically receives the correlated result;
260
+ - no session receives simultaneous Codex turns;
261
+ - failure and disconnect leave an inspectable terminal or retryable state.
262
+
263
+ ### M5: Single-command local UX
264
+
265
+ Status: implemented; clean-tarball validation is complete, publication remains deferred.
266
+
267
+ Make `portal codex` lazily initialize local state and start a compatible
268
+ daemon. Add `--agent` and `--server`, with `--server` taking precedence over an
269
+ environment variable or stored default. Keep `portal run codex` temporarily as
270
+ a compatibility alias. Validate the packaged layout locally without publishing
271
+ it.
272
+
273
+ Checkpoint:
274
+
275
+ - `npm pack` installs and runs in a clean temporary directory;
276
+ - the packed tarball installs and `portal codex --agent leo --server <url>`
277
+ works from a clean temporary environment;
278
+ - a stale daemon is detected through a version handshake and restarted safely;
279
+ - Portal flags are not forwarded to Codex, while arguments after `--` are;
280
+ - exiting Codex unregisters the advertised agent and cleans up its lease.
281
+
282
+ ### M6: Two-machine demonstration
283
+
284
+ Status: two isolated local daemons pass; physical two-Mac/tunnel validation remains deferred.
285
+
286
+ First run two isolated Portal daemons and managed-agent fixtures against the
287
+ local server. Once a tunnel provider and package distribution are selected,
288
+ repeat the same protocol flow across two Macs without changing the application
289
+ protocol. Execute a task that requires negotiation, parallel work, at least one
290
+ progress exchange, and a returned result.
291
+
292
+ Checkpoint:
293
+
294
+ - the local two-daemon flow passes before tunnel or npm publication work;
295
+ - later, the friend uses only the supplied single command after installing
296
+ Codex;
297
+ - the initiating agent discovers the friend's agent without a session ID;
298
+ - both users visibly see offer, acceptance, progress, and completion;
299
+ - the agents collaborate without either user relaying text;
300
+ - logs correlate every hop by task and event ID;
301
+ - the demo succeeds twice from a fresh server process.
302
+
303
+ ## Execution order
304
+
305
+ M0 is the integration boundary. After it lands, the M1 server and most of the
306
+ M2 daemon connection work can proceed in parallel. M3 joins those paths. M4
307
+ depends on reliable remote delivery, and M5 should package the behavior only
308
+ after the protocol is stable. M6 is the release gate.
309
+
310
+ ```text
311
+ M0 ---> M1 ---+
312
+ \ +---> M3 ---> M4 ---> M5 ---> M6
313
+ +---> M2 --+
314
+ ```
315
+
316
+ ## Reuse from the local MVP
317
+
318
+ - user-only daemon and JSON-RPC control plane;
319
+ - SQLite persistence;
320
+ - Portal-managed Codex app-server and runner;
321
+ - automatic visible-turn delivery;
322
+ - session profiles and ambiguity-safe resolution;
323
+ - MCP injection and session credentials;
324
+ - message IDs, reply correlation, idempotency, rate limits, and receipts; and
325
+ - captured Codex answers and bounded tool-result summaries.
326
+
327
+ The remote path extends these components but does not expose the local RPC
328
+ socket or local session credential to the network.
329
+
330
+ ## Not in scope
331
+
332
+ - production authentication, authorization, invitations, blocking, or abuse
333
+ controls;
334
+ - durable public names, accounts, or a production nameserver;
335
+ - offline remote delivery or persistence in the relay;
336
+ - public deployment of the Portal server;
337
+ - npm publication and package-name selection;
338
+ - tunnel-provider selection and tunnel setup automation;
339
+ - end-to-end encryption;
340
+ - peer-to-peer transport, WebRTC, STUN, or TURN;
341
+ - remote transcript recall;
342
+ - automatic artifact synchronization or Git workflows;
343
+ - Claude or unmanaged Codex active delivery;
344
+ - browser, desktop, and Slack sessions;
345
+ - a Portal-owned autonomous supervisor agent; and
346
+ - production availability, scaling, federation, or multi-region operation.
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@mquan/portal",
3
+ "version": "0.0.1",
4
+ "description": "Local cross-agent session awareness, recall, and messaging",
5
+ "type": "module",
6
+ "bin": {
7
+ "portal": "./dist/src/cli.js",
8
+ "portal-mcp": "./dist/src/mcp.js"
9
+ },
10
+ "files": [
11
+ "dist/src",
12
+ "docs",
13
+ "README.md"
14
+ ],
15
+ "engines": {
16
+ "node": ">=22.13"
17
+ },
18
+ "publishConfig": {
19
+ "access": "public",
20
+ "tag": "demo"
21
+ },
22
+ "scripts": {
23
+ "build": "tsc -p tsconfig.json",
24
+ "check": "tsc -p tsconfig.json --noEmit",
25
+ "dev": "node --import tsx src/cli.ts",
26
+ "mcp": "node --import tsx src/mcp.ts",
27
+ "probe:codex": "node --import tsx src/provider/codex/probe.ts",
28
+ "test": "node --import tsx --test test/**/*.test.ts",
29
+ "probe:claude": "PORTAL_REAL_CLAUDE_PROBE=1 node --import tsx --test test/claude-provider.real.test.ts",
30
+ "prepack": "npm run check && npm test && npm run build"
31
+ },
32
+ "dependencies": {
33
+ "@anthropic-ai/claude-agent-sdk": "0.3.251",
34
+ "@modelcontextprotocol/sdk": "1.30.0",
35
+ "ws": "^8.21.3",
36
+ "zod": "4.5.4"
37
+ },
38
+ "devDependencies": {
39
+ "@types/node": "latest",
40
+ "@types/ws": "^8.18.1",
41
+ "tsx": "latest",
42
+ "typescript": "latest"
43
+ },
44
+ "overrides": {
45
+ "qs": "6.16.0"
46
+ }
47
+ }