@hua-labs/tap 0.2.3 → 0.2.5
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/README.md +194 -194
- package/dist/bridges/codex-app-server-auth-gateway.mjs +11 -3
- package/dist/bridges/codex-app-server-auth-gateway.mjs.map +1 -1
- package/dist/bridges/codex-app-server-bridge.d.mts +11 -1
- package/dist/bridges/codex-app-server-bridge.mjs +109 -50
- package/dist/bridges/codex-app-server-bridge.mjs.map +1 -1
- package/dist/bridges/codex-bridge-runner.mjs +23 -17
- package/dist/bridges/codex-bridge-runner.mjs.map +1 -1
- package/dist/cli.mjs +1063 -237
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +4 -1
- package/dist/index.mjs +446 -140
- package/dist/index.mjs.map +1 -1
- package/package.json +65 -65
package/README.md
CHANGED
|
@@ -1,194 +1,194 @@
|
|
|
1
|
-
# @hua-labs/tap
|
|
2
|
-
|
|
3
|
-
Zero-dependency CLI for cross-model AI agent communication setup.
|
|
4
|
-
|
|
5
|
-
One command to connect Claude, Codex, and Gemini agents through a shared file-based communication layer.
|
|
6
|
-
|
|
7
|
-
## Quick Start
|
|
8
|
-
|
|
9
|
-
> `bun` is required to run the managed tap MCP server. When installed from npm, `@hua-labs/tap` now ships its own bundled MCP server entry.
|
|
10
|
-
|
|
11
|
-
```bash
|
|
12
|
-
# 1. Initialize comms directory and state
|
|
13
|
-
npx @hua-labs/tap init
|
|
14
|
-
|
|
15
|
-
# 2. Add runtimes
|
|
16
|
-
npx @hua-labs/tap add claude
|
|
17
|
-
npx @hua-labs/tap add codex
|
|
18
|
-
npx @hua-labs/tap add gemini
|
|
19
|
-
|
|
20
|
-
# 3. Check status
|
|
21
|
-
npx @hua-labs/tap status
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
Your agents can now communicate through the shared comms directory.
|
|
25
|
-
|
|
26
|
-
## Commands
|
|
27
|
-
|
|
28
|
-
### `init`
|
|
29
|
-
|
|
30
|
-
Initialize the comms directory and `.tap-comms/` state.
|
|
31
|
-
|
|
32
|
-
By default, the comms directory is created inside the current repo at `./tap-comms`.
|
|
33
|
-
|
|
34
|
-
```bash
|
|
35
|
-
npx @hua-labs/tap init
|
|
36
|
-
npx @hua-labs/tap init --comms-dir /path/to/comms
|
|
37
|
-
npx @hua-labs/tap init --permissions safe # default: deny destructive ops
|
|
38
|
-
npx @hua-labs/tap init --permissions full # no restrictions (use with caution)
|
|
39
|
-
npx @hua-labs/tap init --force # re-initialize
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
### `add <runtime>`
|
|
43
|
-
|
|
44
|
-
Add a runtime. Probes config, plans patches, applies, and verifies.
|
|
45
|
-
|
|
46
|
-
```bash
|
|
47
|
-
npx @hua-labs/tap add claude
|
|
48
|
-
npx @hua-labs/tap add codex
|
|
49
|
-
npx @hua-labs/tap add gemini
|
|
50
|
-
npx @hua-labs/tap add claude --force # re-install
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
### `remove <runtime>`
|
|
54
|
-
|
|
55
|
-
Remove a runtime and rollback config changes.
|
|
56
|
-
|
|
57
|
-
```bash
|
|
58
|
-
npx @hua-labs/tap remove claude
|
|
59
|
-
npx @hua-labs/tap remove codex
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
### `status`
|
|
63
|
-
|
|
64
|
-
Show installed runtimes and their status.
|
|
65
|
-
|
|
66
|
-
```bash
|
|
67
|
-
npx @hua-labs/tap status
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
Output shows three status levels:
|
|
71
|
-
|
|
72
|
-
- **installed** — config written but not verified
|
|
73
|
-
- **configured** — config written and verified
|
|
74
|
-
- **active** — runtime is running and connected
|
|
75
|
-
|
|
76
|
-
### `serve`
|
|
77
|
-
|
|
78
|
-
Start the tap-comms MCP server (stdio). Convenience command for running the MCP server locally.
|
|
79
|
-
|
|
80
|
-
```bash
|
|
81
|
-
npx @hua-labs/tap serve
|
|
82
|
-
npx @hua-labs/tap serve --comms-dir /path/to/comms
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
Requires `bun`. Uses the bundled MCP server entry from `@hua-labs/tap`, with a repo-local fallback for monorepo checkouts.
|
|
86
|
-
|
|
87
|
-
## Supported Runtimes
|
|
88
|
-
|
|
89
|
-
| Runtime | Config | Bridge | Mode |
|
|
90
|
-
| ------- | ----------------------- | ---------------------- | ------------------ |
|
|
91
|
-
| Claude | `.mcp.json` | native-push (fs.watch) | No daemon needed |
|
|
92
|
-
| Codex | `~/.codex/config.toml` | WebSocket bridge | Daemon per session |
|
|
93
|
-
| Gemini | `.gemini/settings.json` | polling | No daemon needed |
|
|
94
|
-
|
|
95
|
-
## `--json` Flag
|
|
96
|
-
|
|
97
|
-
All commands support `--json` for machine-readable output. Returns a single JSON object to stdout with no human log noise.
|
|
98
|
-
|
|
99
|
-
```bash
|
|
100
|
-
npx @hua-labs/tap status --json
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
```json
|
|
104
|
-
{
|
|
105
|
-
"ok": true,
|
|
106
|
-
"command": "status",
|
|
107
|
-
"code": "TAP_STATUS_OK",
|
|
108
|
-
"message": "2 runtime(s) installed",
|
|
109
|
-
"warnings": [],
|
|
110
|
-
"data": {
|
|
111
|
-
"version": "0.2.2",
|
|
112
|
-
"commsDir": "/path/to/comms",
|
|
113
|
-
"runtimes": {
|
|
114
|
-
"claude": { "status": "active", "bridgeMode": "native-push" },
|
|
115
|
-
"codex": { "status": "configured", "bridgeMode": "app-server" }
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
Error codes use `TAP_*` prefix: `TAP_ADD_OK`, `TAP_NO_OP`, `TAP_PATCH_FAILED`, etc.
|
|
122
|
-
|
|
123
|
-
Exit codes: `0` = ok, `1` = error.
|
|
124
|
-
|
|
125
|
-
## Permissions
|
|
126
|
-
|
|
127
|
-
`tap init` auto-configures runtime permissions.
|
|
128
|
-
|
|
129
|
-
### Safe mode (default)
|
|
130
|
-
|
|
131
|
-
**Claude**: Adds deny rules to `.claude/settings.local.json` blocking destructive operations (force push, hard reset, rm -rf, etc.).
|
|
132
|
-
|
|
133
|
-
**Codex**: Sets `workspace-write` sandbox, `full` network access, trusted project paths, and writable roots in `~/.codex/config.toml`.
|
|
134
|
-
|
|
135
|
-
### Full mode
|
|
136
|
-
|
|
137
|
-
```bash
|
|
138
|
-
npx @hua-labs/tap init --permissions full
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
**Claude**: Removes tap-managed deny rules. User-added rules preserved.
|
|
142
|
-
|
|
143
|
-
**Codex**: Sets `danger-full-access` sandbox. Use on trusted local machines only.
|
|
144
|
-
|
|
145
|
-
## How It Works
|
|
146
|
-
|
|
147
|
-
Agents communicate through a shared directory (`comms/`) using markdown files:
|
|
148
|
-
|
|
149
|
-
```
|
|
150
|
-
comms/
|
|
151
|
-
├── inbox/ # Agent-to-agent messages
|
|
152
|
-
├── reviews/ # Code review results
|
|
153
|
-
├── findings/ # Out-of-scope discoveries
|
|
154
|
-
├── handoff/ # Session handoff documents
|
|
155
|
-
├── retros/ # Retrospectives
|
|
156
|
-
└── archive/ # Archived messages
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
Each runtime has an adapter that:
|
|
160
|
-
|
|
161
|
-
1. **Probes** — finds config files, checks runtime installation
|
|
162
|
-
2. **Plans** — determines what patches to apply
|
|
163
|
-
3. **Applies** — backs up and patches config files
|
|
164
|
-
4. **Verifies** — confirms the runtime can read the config
|
|
165
|
-
|
|
166
|
-
The adapter contract (`RuntimeAdapter`) is the extension point for adding new runtimes.
|
|
167
|
-
|
|
168
|
-
## Changelog (0.2.2)
|
|
169
|
-
|
|
170
|
-
### Bridge
|
|
171
|
-
|
|
172
|
-
- **Auth gateway** — Managed bridge now includes an auth proxy with timing-safe token validation (M99)
|
|
173
|
-
- **`--no-auth` flag** — Skip auth gateway for localhost-only setups; app-server listens directly on public port (M102)
|
|
174
|
-
- **TUI connect URL** — `bridge start` and `bridge status` output shows where to connect Codex TUI (M102)
|
|
175
|
-
- **Identity routing** — Bridge matches inbox messages by both `agentId` and `agentName`; self echo-back filtered by both (M101)
|
|
176
|
-
- **Display labels** — Bridge prompts, `tap_who`, and notifications use `name [id]` format (M101)
|
|
177
|
-
|
|
178
|
-
### CLI
|
|
179
|
-
|
|
180
|
-
- **`tap doctor`** — Diagnose comms, bridge, message, and MCP issues (M95)
|
|
181
|
-
- **`tap doctor --fix`** — Auto-fix common issues with post-fix revalidation (M100)
|
|
182
|
-
- **Error codes** — 24 CLI error codes with consistent `TAP_*` prefix (M91)
|
|
183
|
-
- **Boot streamline** — Faster CLI startup with agent-name persistence (M92)
|
|
184
|
-
|
|
185
|
-
### Infrastructure
|
|
186
|
-
|
|
187
|
-
- **Auto-poll fallback** — Bridge falls back to polling when fs.watch is unavailable (M93)
|
|
188
|
-
- **Watcher dedup** — Root-cause fix for duplicate message dispatch (M90)
|
|
189
|
-
- **tap-plugin test infra** — In-memory test harness for MCP channel tests (M94)
|
|
190
|
-
- **Blind test CI** — Cross-model communication verification framework (M98)
|
|
191
|
-
|
|
192
|
-
## License
|
|
193
|
-
|
|
194
|
-
MIT
|
|
1
|
+
# @hua-labs/tap
|
|
2
|
+
|
|
3
|
+
Zero-dependency CLI for cross-model AI agent communication setup.
|
|
4
|
+
|
|
5
|
+
One command to connect Claude, Codex, and Gemini agents through a shared file-based communication layer.
|
|
6
|
+
|
|
7
|
+
## Quick Start
|
|
8
|
+
|
|
9
|
+
> `bun` is required to run the managed tap MCP server. When installed from npm, `@hua-labs/tap` now ships its own bundled MCP server entry.
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# 1. Initialize comms directory and state
|
|
13
|
+
npx @hua-labs/tap init
|
|
14
|
+
|
|
15
|
+
# 2. Add runtimes
|
|
16
|
+
npx @hua-labs/tap add claude
|
|
17
|
+
npx @hua-labs/tap add codex
|
|
18
|
+
npx @hua-labs/tap add gemini
|
|
19
|
+
|
|
20
|
+
# 3. Check status
|
|
21
|
+
npx @hua-labs/tap status
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Your agents can now communicate through the shared comms directory.
|
|
25
|
+
|
|
26
|
+
## Commands
|
|
27
|
+
|
|
28
|
+
### `init`
|
|
29
|
+
|
|
30
|
+
Initialize the comms directory and `.tap-comms/` state.
|
|
31
|
+
|
|
32
|
+
By default, the comms directory is created inside the current repo at `./tap-comms`.
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npx @hua-labs/tap init
|
|
36
|
+
npx @hua-labs/tap init --comms-dir /path/to/comms
|
|
37
|
+
npx @hua-labs/tap init --permissions safe # default: deny destructive ops
|
|
38
|
+
npx @hua-labs/tap init --permissions full # no restrictions (use with caution)
|
|
39
|
+
npx @hua-labs/tap init --force # re-initialize
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### `add <runtime>`
|
|
43
|
+
|
|
44
|
+
Add a runtime. Probes config, plans patches, applies, and verifies.
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
npx @hua-labs/tap add claude
|
|
48
|
+
npx @hua-labs/tap add codex
|
|
49
|
+
npx @hua-labs/tap add gemini
|
|
50
|
+
npx @hua-labs/tap add claude --force # re-install
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### `remove <runtime>`
|
|
54
|
+
|
|
55
|
+
Remove a runtime and rollback config changes.
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
npx @hua-labs/tap remove claude
|
|
59
|
+
npx @hua-labs/tap remove codex
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### `status`
|
|
63
|
+
|
|
64
|
+
Show installed runtimes and their status.
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
npx @hua-labs/tap status
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Output shows three status levels:
|
|
71
|
+
|
|
72
|
+
- **installed** — config written but not verified
|
|
73
|
+
- **configured** — config written and verified
|
|
74
|
+
- **active** — runtime is running and connected
|
|
75
|
+
|
|
76
|
+
### `serve`
|
|
77
|
+
|
|
78
|
+
Start the tap-comms MCP server (stdio). Convenience command for running the MCP server locally.
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
npx @hua-labs/tap serve
|
|
82
|
+
npx @hua-labs/tap serve --comms-dir /path/to/comms
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Requires `bun`. Uses the bundled MCP server entry from `@hua-labs/tap`, with a repo-local fallback for monorepo checkouts.
|
|
86
|
+
|
|
87
|
+
## Supported Runtimes
|
|
88
|
+
|
|
89
|
+
| Runtime | Config | Bridge | Mode |
|
|
90
|
+
| ------- | ----------------------- | ---------------------- | ------------------ |
|
|
91
|
+
| Claude | `.mcp.json` | native-push (fs.watch) | No daemon needed |
|
|
92
|
+
| Codex | `~/.codex/config.toml` | WebSocket bridge | Daemon per session |
|
|
93
|
+
| Gemini | `.gemini/settings.json` | polling | No daemon needed |
|
|
94
|
+
|
|
95
|
+
## `--json` Flag
|
|
96
|
+
|
|
97
|
+
All commands support `--json` for machine-readable output. Returns a single JSON object to stdout with no human log noise.
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
npx @hua-labs/tap status --json
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
```json
|
|
104
|
+
{
|
|
105
|
+
"ok": true,
|
|
106
|
+
"command": "status",
|
|
107
|
+
"code": "TAP_STATUS_OK",
|
|
108
|
+
"message": "2 runtime(s) installed",
|
|
109
|
+
"warnings": [],
|
|
110
|
+
"data": {
|
|
111
|
+
"version": "0.2.2",
|
|
112
|
+
"commsDir": "/path/to/comms",
|
|
113
|
+
"runtimes": {
|
|
114
|
+
"claude": { "status": "active", "bridgeMode": "native-push" },
|
|
115
|
+
"codex": { "status": "configured", "bridgeMode": "app-server" }
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Error codes use `TAP_*` prefix: `TAP_ADD_OK`, `TAP_NO_OP`, `TAP_PATCH_FAILED`, etc.
|
|
122
|
+
|
|
123
|
+
Exit codes: `0` = ok, `1` = error.
|
|
124
|
+
|
|
125
|
+
## Permissions
|
|
126
|
+
|
|
127
|
+
`tap init` auto-configures runtime permissions.
|
|
128
|
+
|
|
129
|
+
### Safe mode (default)
|
|
130
|
+
|
|
131
|
+
**Claude**: Adds deny rules to `.claude/settings.local.json` blocking destructive operations (force push, hard reset, rm -rf, etc.).
|
|
132
|
+
|
|
133
|
+
**Codex**: Sets `workspace-write` sandbox, `full` network access, trusted project paths, and writable roots in `~/.codex/config.toml`.
|
|
134
|
+
|
|
135
|
+
### Full mode
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
npx @hua-labs/tap init --permissions full
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
**Claude**: Removes tap-managed deny rules. User-added rules preserved.
|
|
142
|
+
|
|
143
|
+
**Codex**: Sets `danger-full-access` sandbox. Use on trusted local machines only.
|
|
144
|
+
|
|
145
|
+
## How It Works
|
|
146
|
+
|
|
147
|
+
Agents communicate through a shared directory (`comms/`) using markdown files:
|
|
148
|
+
|
|
149
|
+
```
|
|
150
|
+
comms/
|
|
151
|
+
├── inbox/ # Agent-to-agent messages
|
|
152
|
+
├── reviews/ # Code review results
|
|
153
|
+
├── findings/ # Out-of-scope discoveries
|
|
154
|
+
├── handoff/ # Session handoff documents
|
|
155
|
+
├── retros/ # Retrospectives
|
|
156
|
+
└── archive/ # Archived messages
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Each runtime has an adapter that:
|
|
160
|
+
|
|
161
|
+
1. **Probes** — finds config files, checks runtime installation
|
|
162
|
+
2. **Plans** — determines what patches to apply
|
|
163
|
+
3. **Applies** — backs up and patches config files
|
|
164
|
+
4. **Verifies** — confirms the runtime can read the config
|
|
165
|
+
|
|
166
|
+
The adapter contract (`RuntimeAdapter`) is the extension point for adding new runtimes.
|
|
167
|
+
|
|
168
|
+
## Changelog (0.2.2)
|
|
169
|
+
|
|
170
|
+
### Bridge
|
|
171
|
+
|
|
172
|
+
- **Auth gateway** — Managed bridge now includes an auth proxy with timing-safe token validation (M99)
|
|
173
|
+
- **`--no-auth` flag** — Skip auth gateway for localhost-only setups; app-server listens directly on public port (M102)
|
|
174
|
+
- **TUI connect URL** — `bridge start` and `bridge status` output shows where to connect Codex TUI (M102)
|
|
175
|
+
- **Identity routing** — Bridge matches inbox messages by both `agentId` and `agentName`; self echo-back filtered by both (M101)
|
|
176
|
+
- **Display labels** — Bridge prompts, `tap_who`, and notifications use `name [id]` format (M101)
|
|
177
|
+
|
|
178
|
+
### CLI
|
|
179
|
+
|
|
180
|
+
- **`tap doctor`** — Diagnose comms, bridge, message, and MCP issues (M95)
|
|
181
|
+
- **`tap doctor --fix`** — Auto-fix common issues with post-fix revalidation (M100)
|
|
182
|
+
- **Error codes** — 24 CLI error codes with consistent `TAP_*` prefix (M91)
|
|
183
|
+
- **Boot streamline** — Faster CLI startup with agent-name persistence (M92)
|
|
184
|
+
|
|
185
|
+
### Infrastructure
|
|
186
|
+
|
|
187
|
+
- **Auto-poll fallback** — Bridge falls back to polling when fs.watch is unavailable (M93)
|
|
188
|
+
- **Watcher dedup** — Root-cause fix for duplicate message dispatch (M90)
|
|
189
|
+
- **tap-plugin test infra** — In-memory test harness for MCP channel tests (M94)
|
|
190
|
+
- **Blind test CI** — Cross-model communication verification framework (M98)
|
|
191
|
+
|
|
192
|
+
## License
|
|
193
|
+
|
|
194
|
+
MIT
|
|
@@ -4,7 +4,7 @@ import { resolve } from "path";
|
|
|
4
4
|
import { pathToFileURL } from "url";
|
|
5
5
|
import { timingSafeEqual } from "crypto";
|
|
6
6
|
import { WebSocket, WebSocketServer } from "ws";
|
|
7
|
-
var
|
|
7
|
+
var AUTH_SUBPROTOCOL_PREFIX = "tap-auth-";
|
|
8
8
|
var CLOSE_UNAUTHORIZED = 4401;
|
|
9
9
|
var CLOSE_UPSTREAM_ERROR = 1013;
|
|
10
10
|
function normalizeUrl(value) {
|
|
@@ -105,7 +105,9 @@ async function main() {
|
|
|
105
105
|
const host = listen.hostname === "localhost" ? "127.0.0.1" : listen.hostname;
|
|
106
106
|
const port = Number.parseInt(listen.port, 10);
|
|
107
107
|
if (!Number.isFinite(port) || port <= 0) {
|
|
108
|
-
throw new Error(
|
|
108
|
+
throw new Error(
|
|
109
|
+
`Gateway listen URL must include a valid port: ${options.listenUrl}`
|
|
110
|
+
);
|
|
109
111
|
}
|
|
110
112
|
const server = new WebSocketServer({
|
|
111
113
|
host,
|
|
@@ -114,8 +116,14 @@ async function main() {
|
|
|
114
116
|
perMessageDeflate: false
|
|
115
117
|
});
|
|
116
118
|
server.on("connection", (client, request) => {
|
|
119
|
+
const protocols = request.headers["sec-websocket-protocol"]?.split(",").map((s) => s.trim()) ?? [];
|
|
120
|
+
const authProtocol = protocols.find(
|
|
121
|
+
(p) => p.startsWith(AUTH_SUBPROTOCOL_PREFIX)
|
|
122
|
+
);
|
|
123
|
+
const subprotocolToken = authProtocol?.slice(AUTH_SUBPROTOCOL_PREFIX.length) ?? null;
|
|
117
124
|
const requestUrl = new URL(request.url ?? "/", options.listenUrl);
|
|
118
|
-
const
|
|
125
|
+
const queryToken = requestUrl.searchParams.get("tap_token");
|
|
126
|
+
const presentedToken = subprotocolToken ?? queryToken;
|
|
119
127
|
if (!tokensMatch(presentedToken, options.token)) {
|
|
120
128
|
closeSocket(client, CLOSE_UNAUTHORIZED, "Unauthorized");
|
|
121
129
|
return;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/bridges/codex-app-server-auth-gateway.ts"],"sourcesContent":["import type { IncomingMessage } from \"node:http\";\nimport { readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\nimport { timingSafeEqual } from \"node:crypto\";\nimport { WebSocket, WebSocketServer, type RawData } from \"ws\";\n\nconst AUTH_QUERY_PARAM = \"tap_token\";\nconst CLOSE_UNAUTHORIZED = 4401;\nconst CLOSE_UPSTREAM_ERROR = 1013;\n\ninterface GatewayOptions {\n listenUrl: string;\n upstreamUrl: string;\n token: string;\n}\n\nfunction normalizeUrl(value: string): string {\n return value.replace(/\\/$/, \"\");\n}\n\nfunction closeSocket(\n socket: Pick<WebSocket, \"readyState\" | \"close\">,\n code: number,\n reason: string,\n): void {\n if (\n socket.readyState === WebSocket.CLOSING ||\n socket.readyState === WebSocket.CLOSED\n ) {\n return;\n }\n\n try {\n socket.close(code, reason);\n } catch {\n // Best-effort cleanup only.\n }\n}\n\nfunction readFlagValue(argv: string[], index: number, flag: string): string {\n const current = argv[index] ?? \"\";\n const eqIndex = current.indexOf(\"=\");\n if (eqIndex >= 0) {\n return current.slice(eqIndex + 1);\n }\n\n const next = argv[index + 1];\n if (!next || next.startsWith(\"--\")) {\n throw new Error(`Missing value for ${flag}`);\n }\n return next;\n}\n\nexport function buildGatewayOptions(argv: string[]): GatewayOptions {\n let listenUrl = process.env.TAP_GATEWAY_LISTEN_URL?.trim() || \"\";\n let upstreamUrl = process.env.TAP_GATEWAY_UPSTREAM_URL?.trim() || \"\";\n let tokenFile = process.env.TAP_GATEWAY_TOKEN_FILE?.trim() || \"\";\n let token = process.env.TAP_GATEWAY_TOKEN?.trim() || \"\";\n\n for (let index = 0; index < argv.length; index += 1) {\n const flag = argv[index] ?? \"\";\n const consumesNext = !flag.includes(\"=\");\n\n if (flag.startsWith(\"--listen-url\")) {\n listenUrl = readFlagValue(argv, index, \"--listen-url\").trim();\n if (consumesNext) index += 1;\n continue;\n }\n\n if (flag.startsWith(\"--upstream-url\")) {\n upstreamUrl = readFlagValue(argv, index, \"--upstream-url\").trim();\n if (consumesNext) index += 1;\n continue;\n }\n\n if (flag.startsWith(\"--token\")) {\n token = readFlagValue(argv, index, \"--token\").trim();\n if (consumesNext) index += 1;\n continue;\n }\n\n if (flag.startsWith(\"--token-file\")) {\n tokenFile = readFlagValue(argv, index, \"--token-file\").trim();\n if (consumesNext) index += 1;\n continue;\n }\n }\n\n if (tokenFile) {\n token = readFileSync(tokenFile, \"utf8\").trim();\n }\n\n if (!listenUrl) {\n throw new Error(\"Missing gateway listen URL\");\n }\n if (!upstreamUrl) {\n throw new Error(\"Missing gateway upstream URL\");\n }\n if (!token) {\n throw new Error(\"Missing gateway auth token\");\n }\n\n const listen = new URL(listenUrl);\n const upstream = new URL(upstreamUrl);\n if (!/^wss?:$/.test(listen.protocol)) {\n throw new Error(`Unsupported gateway listen protocol: ${listen.protocol}`);\n }\n if (!/^wss?:$/.test(upstream.protocol)) {\n throw new Error(\n `Unsupported gateway upstream protocol: ${upstream.protocol}`,\n );\n }\n\n return {\n listenUrl: normalizeUrl(listen.toString()),\n upstreamUrl: normalizeUrl(upstream.toString()),\n token,\n };\n}\n\nfunction tokensMatch(presentedToken: string | null, expectedToken: string): boolean {\n if (!presentedToken) {\n return false;\n }\n\n const presented = Buffer.from(presentedToken, \"utf8\");\n const expected = Buffer.from(expectedToken, \"utf8\");\n if (presented.length !== expected.length) {\n return false;\n }\n\n return timingSafeEqual(presented, expected);\n}\n\nasync function main(): Promise<void> {\n const options = buildGatewayOptions(process.argv.slice(2));\n const listen = new URL(options.listenUrl);\n const host = listen.hostname === \"localhost\" ? \"127.0.0.1\" : listen.hostname;\n const port = Number.parseInt(listen.port, 10);\n if (!Number.isFinite(port) || port <= 0) {\n throw new Error(`Gateway listen URL must include a valid port: ${options.listenUrl}`);\n }\n\n const server = new WebSocketServer({\n host,\n port,\n path: listen.pathname === \"/\" ? undefined : listen.pathname,\n perMessageDeflate: false,\n });\n\n server.on(\"connection\", (client: WebSocket, request: IncomingMessage) => {\n const requestUrl = new URL(request.url ?? \"/\", options.listenUrl);\n const presentedToken = requestUrl.searchParams.get(AUTH_QUERY_PARAM);\n if (!tokensMatch(presentedToken, options.token)) {\n closeSocket(client, CLOSE_UNAUTHORIZED, \"Unauthorized\");\n return;\n }\n\n const upstream = new WebSocket(options.upstreamUrl, {\n perMessageDeflate: false,\n });\n\n upstream.on(\"message\", (data: RawData, isBinary: boolean) => {\n if (client.readyState === WebSocket.OPEN) {\n client.send(data, { binary: isBinary });\n }\n });\n\n client.on(\"message\", (data: RawData, isBinary: boolean) => {\n if (upstream.readyState === WebSocket.OPEN) {\n upstream.send(data, { binary: isBinary });\n }\n });\n\n upstream.on(\"close\", (code: number, reasonBuffer: Buffer) => {\n const reason = reasonBuffer.toString() || \"Upstream closed\";\n closeSocket(client, code || 1000, reason);\n });\n\n client.on(\"close\", (code: number, reasonBuffer: Buffer) => {\n const reason = reasonBuffer.toString() || \"Client closed\";\n closeSocket(upstream, code || 1000, reason);\n });\n\n upstream.on(\"error\", (error: Error) => {\n console.error(`[auth-gateway] upstream error: ${String(error)}`);\n closeSocket(client, CLOSE_UPSTREAM_ERROR, \"Upstream unavailable\");\n closeSocket(upstream, CLOSE_UPSTREAM_ERROR, \"Upstream unavailable\");\n });\n\n client.on(\"error\", (error: Error) => {\n console.error(`[auth-gateway] client error: ${String(error)}`);\n closeSocket(upstream, 1011, \"Client error\");\n });\n });\n\n server.on(\"listening\", () => {\n console.log(\n `[auth-gateway] listening ${options.listenUrl} -> ${options.upstreamUrl}`,\n );\n });\n\n const shutdown = () => {\n server.close(() => {\n process.exit(0);\n });\n };\n\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n}\n\nfunction isDirectExecution(): boolean {\n const entry = process.argv[1];\n if (!entry) return false;\n return import.meta.url === pathToFileURL(resolve(entry)).href;\n}\n\nif (isDirectExecution()) {\n main().catch((error) => {\n console.error(\n error instanceof Error ? (error.stack ?? error.message) : String(error),\n );\n process.exit(1);\n });\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/bridges/codex-app-server-auth-gateway.ts"],"sourcesContent":["import type { IncomingMessage } from \"node:http\";\nimport { readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\nimport { timingSafeEqual } from \"node:crypto\";\nimport { WebSocket, WebSocketServer, type RawData } from \"ws\";\n\nconst AUTH_SUBPROTOCOL_PREFIX = \"tap-auth-\";\nconst CLOSE_UNAUTHORIZED = 4401;\nconst CLOSE_UPSTREAM_ERROR = 1013;\n\ninterface GatewayOptions {\n listenUrl: string;\n upstreamUrl: string;\n token: string;\n}\n\nfunction normalizeUrl(value: string): string {\n return value.replace(/\\/$/, \"\");\n}\n\nfunction closeSocket(\n socket: Pick<WebSocket, \"readyState\" | \"close\">,\n code: number,\n reason: string,\n): void {\n if (\n socket.readyState === WebSocket.CLOSING ||\n socket.readyState === WebSocket.CLOSED\n ) {\n return;\n }\n\n try {\n socket.close(code, reason);\n } catch {\n // Best-effort cleanup only.\n }\n}\n\nfunction readFlagValue(argv: string[], index: number, flag: string): string {\n const current = argv[index] ?? \"\";\n const eqIndex = current.indexOf(\"=\");\n if (eqIndex >= 0) {\n return current.slice(eqIndex + 1);\n }\n\n const next = argv[index + 1];\n if (!next || next.startsWith(\"--\")) {\n throw new Error(`Missing value for ${flag}`);\n }\n return next;\n}\n\nexport function buildGatewayOptions(argv: string[]): GatewayOptions {\n let listenUrl = process.env.TAP_GATEWAY_LISTEN_URL?.trim() || \"\";\n let upstreamUrl = process.env.TAP_GATEWAY_UPSTREAM_URL?.trim() || \"\";\n let tokenFile = process.env.TAP_GATEWAY_TOKEN_FILE?.trim() || \"\";\n let token = process.env.TAP_GATEWAY_TOKEN?.trim() || \"\";\n\n for (let index = 0; index < argv.length; index += 1) {\n const flag = argv[index] ?? \"\";\n const consumesNext = !flag.includes(\"=\");\n\n if (flag.startsWith(\"--listen-url\")) {\n listenUrl = readFlagValue(argv, index, \"--listen-url\").trim();\n if (consumesNext) index += 1;\n continue;\n }\n\n if (flag.startsWith(\"--upstream-url\")) {\n upstreamUrl = readFlagValue(argv, index, \"--upstream-url\").trim();\n if (consumesNext) index += 1;\n continue;\n }\n\n if (flag.startsWith(\"--token\")) {\n token = readFlagValue(argv, index, \"--token\").trim();\n if (consumesNext) index += 1;\n continue;\n }\n\n if (flag.startsWith(\"--token-file\")) {\n tokenFile = readFlagValue(argv, index, \"--token-file\").trim();\n if (consumesNext) index += 1;\n continue;\n }\n }\n\n if (tokenFile) {\n token = readFileSync(tokenFile, \"utf8\").trim();\n }\n\n if (!listenUrl) {\n throw new Error(\"Missing gateway listen URL\");\n }\n if (!upstreamUrl) {\n throw new Error(\"Missing gateway upstream URL\");\n }\n if (!token) {\n throw new Error(\"Missing gateway auth token\");\n }\n\n const listen = new URL(listenUrl);\n const upstream = new URL(upstreamUrl);\n if (!/^wss?:$/.test(listen.protocol)) {\n throw new Error(`Unsupported gateway listen protocol: ${listen.protocol}`);\n }\n if (!/^wss?:$/.test(upstream.protocol)) {\n throw new Error(\n `Unsupported gateway upstream protocol: ${upstream.protocol}`,\n );\n }\n\n return {\n listenUrl: normalizeUrl(listen.toString()),\n upstreamUrl: normalizeUrl(upstream.toString()),\n token,\n };\n}\n\nfunction tokensMatch(\n presentedToken: string | null,\n expectedToken: string,\n): boolean {\n if (!presentedToken) {\n return false;\n }\n\n const presented = Buffer.from(presentedToken, \"utf8\");\n const expected = Buffer.from(expectedToken, \"utf8\");\n if (presented.length !== expected.length) {\n return false;\n }\n\n return timingSafeEqual(presented, expected);\n}\n\nasync function main(): Promise<void> {\n const options = buildGatewayOptions(process.argv.slice(2));\n const listen = new URL(options.listenUrl);\n const host = listen.hostname === \"localhost\" ? \"127.0.0.1\" : listen.hostname;\n const port = Number.parseInt(listen.port, 10);\n if (!Number.isFinite(port) || port <= 0) {\n throw new Error(\n `Gateway listen URL must include a valid port: ${options.listenUrl}`,\n );\n }\n\n const server = new WebSocketServer({\n host,\n port,\n path: listen.pathname === \"/\" ? undefined : listen.pathname,\n perMessageDeflate: false,\n });\n\n server.on(\"connection\", (client: WebSocket, request: IncomingMessage) => {\n // Extract token from Sec-WebSocket-Protocol header (subprotocol auth).\n // Client sends: WebSocket(url, [\"tap-auth-<token>\"])\n // Falls back to query param for backward compatibility during migration.\n const protocols =\n request.headers[\"sec-websocket-protocol\"]\n ?.split(\",\")\n .map((s) => s.trim()) ?? [];\n const authProtocol = protocols.find((p) =>\n p.startsWith(AUTH_SUBPROTOCOL_PREFIX),\n );\n const subprotocolToken =\n authProtocol?.slice(AUTH_SUBPROTOCOL_PREFIX.length) ?? null;\n\n // Legacy fallback: query param (will be removed in future version)\n const requestUrl = new URL(request.url ?? \"/\", options.listenUrl);\n const queryToken = requestUrl.searchParams.get(\"tap_token\");\n\n const presentedToken = subprotocolToken ?? queryToken;\n if (!tokensMatch(presentedToken, options.token)) {\n closeSocket(client, CLOSE_UNAUTHORIZED, \"Unauthorized\");\n return;\n }\n\n const upstream = new WebSocket(options.upstreamUrl, {\n perMessageDeflate: false,\n });\n\n upstream.on(\"message\", (data: RawData, isBinary: boolean) => {\n if (client.readyState === WebSocket.OPEN) {\n client.send(data, { binary: isBinary });\n }\n });\n\n client.on(\"message\", (data: RawData, isBinary: boolean) => {\n if (upstream.readyState === WebSocket.OPEN) {\n upstream.send(data, { binary: isBinary });\n }\n });\n\n upstream.on(\"close\", (code: number, reasonBuffer: Buffer) => {\n const reason = reasonBuffer.toString() || \"Upstream closed\";\n closeSocket(client, code || 1000, reason);\n });\n\n client.on(\"close\", (code: number, reasonBuffer: Buffer) => {\n const reason = reasonBuffer.toString() || \"Client closed\";\n closeSocket(upstream, code || 1000, reason);\n });\n\n upstream.on(\"error\", (error: Error) => {\n console.error(`[auth-gateway] upstream error: ${String(error)}`);\n closeSocket(client, CLOSE_UPSTREAM_ERROR, \"Upstream unavailable\");\n closeSocket(upstream, CLOSE_UPSTREAM_ERROR, \"Upstream unavailable\");\n });\n\n client.on(\"error\", (error: Error) => {\n console.error(`[auth-gateway] client error: ${String(error)}`);\n closeSocket(upstream, 1011, \"Client error\");\n });\n });\n\n server.on(\"listening\", () => {\n console.log(\n `[auth-gateway] listening ${options.listenUrl} -> ${options.upstreamUrl}`,\n );\n });\n\n const shutdown = () => {\n server.close(() => {\n process.exit(0);\n });\n };\n\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n}\n\nfunction isDirectExecution(): boolean {\n const entry = process.argv[1];\n if (!entry) return false;\n return import.meta.url === pathToFileURL(resolve(entry)).href;\n}\n\nif (isDirectExecution()) {\n main().catch((error) => {\n console.error(\n error instanceof Error ? (error.stack ?? error.message) : String(error),\n );\n process.exit(1);\n });\n}\n"],"mappings":"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","names":[]}
|
|
@@ -14,6 +14,7 @@ interface Options {
|
|
|
14
14
|
waitAfterDispatchSeconds: number;
|
|
15
15
|
appServerUrl: string;
|
|
16
16
|
connectAppServerUrl: string;
|
|
17
|
+
gatewayToken: string | null;
|
|
17
18
|
gatewayTokenFile: string | null;
|
|
18
19
|
busyMode: BusyMode;
|
|
19
20
|
threadId: string | null;
|
|
@@ -35,12 +36,21 @@ interface HeadlessWarmupClient {
|
|
|
35
36
|
startTurn(inputText: string): Promise<string | null>;
|
|
36
37
|
refreshCurrentThreadState(): Promise<void>;
|
|
37
38
|
}
|
|
39
|
+
interface LoadedThreadCandidate {
|
|
40
|
+
id: string;
|
|
41
|
+
cwd: string;
|
|
42
|
+
updatedAt: number;
|
|
43
|
+
statusType: string | null;
|
|
44
|
+
thread: any;
|
|
45
|
+
}
|
|
38
46
|
interface HeartbeatStoreRecord {
|
|
39
47
|
id?: string;
|
|
40
48
|
agent?: string;
|
|
41
49
|
}
|
|
42
50
|
type HeartbeatStore = Record<string, HeartbeatStoreRecord>;
|
|
43
51
|
declare const HEADLESS_WARMUP_PROMPT: string;
|
|
52
|
+
declare function threadCwdMatches(expectedCwd: string, actualCwd: string | null | undefined): boolean;
|
|
53
|
+
declare function chooseLoadedThreadForCwd(cwd: string, threads: LoadedThreadCandidate[]): LoadedThreadCandidate | null;
|
|
44
54
|
declare function resolveAgentId(preferredAgentName?: string | null): string;
|
|
45
55
|
declare function recipientMatchesAgent(recipient: string, agentId: string, agentName: string): boolean;
|
|
46
56
|
declare function isOwnMessageSender(sender: string, agentId: string, agentName: string): boolean;
|
|
@@ -52,4 +62,4 @@ declare function maybeBootstrapHeadlessTurn(options: Options, cutoff: Date, clie
|
|
|
52
62
|
declare function buildOptions(argv: string[]): Options;
|
|
53
63
|
declare function main(): Promise<void>;
|
|
54
64
|
|
|
55
|
-
export { HEADLESS_WARMUP_PROMPT, type HeadlessWarmupClient, buildOptions, buildUserInput, isOwnMessageSender, main, maybeBootstrapHeadlessTurn, recipientMatchesAgent, resolveAddressLabel, resolveAgentId, resolveCurrentAgentName, waitForTurnCompletion };
|
|
65
|
+
export { HEADLESS_WARMUP_PROMPT, type HeadlessWarmupClient, type LoadedThreadCandidate, buildOptions, buildUserInput, chooseLoadedThreadForCwd, isOwnMessageSender, main, maybeBootstrapHeadlessTurn, recipientMatchesAgent, resolveAddressLabel, resolveAgentId, resolveCurrentAgentName, threadCwdMatches, waitForTurnCompletion };
|