@jasonews/agent-squad-gateway 1.0.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 (149) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +200 -0
  3. package/SECURITY.md +44 -0
  4. package/dist/bin/agent-squad-gateway.js +55 -0
  5. package/dist/bin/agent-squad-gateway.js.map +1 -0
  6. package/dist/config/config.js +124 -0
  7. package/dist/config/config.js.map +1 -0
  8. package/dist/config/paths.js +16 -0
  9. package/dist/config/paths.js.map +1 -0
  10. package/dist/control-plane/clients.js +77 -0
  11. package/dist/control-plane/clients.js.map +1 -0
  12. package/dist/control-plane/core-connection.js +54 -0
  13. package/dist/control-plane/core-connection.js.map +1 -0
  14. package/dist/control-plane/credentials.js +134 -0
  15. package/dist/control-plane/credentials.js.map +1 -0
  16. package/dist/control-plane/db.js +33 -0
  17. package/dist/control-plane/db.js.map +1 -0
  18. package/dist/control-plane/extensions.js +59 -0
  19. package/dist/control-plane/extensions.js.map +1 -0
  20. package/dist/control-plane/grants.js +107 -0
  21. package/dist/control-plane/grants.js.map +1 -0
  22. package/dist/control-plane/idempotency.js +132 -0
  23. package/dist/control-plane/idempotency.js.map +1 -0
  24. package/dist/control-plane/migrations.js +133 -0
  25. package/dist/control-plane/migrations.js.map +1 -0
  26. package/dist/control-plane/response-sessions.js +359 -0
  27. package/dist/control-plane/response-sessions.js.map +1 -0
  28. package/dist/control-plane/retention.js +138 -0
  29. package/dist/control-plane/retention.js.map +1 -0
  30. package/dist/control-plane/runs.js +156 -0
  31. package/dist/control-plane/runs.js.map +1 -0
  32. package/dist/control-plane/targets.js +449 -0
  33. package/dist/control-plane/targets.js.map +1 -0
  34. package/dist/control-plane/types.js +2 -0
  35. package/dist/control-plane/types.js.map +1 -0
  36. package/dist/core-client/client.js +275 -0
  37. package/dist/core-client/client.js.map +1 -0
  38. package/dist/core-client/event-proxy.js +105 -0
  39. package/dist/core-client/event-proxy.js.map +1 -0
  40. package/dist/core-client/types.js +2 -0
  41. package/dist/core-client/types.js.map +1 -0
  42. package/dist/extensions/contract.js +2 -0
  43. package/dist/extensions/contract.js.map +1 -0
  44. package/dist/extensions/openai/auth.js +16 -0
  45. package/dist/extensions/openai/auth.js.map +1 -0
  46. package/dist/extensions/openai/chat.js +403 -0
  47. package/dist/extensions/openai/chat.js.map +1 -0
  48. package/dist/extensions/openai/errors.js +79 -0
  49. package/dist/extensions/openai/errors.js.map +1 -0
  50. package/dist/extensions/openai/image-input.js +79 -0
  51. package/dist/extensions/openai/image-input.js.map +1 -0
  52. package/dist/extensions/openai/models.js +40 -0
  53. package/dist/extensions/openai/models.js.map +1 -0
  54. package/dist/extensions/openai/responses.js +731 -0
  55. package/dist/extensions/openai/responses.js.map +1 -0
  56. package/dist/extensions/openai/routes.js +81 -0
  57. package/dist/extensions/openai/routes.js.map +1 -0
  58. package/dist/extensions/openai/run-attempt.js +96 -0
  59. package/dist/extensions/openai/run-attempt.js.map +1 -0
  60. package/dist/extensions/openai/schemas.js +176 -0
  61. package/dist/extensions/openai/schemas.js.map +1 -0
  62. package/dist/extensions/openai/sse.js +421 -0
  63. package/dist/extensions/openai/sse.js.map +1 -0
  64. package/dist/extensions/openai/tools.js +727 -0
  65. package/dist/extensions/openai/tools.js.map +1 -0
  66. package/dist/extensions/registry.js +30 -0
  67. package/dist/extensions/registry.js.map +1 -0
  68. package/dist/lifecycle/daemon.js +437 -0
  69. package/dist/lifecycle/daemon.js.map +1 -0
  70. package/dist/lifecycle/pidfile.js +34 -0
  71. package/dist/lifecycle/pidfile.js.map +1 -0
  72. package/dist/provider-runtime/antigravity/adapter.js +398 -0
  73. package/dist/provider-runtime/antigravity/adapter.js.map +1 -0
  74. package/dist/provider-runtime/antigravity/conversation.js +31 -0
  75. package/dist/provider-runtime/antigravity/conversation.js.map +1 -0
  76. package/dist/provider-runtime/capability-service.js +191 -0
  77. package/dist/provider-runtime/capability-service.js.map +1 -0
  78. package/dist/provider-runtime/claude/adapter.js +397 -0
  79. package/dist/provider-runtime/claude/adapter.js.map +1 -0
  80. package/dist/provider-runtime/claude/protocol.js +143 -0
  81. package/dist/provider-runtime/claude/protocol.js.map +1 -0
  82. package/dist/provider-runtime/codex/adapter.js +440 -0
  83. package/dist/provider-runtime/codex/adapter.js.map +1 -0
  84. package/dist/provider-runtime/codex/json-rpc-client.js +182 -0
  85. package/dist/provider-runtime/codex/json-rpc-client.js.map +1 -0
  86. package/dist/provider-runtime/codex/protocol.js +128 -0
  87. package/dist/provider-runtime/codex/protocol.js.map +1 -0
  88. package/dist/provider-runtime/command-discovery.js +106 -0
  89. package/dist/provider-runtime/command-discovery.js.map +1 -0
  90. package/dist/provider-runtime/cursor/adapter.js +427 -0
  91. package/dist/provider-runtime/cursor/adapter.js.map +1 -0
  92. package/dist/provider-runtime/cursor/protocol.js +81 -0
  93. package/dist/provider-runtime/cursor/protocol.js.map +1 -0
  94. package/dist/provider-runtime/fake/adapter.js +138 -0
  95. package/dist/provider-runtime/fake/adapter.js.map +1 -0
  96. package/dist/provider-runtime/image-assets.js +409 -0
  97. package/dist/provider-runtime/image-assets.js.map +1 -0
  98. package/dist/provider-runtime/image-support.js +5 -0
  99. package/dist/provider-runtime/image-support.js.map +1 -0
  100. package/dist/provider-runtime/invocation-service.js +503 -0
  101. package/dist/provider-runtime/invocation-service.js.map +1 -0
  102. package/dist/provider-runtime/kimi/adapter.js +657 -0
  103. package/dist/provider-runtime/kimi/adapter.js.map +1 -0
  104. package/dist/provider-runtime/opencode/adapter.js +610 -0
  105. package/dist/provider-runtime/opencode/adapter.js.map +1 -0
  106. package/dist/provider-runtime/opencode/server.js +46 -0
  107. package/dist/provider-runtime/opencode/server.js.map +1 -0
  108. package/dist/provider-runtime/process/line-reader.js +49 -0
  109. package/dist/provider-runtime/process/line-reader.js.map +1 -0
  110. package/dist/provider-runtime/process/managed-process.js +151 -0
  111. package/dist/provider-runtime/process/managed-process.js.map +1 -0
  112. package/dist/provider-runtime/provider-input.js +77 -0
  113. package/dist/provider-runtime/provider-input.js.map +1 -0
  114. package/dist/provider-runtime/register-providers.js +26 -0
  115. package/dist/provider-runtime/register-providers.js.map +1 -0
  116. package/dist/provider-runtime/registry.js +35 -0
  117. package/dist/provider-runtime/registry.js.map +1 -0
  118. package/dist/provider-runtime/replay-buffer.js +168 -0
  119. package/dist/provider-runtime/replay-buffer.js.map +1 -0
  120. package/dist/provider-runtime/scheduler.js +155 -0
  121. package/dist/provider-runtime/scheduler.js.map +1 -0
  122. package/dist/provider-runtime/types.js +2 -0
  123. package/dist/provider-runtime/types.js.map +1 -0
  124. package/dist/provider-runtime/workspaces.js +814 -0
  125. package/dist/provider-runtime/workspaces.js.map +1 -0
  126. package/dist/security/admin-auth.js +109 -0
  127. package/dist/security/admin-auth.js.map +1 -0
  128. package/dist/security/crypto.js +52 -0
  129. package/dist/security/crypto.js.map +1 -0
  130. package/dist/security/secret-files.js +33 -0
  131. package/dist/security/secret-files.js.map +1 -0
  132. package/dist/server/admin-routes.js +610 -0
  133. package/dist/server/admin-routes.js.map +1 -0
  134. package/dist/server/app.js +178 -0
  135. package/dist/server/app.js.map +1 -0
  136. package/dist/server/auth-hooks.js +43 -0
  137. package/dist/server/auth-hooks.js.map +1 -0
  138. package/dist/server/core-routes.js +127 -0
  139. package/dist/server/core-routes.js.map +1 -0
  140. package/dist/server/errors.js +19 -0
  141. package/dist/server/errors.js.map +1 -0
  142. package/dist/server/ui-routes.js +77 -0
  143. package/dist/server/ui-routes.js.map +1 -0
  144. package/dist/web/assets/index-BhqNoPpE.js +206 -0
  145. package/dist/web/assets/index-BhqNoPpE.js.map +1 -0
  146. package/dist/web/assets/index-C5nnaBfZ.css +1 -0
  147. package/dist/web/index.html +14 -0
  148. package/package.json +74 -0
  149. package/translations/README.zh-CN.md +175 -0
