agl 21.0.2 → 22.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.
- package/changelog.md +9 -0
- package/dist_serve/bundle.js +1 -1
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.aglhome.d.ts +25 -0
- package/dist_ts/classes.aglhome.js +65 -0
- package/dist_ts/classes.authmodels.js +7 -12
- package/dist_ts/classes.authstore.js +2 -29
- package/dist_ts/classes.cli.js +93 -35
- package/dist_ts/classes.config.d.ts +2 -11
- package/dist_ts/classes.config.js +14 -80
- package/dist_ts/classes.controller.d.ts +4 -1
- package/dist_ts/classes.controller.js +64 -26
- package/dist_ts/classes.embeddeddb.js +6 -5
- package/dist_ts/classes.gitreversion.js +3 -2
- package/dist_ts/classes.upgradecoordinator.d.ts +6 -1
- package/dist_ts/classes.upgradecoordinator.js +588 -177
- package/dist_ts/classes.upgradetransaction.js +46 -12
- package/dist_ts/classes.uploadmanager.d.ts +12 -0
- package/dist_ts/classes.uploadmanager.js +204 -2
- package/dist_ts/constants.upgradeenvironment.d.ts +2 -0
- package/dist_ts/constants.upgradeenvironment.js +3 -0
- package/dist_ts/functions.controllerdataroot.d.ts +4 -5
- package/dist_ts/functions.controllerdataroot.js +4 -29
- package/dist_ts/functions.embeddeddb.d.ts +1 -1
- package/dist_ts/functions.embeddeddb.js +8 -3
- package/dist_ts/functions.runtimeenvironment.js +2 -1
- package/dist_ts/index.d.ts +1 -0
- package/dist_ts/index.js +2 -1
- package/dist_ts/interfaces.config.d.ts +5 -7
- package/dist_ts_migration/classes.documentmigrationrunner.js +3 -1
- package/dist_ts_migration/index.d.ts +2 -0
- package/dist_ts_migration/index.js +3 -1
- package/dist_ts_migration/v23_aglhome.d.ts +91 -0
- package/dist_ts_migration/v23_aglhome.js +1775 -0
- package/dist_ts_migration/v23_runtimeconfig.d.ts +11 -0
- package/dist_ts_migration/v23_runtimeconfig.js +87 -0
- package/dist_ts_migration/v2_controllerdataroot.d.ts +5 -0
- package/dist_ts_migration/v2_controllerdataroot.js +176 -15
- package/package.json +1 -1
- package/readme.md +116 -35
- package/readme.plan.md +24 -9
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.aglhome.ts +116 -0
- package/ts/classes.authmodels.ts +5 -13
- package/ts/classes.authstore.ts +1 -39
- package/ts/classes.cli.ts +92 -34
- package/ts/classes.config.ts +20 -117
- package/ts/classes.controller.ts +92 -26
- package/ts/classes.embeddeddb.ts +5 -4
- package/ts/classes.gitreversion.ts +3 -2
- package/ts/classes.upgradecoordinator.ts +668 -191
- package/ts/classes.upgradetransaction.ts +46 -11
- package/ts/classes.uploadmanager.ts +231 -1
- package/ts/constants.upgradeenvironment.ts +2 -0
- package/ts/functions.controllerdataroot.ts +11 -47
- package/ts/functions.embeddeddb.ts +13 -2
- package/ts/functions.runtimeenvironment.ts +1 -0
- package/ts/index.ts +1 -0
- package/ts/interfaces.config.ts +5 -7
- package/ts_migration/classes.documentmigrationrunner.ts +2 -0
- package/ts_migration/index.ts +2 -0
- package/ts_migration/v23_aglhome.ts +2101 -0
- package/ts_migration/v23_runtimeconfig.ts +112 -0
- package/ts_migration/v2_controllerdataroot.ts +174 -16
- package/ts_web/00_commitinfo_data.ts +1 -1
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -40,7 +40,7 @@ OpenCode and Flex are independently supervised. An unexpected Flex child exit cl
|
|
|
40
40
|
|
|
41
41
|
FlexHarness private state is persisted in separate SmartData domains for scope metadata, public message projections, remembered permissions, canonical Agent events and archives, and background tool jobs. A fenced per-project writer lease excludes overlapping Flex children, while the parent reads a separately committed bounded public projection. Lease heartbeats and writes share one serialized transition boundary; an expired lease still held by the same controller recovers with a fresh owner token and incremented epoch before work continues. Hidden terminal staging and private canonical events never enter that browser-readable projection; a visible Flex save is acknowledged only after the corresponding bounded view is durable.
|
|
42
42
|
|
|
43
|
-
Flex runtime implementation classes and child internals are private. Supported package surfaces include the `agl` CLI, protocol-v22 controller DTO and request contracts, and the root-exported controller-owned Git reversion types and engine.
|
|
43
|
+
Flex runtime implementation classes and child internals are private. Supported package surfaces include the `agl` CLI, protocol-v22 controller DTO and request contracts, the root-exported AGL home path helpers, and the root-exported controller-owned Git reversion types and engine.
|
|
44
44
|
|
|
45
45
|
## Requirements
|
|
46
46
|
|
|
@@ -48,6 +48,7 @@ Flex runtime implementation classes and child internals are private. Supported p
|
|
|
48
48
|
- Git 2.43 or newer for workspace reversion and controller-owned worktrees
|
|
49
49
|
- A WebAuthn-capable browser
|
|
50
50
|
- Linux or macOS for detached `start` and CLI-managed `stop`
|
|
51
|
+
- A POSIX user identity for data-using startup, migration, and embedded-database commands
|
|
51
52
|
- Native `node-pty` support. When no prebuilt binary is available, installation requires Python 3, `make`, and a C/C++ build toolchain.
|
|
52
53
|
|
|
53
54
|
No database server is required: the controller ships an embedded engine
|
|
@@ -56,7 +57,7 @@ be configured instead.
|
|
|
56
57
|
|
|
57
58
|
The package pins `@opencode-ai/sdk` and the official OS/architecture-specific OpenCode binary packages to the same tested version. It resolves the lockfile-installed platform executable directly, without a wrapper postinstall or unrelated global binary. This release is tested against OpenCode `1.18.15`.
|
|
58
59
|
|
|
59
|
-
The OpenCode child, Flex child, and controller-owned terminal shells receive only ordinary runtime path, user, locale, temporary-directory, XDG, proxy, and CA environment variables by default.
|
|
60
|
+
The OpenCode child, Flex child, and controller-owned terminal shells receive only ordinary runtime path, user, locale, temporary-directory, XDG, proxy, and CA environment variables by default. `AGL_HOME` is the deliberate controller-owned path exception; controller/database variables and unrelated parent secrets are not inherited. Explicitly delegate additional environment variables when needed:
|
|
60
61
|
|
|
61
62
|
```sh
|
|
62
63
|
export HARNESS_CONTROLLER_FORWARD_ENV='ANTHROPIC_API_KEY,OPENAI_API_KEY'
|
|
@@ -120,9 +121,11 @@ agl help
|
|
|
120
121
|
|
|
121
122
|
Use the controller's configured port instead of `4097` for both invocations.
|
|
122
123
|
Do not skip the bridge or manually replace the package. This package transition
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
124
|
+
itself leaves controller data under `$XDG_CONFIG_HOME/hcon`. The first data-using
|
|
125
|
+
command (`start`, `foreground`, `upgrade`, or `temp-password`) from an AGL
|
|
126
|
+
version with `AGL_HOME` support subsequently moves that retained state into the
|
|
127
|
+
canonical AGL home described below without changing persisted
|
|
128
|
+
controller, authentication, project, session, Flex, or Git identities.
|
|
126
129
|
|
|
127
130
|
### Upgrade behavior
|
|
128
131
|
|
|
@@ -165,6 +168,12 @@ stopped-controller upgrade has no inherited cohort and leaves the target
|
|
|
165
168
|
stopped. The worker also refuses an upgrade when another pnpm-global controller
|
|
166
169
|
instance exists outside the exact authorized process.
|
|
167
170
|
|
|
171
|
+
New coordination transactions live under `$AGL_HOME/upgrade`. A target invoked
|
|
172
|
+
by an already-released 21.0.2 worker recognizes that token-bound worker's legacy
|
|
173
|
+
`/tmp/harness-controller-upgrade-*` directory only until the inherited
|
|
174
|
+
transaction reaches its terminal cleanup; unrelated or unbound legacy paths are
|
|
175
|
+
never used as fallback coordination.
|
|
176
|
+
|
|
168
177
|
`hcon upgrade` must be run from the active pnpm-global installation and is currently supported on Linux and macOS. Before starting the AGL transition, if a prior manual same-package upgrade replaced the management CLI while an older controller remained running, restore that exact older package first so its process identity can be stopped safely:
|
|
169
178
|
|
|
170
179
|
```sh
|
|
@@ -176,33 +185,86 @@ hcon start --port 4097
|
|
|
176
185
|
|
|
177
186
|
This repair sequence does not replace the two bridge-migration invocations.
|
|
178
187
|
|
|
188
|
+
## AGL home
|
|
189
|
+
|
|
190
|
+
AGL-owned persistent state has one private root selected by `AGL_HOME`. The
|
|
191
|
+
in-progress home migration also uses a transient sibling lock beside that root;
|
|
192
|
+
it is not normal persisted state. Installed packages default to
|
|
193
|
+
`$XDG_CONFIG_HOME/agl` (normally `~/.config/agl`); repository checkouts default to
|
|
194
|
+
`<checkout>/.nogit/agl`. `AGL_HOME`, when supplied, must be an absolute normalized
|
|
195
|
+
non-root path whose direct parent already exists; this parent requirement also
|
|
196
|
+
applies to the selected default root. AGL owns
|
|
197
|
+
these namespaces:
|
|
198
|
+
|
|
199
|
+
- `database` for the embedded SmartDB storage root;
|
|
200
|
+
- `credentials` for sealed Flex provider credentials;
|
|
201
|
+
- `git-reversion` for captures and controller-owned worktrees;
|
|
202
|
+
- `logs` for controller and upgrade logs;
|
|
203
|
+
- `upgrade` for durable upgrade coordination;
|
|
204
|
+
- `cache` for AGL-owned caches;
|
|
205
|
+
- `runtime/browser`, `runtime/uploads`, `runtime/sockets`, and
|
|
206
|
+
`runtime/opencode` for private runtime state;
|
|
207
|
+
- `migration` for the current journal and verified retained migration records.
|
|
208
|
+
|
|
209
|
+
Library consumers can resolve the same root-exported path contract and bind it
|
|
210
|
+
into a child environment without deriving paths from the child's CWD:
|
|
211
|
+
|
|
212
|
+
```ts
|
|
213
|
+
import { bindAGLHomeEnvironment, resolveAGLHomePaths } from 'agl';
|
|
214
|
+
|
|
215
|
+
const paths = resolveAGLHomePaths();
|
|
216
|
+
const childEnvironment = bindAGLHomeEnvironment(process.env, paths);
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
`resolveAGLHomePaths()` returns `development`, `root`, `database`,
|
|
220
|
+
`credentials`, `gitReversion`, `logs`, `legacyLogs`, `upgrade`, `cache`,
|
|
221
|
+
`runtime`, `browserRuntime`, `uploads`, `sockets`, `openCodeRuntime`, and
|
|
222
|
+
`migration`. `bindAGLHomeEnvironment()` returns a copy of the supplied
|
|
223
|
+
environment with `AGL_HOME` bound to `paths.root`.
|
|
224
|
+
|
|
225
|
+
The package installation itself, user-selected project directories, an explicit
|
|
226
|
+
`HARNESS_CONTROLLER_DB_DIR` (including one that names a recognized legacy
|
|
227
|
+
SmartDB path), an external MongoDB server, and OpenCode's own user-wide
|
|
228
|
+
configuration are not AGL-owned state and remain outside this root.
|
|
229
|
+
`AGL_HOME` is bound into controller, upgrade, Flex, and OpenCode process
|
|
230
|
+
boundaries so a detached process never derives ownership from its launch CWD.
|
|
231
|
+
|
|
232
|
+
The one-time v23 home migration runs before database model access from `start`,
|
|
233
|
+
`foreground`, `upgrade`, or `temp-password`. It first
|
|
234
|
+
normalizes the prior `harness-controller` to `hcon` migration when necessary,
|
|
235
|
+
then uses SmartDB and SmartSecret relocation APIs for the active database and
|
|
236
|
+
sealed credentials. Git state, browser state, logs, and verified legacy records
|
|
237
|
+
move under the same journal. The migration is same-filesystem only, checks
|
|
238
|
+
private ownership, exact source inventories, live writers, sockets, symlinks,
|
|
239
|
+
target collisions, and source identities before mutation, and safely reconciles
|
|
240
|
+
an interruption. Active controller-owned Git worktree state blocks migration
|
|
241
|
+
because Git's linked-worktree metadata contains absolute paths. Unknown or
|
|
242
|
+
ambiguous state fails closed. Verified historical database material is retained
|
|
243
|
+
under `$AGL_HOME/migration`; committed legacy roots are removed only when their
|
|
244
|
+
known entries are empty. Never move these directories or edit their journals by
|
|
245
|
+
hand.
|
|
246
|
+
|
|
247
|
+
An explicit non-default `AGL_HOME` in a development checkout starts as an
|
|
248
|
+
isolated fresh home and does not consume that checkout's shared legacy
|
|
249
|
+
`.nogit` state.
|
|
250
|
+
|
|
179
251
|
## Database configuration
|
|
180
252
|
|
|
181
253
|
Structured application state is stored exclusively through SmartData. There is no PID, configuration, authentication, or JSON persistence fallback on the filesystem.
|
|
182
254
|
|
|
183
255
|
By default the controller runs an embedded `@push.rocks/smartdb` engine — no
|
|
184
256
|
external database server. Storage lives under
|
|
185
|
-
`$
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
completes any prior XDG-state database import under the retained
|
|
190
|
-
`$XDG_CONFIG_HOME/harness-controller` root. When that config-root database
|
|
191
|
-
exists, SmartDB's stopped-root relocation moves its exact inode to the `hcon`
|
|
192
|
-
root and replaces the old `smartdb` pathname with a verified receipt, not a
|
|
193
|
-
database backup. The controller verifies that receipt, storage identity, and
|
|
194
|
-
logical content digest. This relocation is supported on SmartDB-qualified Linux
|
|
195
|
-
filesystems and writable local APFS on macOS. When only the historical XDG-state
|
|
196
|
-
database exists, it is instead imported directly into the new root with the same
|
|
197
|
-
logical-digest verification while retaining that historical source as the
|
|
198
|
-
non-authoritative backup. The outer migration is journaled and safely retries
|
|
199
|
-
its exact operation after an interruption. Retained legacy sources are never
|
|
200
|
-
imported again after completion; an older controller binary can still open and
|
|
201
|
-
diverge a retained historical backup, so do not resume using it. Keep the old
|
|
202
|
-
controller stopped throughout the one-time migration. Explicit database paths
|
|
203
|
-
and external MongoDB configurations are never moved. The engine listens on a unix socket, and a second controller
|
|
257
|
+
`$AGL_HOME/database`. Override the directory with
|
|
258
|
+
`HARNESS_CONTROLLER_DB_DIR` (absolute path). Explicit database paths and
|
|
259
|
+
external MongoDB configurations are never moved. The engine listens on a
|
|
260
|
+
deterministic Unix socket under `$AGL_HOME/runtime/sockets`, and a second controller
|
|
204
261
|
process on the same machine (for example `agl temp-password`) attaches to the
|
|
205
|
-
running daemon's engine instead of opening the storage files again.
|
|
262
|
+
running daemon's engine instead of opening the storage files again. The complete
|
|
263
|
+
embedded Unix socket path must be no longer than 100 UTF-8 bytes; a second
|
|
264
|
+
controller must use the same `AGL_HOME` and database directory to attach.
|
|
265
|
+
During the one-time home migration only, AGL also probes the published legacy
|
|
266
|
+
`/tmp/harness-controller-<16-hex>.sock` name to prove that the old embedded
|
|
267
|
+
engine is stopped. New runtime sockets are never created at that legacy path.
|
|
206
268
|
|
|
207
269
|
When upgrading from a controller release backed by FlexHarness 2, the new Flex child migrates each active project's legacy snapshot during initialization, before admitting runtime work. Migration preflights every destination, writes per-session domains before publishing scope discovery, repairs crash-interrupted streaming messages as cancelled, and safely reruns after partial completion. A durable completion marker prevents the retained source from being reapplied after live FlexHarness 3 state evolves. The legacy private snapshot remains retained as a non-authoritative recovery source until that project is removed; startup also repairs any bounded public projection whose prior save was not acknowledged.
|
|
208
270
|
|
|
@@ -226,12 +288,18 @@ Use the `MONGODB_URL` from `.nogit/env.json` as `HARNESS_CONTROLLER_MONGO_URL`.
|
|
|
226
288
|
|
|
227
289
|
## First start
|
|
228
290
|
|
|
229
|
-
|
|
291
|
+
Start AGL without creating an implicit project:
|
|
230
292
|
|
|
231
293
|
```sh
|
|
232
294
|
agl start
|
|
233
295
|
```
|
|
234
296
|
|
|
297
|
+
To register one existing directory during startup, pass it explicitly:
|
|
298
|
+
|
|
299
|
+
```sh
|
|
300
|
+
agl start --directory /absolute/path/to/project
|
|
301
|
+
```
|
|
302
|
+
|
|
235
303
|
The detached process first reserves the controller listener (`127.0.0.1:4097` in local mode; `0.0.0.0:4097` only with `--behind-tls-proxy`), spawns the isolated Flex child, starts the official OpenCode server in headless server mode on `127.0.0.1:4098`, and validates local PTY support. On an unsupported runtime the spawned Flex child reports `unsupported`; Flex startup is isolated and does not block OpenCode readiness. Before the first enrollment only, the terminal prints a one-time setup code — a random 32-byte value by default, or the operator's own value when `--setup-code` is passed.
|
|
236
304
|
|
|
237
305
|
Open the printed URL, enter the setup code, and create a passkey. The code expires after 30 minutes and is atomically consumed by the first successful credential enrollment. If an unenrolled code expires, stop and start the controller to rotate it.
|
|
@@ -255,27 +323,28 @@ agl help
|
|
|
255
323
|
agl --version
|
|
256
324
|
```
|
|
257
325
|
|
|
258
|
-
|
|
326
|
+
Initial runtime options:
|
|
259
327
|
|
|
260
328
|
```text
|
|
261
329
|
--port <port> controller port, default 4097
|
|
262
330
|
--opencode-port <port> loopback OpenCode port, default 4098
|
|
263
|
-
--directory <path>
|
|
331
|
+
--directory <path> register this existing directory as an initial project;
|
|
332
|
+
omitted means no implicit project
|
|
264
333
|
--projects-root <path> default base directory: relative project paths and
|
|
265
334
|
initial suggestions resolve here; explicitly typed
|
|
266
|
-
|
|
267
|
-
|
|
335
|
+
absolute projects may be outside it; fresh installs
|
|
336
|
+
default to the user home and it is immutable afterward
|
|
268
337
|
--public-origin <origin> exact browser WebAuthn origin
|
|
269
338
|
--rp-id <hostname> WebAuthn RP ID
|
|
270
339
|
--behind-tls-proxy declare trusted TLS termination upstream
|
|
271
340
|
--setup-code <code> operator-chosen setup code (see the warning above)
|
|
272
341
|
```
|
|
273
342
|
|
|
274
|
-
Runtime configuration is durable and immutable per controller port. Later management commands must use the same `--port`; omitted
|
|
343
|
+
Runtime configuration is durable and immutable per controller port. Later management commands must use the same `--port`; omitted initial runtime options are loaded from SmartData. `--directory` is different: it is a transient request to register one existing project and may be supplied on a later start without changing the durable runtime configuration.
|
|
275
344
|
|
|
276
345
|
`upgrade` keeps the CLI attached to its detached, crash-survivable worker and streams progress until the durable transaction succeeds or fails. Before stopping a running controller it closes new harness-prompt admission, durably records the exact cohort and absolute preparation deadline, then acknowledges the bounded request while controller-owned work waits for active operations and asks those sessions to pause at the earliest convenience. The worker polls that private transaction with bounded source-process checks; it cannot stop the controller or enter package installation until preparation completion is durably committed on time. The global upgrade lock also drains controller-start and `temp-password` database-writer leases, then package replacement requires the source port to be closed and no exact source controller, `temp-password`, or Flex child writer to remain. A failed or expired preparation compensates settled pauses and leaves uncertain cleanup fail-closed. The retained cohort is limited to 64 exact sessions. `--grace-period-seconds` accepts 1–3600 seconds and defaults to 300. Without `--continue-sessions`, the upgraded controller reopens prompt admission without sending another message. With it, the matching continuation message is submitted at most once to the durably retained paused cohort after the upgraded controller returns. Rollback recovery attempts to compensate paused sessions within a separate maximum 60-second finalization window and does not overlap an unresolved pause with a continuation. `--json` replaces progress lines with one terminal JSON document after worker admission; preflight and launch failures still use normal CLI errors. That document includes worker identity and log path, whether the controller was running, source version, optional target version, terminal phase/success/error, and pause/continuation state counts; failures exit nonzero after writing it.
|
|
277
346
|
|
|
278
|
-
The detached CLI does not write a PID file. It appends the controller's stderr diagnostics to `$
|
|
347
|
+
The detached CLI does not write a PID file. It appends the controller's stderr diagnostics to `$AGL_HOME/logs/controller-<port>.log` (rotated to `.old` past 1 MiB on start; stdout stays discarded because the transport logs every connection there). The log file must be a regular file owned by the current user without group or world access — the start fails loudly rather than logging somewhere surprising. The initial setup code travels once over parent-child IPC. `status` connects only to `127.0.0.1` and validates package and protocol identity. `stop` additionally validates the exact CLI path (relative launches are resolved against the process's working directory), command, effective port (an argv without `--port` verifies as the default port), process group, and process-start fingerprint. It sends cooperative `SIGTERM` only to that exact PID so the controller can stop harnesses and database resources in order. If the process remains after 30 seconds, a foreground controller receives exact-PID `SIGKILL`; a detached controller must still be the reverified process-group leader before its complete group receives `SIGKILL`. It never kills processes by name.
|
|
279
348
|
|
|
280
349
|
`foreground` is available for attached operation and diagnostics. Detached management is intentionally limited to Linux and macOS in this release.
|
|
281
350
|
|
|
@@ -287,7 +356,19 @@ The command talks to the controller database directly, so it requires the same d
|
|
|
287
356
|
|
|
288
357
|
## Projects and default models
|
|
289
358
|
|
|
290
|
-
|
|
359
|
+
Startup preserves the exact project registry: an empty controller remains
|
|
360
|
+
empty, and only an explicit `--directory` registers an initial project. The
|
|
361
|
+
supervised OpenCode server runs from
|
|
362
|
+
`$AGL_HOME/runtime/opencode/<controller-hash>`, never from a project or the
|
|
363
|
+
invoking shell's CWD for its implicit runtime state. Relative `--directory` and
|
|
364
|
+
`--projects-root` values are resolved against the invoking shell before the
|
|
365
|
+
detached process starts. A stored `projectsRoot` is validated lexically during
|
|
366
|
+
startup and resolved only when a relative project operation needs it, so a
|
|
367
|
+
temporarily unavailable root does not stop the controller. This v23 persisted
|
|
368
|
+
document/home migration revision supersedes the historical v2 automatic-workspace
|
|
369
|
+
registration; the wire protocol remains v22.
|
|
370
|
+
|
|
371
|
+
Chats are organized into projects. A project is any registered **existing** directory on disk: the browser's add-project field accepts full absolute paths (`/any/path/on/disk`) as well as paths relative to the configured `--projects-root` default base (`org/repo`), and suggests matching folders live while typing either form — absolute queries browse the actual filesystem. Hidden directories are never suggested (though an explicitly typed absolute path may contain dot segments), dot navigation is rejected, and registration fails visibly when the path does not exist — the controller never creates project directories. Projects inside the default base get short relative display names; projects elsewhere are named by their absolute path. Every session method is scoped to one project. One supervised OpenCode server serves all projects through per-call directory scoping, while the Flex child resolves each session to its registered project root before providing filesystem or shell tools. Startup never derives or registers a project from the process CWD. Removing a project first blocks new resource operations and requires every durable terminal and browser resource to be retired. The controller then persists removal intent before destructive work, hides the project from normal admission, drains Session Intelligence and harness operations, clears controller-owned state, asks Flex to remove that exact project namespace, and removes controller-owned Git worktrees, worktree records, and reversion capture roots. Interrupted removals resume on startup and remain pending when required runtime cleanup cannot be confirmed. Flex storage is never deleted merely because a project is absent from an active-project snapshot. Files in the registered project working tree are never deleted; path cleanup is limited to controller-owned worktrees, while controller-private capture stores and worktree records are removed only from controller-owned storage. Note the trust consequence: an authenticated browser session can register and operate on any directory the controller's user can read.
|
|
291
372
|
|
|
292
373
|
Registration persists the complete ordered device/inode ancestry from the filesystem root through every project path component, with the target recorded as a directory. Controller requests, Flex tools, Git reversion, background cleanup, and shutdown revalidate the entire chain before using the path, so preserving the project leaf inode beneath a substituted ancestor does not authorize the replacement. The first upgrade to this format binds each currently available truly legacy project exactly once under its owning controller. A legacy project that is unavailable during that migration remains terminally unbound, and an incomplete pre-release `bound` record becomes terminally unbound instead of acquiring missing ancestors. Such projects stay visible for operator diagnosis but are excluded from runtime admission and path-sensitive cleanup. Their removal retires controller-owned metadata while intentionally retaining retired resource tombstones and path-scoped OpenCode, Flex, and Git artifacts. A pending bound removal remains durable until its original complete binding is available, rather than completing against an untrusted replacement.
|
|
293
374
|
|
|
@@ -301,7 +382,7 @@ Flex `/undo` and `/redo` move transcript and workspace history together. Startin
|
|
|
301
382
|
|
|
302
383
|
Each capture owns one controller-private `captures/<captureKey>` root. Every participating repository gets a private bare Git metadata store and disposable object generations; the bound source object database is available only as a read-only alternate. Capture, finalize, inspection, and release therefore do not add controller refs, metadata blobs, trees, or generated objects to the source repository. Apply is the sole capture-traversal operation that mutates captured worktree, index, or ref state: before mutation it creates an operation-unique, self-contained closure pack in private storage, imports that exact bounded pack without a reverse index or controller ref, and retains it with an operation-owned `.keep` file through roll-forward or compensation. The explicit worktree create/remove methods separately update the source repository's linked-worktree metadata. Private publication and capture-root lifecycle are journaled so startup can recover interrupted publication, apply, creation, and deletion without trusting files inside the tree being removed.
|
|
303
384
|
|
|
304
|
-
Library consumers can construct the root-exported `ControllerGitReversion` with `IGitReversionOptions`, register each project with its canonical `IControllerProjectFilesystemIdentity`, and call `init()` or `recover()` before source-sensitive work. Capture flow uses `IGitReversionCaptureRequest` with `prepare()`, `inspectCapture()`, and `finalize()`; undo and redo use `IGitReversionApplyRequest` with `inspectApply()` and `apply()`. `IGitReversionMetadataRequest` drives `listAffected()` and `getReversionMetadata()`, while `IGitReversionWorktreeRequest` and `IGitReversionWorktreeRemoveRequest` drive the detached-session worktree methods. Only when no further undo, redo, inspection, or recovery is needed should terminal cleanup call `release()` with `IGitReversionReleaseRequest`, which permanently deletes the capture root. Call `close()` to seal admission and drain or fail closed on owned work.
|
|
385
|
+
Library consumers can construct the root-exported `ControllerGitReversion` with `IGitReversionOptions`, register each project with its canonical `IControllerProjectFilesystemIdentity`, and call `init()` or `recover()` before source-sensitive work. Its default private root is under `$AGL_HOME/git-reversion`; `temporaryRoot` can still supply an explicit absolute owner root. Capture flow uses `IGitReversionCaptureRequest` with `prepare()`, `inspectCapture()`, and `finalize()`; undo and redo use `IGitReversionApplyRequest` with `inspectApply()` and `apply()`. `IGitReversionMetadataRequest` drives `listAffected()` and `getReversionMetadata()`, while `IGitReversionWorktreeRequest` and `IGitReversionWorktreeRemoveRequest` drive the detached-session worktree methods. Only when no further undo, redo, inspection, or recovery is needed should terminal cleanup call `release()` with `IGitReversionReleaseRequest`, which permanently deletes the capture root. Call `close()` to seal admission and drain or fail closed on owned work.
|
|
305
386
|
|
|
306
387
|
Flex also exposes `/worktree create`, `/worktree list`, and `/worktree remove <worktreeId>`. These commands explicitly create and manage detached, locked, controller-owned worktrees for the current conversation; worktrees are never created automatically. Creation durably binds the private parent before `git worktree add`, then binds the child before activation. Recovery, cleanup, and removal revalidate the required parent and child bindings; a replacement is fenced and retained. Listing validates the registered project before reading bounded private records, but does not dereference or revalidate every listed worktree path. The run-scoped `git_worktree` tool provides the same create/list/remove operations to the agent, with permission required for create and remove. Removal refuses any worktree containing tracked, untracked, or ignored changes.
|
|
307
388
|
|
|
@@ -442,7 +523,7 @@ Audit records may contain bounded peer, credential, session, or permission-reque
|
|
|
442
523
|
|
|
443
524
|
Flex private snapshots and public projections are published separately. The controller reads only integrity-checked public records. For each project, every public candidate is bounded to 2,048 sessions, 2,048 messages, 64 MiB of message data, and 480 KiB per message; up to four public candidates are retained per project. Pages contain at most 50 messages or 512 KiB. Unreferenced staged candidates are age-gated and reconciled through paged, batched maintenance. Provider credentials never enter these records.
|
|
444
525
|
|
|
445
|
-
Running terminal processes, output scrollback, peer cursors, grid state, active human browser views, browser-frame streams, BrowserRuntime capabilities and leases, Flex browser channels, and composer drafts are memory-only. Durable terminal resource identifiers may appear as bounded request identifiers in lifecycle audit records, but session layouts contain only OpenCode and Flex session IDs. Terminal input and output are never persisted. Draft text and attachment bytes are never written to SmartData or audit records. Attachment files exist only in private
|
|
526
|
+
Running terminal processes, output scrollback, peer cursors, grid state, active human browser views, browser-frame streams, BrowserRuntime capabilities and leases, Flex browser channels, and composer drafts are memory-only. Durable terminal resource identifiers may appear as bounded request identifiers in lifecycle audit records, but session layouts contain only OpenCode and Flex session IDs. Terminal input and output are never persisted. Draft text and attachment bytes are never written to SmartData or audit records. Attachment files exist only in private operation directories under `$AGL_HOME/runtime/uploads` for the lifetime of their exact harness operation and are removed on operation cleanup or process shutdown.
|
|
446
527
|
|
|
447
528
|
## Development
|
|
448
529
|
|
package/readme.plan.md
CHANGED
|
@@ -36,14 +36,27 @@ targets.
|
|
|
36
36
|
credential records, SmartSecret service/store/account identities, harness
|
|
37
37
|
IDs, audit vocabulary, browser storage keys, and persisted protocol fields.
|
|
38
38
|
- Preserve existing `HARNESS_CONTROLLER_*` environment variables, private IPC
|
|
39
|
-
identities,
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
39
|
+
identities, and persisted controller, project, session, Flex, Git, and
|
|
40
|
+
WebAuthn identities. Physical AGL-owned paths may move only through a
|
|
41
|
+
versioned migration under `ts_migration/`.
|
|
42
|
+
- Post-transition AGL versions use one canonical `AGL_HOME`: installed packages
|
|
43
|
+
default to `$XDG_CONFIG_HOME/agl`, and development checkouts to `.nogit/agl`.
|
|
44
|
+
The v23 migration moves the retained `hcon`, `harness-controller`, and
|
|
45
|
+
historical state assets into that root without reimporting committed data.
|
|
46
|
+
- Persisted `workspaceDirectory` is removed by the v23 document migration.
|
|
47
|
+
Existing project records remain authoritative; an empty registry remains
|
|
48
|
+
empty, and `--directory` is a transient project-registration request that can
|
|
49
|
+
also be supplied on a later start.
|
|
50
|
+
A missing persisted `projectsRoot` adopts the legacy workspace when present;
|
|
51
|
+
otherwise fresh configuration defaults it to the user home.
|
|
52
|
+
- New logs and management coordination live under `AGL_HOME`; the in-progress
|
|
53
|
+
home-migration lock is a transient sibling beside that root. The legacy `/tmp`
|
|
54
|
+
coordination path remains recognized only for an exact token-bound worker
|
|
55
|
+
inherited from the already-published 21.0.2 implementation.
|
|
56
|
+
- An explicit `HARNESS_CONTROLLER_DB_DIR` remains outside the migration root and
|
|
57
|
+
is never relocated, even when it names the recognized legacy SmartDB path.
|
|
58
|
+
- The v23 document/home revision is persisted-state migration metadata; the wire
|
|
59
|
+
protocol remains v22.
|
|
47
60
|
|
|
48
61
|
## Coordination Compatibility
|
|
49
62
|
|
|
@@ -185,4 +198,6 @@ for recovery.
|
|
|
185
198
|
no-traversal recovery, bounded discovery barriers, and crash-safe replay.
|
|
186
199
|
- Final E2E from actual published 20.0.2 to bridge 20.0.3 to target 21.0.0,
|
|
187
200
|
confirming data, credentials, sessions, Git state, command ownership, and the
|
|
188
|
-
|
|
201
|
+
preserved legacy `~/.config/hcon` root before the first v23 startup; then
|
|
202
|
+
confirm that v23 moves that state into canonical `AGL_HOME` without identity
|
|
203
|
+
changes.
|
package/ts/00_commitinfo_data.ts
CHANGED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import * as plugins from './plugins.js';
|
|
2
|
+
|
|
3
|
+
export const aglHomeEnvironmentVariable = 'AGL_HOME';
|
|
4
|
+
|
|
5
|
+
export interface IAGLHomeOptions {
|
|
6
|
+
packageRoot?: string;
|
|
7
|
+
homeDirectory?: string;
|
|
8
|
+
environment?: NodeJS.ProcessEnv;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface IAGLHomePaths {
|
|
12
|
+
development: boolean;
|
|
13
|
+
root: string;
|
|
14
|
+
database: string;
|
|
15
|
+
credentials: string;
|
|
16
|
+
gitReversion: string;
|
|
17
|
+
logs: string;
|
|
18
|
+
legacyLogs: string;
|
|
19
|
+
upgrade: string;
|
|
20
|
+
cache: string;
|
|
21
|
+
runtime: string;
|
|
22
|
+
browserRuntime: string;
|
|
23
|
+
uploads: string;
|
|
24
|
+
sockets: string;
|
|
25
|
+
openCodeRuntime: string;
|
|
26
|
+
migration: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const modulePackageRoot = plugins.path.resolve(
|
|
30
|
+
plugins.path.dirname(plugins.url.fileURLToPath(import.meta.url)),
|
|
31
|
+
'..',
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
const isDevelopmentCheckout = (packageRootArg: string): boolean => {
|
|
35
|
+
try {
|
|
36
|
+
const stats = plugins.fs.lstatSync(plugins.path.join(packageRootArg, '.git'));
|
|
37
|
+
return stats.isDirectory() || stats.isFile();
|
|
38
|
+
} catch {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const assertAbsoluteNormalizedPath = (pathArg: string, labelArg: string): string => {
|
|
44
|
+
if (
|
|
45
|
+
typeof pathArg !== 'string'
|
|
46
|
+
|| !plugins.path.isAbsolute(pathArg)
|
|
47
|
+
|| plugins.path.normalize(pathArg) !== pathArg
|
|
48
|
+
|| plugins.path.parse(pathArg).root === pathArg
|
|
49
|
+
|| pathArg.includes('\0')
|
|
50
|
+
|| Buffer.byteLength(pathArg, 'utf8') > 4096
|
|
51
|
+
) {
|
|
52
|
+
throw new Error(`${labelArg} must be an absolute normalized non-root path.`);
|
|
53
|
+
}
|
|
54
|
+
return pathArg;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const defaultConfigHome = (
|
|
58
|
+
environmentArg: NodeJS.ProcessEnv,
|
|
59
|
+
homeDirectoryArg: string,
|
|
60
|
+
): string => {
|
|
61
|
+
const configured = environmentArg.XDG_CONFIG_HOME?.trim();
|
|
62
|
+
return configured && plugins.path.isAbsolute(configured)
|
|
63
|
+
? plugins.path.normalize(configured)
|
|
64
|
+
: plugins.path.join(homeDirectoryArg, '.config');
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export const resolveAGLHomePaths = (
|
|
68
|
+
optionsArg: IAGLHomeOptions = {},
|
|
69
|
+
): IAGLHomePaths => {
|
|
70
|
+
const environment = optionsArg.environment ?? process.env;
|
|
71
|
+
const packageRoot = assertAbsoluteNormalizedPath(
|
|
72
|
+
plugins.path.normalize(optionsArg.packageRoot ?? modulePackageRoot),
|
|
73
|
+
'AGL package root',
|
|
74
|
+
);
|
|
75
|
+
const homeDirectory = assertAbsoluteNormalizedPath(
|
|
76
|
+
plugins.path.normalize(optionsArg.homeDirectory ?? plugins.os.homedir()),
|
|
77
|
+
'User home directory',
|
|
78
|
+
);
|
|
79
|
+
const configuredHome = environment[aglHomeEnvironmentVariable]?.trim();
|
|
80
|
+
const development = isDevelopmentCheckout(packageRoot);
|
|
81
|
+
const root = assertAbsoluteNormalizedPath(
|
|
82
|
+
configuredHome
|
|
83
|
+
? configuredHome
|
|
84
|
+
: development
|
|
85
|
+
? plugins.path.join(packageRoot, '.nogit', 'agl')
|
|
86
|
+
: plugins.path.join(defaultConfigHome(environment, homeDirectory), 'agl'),
|
|
87
|
+
aglHomeEnvironmentVariable,
|
|
88
|
+
);
|
|
89
|
+
const runtime = plugins.path.join(root, 'runtime');
|
|
90
|
+
const logs = plugins.path.join(root, 'logs');
|
|
91
|
+
return {
|
|
92
|
+
development,
|
|
93
|
+
root,
|
|
94
|
+
database: plugins.path.join(root, 'database'),
|
|
95
|
+
credentials: plugins.path.join(root, 'credentials'),
|
|
96
|
+
gitReversion: plugins.path.join(root, 'git-reversion'),
|
|
97
|
+
logs,
|
|
98
|
+
legacyLogs: plugins.path.join(logs, 'legacy'),
|
|
99
|
+
upgrade: plugins.path.join(root, 'upgrade'),
|
|
100
|
+
cache: plugins.path.join(root, 'cache'),
|
|
101
|
+
runtime,
|
|
102
|
+
browserRuntime: plugins.path.join(runtime, 'browser'),
|
|
103
|
+
uploads: plugins.path.join(runtime, 'uploads'),
|
|
104
|
+
sockets: plugins.path.join(runtime, 'sockets'),
|
|
105
|
+
openCodeRuntime: plugins.path.join(runtime, 'opencode'),
|
|
106
|
+
migration: plugins.path.join(root, 'migration'),
|
|
107
|
+
};
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
export const bindAGLHomeEnvironment = (
|
|
111
|
+
environmentArg: NodeJS.ProcessEnv,
|
|
112
|
+
pathsArg: IAGLHomePaths = resolveAGLHomePaths({ environment: environmentArg }),
|
|
113
|
+
): NodeJS.ProcessEnv => ({
|
|
114
|
+
...environmentArg,
|
|
115
|
+
[aglHomeEnvironmentVariable]: pathsArg.root,
|
|
116
|
+
});
|
package/ts/classes.authmodels.ts
CHANGED
|
@@ -92,30 +92,22 @@ const isValidPort = (valueArg: unknown): valueArg is number =>
|
|
|
92
92
|
const isRuntimeConfig = (valueArg: unknown): valueArg is IControllerRuntimeConfig => {
|
|
93
93
|
if (!isPlainObject(valueArg)) return false;
|
|
94
94
|
if (
|
|
95
|
-
// projectsRoot is optional only because pre-v2 documents lack it; once
|
|
96
|
-
// present it must be a canonical absolute path like the workspace.
|
|
97
95
|
!hasExactKeys(valueArg, [
|
|
98
96
|
'controllerPort',
|
|
99
97
|
'publicOrigin',
|
|
100
98
|
'rpId',
|
|
101
|
-
'
|
|
99
|
+
'projectsRoot',
|
|
102
100
|
'opencodePort',
|
|
103
101
|
'tlsMode',
|
|
104
|
-
]
|
|
105
|
-
|| (
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
!isNonEmptyString(valueArg.projectsRoot)
|
|
109
|
-
|| !plugins.path.isAbsolute(valueArg.projectsRoot)
|
|
110
|
-
)
|
|
111
|
-
)
|
|
102
|
+
])
|
|
103
|
+
|| !isNonEmptyString(valueArg.projectsRoot)
|
|
104
|
+
|| !plugins.path.isAbsolute(valueArg.projectsRoot)
|
|
105
|
+
|| plugins.path.normalize(valueArg.projectsRoot) !== valueArg.projectsRoot
|
|
112
106
|
|| !isValidPort(valueArg.controllerPort)
|
|
113
107
|
|| !isValidPort(valueArg.opencodePort)
|
|
114
108
|
|| valueArg.controllerPort === valueArg.opencodePort
|
|
115
109
|
|| !isNonEmptyString(valueArg.publicOrigin)
|
|
116
110
|
|| !isNonEmptyString(valueArg.rpId)
|
|
117
|
-
|| !isNonEmptyString(valueArg.workspaceDirectory)
|
|
118
|
-
|| !plugins.path.isAbsolute(valueArg.workspaceDirectory)
|
|
119
111
|
|| !['local', 'proxy'].includes(valueArg.tlsMode as string)
|
|
120
112
|
) {
|
|
121
113
|
return false;
|
package/ts/classes.authstore.ts
CHANGED
|
@@ -154,11 +154,7 @@ const cloneRuntimeConfig = (
|
|
|
154
154
|
controllerPort: runtimeConfigArg.controllerPort,
|
|
155
155
|
publicOrigin: runtimeConfigArg.publicOrigin,
|
|
156
156
|
rpId: runtimeConfigArg.rpId,
|
|
157
|
-
|
|
158
|
-
// The key must be absent, not undefined-valued, for exact persistence.
|
|
159
|
-
...(runtimeConfigArg.projectsRoot !== undefined
|
|
160
|
-
? { projectsRoot: runtimeConfigArg.projectsRoot }
|
|
161
|
-
: {}),
|
|
157
|
+
projectsRoot: runtimeConfigArg.projectsRoot,
|
|
162
158
|
opencodePort: runtimeConfigArg.opencodePort,
|
|
163
159
|
tlsMode: runtimeConfigArg.tlsMode,
|
|
164
160
|
});
|
|
@@ -169,7 +165,6 @@ const runtimeConfigsEqual = (
|
|
|
169
165
|
): boolean => leftArg.controllerPort === rightArg.controllerPort
|
|
170
166
|
&& leftArg.publicOrigin === rightArg.publicOrigin
|
|
171
167
|
&& leftArg.rpId === rightArg.rpId
|
|
172
|
-
&& leftArg.workspaceDirectory === rightArg.workspaceDirectory
|
|
173
168
|
&& leftArg.projectsRoot === rightArg.projectsRoot
|
|
174
169
|
&& leftArg.opencodePort === rightArg.opencodePort
|
|
175
170
|
&& leftArg.tlsMode === rightArg.tlsMode;
|
|
@@ -726,39 +721,6 @@ export class SmartDataAuthStore implements IAuthStore {
|
|
|
726
721
|
const existing = await ControllerAuthModel.exact.findStoredOne({ id: controllerId });
|
|
727
722
|
if (existing) {
|
|
728
723
|
const document = ControllerAuthModel.exact.toPersisted(existing);
|
|
729
|
-
if (
|
|
730
|
-
document.config.projectsRoot === undefined
|
|
731
|
-
&& runtimeConfigArg.projectsRoot !== undefined
|
|
732
|
-
) {
|
|
733
|
-
// One-time v2 upgrade: adopt the resolved projects root into the
|
|
734
|
-
// stored document; every other field must still match exactly.
|
|
735
|
-
this.assertRuntimeConfigMatches(
|
|
736
|
-
{ ...document.config, projectsRoot: runtimeConfigArg.projectsRoot },
|
|
737
|
-
runtimeConfigArg,
|
|
738
|
-
);
|
|
739
|
-
const upgraded = cloneRuntimeConfig(runtimeConfigArg);
|
|
740
|
-
const result = await ControllerAuthModel.exact.transition({
|
|
741
|
-
current: existing,
|
|
742
|
-
change: (model) => {
|
|
743
|
-
model.config = upgraded;
|
|
744
|
-
},
|
|
745
|
-
});
|
|
746
|
-
if (result.status === 'transitioned') {
|
|
747
|
-
return this.finalizeRuntimeConfigDocument(
|
|
748
|
-
ControllerAuthModel.exact.toPersisted(result.document),
|
|
749
|
-
runtimeConfigArg,
|
|
750
|
-
);
|
|
751
|
-
}
|
|
752
|
-
const reconciled = await ControllerAuthModel.exact.findStoredOne({ id: controllerId });
|
|
753
|
-
if (!reconciled) {
|
|
754
|
-
throw new AuthError(
|
|
755
|
-
'ambiguous_write',
|
|
756
|
-
'Controller runtime configuration upgrade has an ambiguous outcome.',
|
|
757
|
-
);
|
|
758
|
-
}
|
|
759
|
-
const reconciledDocument = ControllerAuthModel.exact.toPersisted(reconciled);
|
|
760
|
-
return this.finalizeRuntimeConfigDocument(reconciledDocument, runtimeConfigArg);
|
|
761
|
-
}
|
|
762
724
|
return this.finalizeRuntimeConfigDocument(document, runtimeConfigArg);
|
|
763
725
|
}
|
|
764
726
|
|