antechamber 0.1.1 → 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/CHANGELOG.md +28 -0
- package/README.md +17 -14
- package/bin/opencode-antechamber +10 -1
- package/bin/opencode-pi +11 -1
- package/dist/approvals.d.ts +4 -1
- package/dist/approvals.js +51 -33
- package/dist/catalog.d.ts +1 -1
- package/dist/catalog.js +1 -1
- package/dist/commands/context.d.ts +0 -1
- package/dist/commands/context.js +1 -1
- package/dist/commands/doctor.d.ts +3 -2
- package/dist/commands/serve.d.ts +2 -1
- package/dist/commands/setup.d.ts +2 -3
- package/dist/commands/setup.js +16 -4
- package/dist/http.d.ts +8 -1
- package/dist/http.js +42 -19
- package/dist/pi-event-translation.d.ts +9 -5
- package/dist/pi-event-translation.js +233 -79
- package/dist/pi-rpc.d.ts +20 -2
- package/dist/pi-rpc.js +96 -29
- package/dist/prompt-parts.d.ts +40 -0
- package/dist/prompt-parts.js +157 -0
- package/dist/route-services.d.ts +45 -2
- package/dist/route-services.js +70 -3
- package/dist/routes-v1.js +110 -65
- package/dist/routes-v2.js +376 -73
- package/dist/server.d.ts +9 -1
- package/dist/server.js +109 -9
- package/dist/session-commands.d.ts +12 -15
- package/dist/session-commands.js +52 -11
- package/dist/session-diff.d.ts +1 -1
- package/dist/session-diff.js +10 -2
- package/dist/session-history.js +108 -42
- package/dist/session-hosts.js +2 -2
- package/dist/session-store.d.ts +8 -4
- package/dist/session-store.js +183 -33
- package/dist/session-tree.d.ts +9 -1
- package/dist/session-tree.js +51 -26
- package/dist/sessions.d.ts +32 -5
- package/dist/sessions.js +330 -119
- package/dist/sse.d.ts +16 -6
- package/dist/sse.js +77 -21
- package/dist/subagents.d.ts +5 -0
- package/dist/subagents.js +84 -71
- package/dist/tool-map.d.ts +3 -2
- package/dist/types.d.ts +152 -1
- package/dist/v2.d.ts +18 -4
- package/dist/v2.js +128 -44
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/extensions/omo-dag/README.md +2 -0
- package/extensions/omo-dag/docs/DESIGN.md +10 -1
- package/extensions/omo-dag/panel/index.html +1 -1
- package/extensions/omo-dag/panel/main.js +60 -60
- package/extensions/omo-dag/service/main.js +72 -8
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,34 @@ and this project uses [Semantic Versioning](https://semver.org/).
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.2.0] - 2026-10-14
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- feat(api): add v2 credential, integration, file-search, VCS, and synthetic-context routes
|
|
15
|
+
- feat(events): replay retained SSE events and expose v2 shell, compaction, and execution terminals
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- fix(session): preserve rich prompt parts, metadata, ancestry, and part identities across reloads
|
|
20
|
+
- fix(runtime): retain operation ordering after caller cancellation and finalize interrupted tool cards
|
|
21
|
+
- fix(rpc): close failed transports and reject responses correlated to the wrong command
|
|
22
|
+
- fix(http): allow passwordless loopback origins across ports while retaining explicit origin checks
|
|
23
|
+
- fix(security): validate registry records and write registry state with private permissions
|
|
24
|
+
- fix(cli): advertise OpenCode API compatibility as 2.0.20
|
|
25
|
+
- fix(api): return OpenCode 2.x bootstrap shapes for `/api/config`, `/api/shell`, `/api/websearch/provider` and `GET /api/mcp` so OpenChamber 2.3.2 can initialize workspaces
|
|
26
|
+
- fix(api): answer v2 synthetic context admission with the admitted record and return the bare interrupt flag, so OpenChamber 2.3.2 can send prompts and stop turns
|
|
27
|
+
- fix(extension): bound graph responses and display omitted-node counts
|
|
28
|
+
- fix(api): carry v2 prompt attachments, agent mentions and metadata into OmO's input, admission events and message history, and answer skill references with OpenCode's tagged not-found error
|
|
29
|
+
- fix(session): admit synthetic context and v1 `noReply` messages into OmO's conversation without a turn, keep them after restarts even when no prompt follows, and stop context from titling sessions
|
|
30
|
+
- fix(events): keep an unscoped v2 `/api/event` stream global, as OpenChamber 2.3.2 subscribes
|
|
31
|
+
- fix(rpc): after the agent exits during shutdown, kill only re-identified members of its process group
|
|
32
|
+
|
|
33
|
+
### Changed
|
|
34
|
+
|
|
35
|
+
- docs(api): document route statuses, response envelopes, stream framing, and unsupported capabilities
|
|
36
|
+
- docs(maintainers): correct smoke paths, typecheck commands, and release guidance
|
|
37
|
+
|
|
10
38
|
## [0.1.1] - 2026-10-14
|
|
11
39
|
|
|
12
40
|
### Added
|
package/README.md
CHANGED
|
@@ -55,15 +55,15 @@ another executable (see [docs/configuration.md](docs/configuration.md)).
|
|
|
55
55
|
|
|
56
56
|
| OpenChamber | API | Status |
|
|
57
57
|
|---|---|---|
|
|
58
|
-
| >= 2.0.4 | v2 (`@opencode-ai/sdk/v2`) | primary target; antechamber advertises `opencode v2.0.
|
|
58
|
+
| >= 2.0.4 | v2 (`@opencode-ai/sdk/v2`) | primary target; antechamber advertises `opencode v2.0.20` on `--version` |
|
|
59
59
|
| 1.24.x | v1 | supported |
|
|
60
60
|
| other versions | - | not tested |
|
|
61
61
|
|
|
62
62
|
OpenChamber changes which OpenCode API calls it makes between releases. If a
|
|
63
63
|
feature breaks on a newer OpenChamber, run `npm run smoke` to see which check
|
|
64
64
|
failed, then open an issue with the output. See
|
|
65
|
-
[docs/api-surface.md](docs/api-surface.md) for the
|
|
66
|
-
|
|
65
|
+
[docs/api-surface.md](docs/api-surface.md) for the maintained route, response,
|
|
66
|
+
and event-stream inventory.
|
|
67
67
|
|
|
68
68
|
## Quick start
|
|
69
69
|
|
|
@@ -76,10 +76,6 @@ antechamber doctor
|
|
|
76
76
|
antechamber setup
|
|
77
77
|
```
|
|
78
78
|
|
|
79
|
-
The first npm publication (0.1.0) is part of the
|
|
80
|
-
[release process](docs/releasing.md). If it is not on the registry yet, use
|
|
81
|
-
[From source](#from-source) below.
|
|
82
|
-
|
|
83
79
|
`setup` updates OpenChamber's `opencodeBinary`, preserves all other settings,
|
|
84
80
|
and backs up an existing settings file before changing it. Restart OpenChamber
|
|
85
81
|
after setup. Preview changes with `antechamber setup --dry-run`.
|
|
@@ -137,7 +133,7 @@ antechamber serve --hostname 127.0.0.1 --port 4096
|
|
|
137
133
|
| `antechamber serve` | OpenCode-compatible server; `--hostname`/`--host`, `--port`, `--directory`. Bare `antechamber` also starts the server. |
|
|
138
134
|
| `antechamber setup` | Configure OpenChamber; `--settings <file>`, `--profile <dir>`, `--binary <path>`, `--dry-run`, `--systemd [unit]`, `--pi-binary <path>`, `--restart`. |
|
|
139
135
|
| `antechamber doctor` | Read-only Node, backend, agent/state directories, teardown, OpenChamber and auth checks; `--json`, `--settings <file>`, `--profile <dir>`. Exits 1 on any FAIL; warnings do not fail. |
|
|
140
|
-
| `antechamber version` | Package version plus OpenCode API contract version. `--version`/`-v` deliberately print exactly `opencode v2.0.
|
|
136
|
+
| `antechamber version` | Package version plus OpenCode API contract version. `--version`/`-v` deliberately print exactly `opencode v2.0.20` for OpenChamber. |
|
|
141
137
|
| `antechamber help` | Usage and options; also `--help`/`-h`. |
|
|
142
138
|
|
|
143
139
|
## Upgrade, uninstall and rollback
|
|
@@ -218,9 +214,11 @@ extension, and refuses it as an invalid manifest. Details:
|
|
|
218
214
|
|
|
219
215
|
## Configuration
|
|
220
216
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
217
|
+
Connector settings use the `ANTECHAMBER_` prefix; the deprecated `OCPI_`
|
|
218
|
+
prefix is still accepted as a fallback. RPC capability passthrough also reads
|
|
219
|
+
`OMO_RPC_CLIENT_CAPABILITIES` and `SENPI_RPC_CLIENT_CAPABILITIES` directly.
|
|
220
|
+
Full reference, defaults, and units:
|
|
221
|
+
[docs/configuration.md](docs/configuration.md).
|
|
224
222
|
|
|
225
223
|
| Variable | Purpose |
|
|
226
224
|
|---|---|
|
|
@@ -231,6 +229,7 @@ deployments. Full reference, defaults, and units: [docs/configuration.md](docs/c
|
|
|
231
229
|
| `ANTECHAMBER_IDLE_TIMEOUT_MS` | idle session reap timeout |
|
|
232
230
|
| `ANTECHAMBER_MAX_LIVE` | cap on concurrent live agent processes |
|
|
233
231
|
| `ANTECHAMBER_DEBUG` / `ANTECHAMBER_DEBUG_LOG` | request/event debug logging |
|
|
232
|
+
| `OMO_RPC_CLIENT_CAPABILITIES` / `SENPI_RPC_CLIENT_CAPABILITIES` | inherited RPC capabilities merged for the agent process |
|
|
234
233
|
|
|
235
234
|
## Security
|
|
236
235
|
|
|
@@ -244,8 +243,11 @@ Hardening controls (full reference in [docs/configuration.md](docs/configuration
|
|
|
244
243
|
- Optional HTTP Basic auth, matching OpenCode's own contract:
|
|
245
244
|
`OPENCODE_SERVER_PASSWORD` (plus `OPENCODE_SERVER_USERNAME`, default
|
|
246
245
|
`opencode`).
|
|
247
|
-
- Without a password,
|
|
248
|
-
|
|
246
|
+
- Without a password, browser origins are limited to the adapter's own origin,
|
|
247
|
+
HTTP(S) loopback origins (including a different loopback port), and origins
|
|
248
|
+
listed in `ANTECHAMBER_ALLOWED_ORIGINS`; other origins get `403`. With a
|
|
249
|
+
password, other origins may make authenticated requests but receive no CORS
|
|
250
|
+
headers unless explicitly allowed.
|
|
249
251
|
- Binding to a non-loopback host requires a password, `ANTECHAMBER_ALLOW_INSECURE=1`,
|
|
250
252
|
or the `--insecure-no-auth` flag.
|
|
251
253
|
- Request bodies are capped by `ANTECHAMBER_MAX_BODY_BYTES` (default 10 MiB);
|
|
@@ -268,7 +270,8 @@ of crashing; antechamber never falls back to signalling numeric PIDs. macOS is u
|
|
|
268
270
|
level, not the model level.
|
|
269
271
|
- Some OpenCode routes have no pi/OmO equivalent and are stubbed or return
|
|
270
272
|
`400`/`501` (OAuth-based provider auth, MCP connect/disconnect, session
|
|
271
|
-
share). See [docs/api-surface.md](docs/api-surface.md) for the
|
|
273
|
+
share). See [docs/api-surface.md](docs/api-surface.md) for the maintained
|
|
274
|
+
route and unsupported-response inventory.
|
|
272
275
|
|
|
273
276
|
## Development
|
|
274
277
|
|
package/bin/opencode-antechamber
CHANGED
|
@@ -4,7 +4,16 @@
|
|
|
4
4
|
# `<binary> --version`; anything else is rejected.
|
|
5
5
|
# Requires a build (`npm run build`) so that dist/cli.js exists.
|
|
6
6
|
set -eu
|
|
7
|
-
|
|
7
|
+
SCRIPT_PATH=$0
|
|
8
|
+
while [ -h "$SCRIPT_PATH" ]; do
|
|
9
|
+
SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$SCRIPT_PATH")" && pwd)"
|
|
10
|
+
LINK="$(readlink "$SCRIPT_PATH")"
|
|
11
|
+
case "$LINK" in
|
|
12
|
+
/*) SCRIPT_PATH=$LINK ;;
|
|
13
|
+
*) SCRIPT_PATH="$SCRIPT_DIR/$LINK" ;;
|
|
14
|
+
esac
|
|
15
|
+
done
|
|
16
|
+
SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$SCRIPT_PATH")" && pwd)"
|
|
8
17
|
CLI="$SCRIPT_DIR/../dist/cli.js"
|
|
9
18
|
if [ ! -f "$CLI" ]; then
|
|
10
19
|
echo "opencode-antechamber: $CLI not found; run 'npm run build' first" >&2
|
package/bin/opencode-pi
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
1
|
#!/bin/sh
|
|
2
2
|
# Deprecated name kept for existing OpenChamber settings; use opencode-antechamber.
|
|
3
|
-
|
|
3
|
+
SCRIPT_PATH=$0
|
|
4
|
+
while [ -h "$SCRIPT_PATH" ]; do
|
|
5
|
+
SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$SCRIPT_PATH")" && pwd)"
|
|
6
|
+
LINK="$(readlink "$SCRIPT_PATH")"
|
|
7
|
+
case "$LINK" in
|
|
8
|
+
/*) SCRIPT_PATH=$LINK ;;
|
|
9
|
+
*) SCRIPT_PATH="$SCRIPT_DIR/$LINK" ;;
|
|
10
|
+
esac
|
|
11
|
+
done
|
|
12
|
+
SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$SCRIPT_PATH")" && pwd)"
|
|
13
|
+
exec "$SCRIPT_DIR/opencode-antechamber" "$@"
|
package/dist/approvals.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ export type ApprovalEvent = WithoutEventID<EventPermissionAsked | EventPermissio
|
|
|
12
12
|
formAnswer?: Record<string, string | string[]>;
|
|
13
13
|
};
|
|
14
14
|
};
|
|
15
|
-
|
|
15
|
+
type PermissionReply = "once" | "always" | "reject";
|
|
16
16
|
export type PendingPermission = PermissionRequest;
|
|
17
17
|
export type PendingQuestion = QuestionRequest & {
|
|
18
18
|
questionIDs?: string[];
|
|
@@ -75,10 +75,13 @@ export declare class ApprovalBridge {
|
|
|
75
75
|
observeQuestion(ev: Record<string, unknown>): void;
|
|
76
76
|
observe(context: Context, ev: Record<string, unknown>): void;
|
|
77
77
|
private ask;
|
|
78
|
+
private askStructuredQuestion;
|
|
79
|
+
private askLegacyDialog;
|
|
78
80
|
replyPermission(id: string, reply: PermissionReply, message?: string, sessionID?: string): boolean;
|
|
79
81
|
replyQuestion(id: string, answers: string[][], sessionID?: string, comment?: string): boolean | Promise<boolean>;
|
|
80
82
|
rejectQuestion(id: string, sessionID?: string): boolean | Promise<boolean>;
|
|
81
83
|
private respondStructured;
|
|
84
|
+
private rememberSettled;
|
|
82
85
|
private follow;
|
|
83
86
|
private remove;
|
|
84
87
|
clear(sessionID: string, cancel: boolean, callID?: string): void;
|
package/dist/approvals.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
export class ApprovalInputError extends Error {
|
|
3
3
|
}
|
|
4
|
+
const MAX_SETTLED_STRUCTURED_QUESTIONS = 256;
|
|
4
5
|
function record(value) {
|
|
5
6
|
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
6
7
|
? value
|
|
@@ -169,7 +170,7 @@ export class ApprovalBridge {
|
|
|
169
170
|
const p = [...this.pending.values()].find((p) => p.request.method === "question" &&
|
|
170
171
|
(p.request.id === ev.id || (typeof ev.requestId === "string" && p.request.requestId === ev.requestId)));
|
|
171
172
|
if (ev.type === "question_resolved" && typeof ev.requestId === "string")
|
|
172
|
-
this.
|
|
173
|
+
this.rememberSettled(ev.requestId);
|
|
173
174
|
if (!p || p.kind !== "question")
|
|
174
175
|
return;
|
|
175
176
|
if (ev.type === "question_updated") {
|
|
@@ -181,7 +182,7 @@ export class ApprovalBridge {
|
|
|
181
182
|
p.card.metadata = { ...p.card.metadata, deadlineAtMs: p.request.deadlineAtMs, draft: p.request.draft };
|
|
182
183
|
return;
|
|
183
184
|
}
|
|
184
|
-
this.
|
|
185
|
+
this.rememberSettled(p.request.requestId);
|
|
185
186
|
if (ev.outcome !== "answered" && ev.outcome !== "comment-submitted") {
|
|
186
187
|
this.remove(p, true);
|
|
187
188
|
return;
|
|
@@ -264,41 +265,47 @@ export class ApprovalBridge {
|
|
|
264
265
|
if (!request)
|
|
265
266
|
return;
|
|
266
267
|
if (request.method === "question") {
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
}
|
|
285
|
-
const card = {
|
|
286
|
-
id: publicID,
|
|
287
|
-
sessionID: context.sessionID,
|
|
288
|
-
questions: (request.questions ?? []).map(({ id: _id, ...q }) => q),
|
|
289
|
-
questionIDs: (request.questions ?? []).map((q) => q.id),
|
|
290
|
-
metadata: {
|
|
268
|
+
this.askStructuredQuestion(context, request);
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
this.askLegacyDialog(context, request);
|
|
272
|
+
}
|
|
273
|
+
askStructuredQuestion(context, request) {
|
|
274
|
+
if (this.settled.has(request.requestId ?? ""))
|
|
275
|
+
return;
|
|
276
|
+
const publicID = `que_${createHash("sha256")
|
|
277
|
+
.update(JSON.stringify([context.sessionID, request.requestId]))
|
|
278
|
+
.digest("hex")
|
|
279
|
+
.slice(0, 32)}`;
|
|
280
|
+
const existing = this.pending.get(publicID);
|
|
281
|
+
if (existing) {
|
|
282
|
+
existing.request = request;
|
|
283
|
+
if (existing.kind === "question")
|
|
284
|
+
existing.card.metadata = {
|
|
291
285
|
requestId: request.requestId,
|
|
292
286
|
waitForAnswer: request.waitForAnswer,
|
|
293
287
|
deadlineAtMs: request.deadlineAtMs,
|
|
294
288
|
draft: request.draft,
|
|
295
|
-
}
|
|
296
|
-
tool: { messageID: context.messageID, callID: request.requestId ?? "" },
|
|
297
|
-
};
|
|
298
|
-
this.pending.set(publicID, { kind: "question", card, context, request });
|
|
299
|
-
this.emit(context.directory, { type: "question.asked", properties: card });
|
|
289
|
+
};
|
|
300
290
|
return;
|
|
301
291
|
}
|
|
292
|
+
const card = {
|
|
293
|
+
id: publicID,
|
|
294
|
+
sessionID: context.sessionID,
|
|
295
|
+
questions: (request.questions ?? []).map(({ id: _id, ...q }) => q),
|
|
296
|
+
questionIDs: (request.questions ?? []).map((q) => q.id),
|
|
297
|
+
metadata: {
|
|
298
|
+
requestId: request.requestId,
|
|
299
|
+
waitForAnswer: request.waitForAnswer,
|
|
300
|
+
deadlineAtMs: request.deadlineAtMs,
|
|
301
|
+
draft: request.draft,
|
|
302
|
+
},
|
|
303
|
+
tool: { messageID: context.messageID, callID: request.requestId ?? "" },
|
|
304
|
+
};
|
|
305
|
+
this.pending.set(publicID, { kind: "question", card, context, request });
|
|
306
|
+
this.emit(context.directory, { type: "question.asked", properties: card });
|
|
307
|
+
}
|
|
308
|
+
askLegacyDialog(context, request) {
|
|
302
309
|
if (this.pending.has(request.id))
|
|
303
310
|
return;
|
|
304
311
|
const calls = this.questionCalls.get(context.sessionID);
|
|
@@ -470,6 +477,17 @@ export class ApprovalBridge {
|
|
|
470
477
|
p.replying = false;
|
|
471
478
|
}
|
|
472
479
|
}
|
|
480
|
+
rememberSettled(requestID) {
|
|
481
|
+
if (!requestID)
|
|
482
|
+
return;
|
|
483
|
+
this.settled.delete(requestID);
|
|
484
|
+
this.settled.add(requestID);
|
|
485
|
+
if (this.settled.size <= MAX_SETTLED_STRUCTURED_QUESTIONS)
|
|
486
|
+
return;
|
|
487
|
+
const oldest = this.settled.values().next().value;
|
|
488
|
+
if (oldest !== undefined)
|
|
489
|
+
this.settled.delete(oldest);
|
|
490
|
+
}
|
|
473
491
|
follow(p, value, kind) {
|
|
474
492
|
const callID = p.request.toolCallId;
|
|
475
493
|
if (callID === undefined)
|
|
@@ -502,8 +520,8 @@ export class ApprovalBridge {
|
|
|
502
520
|
continue;
|
|
503
521
|
if (p.request.method === "question" && !cancel)
|
|
504
522
|
continue;
|
|
505
|
-
if (cancel
|
|
506
|
-
this.
|
|
523
|
+
if (cancel)
|
|
524
|
+
this.rememberSettled(p.request.requestId);
|
|
507
525
|
if (cancel)
|
|
508
526
|
p.context.send({ type: "extension_ui_response", id: p.request.id, cancelled: true });
|
|
509
527
|
this.remove(p, true);
|
package/dist/catalog.d.ts
CHANGED
package/dist/catalog.js
CHANGED
|
@@ -45,7 +45,7 @@ function toOCModel(m) {
|
|
|
45
45
|
};
|
|
46
46
|
}
|
|
47
47
|
export async function loadCatalog(directory) {
|
|
48
|
-
const pi = new PiRpcClient({ cwd: directory });
|
|
48
|
+
const pi = new PiRpcClient({ cwd: directory, args: ["--no-session"] });
|
|
49
49
|
try {
|
|
50
50
|
const [modelsRes, firstState] = await Promise.all([
|
|
51
51
|
pi.command({ type: "get_available_models" }),
|
|
@@ -13,7 +13,6 @@ export interface CommandContext {
|
|
|
13
13
|
readonly launcherPath: string;
|
|
14
14
|
}
|
|
15
15
|
export declare function createContext(overrides?: Partial<CommandContext>): CommandContext;
|
|
16
|
-
export declare const packageCliPath: string;
|
|
17
16
|
/** Resolve executables without shell evaluation, respecting the supplied PATH. */
|
|
18
17
|
export declare function resolveBinary(binary: string, ctx: CommandContext): string | undefined;
|
|
19
18
|
export interface SettingsOptions {
|
package/dist/commands/context.js
CHANGED
|
@@ -35,7 +35,7 @@ export function createContext(overrides = {}) {
|
|
|
35
35
|
...overrides,
|
|
36
36
|
};
|
|
37
37
|
}
|
|
38
|
-
|
|
38
|
+
const packageCliPath = fileURLToPath(new URL("../../dist/cli.js", import.meta.url));
|
|
39
39
|
/** Resolve executables without shell evaluation, respecting the supplied PATH. */
|
|
40
40
|
export function resolveBinary(binary, ctx) {
|
|
41
41
|
const candidates = isAbsolute(binary) || binary.includes("/") || binary.includes("\\")
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { type CommandContext, type SettingsOptions } from "./context.js";
|
|
2
|
-
|
|
2
|
+
interface DoctorCheck {
|
|
3
3
|
readonly name: string;
|
|
4
4
|
readonly status: "PASS" | "WARN" | "FAIL" | "INFO";
|
|
5
5
|
readonly message: string;
|
|
6
6
|
}
|
|
7
|
-
|
|
7
|
+
interface DoctorReport {
|
|
8
8
|
readonly checks: readonly DoctorCheck[];
|
|
9
9
|
readonly exitCode: 0 | 1;
|
|
10
10
|
}
|
|
11
11
|
/** Checks capabilities with access/stat only, never creating state or config files. */
|
|
12
12
|
export declare function doctor(options?: SettingsOptions, ctx?: CommandContext): DoctorReport;
|
|
13
13
|
export declare function formatDoctor(report: DoctorReport, json: boolean): string;
|
|
14
|
+
export {};
|
package/dist/commands/serve.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
interface ServeOptions {
|
|
2
2
|
readonly port: number;
|
|
3
3
|
readonly host: string;
|
|
4
4
|
readonly directory: string;
|
|
@@ -6,3 +6,4 @@ export interface ServeOptions {
|
|
|
6
6
|
readonly compatible: boolean;
|
|
7
7
|
}
|
|
8
8
|
export declare function serve(options: ServeOptions): Promise<void>;
|
|
9
|
+
export {};
|
package/dist/commands/setup.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type CommandContext, type SettingsOptions } from "./context.js";
|
|
2
|
-
|
|
2
|
+
interface SetupOptions extends SettingsOptions {
|
|
3
3
|
readonly binary?: string;
|
|
4
4
|
readonly dryRun?: boolean;
|
|
5
5
|
readonly systemd?: string;
|
|
@@ -8,7 +8,6 @@ export interface SetupOptions extends SettingsOptions {
|
|
|
8
8
|
/** User-unit root, overridable without touching the real systemd configuration. */
|
|
9
9
|
readonly systemdDir?: string;
|
|
10
10
|
}
|
|
11
|
-
/** The bundled OmO Graph extension folder, when this installation has its built files. */
|
|
12
|
-
export declare function omoGraphExtensionDir(ctx: CommandContext): string | undefined;
|
|
13
11
|
/** Plan all inputs before applying changes; dependencies are injected for hermetic tests. */
|
|
14
12
|
export declare function setup(options: SetupOptions, ctx?: CommandContext): string[];
|
|
13
|
+
export {};
|
package/dist/commands/setup.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
1
2
|
import { dirname, join, resolve } from "node:path";
|
|
2
3
|
import { createContext, isPackageBinary, readSettings, resolveBinary, settingsPath, shellQuote, } from "./context.js";
|
|
3
4
|
function systemdValue(value) {
|
|
@@ -5,8 +6,13 @@ function systemdValue(value) {
|
|
|
5
6
|
throw new Error("systemd environment values cannot contain newlines or NUL");
|
|
6
7
|
return value.replaceAll("\\", "\\\\").replaceAll('"', '\\"').replaceAll("%", "%%");
|
|
7
8
|
}
|
|
9
|
+
function writeAtomically(path, content, ctx) {
|
|
10
|
+
const temporary = join(dirname(path), `.antechamber-${randomUUID()}.tmp`);
|
|
11
|
+
ctx.fs.writeFileSync(temporary, content, { mode: 0o600, flag: "wx" });
|
|
12
|
+
ctx.fs.renameSync(temporary, path);
|
|
13
|
+
}
|
|
8
14
|
/** The bundled OmO Graph extension folder, when this installation has its built files. */
|
|
9
|
-
|
|
15
|
+
function omoGraphExtensionDir(ctx) {
|
|
10
16
|
const dir = resolve(dirname(ctx.cliPath), "..", "extensions", "omo-dag");
|
|
11
17
|
const built = ["package.json", "panel/main.js", "service/main.js"].every((file) => ctx.fs.existsSync(join(dir, file)));
|
|
12
18
|
return built ? dir : undefined;
|
|
@@ -75,8 +81,14 @@ export function setup(options, ctx = createContext()) {
|
|
|
75
81
|
const stamp = ctx.now().toISOString().replace(/\D/g, "").slice(0, 14);
|
|
76
82
|
const backup = (path) => {
|
|
77
83
|
const target = `${path}.bak-antechamber-${stamp}`;
|
|
78
|
-
if (options.dryRun)
|
|
84
|
+
if (options.dryRun) {
|
|
85
|
+
if (ctx.fs.existsSync(target)) {
|
|
86
|
+
throw Object.assign(new Error(`EEXIST: file already exists, copyfile '${path}' -> '${target}'`), {
|
|
87
|
+
code: "EEXIST",
|
|
88
|
+
});
|
|
89
|
+
}
|
|
79
90
|
report(`Would back up ${path} to ${target}`);
|
|
91
|
+
}
|
|
80
92
|
else {
|
|
81
93
|
ctx.fs.copyFileSync(path, target, ctx.fs.constants.COPYFILE_EXCL);
|
|
82
94
|
report(`Backed up ${path} to ${target}`);
|
|
@@ -98,7 +110,7 @@ export function setup(options, ctx = createContext()) {
|
|
|
98
110
|
backup(settings);
|
|
99
111
|
if (!options.dryRun) {
|
|
100
112
|
ctx.fs.mkdirSync(dirname(settings), { recursive: true });
|
|
101
|
-
|
|
113
|
+
writeAtomically(settings, updated, ctx);
|
|
102
114
|
report(`Wrote ${settings}`);
|
|
103
115
|
}
|
|
104
116
|
}
|
|
@@ -113,7 +125,7 @@ export function setup(options, ctx = createContext()) {
|
|
|
113
125
|
backup(dropIn);
|
|
114
126
|
if (!options.dryRun) {
|
|
115
127
|
ctx.fs.mkdirSync(dirname(dropIn), { recursive: true });
|
|
116
|
-
|
|
128
|
+
writeAtomically(dropIn, dropInText, ctx);
|
|
117
129
|
report(`Wrote ${dropIn}`);
|
|
118
130
|
}
|
|
119
131
|
}
|
package/dist/http.d.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
/** Shared HTTP body, response, authentication, and CORS plumbing. */
|
|
2
2
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
3
|
+
export declare class RequestBodyError extends Error {
|
|
4
|
+
readonly status: 400 | 413;
|
|
5
|
+
constructor(status: 400 | 413, message: string);
|
|
6
|
+
}
|
|
3
7
|
export declare function serverPassword(): string | undefined;
|
|
4
8
|
export declare function isLoopbackHost(host: string): boolean;
|
|
5
9
|
export declare function errorBody(name: string, message: string): {
|
|
@@ -10,9 +14,12 @@ export declare function errorBody(name: string, message: string): {
|
|
|
10
14
|
};
|
|
11
15
|
export declare function json(res: ServerResponse, status: number, body: unknown): void;
|
|
12
16
|
export declare class Http {
|
|
17
|
+
private serverOrigin?;
|
|
13
18
|
private readonly credentials;
|
|
14
19
|
private readonly maxBodyBytes;
|
|
15
|
-
constructor();
|
|
20
|
+
constructor(serverOrigin?: (() => string) | undefined);
|
|
21
|
+
allowsUpgrade(req: IncomingMessage): boolean;
|
|
16
22
|
route(req: IncomingMessage, res: ServerResponse, handle: () => Promise<void>): Promise<void>;
|
|
17
23
|
body(req: IncomingMessage): Promise<unknown>;
|
|
24
|
+
private authorized;
|
|
18
25
|
}
|
package/dist/http.js
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
import { createHash, timingSafeEqual } from "node:crypto";
|
|
3
3
|
import { ApprovalInputError } from "./approvals.js";
|
|
4
4
|
import { envSetting } from "./config.js";
|
|
5
|
+
import { UpstreamProtocolError } from "./pi-rpc.js";
|
|
5
6
|
const DEFAULT_MAX_BODY_BYTES = 10 * 1024 * 1024;
|
|
6
|
-
class RequestBodyError extends Error {
|
|
7
|
+
export class RequestBodyError extends Error {
|
|
7
8
|
status;
|
|
8
9
|
constructor(status, message) {
|
|
9
10
|
super(message);
|
|
@@ -19,7 +20,7 @@ export function isLoopbackHost(host) {
|
|
|
19
20
|
return true;
|
|
20
21
|
return /^127(?:\.\d{1,3}){3}$/.test(host) && host.split(".").every((part) => Number(part) <= 255);
|
|
21
22
|
}
|
|
22
|
-
function allowedOrigin(origin) {
|
|
23
|
+
function allowedOrigin(origin, serverOrigin) {
|
|
23
24
|
try {
|
|
24
25
|
const url = new URL(origin);
|
|
25
26
|
if (!/^https?:\/\/[^/?#]+$/.test(origin) ||
|
|
@@ -29,7 +30,8 @@ function allowedOrigin(origin) {
|
|
|
29
30
|
url.search !== "" ||
|
|
30
31
|
url.hash !== "")
|
|
31
32
|
return false;
|
|
32
|
-
return (
|
|
33
|
+
return (origin === serverOrigin ||
|
|
34
|
+
isLoopbackHost(url.hostname) ||
|
|
33
35
|
(envSetting("ALLOWED_ORIGINS") ?? "").split(",").some((entry) => entry.trim() === origin));
|
|
34
36
|
}
|
|
35
37
|
catch {
|
|
@@ -44,9 +46,11 @@ export function json(res, status, body) {
|
|
|
44
46
|
res.writeHead(status, { "Content-Type": "application/json" }).end(payload);
|
|
45
47
|
}
|
|
46
48
|
export class Http {
|
|
49
|
+
serverOrigin;
|
|
47
50
|
credentials;
|
|
48
51
|
maxBodyBytes;
|
|
49
|
-
constructor() {
|
|
52
|
+
constructor(serverOrigin) {
|
|
53
|
+
this.serverOrigin = serverOrigin;
|
|
50
54
|
const maxBodyBytes = Number(envSetting("MAX_BODY_BYTES"));
|
|
51
55
|
this.maxBodyBytes = Number.isSafeInteger(maxBodyBytes) && maxBodyBytes > 0 ? maxBodyBytes : DEFAULT_MAX_BODY_BYTES;
|
|
52
56
|
const password = serverPassword();
|
|
@@ -57,11 +61,17 @@ export class Http {
|
|
|
57
61
|
.update(`${process.env.OPENCODE_SERVER_USERNAME || "opencode"}:${password}`)
|
|
58
62
|
.digest();
|
|
59
63
|
}
|
|
64
|
+
allowsUpgrade(req) {
|
|
65
|
+
const origin = req.headers.origin;
|
|
66
|
+
if (origin !== undefined && !allowedOrigin(origin, this.serverOrigin?.()) && this.credentials === undefined)
|
|
67
|
+
return false;
|
|
68
|
+
return this.authorized(req);
|
|
69
|
+
}
|
|
60
70
|
async route(req, res, handle) {
|
|
61
71
|
res.setHeader("Vary", "Origin");
|
|
62
72
|
const origin = req.headers.origin;
|
|
63
73
|
if (origin !== undefined) {
|
|
64
|
-
if (allowedOrigin(origin)) {
|
|
74
|
+
if (allowedOrigin(origin, this.serverOrigin?.())) {
|
|
65
75
|
res.setHeader("Access-Control-Allow-Origin", origin);
|
|
66
76
|
res.setHeader("Access-Control-Allow-Credentials", "true");
|
|
67
77
|
}
|
|
@@ -74,16 +84,10 @@ export class Http {
|
|
|
74
84
|
return;
|
|
75
85
|
}
|
|
76
86
|
}
|
|
77
|
-
if (this.
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
.digest();
|
|
82
|
-
if (!match || !timingSafeEqual(this.credentials, supplied)) {
|
|
83
|
-
res.setHeader("WWW-Authenticate", 'Basic realm="OpenCode"');
|
|
84
|
-
json(res, 401, errorBody("UnauthorizedError", "Authentication required"));
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
+
if (!this.authorized(req)) {
|
|
88
|
+
res.setHeader("WWW-Authenticate", 'Basic realm="OpenCode"');
|
|
89
|
+
json(res, 401, errorBody("UnauthorizedError", "Authentication required"));
|
|
90
|
+
return;
|
|
87
91
|
}
|
|
88
92
|
res.setHeader("Access-Control-Allow-Methods", "GET,POST,PUT,PATCH,DELETE,OPTIONS");
|
|
89
93
|
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, x-opencode-directory");
|
|
@@ -100,11 +104,21 @@ export class Http {
|
|
|
100
104
|
res.setHeader("Connection", "close");
|
|
101
105
|
res.once("finish", () => req.destroy());
|
|
102
106
|
}
|
|
103
|
-
json(res, err instanceof
|
|
107
|
+
json(res, err instanceof UpstreamProtocolError
|
|
108
|
+
? 502
|
|
109
|
+
: err instanceof RequestBodyError
|
|
110
|
+
? err.status
|
|
111
|
+
: err instanceof ApprovalInputError || err instanceof URIError
|
|
112
|
+
? 400
|
|
113
|
+
: 500, errorBody(err instanceof UpstreamProtocolError
|
|
104
114
|
? err.name
|
|
105
|
-
: err instanceof
|
|
106
|
-
?
|
|
107
|
-
:
|
|
115
|
+
: err instanceof RequestBodyError
|
|
116
|
+
? err.name
|
|
117
|
+
: err instanceof ApprovalInputError
|
|
118
|
+
? "InvalidRequestError"
|
|
119
|
+
: err instanceof URIError
|
|
120
|
+
? "BadRequestError"
|
|
121
|
+
: "UnknownError", err instanceof URIError ? "invalid URL encoding" : message));
|
|
108
122
|
}
|
|
109
123
|
}
|
|
110
124
|
body(req) {
|
|
@@ -165,4 +179,13 @@ export class Http {
|
|
|
165
179
|
req.once("aborted", onAborted);
|
|
166
180
|
});
|
|
167
181
|
}
|
|
182
|
+
authorized(req) {
|
|
183
|
+
if (this.credentials === undefined)
|
|
184
|
+
return true;
|
|
185
|
+
const match = req.headers.authorization?.match(/^Basic\s+([A-Za-z0-9+/]+=*)$/i);
|
|
186
|
+
const supplied = createHash("sha256")
|
|
187
|
+
.update(Buffer.from(match?.[1] ?? "", "base64"))
|
|
188
|
+
.digest();
|
|
189
|
+
return match !== null && match !== undefined && timingSafeEqual(this.credentials, supplied);
|
|
190
|
+
}
|
|
168
191
|
}
|
|
@@ -22,8 +22,6 @@ export declare function id(prefix: string): string;
|
|
|
22
22
|
export declare function piModelId(responseModel: unknown, model: unknown): unknown;
|
|
23
23
|
export declare function piStopReason(stopReason: string | undefined, errorMessage?: string): Pick<OCAssistantMessage, "finish" | "error">;
|
|
24
24
|
export declare function textOf(content: Array<Record<string, unknown>> | undefined): string;
|
|
25
|
-
export declare function newPart(kind: "text" | "reasoning", sessionID: string, messageID: string): OCPart;
|
|
26
|
-
export declare function applyUsage(msg: OCAssistantMessage, usage: PiUsage | undefined): void;
|
|
27
25
|
/**
|
|
28
26
|
* Recompute the session's own spend from its loaded assistant messages. Subagent
|
|
29
27
|
* spend stays on the child sessions: OpenChamber sums the parentID subtree, so
|
|
@@ -33,9 +31,8 @@ export declare function syncSessionCost(s: ManagedSession): boolean;
|
|
|
33
31
|
/**
|
|
34
32
|
* An OmO compaction as OpenCode shows one: a user turn holding a `compaction`
|
|
35
33
|
* part (OpenChamber renders it as `/compact`) answered by a `summary` message.
|
|
36
|
-
* IDs derive from the compaction
|
|
37
|
-
*
|
|
38
|
-
* reconciles with the live messages instead of duplicating them.
|
|
34
|
+
* IDs derive from the native compaction entry, not its retained boundary:
|
|
35
|
+
* multiple compactions can retain the same entry.
|
|
39
36
|
*/
|
|
40
37
|
export declare function compactionMessages(s: ManagedSession, key: string, summary: string, created: number, model: OCModelRef | undefined): [OCMessageWithParts, OCMessageWithParts];
|
|
41
38
|
interface PiEventServices {
|
|
@@ -53,11 +50,18 @@ interface PiEventServices {
|
|
|
53
50
|
export declare class PiEventTranslator {
|
|
54
51
|
private services;
|
|
55
52
|
constructor(services: PiEventServices);
|
|
53
|
+
/** Close dead-runtime UI state before an error or idle event can reach the client. */
|
|
54
|
+
finishInterruptedRun(s: ManagedSession, error: NonNullable<OCAssistantMessage["error"]>): void;
|
|
56
55
|
setStatus(s: ManagedSession, status: OCSessionStatus): void;
|
|
57
56
|
emitPart(s: ManagedSession, part: OCPart, delta?: string): void;
|
|
58
57
|
deliverInput(s: ManagedSession, messageID: string): void;
|
|
59
58
|
private reconcileUser;
|
|
60
59
|
onPiEvent(s: ManagedSession, ev: Record<string, unknown>): void;
|
|
60
|
+
private onTodoEntry;
|
|
61
|
+
private onLifecycleEvent;
|
|
62
|
+
private onToolEvent;
|
|
63
|
+
private onMessageEvent;
|
|
64
|
+
private onCompactionEvent;
|
|
61
65
|
private finishStreaming;
|
|
62
66
|
private onMessageUpdate;
|
|
63
67
|
private onProviderTools;
|