package/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 JASONews
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.
22
+
package/README.md ADDED
@@ -0,0 +1,200 @@
1
+ # Agent Squad Gateway
2
+
3
+ **A local control plane and OpenAI-compatible API gateway for coding CLI models.**
4
+
5
+ **English (default)** | [简体中文](https://github.com/JASONews/agent-squad-gateway/blob/main/translations/README.zh-CN.md)
6
+
7
+ Agent Squad Gateway exposes models from locally installed coding CLIs through a managed Web UI and
8
+ OpenAI-compatible endpoints. Each provider keeps its existing local login; the Gateway adds model
9
+ aliases, API keys, access grants, concurrency controls, streaming, and capability verification.
10
+
11
+ Agent Squad Core is optional and independent. The Gateway can display Core sessions and pending
12
+ choices for debugging, but Core does not require the Gateway and the Gateway provider runtime can
13
+ operate without Core.
14
+
15
+ ```text
16
+ OpenAI client / LiteLLM / IDE
17
+ |
18
+ /v1 API + SSE
19
+ |
20
+ Agent Squad Gateway + Web UI
21
+ |
22
+ Codex / Claude Code / Cursor Agent / OpenCode / Antigravity / Kimi Code
23
+ ```
24
+
25
+ ## Features
26
+
27
+ - OpenAI-compatible `GET /v1/models`, `POST /v1/chat/completions`, and `POST /v1/responses`.
28
+ - Streaming responses over SSE, including resumable Responses sessions through
29
+ `previous_response_id`.
30
+ - Client-side function tools: the Gateway returns tool calls, while the API client executes them.
31
+ - Provider-native tools and extensions are disabled where supported and rejected at the adapter
32
+ boundary where a CLI cannot disable them completely.
33
+ - Web UI for CLI discovery, invocation targets, verification, clients, recoverable local API keys,
34
+ grants, run metadata, extensions, settings, and optional Core debugging.
35
+ - Per-target model, reasoning effort, isolation, streaming, queue, concurrency, timeout, and
36
+ workspace controls.
37
+ - Version-aware capability verification: a CLI upgrade keeps the provider available while affected
38
+ targets require conformance verification again.
39
+ - Metadata-only Gateway persistence: prompts, completions, tool payloads, and raw provider events
40
+ are not stored in the Gateway database.
41
+
42
+ ## Requirements
43
+
44
+ - Node.js 20 or newer
45
+ - npm
46
+ - At least one installed and authenticated provider CLI:
47
+ - Codex (`codex`)
48
+ - Claude Code (`claude`)
49
+ - Cursor Agent (`cursor-agent`)
50
+ - OpenCode (`opencode`)
51
+ - Antigravity (`agy`)
52
+ - Kimi Code (`kimi`)
53
+
54
+ ## Install
55
+
56
+ ### npm
57
+
58
+ Agent Squad Gateway is a public package on the npm registry. No registry token is required:
59
+
60
+ ```bash
61
+ npm install -g @jasonews/agent-squad-gateway
62
+ ```
63
+
64
+ Verify the command:
65
+
66
+ ```bash
67
+ agent-squad-gateway --help
68
+ ```
69
+
70
+ ### Build From Source
71
+
72
+ ```bash
73
+ git clone https://github.com/JASONews/agent-squad-gateway.git
74
+ cd agent-squad-gateway
75
+ npm ci
76
+ npm run build
77
+ npm link
78
+ ```
79
+
80
+ ## Quick Start
81
+
82
+ ```bash
83
+ agent-squad-gateway start
84
+ agent-squad-gateway open
85
+ ```
86
+
87
+ The default server address is `0.0.0.0:28772`, allowing local containers to connect. On first
88
+ start, the Gateway creates `~/.agent-squad/gateway/config.json`:
89
+
90
+ ```json
91
+ {
92
+ "address": "0.0.0.0",
93
+ "port": 28772,
94
+ "web_ui_auth": "disabled"
95
+ }
96
+ ```
97
+
98
+ For host-only access, change `address` to `127.0.0.1`. Set `web_ui_auth` to `token` when the admin
99
+ UI is reachable from an untrusted network. Restart the Gateway after editing the file.
100
+
101
+ In the Web UI:
102
+
103
+ 1. Scan installed CLIs.
104
+ 2. Create and verify an invocation target. Verification performs real model calls and may consume
105
+ quota.
106
+ 3. Enable the verified target.
107
+ 4. Create a client and API key.
108
+ 5. Enable the OpenAI extension and grant the client access to the target.
109
+
110
+ ## OpenAI-Compatible API
111
+
112
+ Use `http://127.0.0.1:28772/v1` as the base URL. Docker Desktop containers can use
113
+ `http://host.docker.internal:28772/v1`.
114
+
115
+ ```bash
116
+ export AGENT_SQUAD_BASE_URL=http://127.0.0.1:28772/v1
117
+ export AGENT_SQUAD_API_KEY=asqsk_your_key
118
+
119
+ curl "$AGENT_SQUAD_BASE_URL/models" \
120
+ -H "Authorization: Bearer $AGENT_SQUAD_API_KEY"
121
+
122
+ curl "$AGENT_SQUAD_BASE_URL/chat/completions" \
123
+ -H "Authorization: Bearer $AGENT_SQUAD_API_KEY" \
124
+ -H "Content-Type: application/json" \
125
+ -d '{
126
+ "model": "codex-gpt56-max",
127
+ "messages": [{"role":"user","content":"Explain this repository."}],
128
+ "stream": true
129
+ }'
130
+ ```
131
+
132
+ The model name is the enabled invocation target ID or one of its configured aliases. Unconfigured
133
+ models are rejected.
134
+
135
+ ## Tool Calls
136
+
137
+ API tool calls belong to the client. Send OpenAI function definitions in the request, execute the
138
+ returned calls in the client application, and submit tool results on the next turn. The Gateway
139
+ does not expose the provider CLI's personal tools, MCP servers, skills, or workspace as API tools.
140
+
141
+ ## CLI
142
+
143
+ ```bash
144
+ agent-squad-gateway start [--foreground] [--address ADDRESS] [--port PORT]
145
+ agent-squad-gateway stop
146
+ agent-squad-gateway status
147
+ agent-squad-gateway open
148
+ agent-squad-gateway doctor
149
+ ```
150
+
151
+ Configuration priority, from lowest to highest, is built-in defaults, `config.json`, environment
152
+ variables, and CLI options. Supported overrides include:
153
+
154
+ ```bash
155
+ AGENT_SQUAD_GATEWAY_ADDRESS=127.0.0.1
156
+ AGENT_SQUAD_GATEWAY_PORT=28772
157
+ AGENT_SQUAD_GATEWAY_WEB_UI_AUTH=token
158
+ ```
159
+
160
+ ## Managed State
161
+
162
+ Gateway state is stored under `~/.agent-squad/gateway/`:
163
+
164
+ | Path | Purpose |
165
+ | --- | --- |
166
+ | `config.json` | Address, port, and Web UI authentication mode |
167
+ | `gateway.db` | Targets, clients, encrypted credentials, grants, and run metadata |
168
+ | `master.key` | Encryption key for recoverable client API keys |
169
+ | `admin-secret` | Local admin bootstrap secret |
170
+ | `gateway.pid` / `gateway.log` | Background lifecycle state |
171
+ | `workspaces/` | Gateway-managed provider workspaces |
172
+
173
+ Client API keys are encrypted at rest but intentionally recoverable in the local admin UI. Protect
174
+ `gateway.db` and `master.key` together.
175
+
176
+ ## Security
177
+
178
+ The Gateway is local developer infrastructure, not an internet-facing service. Do not expose it
179
+ through a public router or reverse proxy. Review [SECURITY.md](SECURITY.md) before enabling LAN or
180
+ container access, using fixed workspaces, or sending sensitive content to provider CLIs.
181
+
182
+ ## Development
183
+
184
+ ```bash
185
+ npm ci
186
+ npm run build
187
+ npm test
188
+ ```
189
+
190
+ The default test suite uses fake providers. Real-provider tests are opt-in and may consume quota:
191
+
192
+ ```bash
193
+ GATEWAY_REAL_TARGETS=target_a,target_b npm run test:real-providers
194
+ ```
195
+
196
+ For UI development, run `npm run dev:server` and `npm run dev` in separate terminals.
197
+
198
+ ## License
199
+
200
+ [MIT](LICENSE)
package/SECURITY.md ADDED
@@ -0,0 +1,44 @@
1
+ # Security Policy
2
+
3
+ Agent Squad Gateway runs local coding CLIs and exposes a network API. Treat it as local developer
4
+ infrastructure, not as an internet-facing service.
5
+
6
+ ## Network Boundary
7
+
8
+ - The default `0.0.0.0` bind address supports local container access but also exposes the port to
9
+ reachable host networks. Use `127.0.0.1` when LAN or container access is unnecessary.
10
+ - Do not expose the Gateway through a public router, tunnel, or reverse proxy.
11
+ - Web UI authentication defaults to `disabled`. Use `web_ui_auth: "token"` on any untrusted
12
+ network. The `/v1` API always requires a Gateway client API key.
13
+ - CORS is disabled and is not a substitute for a firewall or authentication.
14
+
15
+ ## Credentials and State
16
+
17
+ - Client API keys are encrypted at rest but intentionally recoverable in the local Web UI.
18
+ - Protect `gateway.db` and `master.key` together. Disclosure of both exposes stored client keys;
19
+ losing `master.key` makes those keys unrecoverable.
20
+ - Never commit GitHub tokens, npm tokens, provider credentials, generated Gateway state, or local
21
+ `.npmrc` files.
22
+
23
+ ## Provider Boundaries
24
+
25
+ - Gateway SQLite persistence excludes prompts, completions, tool payloads, and raw provider events,
26
+ but provider CLIs may retain their own transcripts.
27
+ - Provider isolation, sandboxing, cancellation, and transcript suppression are best-effort CLI
28
+ integrations, not operating-system security boundaries.
29
+ - API clients own function execution. Provider-native tool activity is disabled where supported and
30
+ rejected by adapters where a CLI cannot disable tools completely.
31
+ - Antigravity currently receives prompt content as a command-line argument, which can be visible to
32
+ local process inspection or audit tooling.
33
+ - Fixed workspaces reduce isolation. Review the selected path and acknowledgement before enabling a
34
+ target.
35
+
36
+ ## Verification
37
+
38
+ Target verification sends bounded requests to the configured provider model and may consume quota.
39
+ Run it intentionally and review provider account limits before enabling automatic consumers.
40
+
41
+ ## Reporting Vulnerabilities
42
+
43
+ Report security issues privately to the repository owner. Do not include live credentials, private
44
+ repository content, prompts, or provider transcripts in public issues.
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env node
2
+ import { Command, Option } from 'commander';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { doctorGateway, openGateway, startGateway, statusGateway, stopGateway, } from '../lifecycle/daemon.js';
5
+ const defaultActions = {
6
+ start: startGateway,
7
+ stop: stopGateway,
8
+ status: statusGateway,
9
+ open: openGateway,
10
+ doctor: doctorGateway,
11
+ };
12
+ export function createGatewayProgram(actions = defaultActions) {
13
+ const program = new Command();
14
+ program
15
+ .name('agent-squad-gateway')
16
+ .description('Local Agent Squad Gateway')
17
+ .version('0.1.0');
18
+ addEndpointOptions(program.command('start')
19
+ .option('--foreground', 'run in the foreground'))
20
+ .addOption(new Option('--web-ui-auth <mode>', 'Web UI authentication mode')
21
+ .choices(['disabled', 'token'])
22
+ .env('AGENT_SQUAD_GATEWAY_WEB_UI_AUTH'))
23
+ .action(async (options) => {
24
+ await actions.start({
25
+ foreground: options.foreground,
26
+ address: options.address,
27
+ port: options.port,
28
+ webUiAuth: options.webUiAuth,
29
+ });
30
+ });
31
+ program.command('stop').action(async () => { await actions.stop(); });
32
+ addEndpointOptions(program.command('status')).action(async (options) => {
33
+ await actions.status(options);
34
+ });
35
+ addEndpointOptions(program.command('open')).action(async (options) => {
36
+ await actions.open(options);
37
+ });
38
+ program.command('doctor').action(async () => { await actions.doctor(); });
39
+ return program;
40
+ }
41
+ function addEndpointOptions(command) {
42
+ return command
43
+ .addOption(new Option('--address <address>', 'Gateway bind address')
44
+ .env('AGENT_SQUAD_GATEWAY_ADDRESS'))
45
+ .addOption(new Option('--port <port>', 'Gateway bind port')
46
+ .env('AGENT_SQUAD_GATEWAY_PORT')
47
+ .argParser(Number));
48
+ }
49
+ if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
50
+ void createGatewayProgram().parseAsync(process.argv).catch((error) => {
51
+ console.error(error);
52
+ process.exitCode = 1;
53
+ });
54
+ }
55
+ //# sourceMappingURL=agent-squad-gateway.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-squad-gateway.js","sourceRoot":"","sources":["../../src/bin/agent-squad-gateway.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,124 @@
1
+ import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { isIP } from 'node:net';
3
+ import { z } from 'zod';
4
+ import { resolveGatewayPaths } from './paths.js';
5
+ export const GATEWAY_BIND_HOST = '0.0.0.0';
6
+ export const GATEWAY_LOOPBACK_HOST = '127.0.0.1';
7
+ const HOSTNAME_PATTERN = /^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)*[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$/;
8
+ const gatewayAddressSchema = z.string()
9
+ .trim()
10
+ .min(1)
11
+ .max(253)
12
+ .refine((value) => isIP(value) !== 0 || HOSTNAME_PATTERN.test(value), 'invalid address')
13
+ .transform((value) => value.toLowerCase());
14
+ const gatewayConfigFileSchema = z.object({
15
+ address: gatewayAddressSchema,
16
+ port: z.number().int().min(1).max(65_535),
17
+ web_ui_auth: z.enum(['disabled', 'token']),
18
+ }).strict();
19
+ const runtimeGatewayConfigSchema = gatewayConfigFileSchema.extend({
20
+ port: z.number().int().min(0).max(65_535),
21
+ });
22
+ const DEFAULT_GATEWAY_CONFIG_FILE = {
23
+ address: GATEWAY_BIND_HOST,
24
+ port: 28_772,
25
+ web_ui_auth: 'disabled',
26
+ };
27
+ export function resolveGatewayConfig(input = {}) {
28
+ const fileConfig = parseRuntimeConfig({
29
+ address: input.address ?? DEFAULT_GATEWAY_CONFIG_FILE.address,
30
+ port: input.port ?? DEFAULT_GATEWAY_CONFIG_FILE.port,
31
+ web_ui_auth: input.webUiAuth ?? DEFAULT_GATEWAY_CONFIG_FILE.web_ui_auth,
32
+ }, 'Gateway configuration');
33
+ return {
34
+ host: fileConfig.address,
35
+ port: fileConfig.port,
36
+ coreUrl: (input.coreUrl ?? 'http://127.0.0.1:28771').replace(/\/$/, ''),
37
+ webUiAuth: fileConfig.web_ui_auth,
38
+ paths: resolveGatewayPaths(input.baseDir),
39
+ };
40
+ }
41
+ export function loadGatewayConfig(input = {}) {
42
+ const paths = resolveGatewayPaths(input.baseDir);
43
+ const fileConfig = readGatewayConfigFile(paths.configPath) ?? DEFAULT_GATEWAY_CONFIG_FILE;
44
+ const mergedConfig = parseConfigFile({
45
+ address: input.address ?? fileConfig.address,
46
+ port: input.port ?? fileConfig.port,
47
+ web_ui_auth: input.webUiAuth ?? fileConfig.web_ui_auth,
48
+ }, 'Gateway configuration');
49
+ return resolveGatewayConfig({
50
+ ...input,
51
+ address: mergedConfig.address,
52
+ port: mergedConfig.port,
53
+ webUiAuth: mergedConfig.web_ui_auth,
54
+ });
55
+ }
56
+ export function ensureGatewayConfigFile(baseDir) {
57
+ const paths = resolveGatewayPaths(baseDir);
58
+ mkdirSync(paths.stateDir, { recursive: true, mode: 0o700 });
59
+ try {
60
+ writeFileSync(paths.configPath, `${JSON.stringify(DEFAULT_GATEWAY_CONFIG_FILE, null, 2)}\n`, { encoding: 'utf8', flag: 'wx', mode: 0o600 });
61
+ }
62
+ catch (error) {
63
+ if (!isFileExistsError(error))
64
+ throw error;
65
+ }
66
+ return paths.configPath;
67
+ }
68
+ export function gatewayLoopbackOrigin(config) {
69
+ const host = config.host === GATEWAY_BIND_HOST
70
+ ? GATEWAY_LOOPBACK_HOST
71
+ : config.host === '::'
72
+ ? '::1'
73
+ : config.host;
74
+ return `http://${formatGatewayAddress(host, config.port)}`;
75
+ }
76
+ export function formatGatewayAddress(host, port) {
77
+ return `${host.includes(':') ? `[${host}]` : host}:${port}`;
78
+ }
79
+ export function gatewayBindPolicy(host) {
80
+ if (host === '0.0.0.0' || host === '::')
81
+ return 'all-interfaces';
82
+ if (host === '127.0.0.1' || host === '::1' || host === 'localhost')
83
+ return 'loopback';
84
+ return 'configured-address';
85
+ }
86
+ function readGatewayConfigFile(path) {
87
+ let content;
88
+ try {
89
+ content = readFileSync(path, 'utf8');
90
+ }
91
+ catch (error) {
92
+ if (isFileNotFoundError(error))
93
+ return null;
94
+ throw error;
95
+ }
96
+ try {
97
+ return parseConfigFile(JSON.parse(content), `Gateway config file ${path}`);
98
+ }
99
+ catch (error) {
100
+ throw new Error(`invalid Gateway config file: ${path}`, { cause: error });
101
+ }
102
+ }
103
+ function parseConfigFile(value, label) {
104
+ const result = gatewayConfigFileSchema.safeParse(value);
105
+ if (!result.success)
106
+ throw new Error(`invalid ${label}`, { cause: result.error });
107
+ return result.data;
108
+ }
109
+ function parseRuntimeConfig(value, label) {
110
+ const result = runtimeGatewayConfigSchema.safeParse(value);
111
+ if (!result.success)
112
+ throw new Error(`invalid ${label}`, { cause: result.error });
113
+ return result.data;
114
+ }
115
+ function isFileNotFoundError(error) {
116
+ return isNodeError(error) && error.code === 'ENOENT';
117
+ }
118
+ function isFileExistsError(error) {
119
+ return isNodeError(error) && error.code === 'EEXIST';
120
+ }
121
+ function isNodeError(error) {
122
+ return error instanceof Error && 'code' in error;
123
+ }
124
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/config/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import { homedir } from 'node:os';
2
+ import { join } from 'node:path';
3
+ export function resolveGatewayPaths(baseDir = homedir()) {
4
+ const stateDir = join(baseDir, '.agent-squad', 'gateway');
5
+ return {
6
+ stateDir,
7
+ configPath: join(stateDir, 'config.json'),
8
+ dbPath: join(stateDir, 'gateway.db'),
9
+ pidPath: join(stateDir, 'gateway.pid'),
10
+ logPath: join(stateDir, 'gateway.log'),
11
+ masterKeyPath: join(stateDir, 'master.key'),
12
+ adminSecretPath: join(stateDir, 'admin-secret'),
13
+ workspacesDir: join(stateDir, 'workspaces'),
14
+ };
15
+ }
16
+ //# sourceMappingURL=paths.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.js","sourceRoot":"","sources":["../../src/config/paths.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAajC,MAAM,UAAU,mBAAmB,CAAC,OAAO,GAAG,OAAO,EAAE;IACrD,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,EAAE,cAAc,EAAE,SAAS,CAAC,CAAC;IAC1D,OAAO;QACL,QAAQ;QACR,UAAU,EAAE,IAAI,CAAC,QAAQ,EAAE,aAAa,CAAC;QACzC,MAAM,EAAE,IAAI,CAAC,QAAQ,EAAE,YAAY,CAAC;QACpC,OAAO,EAAE,IAAI,CAAC,QAAQ,EAAE,aAAa,CAAC;QACtC,OAAO,EAAE,IAAI,CAAC,QAAQ,EAAE,aAAa,CAAC;QACtC,aAAa,EAAE,IAAI,CAAC,QAAQ,EAAE,YAAY,CAAC;QAC3C,eAAe,EAAE,IAAI,CAAC,QAAQ,EAAE,cAAc,CAAC;QAC/C,aAAa,EAAE,IAAI,CAAC,QAAQ,EAAE,YAAY,CAAC;KAC5C,CAAC;AACJ,CAAC"}
@@ -0,0 +1,77 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ function toClientRecord(row) {
3
+ return {
4
+ id: row.id,
5
+ name: row.name,
6
+ status: row.status,
7
+ createdAt: row.created_at,
8
+ updatedAt: row.updated_at,
9
+ };
10
+ }
11
+ export class ClientRepository {
12
+ db;
13
+ constructor(db) {
14
+ this.db = db;
15
+ }
16
+ create(name) {
17
+ const now = new Date().toISOString();
18
+ const client = {
19
+ id: `client_${randomUUID()}`,
20
+ name,
21
+ status: 'active',
22
+ createdAt: now,
23
+ updatedAt: now,
24
+ };
25
+ try {
26
+ this.db.prepare(`
27
+ INSERT INTO clients (id, name, status, created_at, updated_at)
28
+ VALUES (?, ?, ?, ?, ?)
29
+ `).run(client.id, client.name, client.status, client.createdAt, client.updatedAt);
30
+ }
31
+ catch (error) {
32
+ if (isUniqueNameError(error))
33
+ throw new Error('client_name_exists');
34
+ throw error;
35
+ }
36
+ return client;
37
+ }
38
+ list() {
39
+ return this.db.prepare(`
40
+ SELECT id, name, status, created_at, updated_at
41
+ FROM clients
42
+ ORDER BY created_at DESC, rowid DESC
43
+ `).all().map(toClientRecord);
44
+ }
45
+ setStatus(id, status) {
46
+ const result = this.db.prepare(`
47
+ UPDATE clients SET status = ?, updated_at = ? WHERE id = ?
48
+ `).run(status, new Date().toISOString(), id);
49
+ if (result.changes === 0)
50
+ throw new Error('client_not_found');
51
+ }
52
+ delete(id) {
53
+ const remove = this.db.transaction(() => {
54
+ const client = this.db.prepare('SELECT id FROM clients WHERE id = ?').get(id);
55
+ if (!client)
56
+ throw new Error('client_not_found');
57
+ const active = this.db.prepare(`
58
+ SELECT COUNT(*) AS count FROM runs
59
+ WHERE client_id = ? AND status IN ('queued', 'running')
60
+ `).get(id);
61
+ if ((active?.count ?? 0) > 0)
62
+ throw new Error('client_in_use');
63
+ this.db.prepare('UPDATE runs SET client_id = NULL WHERE client_id = ?').run(id);
64
+ const result = this.db.prepare('DELETE FROM clients WHERE id = ?').run(id);
65
+ if (result.changes !== 1)
66
+ throw new Error('client_not_found');
67
+ });
68
+ remove();
69
+ }
70
+ }
71
+ function isUniqueNameError(error) {
72
+ return typeof error === 'object'
73
+ && error !== null
74
+ && 'code' in error
75
+ && error.code === 'SQLITE_CONSTRAINT_UNIQUE';
76
+ }
77
+ //# sourceMappingURL=clients.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clients.js","sourceRoot":"","sources":["../../src/control-plane/clients.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,54 @@
1
+ import { parseCoreBaseUrl } from '../core-client/client.js';
2
+ export class CoreConnectionRepository {
3
+ db;
4
+ now;
5
+ constructor(db, now = () => new Date()) {
6
+ this.db = db;
7
+ this.now = now;
8
+ }
9
+ get() {
10
+ const row = this.db.prepare(`
11
+ SELECT base_url, last_status, last_version, last_checked_at
12
+ FROM core_connection WHERE singleton = 1
13
+ `).get();
14
+ if (!row)
15
+ throw new Error('core_connection_missing');
16
+ return {
17
+ baseUrl: row.base_url,
18
+ status: asStatus(row.last_status),
19
+ version: row.last_version,
20
+ lastCheckedAt: row.last_checked_at,
21
+ };
22
+ }
23
+ update(baseUrl) {
24
+ const normalized = parseCoreBaseUrl(baseUrl);
25
+ const current = this.get();
26
+ if (current.baseUrl === normalized)
27
+ return current;
28
+ this.db.prepare(`
29
+ UPDATE core_connection
30
+ SET base_url = ?, last_status = NULL, last_version = NULL, last_checked_at = NULL
31
+ WHERE singleton = 1
32
+ `).run(normalized);
33
+ return this.get();
34
+ }
35
+ markHealth(health) {
36
+ const status = health === undefined
37
+ ? 'offline'
38
+ : health.ok && health.db_ok ? 'online' : 'degraded';
39
+ const checkedAt = toIso(this.now());
40
+ this.db.prepare(`
41
+ UPDATE core_connection
42
+ SET last_status = ?, last_version = COALESCE(?, last_version), last_checked_at = ?
43
+ WHERE singleton = 1
44
+ `).run(status, health?.version ?? null, checkedAt);
45
+ return this.get();
46
+ }
47
+ }
48
+ function asStatus(value) {
49
+ return value === 'online' || value === 'degraded' || value === 'offline' ? value : 'unknown';
50
+ }
51
+ function toIso(value) {
52
+ return typeof value === 'string' ? value : value.toISOString();
53
+ }
54
+ //# sourceMappingURL=core-connection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"core-connection.js","sourceRoot":"","sources":["../../src/control-plane/core-connection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAoB5D,MAAM,OAAO,wBAAwB;IAEhB;IACA;IAFnB,YACmB,EAAa,EACb,MAA2B,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE;QAD3C,OAAE,GAAF,EAAE,CAAW;QACb,QAAG,GAAH,GAAG,CAAwC;IAC3D,CAAC;IAEJ,GAAG;QACD,MAAM,GAAG,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAAwB;;;KAGlD,CAAC,CAAC,GAAG,EAAE,CAAC;QACT,IAAI,CAAC,GAAG;YAAE,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC;QACrD,OAAO;YACL,OAAO,EAAE,GAAG,CAAC,QAAQ;YACrB,MAAM,EAAE,QAAQ,CAAC,GAAG,CAAC,WAAW,CAAC;YACjC,OAAO,EAAE,GAAG,CAAC,YAAY;YACzB,aAAa,EAAE,GAAG,CAAC,eAAe;SACnC,CAAC;IACJ,CAAC;IAED,MAAM,CAAC,OAAe;QACpB,MAAM,UAAU,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC;QAC7C,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAC3B,IAAI,OAAO,CAAC,OAAO,KAAK,UAAU;YAAE,OAAO,OAAO,CAAC;QACnD,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC;;;;KAIf,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QACnB,OAAO,IAAI,CAAC,GAAG,EAAE,CAAC;IACpB,CAAC;IAED,UAAU,CAAC,MAAmB;QAC5B,MAAM,MAAM,GAAyB,MAAM,KAAK,SAAS;YACvD,CAAC,CAAC,SAAS;YACX,CAAC,CAAC,MAAM,CAAC,EAAE,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC;QACtD,MAAM,SAAS,GAAG,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;QACpC,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC;;;;KAIf,CAAC,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,IAAI,IAAI,EAAE,SAAS,CAAC,CAAC;QACnD,OAAO,IAAI,CAAC,GAAG,EAAE,CAAC;IACpB,CAAC;CACF;AAED,SAAS,QAAQ,CAAC,KAAoB;IACpC,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,UAAU,IAAI,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;AAC/F,CAAC;AAED,SAAS,KAAK,CAAC,KAAoB;IACjC,OAAO,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC;AACjE,CAAC"}