@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
@@ -97,12 +97,15 @@ function unquoteGitPath(path) {
97
97
  function parseNameStatus(output) {
98
98
  if (!output.trim())
99
99
  return [];
100
- return output.split('\n')
100
+ return output
101
+ .split('\n')
101
102
  .filter(Boolean)
102
103
  .map(line => {
103
104
  const parts = line.split('\t');
104
105
  const status = parts[0] || '?';
105
- const rawPath = parts.length > 2 ? `${parts[1]} -> ${parts[2]}` : parts[1] || line.slice(status.length).trim();
106
+ const rawPath = parts.length > 2
107
+ ? `${parts[1]} -> ${parts[2]}`
108
+ : parts[1] || line.slice(status.length).trim();
106
109
  const path = parts.length > 2
107
110
  ? `${unquoteGitPath(parts[1])} -> ${unquoteGitPath(parts[2])}`
108
111
  : unquoteGitPath(rawPath);
@@ -112,7 +115,8 @@ function parseNameStatus(output) {
112
115
  function parseUntracked(output) {
113
116
  if (!output.trim())
114
117
  return [];
115
- return output.split('\n')
118
+ return output
119
+ .split('\n')
116
120
  .filter(Boolean)
117
121
  .map(path => ({ status: '??', path: unquoteGitPath(path) }));
118
122
  }
@@ -178,7 +182,9 @@ function formatWorkspaceDiff(report, options = {}) {
178
182
  `CWD ${report.cwd}`,
179
183
  'Status not a git repository',
180
184
  report.error ? `Error ${report.error}` : '',
181
- ].filter(Boolean).join('\n');
185
+ ]
186
+ .filter(Boolean)
187
+ .join('\n');
182
188
  }
183
189
  const lines = [
184
190
  'Workspace Diff',
@@ -48,4 +48,4 @@ Trigger: `/review [path]` or `/review all`
48
48
 
49
49
  - Summary of findings
50
50
  - Specific recommendations
51
- - Priority level (Critical/High/Medium/Low)
51
+ - Priority level (Critical/High/Medium/Low)
@@ -51,4 +51,4 @@ Trigger: `/security [path]`
51
51
  - Vulnerability type
52
52
  - Location in code
53
53
  - Severity (Critical/High/Medium/Low)
54
- - Remediation steps
54
+ - Remediation steps
@@ -46,4 +46,4 @@ Trigger: `/test-gen [path]` or `/test-gen [function]`
46
46
  - Test file path
47
47
  - Test cases generated
48
48
  - Coverage estimate
49
- - Recommended assertions
49
+ - Recommended assertions
@@ -22,7 +22,9 @@ function openExternalEditor(options = {}) {
22
22
  try {
23
23
  (0, fs_1.chmodSync)(file, 0o600);
24
24
  }
25
- catch { /* best effort */ }
25
+ catch {
26
+ /* best effort */
27
+ }
26
28
  const result = spawn(editor, [file], {
27
29
  stdio: 'inherit',
28
30
  shell: true,
@@ -47,7 +49,9 @@ function openExternalEditor(options = {}) {
47
49
  try {
48
50
  (0, fs_1.rmSync)(dir, { recursive: true, force: true });
49
51
  }
50
- catch { /* best effort */ }
52
+ catch {
53
+ /* best effort */
54
+ }
51
55
  }
52
56
  }
53
57
  //# sourceMappingURL=editor.js.map
@@ -86,13 +86,17 @@ class TerminalOutputQueue {
86
86
  try {
87
87
  this.writer.off('drain', drainListener);
88
88
  }
89
- catch { /* ok */ }
89
+ catch {
90
+ /* ok */
91
+ }
90
92
  drainListener = null;
91
93
  }
92
94
  try {
93
95
  this.writer.off('error', onError);
94
96
  }
95
- catch { /* ok */ }
97
+ catch {
98
+ /* ok */
99
+ }
96
100
  };
97
101
  const onError = (error) => {
98
102
  cleanup();
@@ -114,7 +118,9 @@ class TerminalOutputQueue {
114
118
  try {
115
119
  this.writer.off('drain', listener);
116
120
  }
117
- catch { /* ok */ }
121
+ catch {
122
+ /* ok */
123
+ }
118
124
  writeNext();
119
125
  };
120
126
  this.writer.on('drain', drainListener);
package/dist/tools/lsp.js CHANGED
@@ -32,7 +32,9 @@ class LspClient extends events_1.EventEmitter {
32
32
  * Uses spawnSync with `which`/`where` to avoid async ENOENT race.
33
33
  */
34
34
  static probeBinary(cmd) {
35
- const res = (0, child_process_1.spawnSync)(process.platform === 'win32' ? 'where' : 'which', [cmd], { stdio: 'ignore' });
35
+ const res = (0, child_process_1.spawnSync)(process.platform === 'win32' ? 'where' : 'which', [cmd], {
36
+ stdio: 'ignore',
37
+ });
36
38
  return res.status === 0;
37
39
  }
38
40
  async start() {
@@ -267,7 +269,7 @@ class LspManager {
267
269
  const client = new LspClient(language, projectRoot);
268
270
  // Fallback error listener — ensures the client NEVER crashes the process
269
271
  // due to an unhandled 'error' event emission.
270
- client.on('error', (msg) => {
272
+ client.on('error', msg => {
271
273
  console.warn(`[LSP:${language}] ${msg}`);
272
274
  });
273
275
  try {
@@ -326,12 +328,14 @@ function formatLocationResult(location) {
326
328
  const locations = Array.isArray(location) ? location : [location];
327
329
  if (locations.length === 0)
328
330
  return 'No definition found';
329
- return locations.map(loc => {
331
+ return locations
332
+ .map(loc => {
330
333
  const path = loc.uri.replace('file://', '');
331
334
  const line = loc.range.start.line + 1;
332
335
  const char = loc.range.start.character + 1;
333
336
  return `${path}:${line}:${char}`;
334
- }).join('\n');
337
+ })
338
+ .join('\n');
335
339
  }
336
340
  /**
337
341
  * 解析 Hover 结果
@@ -543,12 +547,14 @@ exports.lspGetDiagnosticsTool = (0, tool_1.buildTool)({
543
547
  3: 'Information',
544
548
  4: 'Hint',
545
549
  };
546
- const output = diagnostics.map(d => {
550
+ const output = diagnostics
551
+ .map(d => {
547
552
  const sev = severityMap[d.severity] || 'Unknown';
548
553
  const line = d.range.start.line + 1;
549
554
  const char = d.range.start.character + 1;
550
555
  return `[${sev}] ${file_path}:${line}:${char}: ${d.message}`;
551
- }).join('\n');
556
+ })
557
+ .join('\n');
552
558
  return { success: true, output };
553
559
  }
554
560
  catch (err) {
@@ -58,9 +58,7 @@ function setFrameCursor(frame, row, column, visible = true) {
58
58
  };
59
59
  }
60
60
  function renderFrameRows(frame) {
61
- return frame.rows.map(row => row
62
- .map(cell => cell.width === 0 ? '' : cell.char)
63
- .join(''));
61
+ return frame.rows.map(row => row.map(cell => (cell.width === 0 ? '' : cell.char)).join(''));
64
62
  }
65
63
  function diffTuiFrames(previous, next) {
66
64
  if (!previous || previous.width !== next.width || previous.height !== next.height) {
@@ -78,9 +76,9 @@ function diffTuiFrames(previous, next) {
78
76
  }
79
77
  return {
80
78
  changedRows,
81
- cursorChanged: previous.cursor.row !== next.cursor.row
82
- || previous.cursor.column !== next.cursor.column
83
- || previous.cursor.visible !== next.cursor.visible,
79
+ cursorChanged: previous.cursor.row !== next.cursor.row ||
80
+ previous.cursor.column !== next.cursor.column ||
81
+ previous.cursor.visible !== next.cursor.visible,
84
82
  };
85
83
  }
86
84
  function rowsDifferent(prev, next) {
@@ -97,7 +95,11 @@ function rowsDifferent(prev, next) {
97
95
  return false;
98
96
  }
99
97
  function createEmptyRow(width) {
100
- return Array.from({ length: width }, () => ({ char: ' ', width: 1, style: style_1.DEFAULT_STYLE }));
98
+ return Array.from({ length: width }, () => ({
99
+ char: ' ',
100
+ width: 1,
101
+ style: style_1.DEFAULT_STYLE,
102
+ }));
101
103
  }
102
104
  function charWidth(char) {
103
105
  const width = (0, string_width_1.default)(char);
@@ -22,9 +22,9 @@ class TranscriptLayoutCache {
22
22
  }
23
23
  /** Get cached rows for an entry, or null if not cached / stale. */
24
24
  get(id, revision, generation, width, themeId = 'default') {
25
- if (generation !== this.lastGeneration
26
- || width !== this.lastWidth
27
- || themeId !== this.lastThemeId) {
25
+ if (generation !== this.lastGeneration ||
26
+ width !== this.lastWidth ||
27
+ themeId !== this.lastThemeId) {
28
28
  this.invalidate(generation, width, themeId);
29
29
  return null;
30
30
  }
@@ -38,9 +38,9 @@ class TranscriptLayoutCache {
38
38
  }
39
39
  /** Store layout result for an entry. */
40
40
  set(id, revision, rows, generation, width, themeId = 'default') {
41
- if (generation !== this.lastGeneration
42
- || width !== this.lastWidth
43
- || themeId !== this.lastThemeId) {
41
+ if (generation !== this.lastGeneration ||
42
+ width !== this.lastWidth ||
43
+ themeId !== this.lastThemeId) {
44
44
  this.invalidate(generation, width, themeId);
45
45
  }
46
46
  // Evict oldest if at capacity.
@@ -72,7 +72,10 @@ class TranscriptInspectorSurface {
72
72
  this.output.off('error', onError);
73
73
  this.output.off('close', onClose);
74
74
  };
75
- const onDrain = () => { cleanup(); resolve(); };
75
+ const onDrain = () => {
76
+ cleanup();
77
+ resolve();
78
+ };
76
79
  const onError = (error) => {
77
80
  cleanup();
78
81
  reject(error instanceof Error ? error : new Error(String(error ?? 'terminal output error')));
@@ -14,12 +14,7 @@ class TranscriptInspectorController {
14
14
  view(entries, inspector) {
15
15
  const query = inspector.searchQuery.trim().toLowerCase();
16
16
  const filtered = query
17
- ? entries.filter(entry => [
18
- entry.toolName,
19
- entry.callId,
20
- entry.summary,
21
- this.details.get(entry.callId)?.content,
22
- ]
17
+ ? entries.filter(entry => [entry.toolName, entry.callId, entry.summary, this.details.get(entry.callId)?.content]
23
18
  .filter(Boolean)
24
19
  .some(value => String(value).toLowerCase().includes(query)))
25
20
  : entries;
@@ -43,7 +38,7 @@ class TranscriptInspectorController {
43
38
  return;
44
39
  const generation = ++this.requestGeneration;
45
40
  this.details.set(entry.callId, {
46
- content: append ? current?.content ?? '' : '',
41
+ content: append ? (current?.content ?? '') : '',
47
42
  totalBytes: current?.totalBytes ?? entry.outputBytes,
48
43
  nextOffsetBytes: current?.nextOffsetBytes,
49
44
  redacted: current?.redacted ?? false,
@@ -72,7 +67,7 @@ class TranscriptInspectorController {
72
67
  if (generation !== this.requestGeneration)
73
68
  return;
74
69
  this.details.set(entry.callId, {
75
- content: append ? current?.content ?? '' : '',
70
+ content: append ? (current?.content ?? '') : '',
76
71
  totalBytes: current?.totalBytes ?? entry.outputBytes,
77
72
  redacted: current?.redacted ?? false,
78
73
  loading: false,
package/dist/ui/box.js CHANGED
@@ -40,18 +40,25 @@ function renderHeaderBox(config) {
40
40
  // Model name
41
41
  parts.push(ACCENT(config.model));
42
42
  // Provider (shortened)
43
- const providerShort = config.provider === 'Alibaba Cloud' ? 'Qwen'
44
- : config.provider === 'Anthropic' ? 'Anthropic'
45
- : config.provider === 'OpenAI' ? 'OpenAI'
43
+ const providerShort = config.provider === 'Alibaba Cloud'
44
+ ? 'Qwen'
45
+ : config.provider === 'Anthropic'
46
+ ? 'Anthropic'
47
+ : config.provider === 'OpenAI'
48
+ ? 'OpenAI'
46
49
  : config.provider;
47
50
  if (providerShort) {
48
51
  parts.push(DIM(providerShort));
49
52
  }
50
53
  // Status
51
- const statusIcon = config.status === 'ready' ? GREEN('●')
52
- : config.status === 'loading' ? YELLOW('')
53
- : config.status === 'error' ? RED('●')
54
- : config.status === 'processing' ? ACCENT('')
54
+ const statusIcon = config.status === 'ready'
55
+ ? GREEN('')
56
+ : config.status === 'loading'
57
+ ? YELLOW('')
58
+ : config.status === 'error'
59
+ ? RED('●')
60
+ : config.status === 'processing'
61
+ ? ACCENT('◌')
55
62
  : DIM('○');
56
63
  parts.push(statusIcon);
57
64
  return ` ${BRAND('orion')} ${DIM('v' + config.version)} ${DIM('│')} ${parts.join(` ${DIM('│')} `)}`;
@@ -149,8 +149,10 @@ function renderMarkdownFallback(text, maxWidth = DEFAULT_MAX_WIDTH) {
149
149
  const level = headingMatch[1].length;
150
150
  const headingText = headingMatch[2].trim();
151
151
  const prefix = ' '.repeat(level - 1);
152
- const styled = level <= 2 ? BOLD(CYAN(prefix + ' ' + headingText))
153
- : level <= 4 ? BOLD(CYAN(prefix + headingText))
152
+ const styled = level <= 2
153
+ ? BOLD(CYAN(prefix + ' ' + headingText))
154
+ : level <= 4
155
+ ? BOLD(CYAN(prefix + headingText))
154
156
  : BOLD(DIM(headingText));
155
157
  result.push(styled);
156
158
  result.push(DIM('─'.repeat(Math.min(headingText.length + prefix.length + 2, maxWidth - 2))));
@@ -9,13 +9,15 @@ exports.renderFramedInputFrame = renderFramedInputFrame;
9
9
  const chalk_1 = __importDefault(require("chalk"));
10
10
  const text_1 = require("./text");
11
11
  const NO_COLOR = process.env.NO_COLOR !== undefined || process.env.TERM === 'dumb';
12
- const colorize = NO_COLOR ? {
13
- accent: (s) => s,
14
- dim: (s) => s,
15
- } : {
16
- accent: chalk_1.default.hex('#00D4AA'),
17
- dim: chalk_1.default.dim,
18
- };
12
+ const colorize = NO_COLOR
13
+ ? {
14
+ accent: (s) => s,
15
+ dim: (s) => s,
16
+ }
17
+ : {
18
+ accent: chalk_1.default.hex('#00D4AA'),
19
+ dim: chalk_1.default.dim,
20
+ };
19
21
  const INPUT_BG = '\x1b[48;2;56;56;56m';
20
22
  const INPUT_FG = '\x1b[38;2;226;232;240m';
21
23
  const RESET_COLORS = '\x1b[39;49m';
@@ -34,7 +36,9 @@ function renderFramedInputFrame(options) {
34
36
  const prefix = index === 0 ? firstPrompt : continuationPrompt;
35
37
  return renderInputLine((0, text_1.truncateVisible)(prefix + line, inputWidth));
36
38
  });
37
- const statusLine = options.statusText ? renderInputStatusLine(inputWidth, options.statusText) : undefined;
39
+ const statusLine = options.statusText
40
+ ? renderInputStatusLine(inputWidth, options.statusText)
41
+ : undefined;
38
42
  const rowSpans = logicalLines.map((line, index) => {
39
43
  const prefix = index === 0 ? firstPrompt : continuationPrompt;
40
44
  return terminalRowsFor((0, text_1.visualWidth)(prefix) + (0, text_1.visualWidth)(line), width);
@@ -157,7 +157,7 @@ class StreamMarkdownRenderer {
157
157
  // Ordered list
158
158
  const orderedMatch = line.match(/^(\s*)(\d+)\.\s+(.*)$/);
159
159
  if (orderedMatch) {
160
- return orderedMatch[1] + CYAN(orderedMatch[2] + '.') + ' ' + this.renderInline(orderedMatch[3]);
160
+ return (orderedMatch[1] + CYAN(orderedMatch[2] + '.') + ' ' + this.renderInline(orderedMatch[3]));
161
161
  }
162
162
  return this.renderInline(line);
163
163
  }
@@ -46,7 +46,9 @@ function renderToolCard(tool) {
46
46
  lines.push(DIM('│ ') + truncated.padEnd(maxWidth - 4) + DIM(' │'));
47
47
  }
48
48
  if (tool.result.split('\n').length > 6) {
49
- lines.push(DIM('│ ') + DIM(`... ${tool.result.split('\n').length - 6} more lines`).padEnd(maxWidth - 4) + DIM(' │'));
49
+ lines.push(DIM('│ ') +
50
+ DIM(`... ${tool.result.split('\n').length - 6} more lines`).padEnd(maxWidth - 4) +
51
+ DIM(' │'));
50
52
  }
51
53
  // 底部
52
54
  lines.push(DIM('└') + '─'.repeat(maxWidth - 2) + DIM('┘'));
@@ -92,7 +94,11 @@ function renderReadPreview(path, content, success) {
92
94
  const maxWidth = 60;
93
95
  const icon = success ? SUCCESS('📖') : ERROR('📖');
94
96
  const title = `${icon} Read: ${path}`;
95
- lines.push(DIM('┌─ ') + title.slice(0, maxWidth - 5) + DIM(' ') + '─'.repeat(Math.max(0, maxWidth - title.length - 5)) + DIM('┐'));
97
+ lines.push(DIM('┌─ ') +
98
+ title.slice(0, maxWidth - 5) +
99
+ DIM(' ') +
100
+ '─'.repeat(Math.max(0, maxWidth - title.length - 5)) +
101
+ DIM('┐'));
96
102
  if (success && content) {
97
103
  const contentLines = content.split('\n').slice(0, 8);
98
104
  for (const line of contentLines) {
@@ -100,7 +106,9 @@ function renderReadPreview(path, content, success) {
100
106
  lines.push(DIM('│ ') + truncated.padEnd(maxWidth - 4) + DIM(' │'));
101
107
  }
102
108
  if (content.split('\n').length > 8) {
103
- lines.push(DIM('│ ') + DIM(`... ${content.split('\n').length - 8} more lines`).padEnd(maxWidth - 4) + DIM(' │'));
109
+ lines.push(DIM('│ ') +
110
+ DIM(`... ${content.split('\n').length - 8} more lines`).padEnd(maxWidth - 4) +
111
+ DIM(' │'));
104
112
  }
105
113
  }
106
114
  lines.push(DIM('└') + '─'.repeat(maxWidth - 2) + DIM('┘'));
@@ -33,16 +33,22 @@ function visualWidth(text) {
33
33
  let width = 0;
34
34
  for (const ch of text) {
35
35
  const cp = ch.codePointAt(0) || 0;
36
- width += (cp >= 0x1100 && (cp <= 0x115f || cp === 0x2329 || cp === 0x232a ||
37
- (cp >= 0x2e80 && cp <= 0xa4cf && cp !== 0x303f) ||
38
- (cp >= 0xac00 && cp <= 0xd7a3) ||
39
- (cp >= 0xf900 && cp <= 0xfaff) ||
40
- (cp >= 0xfe10 && cp <= 0xfe19) ||
41
- (cp >= 0xfe30 && cp <= 0xfe6f) ||
42
- (cp >= 0xff01 && cp <= 0xff60) ||
43
- (cp >= 0xffe0 && cp <= 0xffe6) ||
44
- (cp >= 0x20000 && cp <= 0x2fffd) ||
45
- (cp >= 0x30000 && cp <= 0x3fffd))) ? 2 : 1;
36
+ width +=
37
+ cp >= 0x1100 &&
38
+ (cp <= 0x115f ||
39
+ cp === 0x2329 ||
40
+ cp === 0x232a ||
41
+ (cp >= 0x2e80 && cp <= 0xa4cf && cp !== 0x303f) ||
42
+ (cp >= 0xac00 && cp <= 0xd7a3) ||
43
+ (cp >= 0xf900 && cp <= 0xfaff) ||
44
+ (cp >= 0xfe10 && cp <= 0xfe19) ||
45
+ (cp >= 0xfe30 && cp <= 0xfe6f) ||
46
+ (cp >= 0xff01 && cp <= 0xff60) ||
47
+ (cp >= 0xffe0 && cp <= 0xffe6) ||
48
+ (cp >= 0x20000 && cp <= 0x2fffd) ||
49
+ (cp >= 0x30000 && cp <= 0x3fffd))
50
+ ? 2
51
+ : 1;
46
52
  }
47
53
  return width;
48
54
  }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * CLI option parsing for `orion web` (extracted from the CLI entry so the
3
+ * grammar has a unit-testable surface — issue #218 regression coverage).
4
+ */
5
+ export declare const DEFAULT_WEB_PORT = 3080;
6
+ export interface WebCliOptions {
7
+ readonly port: number;
8
+ readonly open: boolean;
9
+ readonly cwd: string;
10
+ readonly background: boolean;
11
+ }
12
+ export declare function parseWebCliOptions(args: readonly string[]): WebCliOptions;
13
+ //# sourceMappingURL=cli-options.d.ts.map
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ /**
3
+ * CLI option parsing for `orion web` (extracted from the CLI entry so the
4
+ * grammar has a unit-testable surface — issue #218 regression coverage).
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.DEFAULT_WEB_PORT = void 0;
8
+ exports.parseWebCliOptions = parseWebCliOptions;
9
+ exports.DEFAULT_WEB_PORT = 3080;
10
+ function parseWebCliOptions(args) {
11
+ let port = exports.DEFAULT_WEB_PORT;
12
+ let open = true;
13
+ let cwd = process.cwd();
14
+ let background = false;
15
+ for (let index = 0; index < args.length; index++) {
16
+ const argument = args[index];
17
+ if (argument === '--background' || argument === '--daemon') {
18
+ background = true;
19
+ continue;
20
+ }
21
+ if (argument === '--no-open') {
22
+ open = false;
23
+ continue;
24
+ }
25
+ if (argument === '--port' || argument.startsWith('--port=')) {
26
+ const portValue = argument === '--port' ? args[++index] : argument.slice('--port='.length);
27
+ if (!portValue || !/^\d+$/u.test(portValue)) {
28
+ throw new Error('--port must be an integer from 0 through 65535.');
29
+ }
30
+ port = Number(portValue);
31
+ if (!Number.isSafeInteger(port) || port < 0 || port > 65535) {
32
+ throw new Error('--port must be an integer from 0 through 65535.');
33
+ }
34
+ continue;
35
+ }
36
+ if (argument === '--cwd' || argument.startsWith('--cwd=')) {
37
+ const cwdValue = argument === '--cwd' ? args[++index] : argument.slice('--cwd='.length);
38
+ if (!cwdValue?.trim())
39
+ throw new Error('--cwd requires a directory path.');
40
+ cwd = cwdValue;
41
+ continue;
42
+ }
43
+ throw new Error(`Unknown orion web option: ${argument}`);
44
+ }
45
+ return { port, open, cwd, background };
46
+ }
47
+ //# sourceMappingURL=cli-options.js.map
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Native background/daemon lifecycle for `orion web` (issue #247, S1+S2).
3
+ *
4
+ * A foreground `orion web --background` parent spawns the real host detached,
5
+ * waits for it to publish a pidfile under the config home, then returns. The
6
+ * detached child redirects its stdio to per-host log files and keeps the
7
+ * fail-closed approval / context / sandbox semantics untouched — this is a
8
+ * process-lifecycle concern only, still bound to 127.0.0.1.
9
+ *
10
+ * The pidfile shape is intentionally tiny and human-readable:
11
+ *
12
+ * { "pid": 12345, "port": 3080, "url": "http://127.0.0.1:3080",
13
+ * "startedAt": 1725000000000, "workspace": "/abs/path" }
14
+ */
15
+ import { type ChildProcess } from 'child_process';
16
+ export declare const HOST_PIDFILE_BASE = "web-host";
17
+ export declare const HOST_DAEMON_CHILD_ENV = "ORION_WEB_DAEMON_CHILD";
18
+ export declare const DAEMON_READY_TIMEOUT_MS = 8000;
19
+ export declare const DAEMON_POLL_MS = 120;
20
+ export interface HostPidfile {
21
+ readonly pid: number;
22
+ readonly port: number;
23
+ readonly url: string;
24
+ readonly workspace: string;
25
+ readonly startedAt: number;
26
+ }
27
+ export type HostDaemonStatus = {
28
+ readonly state: 'running';
29
+ readonly pidfile: HostPidfile;
30
+ } | {
31
+ readonly state: 'stopped';
32
+ } | {
33
+ readonly state: 'stale';
34
+ readonly pid: number;
35
+ readonly port: number;
36
+ };
37
+ export declare function hostLogsDirectory(): string;
38
+ export declare function hostPidfilePath(port: number): string;
39
+ export declare function parseHostPidfile(raw: string): HostPidfile | null;
40
+ export declare function readHostPidfile(port: number): HostPidfile | null;
41
+ export declare function writeHostPidfile(pidfile: HostPidfile): void;
42
+ export declare function clearHostPidfile(port: number): void;
43
+ /** Cross-platform process-liveness probe used by `status`. */
44
+ export declare function isProcessAlive(pid: number): boolean;
45
+ export declare function hostDaemonStatus(port: number): HostDaemonStatus;
46
+ /**
47
+ * Parent-side entry for `--background`: spawn the host detached, redirect its
48
+ * stdio to `~/.orion-code/logs/web-<port>.log`, and wait for the pidfile so the
49
+ * foreground command can print the URL and exit cleanly.
50
+ */
51
+ export declare function spawnBackgroundHost(options: {
52
+ readonly cwd: string;
53
+ readonly port: number;
54
+ readonly webArgs: readonly string[];
55
+ }): Promise<{
56
+ readonly child: ChildProcess;
57
+ readonly pidfile: HostPidfile;
58
+ }>;
59
+ /** Terminate a background host by pidfile (SIGTERM, then escalate to SIGKILL). */
60
+ export declare function stopBackgroundHost(port: number): Promise<'stopped' | 'not_running'>;
61
+ //# sourceMappingURL=host-daemon.d.ts.map