@orion-agents/orion-code 0.3.4 → 0.3.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/CHANGELOG.md +157 -0
  2. package/README.md +59 -5
  3. package/README.zh-CN.md +6 -3
  4. package/dist/cli.js +58 -32
  5. package/dist/commands/context-tool-command-handlers.js +1 -5
  6. package/dist/commands/registry.js +1 -4
  7. package/dist/harness/intent.js +69 -16
  8. package/dist/harness/turn-summary.js +3 -1
  9. package/dist/memory/embeddings.js +2 -3
  10. package/dist/memory/prompts.js +1 -1
  11. package/dist/memory/relevant-finder.js +125 -15
  12. package/dist/memory/vector-store.js +13 -8
  13. package/dist/migration/migrate.js +8 -12
  14. package/dist/runtime/composer/history.js +1 -3
  15. package/dist/runtime/composer/layout.js +7 -4
  16. package/dist/runtime/legacy-thread-materializer.d.ts +3 -1
  17. package/dist/runtime/legacy-thread-materializer.js +15 -3
  18. package/dist/runtime/product-bootstrap.d.ts +12 -0
  19. package/dist/runtime/product-bootstrap.js +37 -22
  20. package/dist/runtime/rich-text/ansi-parser.js +12 -2
  21. package/dist/runtime/rich-text/layout.js +15 -19
  22. package/dist/runtime/subagents/budget.js +4 -1
  23. package/dist/runtime/subagents/policy.js +11 -4
  24. package/dist/runtime/subagents/provider-gate.js +6 -4
  25. package/dist/runtime/subagents/research-quality.js +3 -1
  26. package/dist/runtime/subagents/research-renderer.js +9 -2
  27. package/dist/runtime/subagents/result-parser.js +18 -7
  28. package/dist/runtime/thread-event-store.d.ts +5 -3
  29. package/dist/runtime/thread-event-store.js +29 -8
  30. package/dist/runtime/thread-projection.d.ts +17 -0
  31. package/dist/runtime/thread-projection.js +42 -1
  32. package/dist/runtime/tool-output-presentation.js +7 -7
  33. package/dist/runtime/turn-controller.js +1 -2
  34. package/dist/services/auth/auth.js +1 -4
  35. package/dist/services/auth/aws.js +24 -14
  36. package/dist/services/auto-fix/autoFixRunner.js +1 -1
  37. package/dist/services/auto-fix/index.d.ts +1 -1
  38. package/dist/services/diagnostic-tracking/diagnosticTracking.js +4 -2
  39. package/dist/services/diagnostic-tracking/index.d.ts +1 -1
  40. package/dist/services/effort.js +1 -9
  41. package/dist/services/file-glob.js +3 -4
  42. package/dist/services/mcp/transports.js +4 -4
  43. package/dist/services/model-catalog.js +179 -30
  44. package/dist/services/model-context.js +1 -1
  45. package/dist/services/provider-resilience/stream-recovery.js +1 -3
  46. package/dist/services/redaction.js +8 -0
  47. package/dist/services/session-storage.d.ts +28 -3
  48. package/dist/services/session-storage.js +70 -6
  49. package/dist/services/smart-routing.js +19 -11
  50. package/dist/services/verification-profile.js +17 -22
  51. package/dist/services/web-search-provider.js +21 -9
  52. package/dist/services/workspace-diff.js +10 -4
  53. package/dist/skills/builtin/code-review/SKILL.md +1 -1
  54. package/dist/skills/builtin/security-check/SKILL.md +1 -1
  55. package/dist/skills/builtin/test-gen/SKILL.md +1 -1
  56. package/dist/terminal-ui/editor.js +6 -2
  57. package/dist/terminal-ui/output-queue.js +9 -3
  58. package/dist/tools/lsp.js +12 -6
  59. package/dist/tui-core/frame.js +9 -7
  60. package/dist/tui-ui/transcript-cache.js +6 -6
  61. package/dist/tui-ui/transcript-inspector-surface.js +4 -1
  62. package/dist/tui-ui/transcript-inspector.js +3 -8
  63. package/dist/ui/box.js +14 -7
  64. package/dist/ui/markdown.js +4 -2
  65. package/dist/ui/shared/input-frame.js +12 -8
  66. package/dist/ui/stream-markdown.js +1 -1
  67. package/dist/ui/tool-preview.js +11 -3
  68. package/dist/ui/user-input.js +16 -10
  69. package/dist/web/cli-options.d.ts +13 -0
  70. package/dist/web/cli-options.js +47 -0
  71. package/dist/web/host-daemon.d.ts +61 -0
  72. package/dist/web/host-daemon.js +193 -0
  73. package/dist/web/launch.d.ts +3 -0
  74. package/dist/web/launch.js +24 -0
  75. package/dist/web/protocol.d.ts +2 -0
  76. package/dist/web/protocol.js +1 -0
  77. package/dist/web/review-service.js +41 -13
  78. package/dist/web/server.js +69 -0
  79. package/dist/web/session-runtime-registry.d.ts +14 -0
  80. package/dist/web/session-runtime-registry.js +23 -0
  81. package/dist/web/workbench-controller.d.ts +55 -0
  82. package/dist/web/workbench-controller.js +224 -14
  83. package/dist/web-client/assets/{DiffViewer-D6mG729y.js → DiffViewer-MMhvMif-.js} +1 -1
  84. package/dist/web-client/assets/{FilesPanel-CCDoAZ1J.js → FilesPanel-4litEN1k.js} +1 -1
  85. package/dist/web-client/assets/{GitPanel-DtNgiH7A.js → GitPanel-BVwlDRVo.js} +1 -1
  86. package/dist/web-client/assets/ReviewPanel-ChA0Zd2g.js +1 -0
  87. package/dist/web-client/assets/{TerminalPanel-CNxMaBLp.js → TerminalPanel-D8C1uybv.js} +1 -1
  88. package/dist/web-client/assets/index-DQfpqPTD.css +1 -0
  89. package/dist/web-client/assets/index-Dv_uE9dk.js +17 -0
  90. package/dist/web-client/index.html +2 -2
  91. package/npm-shrinkwrap.json +356 -120
  92. package/package.json +3 -1
  93. package/dist/web-client/assets/ReviewPanel-CKEBSt6P.js +0 -1
  94. package/dist/web-client/assets/index-BBvNg3By.css +0 -1
  95. package/dist/web-client/assets/index-C6TDJwMC.js +0 -17
