@orion-agents/orion-code 0.3.19 → 0.3.21
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 +65 -3
- package/README.md +5 -7
- package/README.zh-CN.md +4 -5
- package/dist/runtime/release-receipts.d.ts +19 -2
- package/dist/runtime/release-receipts.js +58 -6
- package/dist/services/workspace-roots-registry.d.ts +46 -0
- package/dist/services/workspace-roots-registry.js +199 -0
- package/dist/web/protocol.d.ts +36 -0
- package/dist/web/server.js +45 -0
- package/dist/web/workbench-controller.d.ts +22 -1
- package/dist/web/workbench-controller.js +107 -0
- package/dist/web/workspace-discovery.d.ts +92 -0
- package/dist/web/workspace-discovery.js +324 -0
- package/dist/web-client/assets/{FilesPanel-DGWgLv6R.js → FilesPanel-CKeXPofZ.js} +1 -1
- package/dist/web-client/assets/{GitPanel-DIuJNXoB.js → GitPanel-DollPxv0.js} +1 -1
- package/dist/web-client/assets/{ReviewPanel-YWZXJdm5.js → ReviewPanel-CNADacIn.js} +1 -1
- package/dist/web-client/assets/{TerminalPanel-DPSMepio.js → TerminalPanel-Cyns4uJt.js} +1 -1
- package/dist/web-client/assets/index-BhGhORAX.js +20 -0
- package/dist/web-client/assets/{index--hnGXXjj.css → index-DCYKIeOj.css} +1 -1
- package/dist/web-client/assets/{useAutoNotice-h7c0Iz1E.js → useAutoNotice-BLADeFFj.js} +1 -1
- package/dist/web-client/index.html +2 -2
- package/npm-shrinkwrap.json +27 -27
- package/package.json +1 -1
- package/dist/web-client/assets/index-DYeu_rs7.js +0 -20
package/CHANGELOG.md
CHANGED
|
@@ -22,6 +22,67 @@ which is **not** a pass.
|
|
|
22
22
|
|
|
23
23
|
## [Unreleased]
|
|
24
24
|
|
|
25
|
+
## [0.3.21] — UNRELEASED
|
|
26
|
+
|
|
27
|
+
> **Status: candidate.** Open. Adds user-authorized local project-root discovery to
|
|
28
|
+
> `LOCAL WORKSPACE` (bounded scan, explicit inspect → confirm → activate reuse, no
|
|
29
|
+
> Home/network-volume scanning, no silent registration) and corrects the v0.3.20
|
|
30
|
+
> release-facts documentation (`docs/plan/evidence/v0.3.20-e2e/release-receipt-v0.3.20.md`).
|
|
31
|
+
> Not merged, tagged or published.
|
|
32
|
+
|
|
33
|
+
### Added (planned)
|
|
34
|
+
|
|
35
|
+
- Saved project roots registry (`WorkspaceRootsRegistryV1`) and a bounded, cancellable
|
|
36
|
+
discovery service with depth/candidate/time budgets and partial-result semantics.
|
|
37
|
+
|
|
38
|
+
### Fixed (planned)
|
|
39
|
+
|
|
40
|
+
- README / README.zh-CN / CHANGELOG release-state drift for v0.3.20, plus a release-check
|
|
41
|
+
guard that fails when a README describes a tagged version as unpublished.
|
|
42
|
+
|
|
43
|
+
## [0.3.20] — 2026-10-07
|
|
44
|
+
|
|
45
|
+
> **Status: published.** Released as `@orion-agents/orion-code@0.3.20` from
|
|
46
|
+
> `refs/tags/v0.3.20`, and merged to `main` as `e658932` (#261). Repairs the Web E2E
|
|
47
|
+
> suite that has been red since v0.3.11 (the dock's tabs became rail buttons)
|
|
48
|
+
> independently of the Git panel work, and fixes the product defects the repair exposed.
|
|
49
|
+
>
|
|
50
|
+
> ### Fixed (product)
|
|
51
|
+
>
|
|
52
|
+
> - The work-panel rail no longer sets `aria-orientation` on `role=navigation`
|
|
53
|
+
> (axe `aria-allowed-attr`, critical), and the navigation aside keeps one stable
|
|
54
|
+
> id so `aria-controls` always resolves.
|
|
55
|
+
> - The first archived-sessions load no longer storms the host (2476 requests
|
|
56
|
+
> measured in ~80s): the loading state claims workspace ownership like the
|
|
57
|
+
> failure path already did.
|
|
58
|
+
> - The narrow-width navigation toggle and the drawer close button are restored
|
|
59
|
+
> (v0.3.15 removed them with the conversation header, leaving sub-~800px
|
|
60
|
+
> viewports without a pointer entry while their CSS and focus wiring survived).
|
|
61
|
+
> - A bare Escape no longer moves focus to a stale drawer trigger, and the
|
|
62
|
+
> shortcut-help dialog restores focus to its trigger explicitly; its scrollable
|
|
63
|
+
> body is keyboard focusable (axe `scrollable-region-focusable`, serious).
|
|
64
|
+
>
|
|
65
|
+
> ### Test infrastructure
|
|
66
|
+
>
|
|
67
|
+
> - Every stale E2E locator and copy assertion traced to its contract change and
|
|
68
|
+
> rewritten; all previously failing specs pass locally (full suite 100/103 —
|
|
69
|
+
> the remainder are sandbox-only: a bulk-delete shim, two perf bounds).
|
|
70
|
+
> - `allowExpectedConsoleErrors(testInfo, text, maximum)` bounds the console
|
|
71
|
+
> noise that deliberate failure journeys (CAS conflicts, phantom catalog
|
|
72
|
+
> sessions) produce; the evidence capture caps can be raised via env for
|
|
73
|
+
> diagnosis.
|
|
74
|
+
> - The receipt upload only fires when the evidence gate actually ran.
|
|
75
|
+
>
|
|
76
|
+
> ### Security
|
|
77
|
+
>
|
|
78
|
+
> - The production dependency tree is patched to zero known high advisories:
|
|
79
|
+
> `js-yaml` 4.3.2 (the advisory the audit job originally failed on) plus the
|
|
80
|
+
> `axios` 1.20.0, `undici` 6.29.0, `brace-expansion`, `braces` and `figlet`
|
|
81
|
+
> patch updates. The audit job now audits `--omit=dev`: the remaining high
|
|
82
|
+
> advisories sit in dev-only tooling (jest 29 → 30, typescript-eslint 7 → 8,
|
|
83
|
+
> nodemon) whose fixes are major tooling upgrades; they are follow-up work,
|
|
84
|
+
> not shipped code.
|
|
85
|
+
|
|
25
86
|
## [0.3.19] — 2026-09-19
|
|
26
87
|
|
|
27
88
|
> **Status: published.** Released as `@orion-agents/orion-code@0.3.19` from
|
|
@@ -39,7 +100,7 @@ which is **not** a pass.
|
|
|
39
100
|
affordances never activated on any load whose status was not already cached. The observer is
|
|
40
101
|
now attached with a callback ref, so it follows the node in and out of the tree.
|
|
41
102
|
- **A discarded diff read left the pane blank with a file still selected.** Only a newer
|
|
42
|
-
|
|
103
|
+
_selection_ supersedes a read now. The document states the revision it was rendered against,
|
|
43
104
|
so accepting it and letting `stale` tell the truth is both safer and correct — dropping it
|
|
44
105
|
because the 2s visible poll had bumped the revision abandoned the read with nothing to
|
|
45
106
|
re-issue it.
|
|
@@ -50,7 +111,7 @@ which is **not** a pass.
|
|
|
50
111
|
### Added
|
|
51
112
|
|
|
52
113
|
- **A bare repository is no longer reported as "not a repository".** `rev-parse
|
|
53
|
-
|
|
114
|
+
--show-toplevel` fails for a bare repository too, and that single failure was read as "no
|
|
54
115
|
repository here". Status now carries `repositoryKind: 'worktree' | 'bare' | 'absent'` and
|
|
55
116
|
`hasWorktree`; every worktree-backed surface gates on `hasWorktree`, which is the only flag
|
|
56
117
|
that cannot be wrong. A bare repository's branch is reported instead of hidden, its change
|
|
@@ -109,6 +170,7 @@ which is **not** a pass.
|
|
|
109
170
|
and the agent-led file-change scenario are still open.
|
|
110
171
|
|
|
111
172
|
---
|
|
173
|
+
|
|
112
174
|
## [0.3.17]
|
|
113
175
|
|
|
114
176
|
> **Status: candidate.** Opening a local project is made fast and honest
|
|
@@ -255,7 +317,7 @@ which is **not** a pass.
|
|
|
255
317
|
same timestamp granularity and with an unchanged size was not re-hashed, so
|
|
256
318
|
`git add` succeeded while changing nothing. The copy now preserves the
|
|
257
319
|
original timestamps, and staging verifies each path with `hash-object
|
|
258
|
-
|
|
320
|
+
--path` against the index entry.
|
|
259
321
|
- **`localhost` was rejected site-wide:** the Host and Origin checks compared
|
|
260
322
|
header strings exactly, so `http://localhost:<port>` returned 421 (and, with
|
|
261
323
|
only the Host check fixed, every mutation would have returned 403). Both now
|
package/README.md
CHANGED
|
@@ -2,12 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
Local-first, goal-driven coding agent for the terminal and browser.
|
|
4
4
|
|
|
5
|
-
> v0.3.
|
|
6
|
-
>
|
|
7
|
-
>
|
|
8
|
-
>
|
|
9
|
-
> dropped the reading position, and a bare repository reported as "not a
|
|
10
|
-
> repository". Published to npm as 0.3.19 and tagged `v0.3.19`.
|
|
5
|
+
> v0.3.21 candidate — open. Adds user-authorized local project-root discovery to
|
|
6
|
+
> `LOCAL WORKSPACE` (bounded scan, explicit inspect → confirm → activate reuse). The previous
|
|
7
|
+
> release 0.3.20 is on npm (registry `latest`) and tagged `v0.3.20` (`e658932`, #261) —
|
|
8
|
+
> release facts: `docs/plan/evidence/v0.3.20-e2e/release-receipt-v0.3.20.md`.
|
|
11
9
|
|
|
12
10
|
[中文说明](README.zh-CN.md) ·
|
|
13
11
|
[v0.3.7 plan](docs/plan/v0.3.7-left-rail-improvement-plan.md) ·
|
|
@@ -172,7 +170,7 @@ current development environments. Node 20 is upstream EOL and is no longer a v0.
|
|
|
172
170
|
After the immutable `0.3.5` npm receipt exists:
|
|
173
171
|
|
|
174
172
|
```bash
|
|
175
|
-
npm install -g @orion-agents/orion-code@0.3.
|
|
173
|
+
npm install -g @orion-agents/orion-code@0.3.21
|
|
176
174
|
orion --version
|
|
177
175
|
orion doctor
|
|
178
176
|
```
|
package/README.zh-CN.md
CHANGED
|
@@ -2,10 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
面向终端与浏览器、本地优先的目标驱动 Coding Agent。
|
|
4
4
|
|
|
5
|
-
> v0.3.
|
|
6
|
-
>
|
|
7
|
-
>
|
|
8
|
-
> 裸仓库被当成「不是仓库」。已发布 npm 0.3.19 并打 tag `v0.3.19`。
|
|
5
|
+
> v0.3.21 候选版本:进行中。为 `LOCAL WORKSPACE` 增加用户显式授权的项目根目录发现(有界
|
|
6
|
+
> 扫描、复用 inspect → confirm → activate)。上一版 0.3.20 已发布 npm(registry `latest`)
|
|
7
|
+
> 并打 tag `v0.3.20`(`e658932`,#261)——发布事实:`docs/plan/evidence/v0.3.20-e2e/release-receipt-v0.3.20.md`。
|
|
9
8
|
|
|
10
9
|
[English](README.md) ·
|
|
11
10
|
[v0.3.7 方案](docs/plan/v0.3.7-left-rail-improvement-plan.md) ·
|
|
@@ -102,7 +101,7 @@ Current。Node 20 已结束上游维护,不再属于 v0.3 Runtime 合同。
|
|
|
102
101
|
当 npm 已存在不可变的 `0.3.4` 发布凭据后:
|
|
103
102
|
|
|
104
103
|
```bash
|
|
105
|
-
npm install -g @orion-agents/orion-code@0.3.
|
|
104
|
+
npm install -g @orion-agents/orion-code@0.3.21
|
|
106
105
|
orion --version
|
|
107
106
|
orion doctor
|
|
108
107
|
```
|
|
@@ -8,12 +8,28 @@ export declare const WEB_E2E_WEB31_SCENARIOS_V1: readonly ["WEB31-P0-01", "WEB31
|
|
|
8
8
|
export declare const WEB_E2E_WEB32_SCENARIOS_V1: readonly ["WEB32-P0-01", "WEB32-P0-02", "WEB32-P0-03", "WEB32-P0-04", "WEB32-P0-05", "WEB32-P0-06", "WEB32-P0-07", "WEB32-P0-08", "WEB32-P0-09", "WEB32-P0-10", "WEB32-P0-11", "WEB32-P0-12"];
|
|
9
9
|
export declare const WEB_E2E_WEB33_THEME_SCENARIOS_V1: readonly ["WEB33-P0-01", "WEB33-P0-02", "WEB33-P0-03", "WEB33-P0-04", "WEB33-P0-05", "WEB33-P0-06", "WEB33-P0-07", "WEB33-P0-08", "WEB33-P0-09", "WEB33-P0-10", "WEB33-P0-11", "WEB33-P0-12"];
|
|
10
10
|
export declare const WEB_E2E_WEB33_SESSION_SCENARIOS_V1: readonly ["WEB33-P0-16", "WEB33-P0-17", "WEB33-P0-18", "WEB33-P0-19", "WEB33-P0-20", "WEB33-P0-21", "WEB33-P0-22", "WEB33-P0-23", "WEB33-P0-24"];
|
|
11
|
-
|
|
11
|
+
/**
|
|
12
|
+
* v0.3.20 — the WEB33 scenarios whose specs shipped between v0.3.13 and
|
|
13
|
+
* v0.3.15 (rail, transcript navigator, brand, files editor, resource layout)
|
|
14
|
+
* and used these ids before they were registered. The receipts' exact
|
|
15
|
+
* coverage check requires every running id to be registered exactly once:
|
|
16
|
+
* WEB33-P0-16/17/18 belong to the session-concurrency journeys, so the
|
|
17
|
+
* v0.3.13 resource-layout specs were renumbered to P0-41/42/43.
|
|
18
|
+
*/
|
|
19
|
+
export declare const WEB_E2E_WEB33_RESOURCE_SCENARIOS_V1: readonly ["WEB33-P0-13", "WEB33-P0-14", "WEB33-P0-15", "WEB33-P0-25", "WEB33-P0-26", "WEB33-P0-27", "WEB33-P0-28", "WEB33-P0-29", "WEB33-P0-30", "WEB33-P0-31", "WEB33-P0-32", "WEB33-P0-34", "WEB33-P0-41", "WEB33-P0-42", "WEB33-P0-43"];
|
|
20
|
+
/**
|
|
21
|
+
* v0.3.15 — keyboard and shell surface journeys.
|
|
22
|
+
*/
|
|
23
|
+
export declare const WEB_E2E_WEB36_SCENARIOS_V1: readonly ["WEB36-P0-01", "WEB36-P1-01", "WEB36-P1-02", "WEB36-P1-03"];
|
|
24
|
+
/**
|
|
25
|
+
* v0.3.16 — transcript / workspace surface journeys.
|
|
26
|
+
*/
|
|
27
|
+
export declare const WEB_E2E_WEB37_SCENARIOS_V1: readonly ["WEB37-P0-01", "WEB37-P0-02", "WEB37-P0-03"];
|
|
12
28
|
/**
|
|
13
29
|
* v0.3.17 — LOCAL WORKSPACE open path. The picker is driven by a script file
|
|
14
30
|
* (`ORION_CODE_WEB_PICKER_FIXTURE`) so CI never opens a real Finder dialog.
|
|
15
31
|
*/
|
|
16
|
-
export declare const WEB_E2E_WEB38_SCENARIOS_V1: readonly ["WEB38-P0-01", "WEB38-P0-02", "WEB38-P0-03", "WEB38-P0-04", "WEB38-P0-05"];
|
|
32
|
+
export declare const WEB_E2E_WEB38_SCENARIOS_V1: readonly ["WEB38-P0-01", "WEB38-P0-02", "WEB38-P0-03", "WEB38-P0-04", "WEB38-P0-05", "WEB38-P0-06"];
|
|
17
33
|
/**
|
|
18
34
|
* v0.3.17 — Git right-hand work panel (`docs/plan/v0.3.17-plan.md`).
|
|
19
35
|
*
|
|
@@ -21,6 +37,7 @@ export declare const WEB_E2E_WEB38_SCENARIOS_V1: readonly ["WEB38-P0-01", "WEB38
|
|
|
21
37
|
* actually verify; the per-stage unit suites are registered separately.
|
|
22
38
|
*/
|
|
23
39
|
export declare const WEB_E2E_WEB317_SCENARIOS_V1: readonly string[];
|
|
40
|
+
export declare const WEB_E2E_WEB33_SCENARIOS_V1: readonly ("WEB33-P0-01" | "WEB33-P0-02" | "WEB33-P0-03" | "WEB33-P0-04" | "WEB33-P0-05" | "WEB33-P0-06" | "WEB33-P0-07" | "WEB33-P0-08" | "WEB33-P0-09" | "WEB33-P0-10" | "WEB33-P0-11" | "WEB33-P0-12" | "WEB33-P0-16" | "WEB33-P0-17" | "WEB33-P0-18" | "WEB33-P0-19" | "WEB33-P0-20" | "WEB33-P0-21" | "WEB33-P0-22" | "WEB33-P0-23" | "WEB33-P0-24" | "WEB33-P0-13" | "WEB33-P0-14" | "WEB33-P0-15" | "WEB33-P0-25" | "WEB33-P0-26" | "WEB33-P0-27" | "WEB33-P0-28" | "WEB33-P0-29" | "WEB33-P0-30" | "WEB33-P0-31" | "WEB33-P0-32" | "WEB33-P0-34" | "WEB33-P0-41" | "WEB33-P0-42" | "WEB33-P0-43")[];
|
|
24
41
|
/**
|
|
25
42
|
* v0.3.19 — closing the v0.3.17 G317 assertion backlog.
|
|
26
43
|
*
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.WEB_E2E_CRITICAL_SCENARIOS_V1 = exports.WEB_E2E_WEB33_CRITICAL_SCENARIOS_V1 = exports.WEB_E2E_WEB32_CRITICAL_SCENARIOS_V1 = exports.WEB_E2E_WEB31_CRITICAL_SCENARIOS_V1 = exports.WEB_E2E_SETTINGS_SCENARIOS_V1 = exports.WEB_E2E_FULL_SCENARIOS_V1 = exports.WEB_E2E_WEB34_SCENARIOS_V1 = exports.WEB_E2E_WEB317_SCENARIOS_V1 = exports.WEB_E2E_WEB38_SCENARIOS_V1 = exports.
|
|
3
|
+
exports.WEB_E2E_CRITICAL_SCENARIOS_V1 = exports.WEB_E2E_WEB33_CRITICAL_SCENARIOS_V1 = exports.WEB_E2E_WEB32_CRITICAL_SCENARIOS_V1 = exports.WEB_E2E_WEB31_CRITICAL_SCENARIOS_V1 = exports.WEB_E2E_SETTINGS_SCENARIOS_V1 = exports.WEB_E2E_FULL_SCENARIOS_V1 = exports.WEB_E2E_WEB34_SCENARIOS_V1 = exports.WEB_E2E_WEB33_SCENARIOS_V1 = exports.WEB_E2E_WEB317_SCENARIOS_V1 = exports.WEB_E2E_WEB38_SCENARIOS_V1 = exports.WEB_E2E_WEB37_SCENARIOS_V1 = exports.WEB_E2E_WEB36_SCENARIOS_V1 = exports.WEB_E2E_WEB33_RESOURCE_SCENARIOS_V1 = exports.WEB_E2E_WEB33_SESSION_SCENARIOS_V1 = exports.WEB_E2E_WEB33_THEME_SCENARIOS_V1 = exports.WEB_E2E_WEB32_SCENARIOS_V1 = exports.WEB_E2E_WEB31_SCENARIOS_V1 = exports.WEB_E2E_LEGACY_SCENARIOS_V1 = exports.SUPPORTED_RELEASE_VERSION_LINE_V1 = exports.SUPPORTED_RELEASE_NODE_FLOORS_V1 = exports.SUPPORTED_RELEASE_NODE_MAJORS_V1 = exports.RELEASE_RECEIPT_VERSION_V1 = void 0;
|
|
4
4
|
exports.createTarballArtifactReceiptV1 = createTarballArtifactReceiptV1;
|
|
5
5
|
exports.verifyTarballArtifactReceiptV1 = verifyTarballArtifactReceiptV1;
|
|
6
6
|
exports.createRuntimeMatrixReceiptV1 = createRuntimeMatrixReceiptV1;
|
|
@@ -97,9 +97,47 @@ exports.WEB_E2E_WEB33_SESSION_SCENARIOS_V1 = Object.freeze([
|
|
|
97
97
|
'WEB33-P0-23',
|
|
98
98
|
'WEB33-P0-24',
|
|
99
99
|
]);
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
100
|
+
/**
|
|
101
|
+
* v0.3.20 — the WEB33 scenarios whose specs shipped between v0.3.13 and
|
|
102
|
+
* v0.3.15 (rail, transcript navigator, brand, files editor, resource layout)
|
|
103
|
+
* and used these ids before they were registered. The receipts' exact
|
|
104
|
+
* coverage check requires every running id to be registered exactly once:
|
|
105
|
+
* WEB33-P0-16/17/18 belong to the session-concurrency journeys, so the
|
|
106
|
+
* v0.3.13 resource-layout specs were renumbered to P0-41/42/43.
|
|
107
|
+
*/
|
|
108
|
+
exports.WEB_E2E_WEB33_RESOURCE_SCENARIOS_V1 = Object.freeze([
|
|
109
|
+
'WEB33-P0-13',
|
|
110
|
+
'WEB33-P0-14',
|
|
111
|
+
'WEB33-P0-15',
|
|
112
|
+
'WEB33-P0-25',
|
|
113
|
+
'WEB33-P0-26',
|
|
114
|
+
'WEB33-P0-27',
|
|
115
|
+
'WEB33-P0-28',
|
|
116
|
+
'WEB33-P0-29',
|
|
117
|
+
'WEB33-P0-30',
|
|
118
|
+
'WEB33-P0-31',
|
|
119
|
+
'WEB33-P0-32',
|
|
120
|
+
'WEB33-P0-34',
|
|
121
|
+
'WEB33-P0-41',
|
|
122
|
+
'WEB33-P0-42',
|
|
123
|
+
'WEB33-P0-43',
|
|
124
|
+
]);
|
|
125
|
+
/**
|
|
126
|
+
* v0.3.15 — keyboard and shell surface journeys.
|
|
127
|
+
*/
|
|
128
|
+
exports.WEB_E2E_WEB36_SCENARIOS_V1 = Object.freeze([
|
|
129
|
+
'WEB36-P0-01',
|
|
130
|
+
'WEB36-P1-01',
|
|
131
|
+
'WEB36-P1-02',
|
|
132
|
+
'WEB36-P1-03',
|
|
133
|
+
]);
|
|
134
|
+
/**
|
|
135
|
+
* v0.3.16 — transcript / workspace surface journeys.
|
|
136
|
+
*/
|
|
137
|
+
exports.WEB_E2E_WEB37_SCENARIOS_V1 = Object.freeze([
|
|
138
|
+
'WEB37-P0-01',
|
|
139
|
+
'WEB37-P0-02',
|
|
140
|
+
'WEB37-P0-03',
|
|
103
141
|
]);
|
|
104
142
|
/**
|
|
105
143
|
* v0.3.17 — LOCAL WORKSPACE open path. The picker is driven by a script file
|
|
@@ -111,6 +149,7 @@ exports.WEB_E2E_WEB38_SCENARIOS_V1 = Object.freeze([
|
|
|
111
149
|
'WEB38-P0-03',
|
|
112
150
|
'WEB38-P0-04',
|
|
113
151
|
'WEB38-P0-05',
|
|
152
|
+
'WEB38-P0-06',
|
|
114
153
|
]);
|
|
115
154
|
/**
|
|
116
155
|
* v0.3.17 — Git right-hand work panel (`docs/plan/v0.3.17-plan.md`).
|
|
@@ -125,6 +164,11 @@ exports.WEB_E2E_WEB317_SCENARIOS_V1 = Object.freeze([
|
|
|
125
164
|
'WEB39-P0-04',
|
|
126
165
|
'WEB39-P0-05',
|
|
127
166
|
]);
|
|
167
|
+
exports.WEB_E2E_WEB33_SCENARIOS_V1 = Object.freeze([
|
|
168
|
+
...exports.WEB_E2E_WEB33_THEME_SCENARIOS_V1,
|
|
169
|
+
...exports.WEB_E2E_WEB33_SESSION_SCENARIOS_V1,
|
|
170
|
+
...exports.WEB_E2E_WEB33_RESOURCE_SCENARIOS_V1,
|
|
171
|
+
]);
|
|
128
172
|
/**
|
|
129
173
|
* v0.3.19 — closing the v0.3.17 G317 assertion backlog.
|
|
130
174
|
*
|
|
@@ -145,6 +189,8 @@ exports.WEB_E2E_FULL_SCENARIOS_V1 = Object.freeze([
|
|
|
145
189
|
...exports.WEB_E2E_WEB31_SCENARIOS_V1,
|
|
146
190
|
...exports.WEB_E2E_WEB32_SCENARIOS_V1,
|
|
147
191
|
...exports.WEB_E2E_WEB33_SCENARIOS_V1,
|
|
192
|
+
...exports.WEB_E2E_WEB36_SCENARIOS_V1,
|
|
193
|
+
...exports.WEB_E2E_WEB37_SCENARIOS_V1,
|
|
148
194
|
...exports.WEB_E2E_WEB38_SCENARIOS_V1,
|
|
149
195
|
...exports.WEB_E2E_WEB317_SCENARIOS_V1,
|
|
150
196
|
...exports.WEB_E2E_WEB34_SCENARIOS_V1,
|
|
@@ -358,10 +404,16 @@ function createWebE2EReleaseReceiptV1(input) {
|
|
|
358
404
|
status: input.liveCanary === 'FAIL' ? 'fail' : 'pass',
|
|
359
405
|
detail: `observed=${input.liveCanary}`,
|
|
360
406
|
});
|
|
407
|
+
// v0.3.20 — the run scenario ids are sorted by the evidence assembler; the
|
|
408
|
+
// registries concatenate their per-epoch groups in delivery order, so both
|
|
409
|
+
// sides are compared as sorted sets (order-sensitive comparison against the
|
|
410
|
+
// declared order rejected runs that covered exactly the registered ids).
|
|
411
|
+
const sortedFull = [...exports.WEB_E2E_FULL_SCENARIOS_V1].sort();
|
|
412
|
+
const sortedCritical = [...exports.WEB_E2E_CRITICAL_SCENARIOS_V1].sort();
|
|
361
413
|
const primaryCoverage = primaryRuns.length === 3 &&
|
|
362
|
-
primaryRuns.every(run => run.role === 'primary' && sameOrderedValues(run.scenarioIds,
|
|
414
|
+
primaryRuns.every(run => run.role === 'primary' && sameOrderedValues(run.scenarioIds, sortedFull));
|
|
363
415
|
const runtimeCoverage = runtimeRuns.length === exports.SUPPORTED_RELEASE_NODE_MAJORS_V1.length &&
|
|
364
|
-
runtimeRuns.every(run => run.role === 'runtime' && sameOrderedValues(run.scenarioIds,
|
|
416
|
+
runtimeRuns.every(run => run.role === 'runtime' && sameOrderedValues(run.scenarioIds, sortedCritical));
|
|
365
417
|
checks.push({
|
|
366
418
|
id: 'scenario_coverage',
|
|
367
419
|
status: primaryCoverage && runtimeCoverage ? 'pass' : 'fail',
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/** Saved roots are user-authorized scan boundaries, not a project catalog. */
|
|
2
|
+
export declare const MAX_WORKSPACE_ROOTS = 12;
|
|
3
|
+
export interface WorkspaceRootEntryV1 {
|
|
4
|
+
readonly id: string;
|
|
5
|
+
readonly canonicalPath: string;
|
|
6
|
+
readonly label: string;
|
|
7
|
+
readonly addedAt: string;
|
|
8
|
+
readonly lastScannedAt?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface WorkspaceRootsRegistryOptions {
|
|
11
|
+
readonly storagePath?: string;
|
|
12
|
+
readonly now?: () => Date;
|
|
13
|
+
readonly createId?: () => string;
|
|
14
|
+
}
|
|
15
|
+
export declare class WorkspaceRootsRegistryError extends Error {
|
|
16
|
+
readonly code: 'workspace_roots_invalid' | 'workspace_roots_capacity' | 'workspace_root_unavailable';
|
|
17
|
+
constructor(message: string, code: 'workspace_roots_invalid' | 'workspace_roots_capacity' | 'workspace_root_unavailable');
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* v0.3.21 — durable registry of user-authorized project-root directories.
|
|
21
|
+
*
|
|
22
|
+
* A root is a scan boundary the user explicitly granted through the native
|
|
23
|
+
* picker (or an explicit migration path); it is a different entity from a
|
|
24
|
+
* workspace and lives in its own versioned file so discovery can never be
|
|
25
|
+
* confused with `WorkspaceRegistryV1` (opened workspaces). Adding a root never
|
|
26
|
+
* registers a workspace, activates a Context, or touches the session catalog.
|
|
27
|
+
*/
|
|
28
|
+
export declare class WorkspaceRootsRegistryV1 {
|
|
29
|
+
private readonly storagePath;
|
|
30
|
+
private readonly now;
|
|
31
|
+
private readonly createId;
|
|
32
|
+
constructor(options?: WorkspaceRootsRegistryOptions);
|
|
33
|
+
list(): readonly WorkspaceRootEntryV1[];
|
|
34
|
+
find(id: string): WorkspaceRootEntryV1 | undefined;
|
|
35
|
+
/** Canonicalize, dedupe by path, cap the count, and persist atomically. */
|
|
36
|
+
add(path: string): WorkspaceRootEntryV1;
|
|
37
|
+
remove(id: string): boolean;
|
|
38
|
+
/** Record that a discovery scan completed for this root (success or partial). */
|
|
39
|
+
markScanned(id: string, scannedAt?: string): WorkspaceRootEntryV1 | undefined;
|
|
40
|
+
private readDocument;
|
|
41
|
+
private writeDocument;
|
|
42
|
+
private ensureStorageDirectory;
|
|
43
|
+
}
|
|
44
|
+
/** Re-exported for tests: a root must be an existing, readable directory. */
|
|
45
|
+
export declare function canonicalWorkspaceRoot(path: string): string;
|
|
46
|
+
//# sourceMappingURL=workspace-roots-registry.d.ts.map
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.WorkspaceRootsRegistryV1 = exports.WorkspaceRootsRegistryError = exports.MAX_WORKSPACE_ROOTS = void 0;
|
|
4
|
+
exports.canonicalWorkspaceRoot = canonicalWorkspaceRoot;
|
|
5
|
+
const crypto_1 = require("crypto");
|
|
6
|
+
const fs_1 = require("fs");
|
|
7
|
+
const path_1 = require("path");
|
|
8
|
+
const paths_1 = require("../product/paths");
|
|
9
|
+
const atomic_write_1 = require("./atomic-write");
|
|
10
|
+
const file_lock_1 = require("./file-lock");
|
|
11
|
+
const workspace_registry_1 = require("./workspace-registry");
|
|
12
|
+
/** Translate the shared directory error into this registry's own code. */
|
|
13
|
+
function canonicalRootPath(path) {
|
|
14
|
+
try {
|
|
15
|
+
return (0, workspace_registry_1.canonicalWorkspaceDirectory)(path);
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
if (error instanceof workspace_registry_1.WorkspaceRegistryError) {
|
|
19
|
+
throw new WorkspaceRootsRegistryError('Workspace root directory is unavailable.', 'workspace_root_unavailable');
|
|
20
|
+
}
|
|
21
|
+
throw error;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
const WORKSPACE_ROOTS_SCHEMA_VERSION = 1;
|
|
25
|
+
const DEFAULT_ROOTS_FILENAME = 'workspace-roots.v1.json';
|
|
26
|
+
/** Saved roots are user-authorized scan boundaries, not a project catalog. */
|
|
27
|
+
exports.MAX_WORKSPACE_ROOTS = 12;
|
|
28
|
+
class WorkspaceRootsRegistryError extends Error {
|
|
29
|
+
constructor(message, code) {
|
|
30
|
+
super(message);
|
|
31
|
+
this.code = code;
|
|
32
|
+
this.name = 'WorkspaceRootsRegistryError';
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
exports.WorkspaceRootsRegistryError = WorkspaceRootsRegistryError;
|
|
36
|
+
/**
|
|
37
|
+
* v0.3.21 — durable registry of user-authorized project-root directories.
|
|
38
|
+
*
|
|
39
|
+
* A root is a scan boundary the user explicitly granted through the native
|
|
40
|
+
* picker (or an explicit migration path); it is a different entity from a
|
|
41
|
+
* workspace and lives in its own versioned file so discovery can never be
|
|
42
|
+
* confused with `WorkspaceRegistryV1` (opened workspaces). Adding a root never
|
|
43
|
+
* registers a workspace, activates a Context, or touches the session catalog.
|
|
44
|
+
*/
|
|
45
|
+
class WorkspaceRootsRegistryV1 {
|
|
46
|
+
constructor(options = {}) {
|
|
47
|
+
this.storagePath = options.storagePath ?? (0, path_1.join)((0, paths_1.getConfigHome)(), DEFAULT_ROOTS_FILENAME);
|
|
48
|
+
this.now = options.now ?? (() => new Date());
|
|
49
|
+
this.createId = options.createId ?? crypto_1.randomUUID;
|
|
50
|
+
}
|
|
51
|
+
list() {
|
|
52
|
+
return freezeEntries(this.readDocument().entries);
|
|
53
|
+
}
|
|
54
|
+
find(id) {
|
|
55
|
+
return this.readDocument().entries.find(entry => entry.id === id);
|
|
56
|
+
}
|
|
57
|
+
/** Canonicalize, dedupe by path, cap the count, and persist atomically. */
|
|
58
|
+
add(path) {
|
|
59
|
+
const canonicalPath = canonicalRootPath(path);
|
|
60
|
+
this.ensureStorageDirectory();
|
|
61
|
+
return (0, file_lock_1.withFileLockSync)(this.storagePath, () => {
|
|
62
|
+
const document = this.readDocument();
|
|
63
|
+
const existing = document.entries.find(entry => entry.canonicalPath === canonicalPath);
|
|
64
|
+
if (existing)
|
|
65
|
+
return existing;
|
|
66
|
+
if (document.entries.length >= exports.MAX_WORKSPACE_ROOTS) {
|
|
67
|
+
throw new WorkspaceRootsRegistryError(`Workspace roots registry cannot contain more than ${exports.MAX_WORKSPACE_ROOTS} entries.`, 'workspace_roots_capacity');
|
|
68
|
+
}
|
|
69
|
+
const now = this.now().toISOString();
|
|
70
|
+
const entry = Object.freeze({
|
|
71
|
+
id: this.createId(),
|
|
72
|
+
canonicalPath,
|
|
73
|
+
label: workspaceRootLabel(canonicalPath),
|
|
74
|
+
addedAt: now,
|
|
75
|
+
});
|
|
76
|
+
this.writeDocument([...document.entries, entry]);
|
|
77
|
+
return entry;
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
remove(id) {
|
|
81
|
+
this.ensureStorageDirectory();
|
|
82
|
+
return (0, file_lock_1.withFileLockSync)(this.storagePath, () => {
|
|
83
|
+
const document = this.readDocument();
|
|
84
|
+
const entries = document.entries.filter(entry => entry.id !== id);
|
|
85
|
+
if (entries.length === document.entries.length)
|
|
86
|
+
return false;
|
|
87
|
+
this.writeDocument(entries);
|
|
88
|
+
return true;
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
/** Record that a discovery scan completed for this root (success or partial). */
|
|
92
|
+
markScanned(id, scannedAt) {
|
|
93
|
+
this.ensureStorageDirectory();
|
|
94
|
+
const at = (scannedAt ?? this.now().toISOString()).trim();
|
|
95
|
+
return (0, file_lock_1.withFileLockSync)(this.storagePath, () => {
|
|
96
|
+
const document = this.readDocument();
|
|
97
|
+
const target = document.entries.find(entry => entry.id === id);
|
|
98
|
+
if (!target)
|
|
99
|
+
return undefined;
|
|
100
|
+
const updated = Object.freeze({
|
|
101
|
+
...target,
|
|
102
|
+
lastScannedAt: Number.isFinite(Date.parse(at)) ? new Date(at).toISOString() : at,
|
|
103
|
+
});
|
|
104
|
+
this.writeDocument(document.entries.map(entry => (entry.id === id ? updated : entry)));
|
|
105
|
+
return updated;
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
readDocument() {
|
|
109
|
+
if (!(0, fs_1.existsSync)(this.storagePath)) {
|
|
110
|
+
return Object.freeze({ schemaVersion: WORKSPACE_ROOTS_SCHEMA_VERSION, entries: [] });
|
|
111
|
+
}
|
|
112
|
+
let parsed;
|
|
113
|
+
try {
|
|
114
|
+
parsed = JSON.parse((0, fs_1.readFileSync)(this.storagePath, 'utf8'));
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
throw new WorkspaceRootsRegistryError('Workspace roots registry is not valid JSON.', 'workspace_roots_invalid');
|
|
118
|
+
}
|
|
119
|
+
return parseDocument(parsed);
|
|
120
|
+
}
|
|
121
|
+
writeDocument(entries) {
|
|
122
|
+
this.ensureStorageDirectory();
|
|
123
|
+
const normalized = [...entries]
|
|
124
|
+
.sort((left, right) => Date.parse(right.addedAt) - Date.parse(left.addedAt) ||
|
|
125
|
+
left.label.localeCompare(right.label) ||
|
|
126
|
+
left.id.localeCompare(right.id))
|
|
127
|
+
.map(entry => ({ ...entry }));
|
|
128
|
+
(0, atomic_write_1.atomicWriteFileSync)(this.storagePath, `${JSON.stringify({ schemaVersion: WORKSPACE_ROOTS_SCHEMA_VERSION, entries: normalized }, null, 2)}\n`, { mode: 0o600, fsync: true });
|
|
129
|
+
}
|
|
130
|
+
ensureStorageDirectory() {
|
|
131
|
+
(0, fs_1.mkdirSync)((0, path_1.dirname)(this.storagePath), { recursive: true, mode: 0o700 });
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
exports.WorkspaceRootsRegistryV1 = WorkspaceRootsRegistryV1;
|
|
135
|
+
function parseDocument(value) {
|
|
136
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
137
|
+
return invalidDocument();
|
|
138
|
+
const row = value;
|
|
139
|
+
if (row.schemaVersion !== WORKSPACE_ROOTS_SCHEMA_VERSION || !Array.isArray(row.entries)) {
|
|
140
|
+
return invalidDocument();
|
|
141
|
+
}
|
|
142
|
+
if (row.entries.length > exports.MAX_WORKSPACE_ROOTS)
|
|
143
|
+
return invalidDocument();
|
|
144
|
+
const entries = row.entries.map(parseEntry);
|
|
145
|
+
if (new Set(entries.map(entry => entry.id)).size !== entries.length ||
|
|
146
|
+
new Set(entries.map(entry => entry.canonicalPath)).size !== entries.length) {
|
|
147
|
+
return invalidDocument();
|
|
148
|
+
}
|
|
149
|
+
return Object.freeze({
|
|
150
|
+
schemaVersion: WORKSPACE_ROOTS_SCHEMA_VERSION,
|
|
151
|
+
entries: freezeEntries(entries),
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
function parseEntry(value) {
|
|
155
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
156
|
+
return invalidDocument();
|
|
157
|
+
const row = value;
|
|
158
|
+
if (typeof row.id !== 'string' ||
|
|
159
|
+
!row.id.trim() ||
|
|
160
|
+
row.id.length > 128 ||
|
|
161
|
+
typeof row.canonicalPath !== 'string' ||
|
|
162
|
+
!row.canonicalPath.trim() ||
|
|
163
|
+
typeof row.label !== 'string' ||
|
|
164
|
+
!row.label.trim() ||
|
|
165
|
+
typeof row.addedAt !== 'string' ||
|
|
166
|
+
!Number.isFinite(Date.parse(row.addedAt)) ||
|
|
167
|
+
(row.lastScannedAt !== undefined &&
|
|
168
|
+
(typeof row.lastScannedAt !== 'string' || !Number.isFinite(Date.parse(row.lastScannedAt))))) {
|
|
169
|
+
return invalidDocument();
|
|
170
|
+
}
|
|
171
|
+
return Object.freeze({
|
|
172
|
+
id: row.id,
|
|
173
|
+
canonicalPath: row.canonicalPath,
|
|
174
|
+
label: row.label,
|
|
175
|
+
addedAt: row.addedAt,
|
|
176
|
+
...(row.lastScannedAt !== undefined ? { lastScannedAt: row.lastScannedAt } : {}),
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
function invalidDocument() {
|
|
180
|
+
throw new WorkspaceRootsRegistryError('Workspace roots registry document is invalid.', 'workspace_roots_invalid');
|
|
181
|
+
}
|
|
182
|
+
function freezeEntries(entries) {
|
|
183
|
+
return Object.freeze([...entries]
|
|
184
|
+
.sort((left, right) => Date.parse(right.addedAt) - Date.parse(left.addedAt) ||
|
|
185
|
+
left.label.localeCompare(right.label) ||
|
|
186
|
+
left.id.localeCompare(right.id))
|
|
187
|
+
.map(entry => Object.freeze({ ...entry })));
|
|
188
|
+
}
|
|
189
|
+
function workspaceRootLabel(path) {
|
|
190
|
+
const normalized = path.replace(/[\\/]+$/u, '');
|
|
191
|
+
return normalized.split(/[\\/]/u).filter(Boolean).at(-1) ?? normalized;
|
|
192
|
+
}
|
|
193
|
+
/** Re-exported for tests: a root must be an existing, readable directory. */
|
|
194
|
+
function canonicalWorkspaceRoot(path) {
|
|
195
|
+
const canonical = (0, workspace_registry_1.canonicalWorkspaceDirectory)(path);
|
|
196
|
+
(0, fs_1.statSync)(canonical);
|
|
197
|
+
return canonical;
|
|
198
|
+
}
|
|
199
|
+
//# sourceMappingURL=workspace-roots-registry.js.map
|
package/dist/web/protocol.d.ts
CHANGED
|
@@ -419,6 +419,42 @@ export interface WebWorkspaceInspectRequestV1 {
|
|
|
419
419
|
readonly path: string;
|
|
420
420
|
readonly source?: WebWorkspaceCandidateSourceV1;
|
|
421
421
|
}
|
|
422
|
+
export type WebWorkspaceRootDiscoveryHintV1 = 'git' | 'manifest';
|
|
423
|
+
export interface WebWorkspaceRootEntryV1 {
|
|
424
|
+
readonly id: string;
|
|
425
|
+
readonly canonicalPath: string;
|
|
426
|
+
readonly label: string;
|
|
427
|
+
readonly addedAt: string;
|
|
428
|
+
readonly lastScannedAt?: string;
|
|
429
|
+
}
|
|
430
|
+
export interface WebWorkspaceRootDiscoveryStatusV1 {
|
|
431
|
+
readonly status: 'ready' | 'partial';
|
|
432
|
+
readonly candidateCount: number;
|
|
433
|
+
readonly durationMs: number;
|
|
434
|
+
readonly errorCode?: string;
|
|
435
|
+
readonly cached: boolean;
|
|
436
|
+
}
|
|
437
|
+
export interface WebWorkspaceRootViewV1 extends WebWorkspaceRootEntryV1 {
|
|
438
|
+
/** The cached outcome of the most recent scan, when one is still fresh. */
|
|
439
|
+
readonly discovery: WebWorkspaceRootDiscoveryStatusV1 | null;
|
|
440
|
+
}
|
|
441
|
+
export interface WebWorkspaceRootCandidateV1 {
|
|
442
|
+
readonly rootId: string;
|
|
443
|
+
readonly relativePath: string;
|
|
444
|
+
readonly label: string;
|
|
445
|
+
readonly hint: WebWorkspaceRootDiscoveryHintV1;
|
|
446
|
+
readonly status: 'ready' | 'partial';
|
|
447
|
+
}
|
|
448
|
+
export interface WebWorkspaceRootDiscoverOutcomeV1 {
|
|
449
|
+
readonly requestId?: string;
|
|
450
|
+
readonly rootId: string;
|
|
451
|
+
readonly status: 'ready' | 'partial';
|
|
452
|
+
readonly candidates: readonly WebWorkspaceRootCandidateV1[];
|
|
453
|
+
readonly errorCode?: string;
|
|
454
|
+
readonly durationMs: number;
|
|
455
|
+
readonly scannedEntries: number;
|
|
456
|
+
readonly lastScannedAt?: string;
|
|
457
|
+
}
|
|
422
458
|
export type WebWorkspaceInvalidationReasonV1 = 'context-change' | 'filesystem-change' | 'terminal-command' | 'tool-finished';
|
|
423
459
|
export type WebToolDetailSummaryV1 = ToolDetailSummary;
|
|
424
460
|
export type WebToolDetailPageV1 = ToolDetailPage;
|
package/dist/web/server.js
CHANGED
|
@@ -300,6 +300,51 @@ async function handleRequest(context) {
|
|
|
300
300
|
sendJson(response, 200, { requestId, candidate });
|
|
301
301
|
return;
|
|
302
302
|
}
|
|
303
|
+
// v0.3.21 — saved project roots: read-only list. Roots are scan boundaries,
|
|
304
|
+
// not workspaces; listing them changes nothing.
|
|
305
|
+
if (method === 'GET' && path === '/workspaces/roots') {
|
|
306
|
+
sendJson(response, 200, { roots: context.workbench.listWorkspaceRoots() });
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
// v0.3.21 — add a root through the native picker's confirmed path. A local
|
|
310
|
+
// configuration mutation: transport-guarded, naturally idempotent (roots
|
|
311
|
+
// dedupe by canonical path), and never a Context mutation.
|
|
312
|
+
if (method === 'POST' && path === '/workspaces/roots/add') {
|
|
313
|
+
assertMutation(request, context.nonce, context.origin);
|
|
314
|
+
const body = requireRecord(await readJson(request), 'Workspace root request');
|
|
315
|
+
assertOnlyKeys(body, ['requestId', 'path']);
|
|
316
|
+
const requestId = requireUuid(body.requestId, 'requestId');
|
|
317
|
+
const rootPath = requireText(body.path, 'path', 4096);
|
|
318
|
+
const root = await context.workbench.addWorkspaceRoot(rootPath, requestId);
|
|
319
|
+
sendJson(response, 200, { requestId, root });
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
// v0.3.21 — remove a root. Cancels any in-flight scan for it first; the
|
|
323
|
+
// workspaces registry is untouched.
|
|
324
|
+
if (method === 'POST' && path === '/workspaces/roots/remove') {
|
|
325
|
+
assertMutation(request, context.nonce, context.origin);
|
|
326
|
+
const body = requireRecord(await readJson(request), 'Workspace root request');
|
|
327
|
+
assertOnlyKeys(body, ['requestId', 'rootId']);
|
|
328
|
+
const requestId = requireUuid(body.requestId, 'requestId');
|
|
329
|
+
const rootId = requireText(body.rootId, 'rootId', 128);
|
|
330
|
+
const removed = context.workbench.removeWorkspaceRoot(rootId);
|
|
331
|
+
sendJson(response, 200, { requestId, removed });
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
// v0.3.21 — bounded discovery inside one saved root. Read-only, abortable,
|
|
335
|
+
// host-wide single-flight; the newest request aborts the previous scan. A
|
|
336
|
+
// candidate is a hint: the client still runs inspect → confirm → activate.
|
|
337
|
+
if (method === 'POST' && path === '/workspaces/roots/discover') {
|
|
338
|
+
assertMutation(request, context.nonce, context.origin);
|
|
339
|
+
const body = requireRecord(await readJson(request), 'Workspace discovery request');
|
|
340
|
+
assertOnlyKeys(body, ['requestId', 'rootId', 'refresh']);
|
|
341
|
+
const requestId = requireUuid(body.requestId, 'requestId');
|
|
342
|
+
const rootId = requireText(body.rootId, 'rootId', 128);
|
|
343
|
+
const refresh = body.refresh === undefined ? true : body.refresh === true;
|
|
344
|
+
const outcome = await context.workbench.discoverWorkspaceRoot(rootId, requestId, { refresh });
|
|
345
|
+
sendJson(response, 200, outcome);
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
303
348
|
if (method === 'GET' && path === '/sessions') {
|
|
304
349
|
const contextGuard = requireContextGuardQuery(url);
|
|
305
350
|
sendJson(response, 200, collectionPage(url, 'sessions', context.workbench.listSessions(contextGuard), session => session.id));
|