package/CHANGELOG.md CHANGED
@@ -22,6 +22,163 @@ which is **not** a pass.
22
22
 
23
23
  ## [Unreleased]
24
24
 
25
+ ## [0.3.9] — CANDIDATE
26
+
27
+ ## [0.3.9] — CANDIDATE
28
+
29
+ > **Status: candidate.** Deterministic-bug-fix release + background host lifecycle
30
+ > (issue #247 S1/S2). Not merged, tagged or published to npm.
31
+
32
+ ### Added
33
+
34
+ - `orion web --background`: starts the Web Workbench host detached from the
35
+ terminal, writes a pidfile (`~/.orion-code/web-host.<port>.pid`), redirects
36
+ logs to `~/.orion-code/logs/web-<port>.log` and prints URL/pid.
37
+ - `orion web status|stop|restart [--port <number>]`: pidfile-based host
38
+ management without third-party supervisors. Still binds 127.0.0.1 and keeps
39
+ the fail-closed approval/context/sandbox semantics unchanged (issue #247).
40
+
41
+ ### Fixed
42
+
43
+ - Issue-sweep triage: #222 (blank session rename), #223 (failed create leaves a
44
+ persisted active session), #234 (URL userinfo password starting with `token:`),
45
+ #238 (busy state-write clearing processing), #242 (stale Settings save into the
46
+ newly active workspace) were all already resolved on `main` — closed with
47
+ source evidence.
48
+ - Web redaction now covers uppercase X-prefixed environment assignments
49
+ (`X_CLIENT_SECRET=…`, `CLIENT_SECRET=…`, `AUTH_TOKEN=…`) whose secret suffix
50
+ hides behind underscore word characters (issue #241).
51
+ - Cmd/Ctrl+B can again open the Project Navigator at 761–799px: expanding the
52
+ navigation preference now resolves to a drawer and opens it explicitly instead
53
+ of leaving an invisible drawer (issue #226).
54
+
55
+ ### Tests
56
+
57
+ - `redaction-xprefixed-env` suite: X-prefixed env values, bare uppercase
58
+ assignments, #234 userinfo regression, #241 JSON keys.
59
+
60
+ ## [0.3.7] — CANDIDATE
61
+
62
+ > **Status: candidate.** This work is isolated on the codex/v0.3.7 worktree (left project
63
+ > rail interaction quality). It is not merged, tagged or published to npm.
64
+
65
+ ### Added
66
+
67
+ - Session **tags**: up to 8 user-assigned tags per Session (each ≤ 32 chars), persisted on
68
+ the Session record (`SessionMeta.tags`), projected into `WebSessionSummaryV1.tags`,
69
+ managed from a new "管理标签…" dialog (chip add via Enter/comma, per-chip remove) and
70
+ rendered as small badges on rail rows. Host endpoint `POST /sessions/:id/tags`.
71
+ - Session **archive / restore**: soft delete via `SessionMeta.archivedAt` that keeps every
72
+ file, hides the Session from default listings and counts, and surfaces it under a new
73
+ "已归档" rail section with a 还原 action (`POST /sessions/:id/archive|restore`;
74
+ `GET /workspaces/:id/sessions/archived`).
75
+ - Session **delete** from the web workbench: destructive confirmation dialog (explicit
76
+ "永久删除" guard, 不可恢复 copy) wired to the existing storage `deleteSession` through a
77
+ new `DELETE /sessions/:id` endpoint; refuse-while-busy checks in the controller.
78
+ - The rail rename affordance becomes a general **overflow menu** (`SessionRowMenu`,
79
+ keyboard navigable ↑↓/Home/End/Esc with focus restore) hosting 重命名… / 管理标签… /
80
+ 归档 / 删除… so future per-Session actions have a stable home.
81
+
82
+ ### Changed
83
+
84
+ - Active-workspace session listings (`listProjectSessions`, `listSessions`,
85
+ `countSessionsByProject`) exclude archived Sessions by default.
86
+ - Rail footer status copy is shortened to 已连接 / 离线 / … with full sentences moved to a
87
+ tooltip (`connectionTitle`); the collapsed rail dot keeps an accessible short label.
88
+ - Project tag chips, archived-section rows and the two new dialogs ship with their own
89
+ styles; stale `.session-rename`/`.session-row` rules were removed.
90
+
91
+ ### Fixed
92
+
93
+ - Rename failures are no longer silent: `RenameDialog` surfaces the server error inline
94
+ (`field-error`) instead of closing without feedback.
95
+ - Session-row menu hint ids are instance-unique (`useId`) so `aria-describedby` can never
96
+ point at another row's hint.
97
+ - The archived rail listing is bound to its owning Workspace: switching projects drops stale
98
+ foreign archive rows immediately and never flashes another project's archive while loading;
99
+ `session_deleted` also clears the stale runtime projection.
100
+
101
+ ### Tests
102
+
103
+ - New contract suites: `web-reducer-session-lifecycle` (14, incl. owner-binding + runtime
104
+ cleanup regression), `web-ui-SessionDialogs` (7), `web-ui-SessionRowMenu` (3),
105
+ `web-ui-WorkspaceDialog` (6), `session-storage-tags-archive` (5, incl. delete),
106
+ plus controller-level guards (tags validation / archive-restore / delete-while-busy /
107
+ foreign-workspace rejection / 404) in `web-workbench-controller.test.ts`; `web-ui-*` now
108
+ 112 cases green. E2E: `web-v037-session-lifecycle.spec.ts` (archive/restore, delete-confirm,
109
+ tags round-trip) runs in CI.
110
+
111
+ ## [0.3.6] — CANDIDATE
112
+
113
+ > **Status: candidate.** This work is isolated on the codex/v0.3.6 worktree (pure web
114
+ > workbench interaction quality). It is not merged, tagged or published to npm.
115
+
116
+ ### Added
117
+
118
+ - Component rendering contract layer for the web workbench: six `.test.tsx` suites
119
+ (89 cases) assert on `react-dom/server.renderToStaticMarkup` output without jsdom —
120
+ Markdown / StateDot / ShortcutHelp / DiffViewer / ApprovalCard / PanelResize.
121
+ - `StateDot` status indicator that never relies on colour alone (WCAG 1.4.1): every dot
122
+ carries a shape channel via `data-tone` (square / circle / pulsing ring / hollow ring /
123
+ triangle) plus a visually-hidden text label for assistive tech; applied to reasoning,
124
+ research sources, review evidence and the work-panel rail.
125
+ - Single-source keyboard binding table (`shortcuts.ts`) plus a `⌘/` shortcut help dialog
126
+ rendered from it, so the panel cannot drift from the live bindings; `matchesShortcut`
127
+ now resolves `Esc` → `Escape` so the help vocabulary matches real key events.
128
+ - Header theme-cycle button (system → light → dark, persisted through the settings
129
+ document); the `SettingsDialog` entry stays intact.
130
+ - Stacked notification toasts (P1-C): multiple notices queue without overwriting, each
131
+ non-recoverable non-error notice auto-dismisses after 5s, error / recovery notices stay
132
+ until acted on, and `aria-live` is graded (alert for errors, status otherwise).
133
+
134
+ ### Changed
135
+
136
+ - `PanelResizeHandle` is keyboard reachable with full separator semantics: `role=separator`
137
+ with live `aria-valuenow`/`aria-valuetext`, ←/→ steps of 2% (Shift 10%), Home/End jump,
138
+ Enter/Space reset to default; stepping logic is extracted as pure functions.
139
+ - Notification region becomes a stacked container (`.workbench-notice-stack`); cards slide
140
+ in on `opacity`/`transform` only and the whole stack is inert while drawers are open.
141
+ - Icon set gains `sun` / `moon` / `monitor` / `keyboard` glyphs.
142
+
143
+ ### Fixed
144
+
145
+ - Markdown heading off-by-one: `##` renders as `<h2>` (was `<h3>`), a lone `#` no longer
146
+ collapses to `<h6>`; >6 hashes stay a plain paragraph per GFM.
147
+
148
+ ## [0.3.5] — CANDIDATE
149
+
150
+ > **Status: candidate.** This work is isolated on the codex/v0.3.5 worktree. It is not
151
+ > merged, tagged or published to npm, and exact-artifact qualification remains a separate gate.
152
+
153
+ ### Added
154
+
155
+ - Version thread projection digests so v0.3.2/v0.3.3 cutover receipts keep verifying while new
156
+ projections carry the full diagnostic/compact shape; forged digests fail closed.
157
+ - Session snapshot sync state is decoupled from the Host transport: a Session snapshot failure
158
+ can no longer flip the live SSE to `replay-required`; Composer and command gates require both
159
+ transport live and the foreground Session snapshot ready.
160
+ - Host and browser diagnostics counters for Session switches, snapshot cache hits/loads/latency,
161
+ and Session actor allocation/eviction/closure, so "pure selection allocates no Runtime" is
162
+ provable from counters instead of prose.
163
+ - Runtime ownership across Workspace Context switches: the Host-level Session registry is created
164
+ once and preserved; control planes and Session actors borrow per-Workspace shared kernels, and
165
+ only Host shutdown closes every actor and kernel.
166
+
167
+ ### Changed
168
+
169
+ - Instant Session switching renders a cached projection immediately, refreshes with a bounded
170
+ tail snapshot in the background, and prefetches the most recent Sessions after a baseline.
171
+ - Version and release hygiene: package identity moves to 0.3.5, `release:check` recognizes
172
+ `codex/vX.Y.Z` and `GITHUB_HEAD_REF` release branches, and the Web rail no longer falls back to
173
+ a hardcoded version string.
174
+
175
+ ### Fixed
176
+
177
+ - Workspace Context activation no longer shuts down resident Session actors of other Workspaces
178
+ or requires every actor to be idle before switching the active control plane.
179
+ - A failed Context activation restores the previous control plane without rebuilding the Session
180
+ registry, so resident actors and their Runtime revisions survive the rollback.
181
+
25
182
  ## [0.3.4] — CANDIDATE
26
183
 
27
184
  > **Status: candidate.** This stabilization work is isolated on the v0.3.4 worktree. It is not
package/README.md CHANGED
@@ -2,11 +2,15 @@
2
2
 
3
3
  Local-first, goal-driven coding agent for the terminal and browser.
4
4
 
5
- > v0.3.4 candidate — a compatible security, terminal-ordering and release-evidence stabilization of
6
- > the v0.3.3 multi-Session Web Workbench. Candidate source is not an npm publication, Git tag, or
7
- > merge-ready release.
5
+ > v0.3.9 candidate — deterministic-bug sweep over the open issue set plus a native
6
+ > background-host lifecycle: `orion web --background` with pidfile/log management
7
+ > (`orion web status|stop|restart`). Candidate source is not an npm publication,
8
+ > Git tag, or merge-ready release.
8
9
 
9
10
  [中文说明](README.zh-CN.md) ·
11
+ [v0.3.7 plan](docs/plan/v0.3.7-left-rail-improvement-plan.md) ·
12
+ [v0.3.6 plan](docs/plan/v0.3.6-plan.md) ·
13
+ [v0.3.5 plan](docs/plan/v0.3.5-plan.md) ·
10
14
  [v0.3.4 stabilization plan](docs/plan/v0.3.4-stabilization-plan.md) ·
11
15
  [v0.3.4 contract](docs/architecture/v0.3.4-stabilization-contract.md) ·
12
16
  [v0.3.4 E2E plan](docs/test/v0.3.4-stabilization-e2e-plan.md) ·
@@ -24,6 +28,56 @@ Local-first, goal-driven coding agent for the terminal and browser.
24
28
  [Settings migration](docs/migration/v0.2.2-to-v0.3.0-settings.md) ·
25
29
  [real-state gallery](docs/assets/screenshots/v0.3.0-web/README.md)
26
30
 
31
+ ## What v0.3.7 changes
32
+
33
+ - **Left rail status copy** is shortened (已连接 / 离线 / …) with full sentences on hover.
34
+ - **Per-Session overflow menu** replaces the rename icon — 重命名… / 管理标签… / 归档 /
35
+ 删除…, fully keyboard navigable, deleting requires an explicit confirmation dialog.
36
+ - **Session tags**: up to 8 persisted tags per Session, edited as chips and shown as
37
+ badges on rows; archive hides a Session (all files kept) into a rail 已归档 section
38
+ with one-click restore.
39
+ - **Workspace dialog** rework: "打开或新增项目" with suggested workspaces, a collapsible
40
+ "其他工作区" list and busy/validation feedback.
41
+
42
+ ## What v0.3.6 changes
43
+
44
+ - **A component-rendering contract layer.** Six `.test.tsx` suites (89 cases) assert the web
45
+ workbench UI through `renderToStaticMarkup` — no jsdom needed — pinning Markdown tables and
46
+ headings, status dots, the shortcut help dialog, the diff viewer, the permission card and the
47
+ resize separator keyboard contract.
48
+ - **Keyboard-reachable resize separators.** Project navigation and work-panel splitters expose
49
+ full separator semantics (`aria-valuenow`/`aria-valuetext`) and answer ←/→ (±2%, Shift ±10%),
50
+ Home/End and Enter/Space from the keyboard.
51
+ - **Status without colour alone.** A shared `StateDot` adds a shape channel (`data-tone`) and a
52
+ screen-reader label to every status dot — reasoning, research sources, review evidence and the
53
+ work-panel rail.
54
+ - **Single-source shortcut help.** `⌘/` opens a help dialog rendered from the same binding table
55
+ the live handlers match against.
56
+ - **Theme cycle in the header.** One click walks system → light → dark and persists through the
57
+ settings document; light tokens stay consolidated in the blocksmith token sheet.
58
+ - **Stacked notifications.** Toasts queue instead of overwriting, auto-dismiss after 5s unless
59
+ error/recovery, and grade `aria-live` by severity.
60
+
61
+ ## What v0.3.5 changes
62
+
63
+ - **Session snapshot state is its own machine.** A failed HTTP snapshot marks only the foreground
64
+ Session `failed` and never flips the live SSE into `replay-required`; Composer and command gates
65
+ require both a live transport and a ready foreground Session snapshot.
66
+ - **Instant Session switching.** A cached projection renders synchronously, a bounded tail snapshot
67
+ refreshes it in the background, the most recent Sessions are prefetched after a baseline, and stale
68
+ responses can never overwrite the browser-local foreground.
69
+ - **Runtime ownership across Workspaces.** The Host-level Session actor registry is created once per
70
+ Host. A Workspace Context switch swaps only the active control plane; Session actors of other
71
+ Workspaces keep running on the Workspace kernel they were created with, and only Host shutdown
72
+ closes every actor and kernel. Failed switches roll back without rebuilding the registry.
73
+ - **Provable runtime ownership.** Host and browser diagnostics count Session snapshot requests,
74
+ latency and failures, Session switches and cache hits, and actor allocation/eviction/closure — so a
75
+ pure selection provably allocates zero Session Runtimes.
76
+ - **Release hygiene.** Version identity moves to 0.3.5; `release:check` understands `codex/vX.Y.Z`
77
+ and `GITHUB_HEAD_REF`; the Web rail no longer hardcodes a fallback version.
78
+
79
+ This branch also carries the merged [v0.3.4 stabilization](#what-v034-stabilizes) below.
80
+
27
81
  ## What v0.3.4 stabilizes
28
82
 
29
83
  - **Context-bound writes.** Settings mutations now carry the active Workspace and Context revision;
@@ -113,10 +167,10 @@ user extension boundaries.
113
167
  Node.js 22.12+, 24, and 26 are supported. Use Node 24 LTS for production or Node 26 Current for
114
168
  current development environments. Node 20 is upstream EOL and is no longer a v0.3 runtime.
115
169
 
116
- After the immutable `0.3.4` npm receipt exists:
170
+ After the immutable `0.3.5` npm receipt exists:
117
171
 
118
172
  ```bash
119
- npm install -g @orion-agents/orion-code@0.3.4
173
+ npm install -g @orion-agents/orion-code@0.3.9
120
174
  orion --version
121
175
  orion doctor
122
176
  ```
package/README.zh-CN.md CHANGED
@@ -2,10 +2,13 @@
2
2
 
3
3
  面向终端与浏览器、本地优先的目标驱动 Coding Agent。
4
4
 
5
- > v0.3.4 候选版本:对 v0.3.3 Session Web Workbench 进行兼容的安全、终端顺序与发布证据
6
- > 稳定化。Candidate 源码不代表已创建 npm 发布、Git tag 或达到可合并状态。
5
+ > v0.3.9 候选版本:对 OPEN issue 集的确定性 bug 清扫 + 原生后台 host 生命周期 ——
6
+ > `orion web --background`(pidfile/日志)与 `orion web status|stop|restart` 管理。
7
+ > Candidate 源码不代表已创建 npm 发布、Git tag 或达到可合并状态。
7
8
 
8
9
  [English](README.md) ·
10
+ [v0.3.7 方案](docs/plan/v0.3.7-left-rail-improvement-plan.md) ·
11
+ [v0.3.6 方案](docs/plan/v0.3.6-plan.md) ·
9
12
  [v0.3.4 稳定化方案](docs/plan/v0.3.4-stabilization-plan.md) ·
10
13
  [v0.3.4 合同](docs/architecture/v0.3.4-stabilization-contract.md) ·
11
14
  [v0.3.4 E2E 方案](docs/test/v0.3.4-stabilization-e2e-plan.md) ·
@@ -98,7 +101,7 @@ Current。Node 20 已结束上游维护,不再属于 v0.3 Runtime 合同。
98
101
  当 npm 已存在不可变的 `0.3.4` 发布凭据后:
99
102
 
100
103
  ```bash
101
- npm install -g @orion-agents/orion-code@0.3.4
104
+ npm install -g @orion-agents/orion-code@0.3.9
102
105
  orion --version
103
106
  orion doctor
104
107
  ```
package/dist/cli.js CHANGED
@@ -18,6 +18,8 @@ const product_bootstrap_1 = require("./runtime/product-bootstrap");
18
18
  const command_1 = require("./migration/command");
19
19
  const version_1 = require("./product/version");
20
20
  const web_1 = require("./web");
21
+ const host_daemon_1 = require("./web/host-daemon");
22
+ const cli_options_1 = require("./web/cli-options");
21
23
  const BRAND = chalk_1.default.hex('#FF6B35');
22
24
  const ACCENT = chalk_1.default.hex('#00D4AA');
23
25
  const DIM = chalk_1.default.dim;
@@ -52,41 +54,36 @@ function showCliHelp() {
52
54
  console.log(DIM('tui is the default product UI. terminal is the technical fallback; print is experimental.'));
53
55
  console.log();
54
56
  }
55
- function parseWebCliOptions(args) {
56
- let port = 3080;
57
- let open = true;
58
- let cwd = process.cwd();
59
- for (let index = 0; index < args.length; index++) {
60
- const argument = args[index];
61
- if (argument === '--no-open') {
62
- open = false;
63
- continue;
57
+ function showWebHelp() {
58
+ console.log('Usage: orion web [--port <number>] [--no-open] [--cwd <directory>] [--background]');
59
+ console.log('Starts the local-only Web Workbench on 127.0.0.1.');
60
+ console.log(' --background run detached in the background (pidfile + logs under ~/.orion-code)');
61
+ console.log('orion web status [--port <number>]');
62
+ console.log('orion web stop [--port <number>]');
63
+ console.log('orion web restart [--port <number>]');
64
+ console.log(' Manage a background host started with --background.');
65
+ }
66
+ async function manageBackgroundHost(command, args) {
67
+ const options = (0, cli_options_1.parseWebCliOptions)(args);
68
+ const status = (0, host_daemon_1.hostDaemonStatus)(options.port);
69
+ if (command === 'status') {
70
+ if (status.state === 'running') {
71
+ console.log(`orion web is running: ${status.pidfile.url} (pid ${status.pidfile.pid})`);
72
+ console.log(`workspace ${status.pidfile.workspace} · started ${new Date(status.pidfile.startedAt).toISOString()}`);
64
73
  }
65
- if (argument === '--port' || argument.startsWith('--port=')) {
66
- const portValue = argument === '--port' ? args[++index] : argument.slice('--port='.length);
67
- if (!portValue || !/^\d+$/u.test(portValue)) {
68
- throw new Error('--port must be an integer from 0 through 65535.');
69
- }
70
- port = Number(portValue);
71
- if (!Number.isSafeInteger(port) || port < 0 || port > 65535) {
72
- throw new Error('--port must be an integer from 0 through 65535.');
73
- }
74
- continue;
74
+ else if (status.state === 'stale') {
75
+ console.log(`orion web is not running (stale pidfile for pid ${status.pid} on port ${status.port}).`);
75
76
  }
76
- if (argument === '--cwd' || argument.startsWith('--cwd=')) {
77
- const cwdValue = argument === '--cwd' ? args[++index] : argument.slice('--cwd='.length);
78
- if (!cwdValue?.trim())
79
- throw new Error('--cwd requires a directory path.');
80
- cwd = cwdValue;
81
- continue;
77
+ else {
78
+ console.log(`orion web is not running on port ${options.port}.`);
82
79
  }
83
- throw new Error(`Unknown orion web option: ${argument}`);
80
+ return;
84
81
  }
85
- return { port, open, cwd };
86
- }
87
- function showWebHelp() {
88
- console.log('Usage: orion web [--port <number>] [--no-open] [--cwd <directory>]');
89
- console.log('Starts the local-only Web Workbench on 127.0.0.1.');
82
+ const result = await (0, host_daemon_1.stopBackgroundHost)(options.port);
83
+ if (result === 'stopped')
84
+ console.log(`orion web on port ${options.port} stopped.`);
85
+ else
86
+ console.log(`orion web was not running on port ${options.port}.`);
90
87
  }
91
88
  function parseCliOptions(args) {
92
89
  let uiRenderer = config_1.DEFAULT_UI_RENDERER;
@@ -159,7 +156,36 @@ async function main() {
159
156
  showWebHelp();
160
157
  return;
161
158
  }
162
- await (0, web_1.runOrionWeb)(parseWebCliOptions(args.slice(1)));
159
+ const rest = args.slice(1);
160
+ if (rest[0] === 'status' || rest[0] === 'stop') {
161
+ await manageBackgroundHost(rest[0], rest.slice(1));
162
+ return;
163
+ }
164
+ if (rest[0] === 'restart') {
165
+ await manageBackgroundHost('stop', rest.slice(1));
166
+ const restartOptions = (0, cli_options_1.parseWebCliOptions)(rest.slice(1));
167
+ await (0, web_1.runOrionWeb)({
168
+ ...restartOptions,
169
+ background: true,
170
+ backgroundArgs: [
171
+ ...(restartOptions.open ? [] : ['--no-open']),
172
+ '--port',
173
+ String(restartOptions.port),
174
+ '--cwd',
175
+ restartOptions.cwd,
176
+ ],
177
+ });
178
+ return;
179
+ }
180
+ const options = (0, cli_options_1.parseWebCliOptions)(rest);
181
+ const backgroundArgs = [
182
+ ...(options.open ? [] : ['--no-open']),
183
+ '--port',
184
+ String(options.port),
185
+ '--cwd',
186
+ options.cwd,
187
+ ];
188
+ await (0, web_1.runOrionWeb)({ ...options, backgroundArgs });
163
189
  return;
164
190
  }
165
191
  if (args[0] === 'migrate') {
@@ -206,11 +206,7 @@ async function showHarness(ctx, args = '') {
206
206
  },
207
207
  };
208
208
  const result = () => ({ success: true, output: lines.join('\n') });
209
- const tokens = args
210
- .trim()
211
- .toLowerCase()
212
- .split(/\s+/u)
213
- .filter(Boolean);
209
+ const tokens = args.trim().toLowerCase().split(/\s+/u).filter(Boolean);
214
210
  const explain = tokens[0] === 'explain';
215
211
  const json = tokens.includes('--json');
216
212
  if (tokens.some(token => token !== 'explain' && token !== '--json')) {
@@ -448,10 +448,7 @@ function registerBuiltinCommands(definitions) {
448
448
  if (ids.has(command.id))
449
449
  throw new Error(`Duplicate command id: ${command.id}`);
450
450
  ids.add(command.id);
451
- for (const value of [
452
- command.name,
453
- ...(command.aliases ?? []),
454
- ]) {
451
+ for (const value of [command.name, ...(command.aliases ?? [])]) {
455
452
  const normalized = value.toLowerCase();
456
453
  const owner = names.get(normalized);
457
454
  if (owner)
@@ -38,14 +38,24 @@ function extractTools(input) {
38
38
  return known.filter(tool => lower.includes(tool.toLowerCase()));
39
39
  }
40
40
  function extractConstraints(input) {
41
- const lines = input.split(/\r?\n/).map(line => line.trim()).filter(Boolean);
41
+ const lines = input
42
+ .split(/\r?\n/)
43
+ .map(line => line.trim())
44
+ .filter(Boolean);
42
45
  const constraintPattern = /(必须|要求|确保|保持|不要|不能|不准|避免|只|仅|must|should|required|ensure|keep|avoid|without|do not|don't|never|only)/i;
43
- return [...new Set(lines.filter(line => constraintPattern.test(line)).map(line => truncate(line, 180)))].slice(0, 10);
46
+ return [
47
+ ...new Set(lines.filter(line => constraintPattern.test(line)).map(line => truncate(line, 180))),
48
+ ].slice(0, 10);
44
49
  }
45
50
  function extractNonGoals(input) {
46
- const lines = input.split(/\r?\n/).map(line => line.trim()).filter(Boolean);
51
+ const lines = input
52
+ .split(/\r?\n/)
53
+ .map(line => line.trim())
54
+ .filter(Boolean);
47
55
  const nonGoalPattern = /(不要|不能|不准|避免|不引入|不新增|禁止|do not|don't|never|avoid|without)/i;
48
- return [...new Set(lines.filter(line => nonGoalPattern.test(line)).map(line => truncate(line, 180)))].slice(0, 8);
56
+ return [
57
+ ...new Set(lines.filter(line => nonGoalPattern.test(line)).map(line => truncate(line, 180))),
58
+ ].slice(0, 8);
49
59
  }
50
60
  function extractQuestions(input) {
51
61
  return input
@@ -75,45 +85,86 @@ function classifyKind(input, state) {
75
85
  return { kind: 'casual_or_feedback', confidence: 0.5, reason: 'empty input' };
76
86
  }
77
87
  if (isGenericContinue(text)) {
78
- return { kind: 'continue_current_task', confidence: 0.78, reason: 'generic continuation request' };
88
+ return {
89
+ kind: 'continue_current_task',
90
+ confidence: 0.78,
91
+ reason: 'generic continuation request',
92
+ };
79
93
  }
80
94
  if (isShortFeedback(text)) {
81
- return { kind: 'casual_or_feedback', confidence: 0.72, reason: 'short feedback should not replace the root objective' };
95
+ return {
96
+ kind: 'casual_or_feedback',
97
+ confidence: 0.72,
98
+ reason: 'short feedback should not replace the root objective',
99
+ };
82
100
  }
83
- if (matches(lower, /\b(verify|test|run tests?|check|lint|typecheck|build)\b/) || matches(text, /(验证|测试|检查|跑一下|运行测试|构建|编译)/)) {
101
+ if (matches(lower, /\b(verify|test|run tests?|check|lint|typecheck|build)\b/) ||
102
+ matches(text, /(验证|测试|检查|跑一下|运行测试|构建|编译)/)) {
84
103
  return { kind: 'verify_or_test', confidence: 0.82, reason: 'verification-oriented wording' };
85
104
  }
86
105
  if (hasRoot && isVersionScopedRefinement(text)) {
87
- return { kind: 'refine_current_task', confidence: 0.78, reason: 'version-scoped refinement within the active task' };
106
+ return {
107
+ kind: 'refine_current_task',
108
+ confidence: 0.78,
109
+ reason: 'version-scoped refinement within the active task',
110
+ };
88
111
  }
89
112
  if (matches(lower, /\b(harness|mcp|session|resume|compact|config|configuration|ui|npm|publish|push|pull request|pr)\b/) ||
90
113
  matches(text, /(配置|会话|恢复|压缩|版本|分支|发布|新ui|界面|工具|权限|确认|配置文件)/)) {
91
114
  if (matches(text, /(基于当前|切\s*v|切到|这个版本|新版本|开始按|完整开发|实现这个方案)/) ||
92
115
  matches(lower, /\b(please implement|implement this plan|start implementing|new version)\b/)) {
93
- return { kind: 'new_task', confidence: 0.88, reason: 'explicit versioned implementation task' };
116
+ return {
117
+ kind: 'new_task',
118
+ confidence: 0.88,
119
+ reason: 'explicit versioned implementation task',
120
+ };
94
121
  }
95
- return { kind: 'meta_configuration', confidence: 0.75, reason: 'configuration or CLI meta request' };
122
+ return {
123
+ kind: 'meta_configuration',
124
+ confidence: 0.75,
125
+ reason: 'configuration or CLI meta request',
126
+ };
96
127
  }
97
128
  if (matches(text, /(不对|不是|错了|改成|改为|换成|重新|重做|不要.+而是|应该.+而不是)/) ||
98
129
  matches(lower, /\b(instead|replace|switch to|redo|wrong)\b/)) {
99
- return { kind: 'interrupt_and_replace_current_step', confidence: 0.8, reason: 'correction or replacement wording' };
130
+ return {
131
+ kind: 'interrupt_and_replace_current_step',
132
+ confidence: 0.8,
133
+ reason: 'correction or replacement wording',
134
+ };
100
135
  }
101
136
  if (matches(text, /(基于|切到|这个版本|新版本|开始|实现|开发|生成|创建|写文档|完成本次|按这个计划|完整支持|帮我完成)/) ||
102
137
  matches(lower, /\b(generate|create|implement|build|start|complete this|write a|please implement)\b/)) {
103
- return { kind: 'new_task', confidence: hasRoot ? 0.76 : 0.86, reason: 'explicit task creation wording' };
138
+ return {
139
+ kind: 'new_task',
140
+ confidence: hasRoot ? 0.76 : 0.86,
141
+ reason: 'explicit task creation wording',
142
+ };
104
143
  }
105
144
  // Clarification: user is asking for explanation or asking about meaning
106
145
  // Must be checked BEFORE refinement since clarification phrases may contain "这个"
107
146
  if (matches(text, /(什么意思|解释一下|解释说明|clarify|explain to me|what does.*mean|what is.*for|can you explain|meaning of)/) ||
108
147
  matches(lower, /^(what|why|how|explain|clarify|could you explain|can you explain)\b/)) {
109
- return { kind: 'clarification', confidence: 0.7, reason: 'clarification or explanation request' };
148
+ return {
149
+ kind: 'clarification',
150
+ confidence: 0.7,
151
+ reason: 'clarification or explanation request',
152
+ };
110
153
  }
111
154
  if (matches(text, /(补充|另外|同时|还有|希望|可以|顺便|完善|优化|灰色|填充|这个|这里|如图|也|继续)/) ||
112
155
  matches(lower, /\b(also|additionally|please also|refine|improve|continue)\b/)) {
113
- return { kind: 'refine_current_task', confidence: hasRoot ? 0.76 : 0.58, reason: 'refinement or addition wording' };
156
+ return {
157
+ kind: 'refine_current_task',
158
+ confidence: hasRoot ? 0.76 : 0.58,
159
+ reason: 'refinement or addition wording',
160
+ };
114
161
  }
115
162
  return hasRoot
116
- ? { kind: 'refine_current_task', confidence: 0.55, reason: 'existing task makes ambiguous input a refinement' }
163
+ ? {
164
+ kind: 'refine_current_task',
165
+ confidence: 0.55,
166
+ reason: 'existing task makes ambiguous input a refinement',
167
+ }
117
168
  : { kind: 'new_task', confidence: 0.62, reason: 'first meaningful user task' };
118
169
  }
119
170
  function classifyIntent(input, state) {
@@ -121,7 +172,9 @@ function classifyIntent(input, state) {
121
172
  const baseEpoch = state?.taskEpoch ?? (state?.contract ? 1 : 0);
122
173
  const classified = classifyKind(input, state);
123
174
  const rootObjectiveChanged = classified.kind === 'new_task' || !state?.contract;
124
- const taskEpoch = rootObjectiveChanged ? Math.max(1, baseEpoch + (state?.contract ? 1 : 0)) : Math.max(1, baseEpoch || 1);
175
+ const taskEpoch = rootObjectiveChanged
176
+ ? Math.max(1, baseEpoch + (state?.contract ? 1 : 0))
177
+ : Math.max(1, baseEpoch || 1);
125
178
  const summary = truncate(input || 'Continue the current task');
126
179
  return {
127
180
  id: (0, crypto_1.randomUUID)(),
@@ -12,7 +12,9 @@ function unique(items) {
12
12
  function parseJsonObject(text) {
13
13
  try {
14
14
  const parsed = JSON.parse(text);
15
- return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : null;
15
+ return parsed && typeof parsed === 'object' && !Array.isArray(parsed)
16
+ ? parsed
17
+ : null;
16
18
  }
17
19
  catch {
18
20
  return null;
@@ -100,7 +100,7 @@ class EmbeddingService {
100
100
  input: text,
101
101
  }, {
102
102
  headers: {
103
- 'Authorization': `Bearer ${apiKey}`,
103
+ Authorization: `Bearer ${apiKey}`,
104
104
  'Content-Type': 'application/json',
105
105
  },
106
106
  timeout: 30000,
@@ -121,8 +121,7 @@ let defaultService = null;
121
121
  function getEmbeddingService(config) {
122
122
  if (!defaultService) {
123
123
  // Auto-detect provider from environment
124
- const provider = process.env[environment_1.ENV.EMBEDDING_PROVIDER] ||
125
- (process.env.OLLAMA_BASE_URL ? 'ollama' : 'openai');
124
+ const provider = process.env[environment_1.ENV.EMBEDDING_PROVIDER] || (process.env.OLLAMA_BASE_URL ? 'ollama' : 'openai');
126
125
  defaultService = new EmbeddingService({
127
126
  provider: provider,
128
127
  model: process.env[environment_1.ENV.EMBEDDING_MODEL],
@@ -24,7 +24,7 @@ function buildMemoryPrompt() {
24
24
  'This directory already exists — write to it directly with the Write tool.',
25
25
  '',
26
26
  'You should build up this memory system over time so that future conversations',
27
- 'can have a complete picture of who the user is, how they\'d like to collaborate,',
27
+ "can have a complete picture of who the user is, how they'd like to collaborate,",
28
28
  'what behaviors to avoid or repeat, and the context behind the work.',
29
29
  '',
30
30
  'If the user explicitly asks you to remember something, save it immediately.',