@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
@@ -38,20 +38,128 @@ function extractKeywords(text, preserveCase = false) {
38
38
  .filter(w => w.length >= 2);
39
39
  // 去除常见停用词
40
40
  const stopWords = new Set([
41
- 'the', 'a', 'an', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for',
42
- 'of', 'with', 'by', 'from', 'as', 'is', 'was', 'are', 'were', 'been',
43
- 'be', 'have', 'has', 'had', 'do', 'does', 'did', 'will', 'would',
44
- 'could', 'should', 'may', 'might', 'must', 'can', 'this', 'that',
45
- 'these', 'those', 'it', 'its', 'they', 'them', 'their', 'we', 'our',
46
- 'you', 'your', 'he', 'him', 'his', 'she', 'her', 'i', 'me', 'my',
47
- 'not', 'no', 'yes', 'all', 'any', 'some', 'each', 'every', 'both',
48
- 'few', 'many', 'more', 'most', 'other', 'another', 'such', 'only',
49
- 'own', 'same', 'so', 'than', 'too', 'very', 'just', 'also', 'now',
50
- 'then', 'here', 'there', 'when', 'where', 'why', 'how', 'what', 'which',
51
- 'who', 'whom', 'whose', 'if', 'else', 'then', 'because', 'since',
52
- 'although', 'though', 'while', 'during', 'before', 'after', 'until',
53
- 'above', 'below', 'between', 'under', 'over', 'out', 'into', 'onto',
54
- 'about', 'against', 'through', 'across', 'along', 'around', 'again',
41
+ 'the',
42
+ 'a',
43
+ 'an',
44
+ 'and',
45
+ 'or',
46
+ 'but',
47
+ 'in',
48
+ 'on',
49
+ 'at',
50
+ 'to',
51
+ 'for',
52
+ 'of',
53
+ 'with',
54
+ 'by',
55
+ 'from',
56
+ 'as',
57
+ 'is',
58
+ 'was',
59
+ 'are',
60
+ 'were',
61
+ 'been',
62
+ 'be',
63
+ 'have',
64
+ 'has',
65
+ 'had',
66
+ 'do',
67
+ 'does',
68
+ 'did',
69
+ 'will',
70
+ 'would',
71
+ 'could',
72
+ 'should',
73
+ 'may',
74
+ 'might',
75
+ 'must',
76
+ 'can',
77
+ 'this',
78
+ 'that',
79
+ 'these',
80
+ 'those',
81
+ 'it',
82
+ 'its',
83
+ 'they',
84
+ 'them',
85
+ 'their',
86
+ 'we',
87
+ 'our',
88
+ 'you',
89
+ 'your',
90
+ 'he',
91
+ 'him',
92
+ 'his',
93
+ 'she',
94
+ 'her',
95
+ 'i',
96
+ 'me',
97
+ 'my',
98
+ 'not',
99
+ 'no',
100
+ 'yes',
101
+ 'all',
102
+ 'any',
103
+ 'some',
104
+ 'each',
105
+ 'every',
106
+ 'both',
107
+ 'few',
108
+ 'many',
109
+ 'more',
110
+ 'most',
111
+ 'other',
112
+ 'another',
113
+ 'such',
114
+ 'only',
115
+ 'own',
116
+ 'same',
117
+ 'so',
118
+ 'than',
119
+ 'too',
120
+ 'very',
121
+ 'just',
122
+ 'also',
123
+ 'now',
124
+ 'then',
125
+ 'here',
126
+ 'there',
127
+ 'when',
128
+ 'where',
129
+ 'why',
130
+ 'how',
131
+ 'what',
132
+ 'which',
133
+ 'who',
134
+ 'whom',
135
+ 'whose',
136
+ 'if',
137
+ 'else',
138
+ 'then',
139
+ 'because',
140
+ 'since',
141
+ 'although',
142
+ 'though',
143
+ 'while',
144
+ 'during',
145
+ 'before',
146
+ 'after',
147
+ 'until',
148
+ 'above',
149
+ 'below',
150
+ 'between',
151
+ 'under',
152
+ 'over',
153
+ 'out',
154
+ 'into',
155
+ 'onto',
156
+ 'about',
157
+ 'against',
158
+ 'through',
159
+ 'across',
160
+ 'along',
161
+ 'around',
162
+ 'again',
55
163
  ]);
56
164
  // 停用词过滤始终大小写不敏感,否则 preserveCase 模式下 "The"/"And" 会漏过。
57
165
  return words.filter(w => !stopWords.has(preserveCase ? w.toLowerCase() : w));
@@ -84,7 +192,9 @@ function calculateKeywordMatch(queryKeywords, memoryEntry, caseSensitive = false
84
192
  for (const mk of memoryKeywords) {
85
193
  const compareMk = caseSensitive ? mk : mk.toLowerCase();
86
194
  // 精确匹配或部分匹配(包含关系)
87
- if (compareMk === compareQk || compareMk.includes(compareQk) || compareQk.includes(compareMk)) {
195
+ if (compareMk === compareQk ||
196
+ compareMk.includes(compareQk) ||
197
+ compareQk.includes(compareMk)) {
88
198
  matchedKeywords.push(qk);
89
199
  matchCount++;
90
200
  break;
@@ -292,11 +292,13 @@ class VectorStore {
292
292
  const scopedIds = projectKeys.map(projectKey => this.memoryId(projectKey, name));
293
293
  const idPlaceholders = scopedIds.map(() => '?').join(', ');
294
294
  const projectPlaceholders = projectKeys.map(() => '?').join(', ');
295
- const rows = this.db.prepare(`
295
+ const rows = this.db
296
+ .prepare(`
296
297
  SELECT id FROM memories
297
298
  WHERE id IN (${idPlaceholders})
298
299
  OR (id = ? AND project IN (${projectPlaceholders}))
299
- `).all(...scopedIds, name, ...projectKeys);
300
+ `)
301
+ .all(...scopedIds, name, ...projectKeys);
300
302
  this.deleteMemoryIds(rows.map(row => row.id));
301
303
  }
302
304
  /** Search by similarity */
@@ -409,12 +411,14 @@ class VectorStore {
409
411
  }
410
412
  /** Count rows by project key for storage maintenance diagnostics. */
411
413
  getProjectStats() {
412
- const rows = this.db.prepare(`
414
+ const rows = this.db
415
+ .prepare(`
413
416
  SELECT COALESCE(project, 'global') as project, COUNT(*) as rows
414
417
  FROM memories
415
418
  GROUP BY COALESCE(project, 'global')
416
419
  ORDER BY rows DESC, project ASC
417
- `).all();
420
+ `)
421
+ .all();
418
422
  return rows.map(row => ({ project: row.project, rows: row.rows }));
419
423
  }
420
424
  /** Delete all rows for projects that are not in validProjectKeys. */
@@ -430,8 +434,7 @@ class VectorStore {
430
434
  return { orphanProjects, deletedRows };
431
435
  }
432
436
  deleteProjectRows(project) {
433
- const ids = this.db.prepare('SELECT id FROM memories WHERE project = ?').all(project)
434
- .map(row => row.id);
437
+ const ids = this.db.prepare('SELECT id FROM memories WHERE project = ?').all(project).map(row => row.id);
435
438
  if (ids.length === 0)
436
439
  return 0;
437
440
  this.deleteMemoryIds(ids);
@@ -449,7 +452,9 @@ class VectorStore {
449
452
  if (!this.hasTable('memory_vectors') || !this.hasTable('vec_memories') || ids.length === 0)
450
453
  return;
451
454
  const placeholders = ids.map(() => '?').join(', ');
452
- this.db.prepare(`DELETE FROM vec_memories WHERE rowid IN (SELECT vector_rowid FROM memory_vectors WHERE memory_id IN (${placeholders}))`).run(...ids);
455
+ this.db
456
+ .prepare(`DELETE FROM vec_memories WHERE rowid IN (SELECT vector_rowid FROM memory_vectors WHERE memory_id IN (${placeholders}))`)
457
+ .run(...ids);
453
458
  this.db.prepare(`DELETE FROM memory_vectors WHERE memory_id IN (${placeholders})`).run(...ids);
454
459
  }
455
460
  /** Canonical project key used by v0.2.8+ storage. */
@@ -467,7 +472,7 @@ class VectorStore {
467
472
  return projectKey === 'global' ? name : `${projectKey}:${name}`;
468
473
  }
469
474
  hasTable(name) {
470
- const row = this.db.prepare("SELECT name FROM sqlite_master WHERE name = ?").get(name);
475
+ const row = this.db.prepare('SELECT name FROM sqlite_master WHERE name = ?').get(name);
471
476
  return !!row;
472
477
  }
473
478
  /** Close database */
@@ -42,14 +42,7 @@ const PROJECT_RENAME_MAP = [
42
42
  { from: 'OPENHORSE.local.md', to: 'ORION.local.md' },
43
43
  ];
44
44
  /** Subdirectories whose contents are copied verbatim. */
45
- const VERBATIM_DIRS = new Set([
46
- 'projects',
47
- 'skills',
48
- 'cost',
49
- 'cache',
50
- 'backups',
51
- 'migration-logs',
52
- ]);
45
+ const VERBATIM_DIRS = new Set(['projects', 'skills', 'cost', 'cache', 'backups', 'migration-logs']);
53
46
  /** Directories whose parent is renamed (e.g. backups → backups/openhorse). */
54
47
  const NESTED_DIRS = {
55
48
  backups: 'openhorse',
@@ -144,9 +137,7 @@ function verifySqlite(path) {
144
137
  try {
145
138
  db = (0, vector_store_1.openBetterSqlite3)(path, { readonly: true });
146
139
  const result = db.pragma('integrity_check');
147
- return result?.[0]?.integrity_check === 'ok'
148
- ? { status: 'verified' }
149
- : { status: 'invalid' };
140
+ return result?.[0]?.integrity_check === 'ok' ? { status: 'verified' } : { status: 'invalid' };
150
141
  }
151
142
  catch (error) {
152
143
  // ABI/module failures are environmental and actionable, not evidence that
@@ -345,7 +336,12 @@ function migrateProjectFiles(projectPath, options = {}) {
345
336
  const warnings = [];
346
337
  const resolved = (0, path_1.resolve)(projectPath);
347
338
  if (!(0, fs_1.existsSync)(resolved)) {
348
- return { success: false, renamed, conflicts, warnings: [`Project path ${resolved} does not exist.`] };
339
+ return {
340
+ success: false,
341
+ renamed,
342
+ conflicts,
343
+ warnings: [`Project path ${resolved} does not exist.`],
344
+ };
349
345
  }
350
346
  for (const { from, to } of PROJECT_RENAME_MAP) {
351
347
  const srcPath = (0, path_1.join)(resolved, from);
@@ -29,9 +29,7 @@ function pushHistoryEntry(state, value) {
29
29
  if (!value)
30
30
  return state;
31
31
  // Deduplicate consecutive identical.
32
- const entries = state.entries[state.entries.length - 1] === value
33
- ? state.entries
34
- : [...state.entries, value];
32
+ const entries = state.entries[state.entries.length - 1] === value ? state.entries : [...state.entries, value];
35
33
  // Bound size.
36
34
  const trimmed = entries.length > exports.MAX_HISTORY_ENTRIES
37
35
  ? entries.slice(entries.length - exports.MAX_HISTORY_ENTRIES)
@@ -124,7 +124,9 @@ function buildPromptVisualLines(value, width, cursor = value.length) {
124
124
  chunkStart: chunk.start,
125
125
  chunkEnd: chunk.end,
126
126
  };
127
- if (cursorInThisLine && clampedCursor - lineStart >= chunk.start && clampedCursor - lineStart <= chunk.end) {
127
+ if (cursorInThisLine &&
128
+ clampedCursor - lineStart >= chunk.start &&
129
+ clampedCursor - lineStart <= chunk.end) {
128
130
  cursorLineIndex = visualLines.length;
129
131
  const cursorInChunk = Math.max(0, clampedCursor - lineStart - chunk.start);
130
132
  cursorColumn = 5 + (0, string_width_1.default)(chunk.content.slice(0, cursorInChunk));
@@ -161,9 +163,10 @@ function formatPromptVisualLine(visualLine, width, options = {}) {
161
163
  const before = content.slice(0, offset);
162
164
  const after = content.slice(offset);
163
165
  const beforeWithCursor = `${before}${exports.PROMPT_CURSOR_GLYPH}`;
164
- content = (0, string_width_1.default)(beforeWithCursor) >= maxContentWidth
165
- ? `${takeVisualWidth(before, maxContentWidth - (0, string_width_1.default)(exports.PROMPT_CURSOR_GLYPH))}${exports.PROMPT_CURSOR_GLYPH}`
166
- : `${beforeWithCursor}${takeVisualWidth(after, maxContentWidth - (0, string_width_1.default)(beforeWithCursor))}`;
166
+ content =
167
+ (0, string_width_1.default)(beforeWithCursor) >= maxContentWidth
168
+ ? `${takeVisualWidth(before, maxContentWidth - (0, string_width_1.default)(exports.PROMPT_CURSOR_GLYPH))}${exports.PROMPT_CURSOR_GLYPH}`
169
+ : `${beforeWithCursor}${takeVisualWidth(after, maxContentWidth - (0, string_width_1.default)(beforeWithCursor))}`;
167
170
  }
168
171
  const raw = `${prefix}${content}`;
169
172
  const padding = Math.max(0, promptContentWidth(width) - (0, string_width_1.default)(raw));
@@ -1,7 +1,7 @@
1
1
  import type { RuntimeEventEnvelopeV1 } from './protocol/runtime-protocol-v1';
2
2
  import { type LegacySessionImportReceiptV1 } from './legacy-session-importer';
3
3
  import { ThreadEventStore, type ThreadCheckpointHeadV1 } from './thread-event-store';
4
- import { type ThreadProjectionV1 } from './thread-projection';
4
+ import { type ThreadProjectionDigestVersionV1, type ThreadProjectionV1 } from './thread-projection';
5
5
  export type LegacyThreadMaterializationBoundaryV1 = 'after_dry_run' | 'after_receipt_staged' | 'after_facts_materialized' | 'after_projection_verified' | 'after_source_recheck' | 'before_index_switch' | 'after_index_switch';
6
6
  export interface LegacyThreadMaterializerOptionsV1 {
7
7
  readonly projectPath: string;
@@ -26,6 +26,8 @@ export interface ThreadCutoverIndexEntryV1 {
26
26
  readonly importDigest: string;
27
27
  readonly eventDigest: string;
28
28
  readonly projectionDigest: string;
29
+ /** Missing on v0.3.2 cutover entries; inferred against exact known schemes. */
30
+ readonly projectionDigestVersion?: ThreadProjectionDigestVersionV1;
29
31
  readonly cursor: number;
30
32
  readonly eventLogFile: string;
31
33
  readonly projectionFile: string;
@@ -82,7 +82,7 @@ function materializeLegacyThreadV1(options) {
82
82
  const existing = current.sessions[options.sessionId];
83
83
  const entry = createIndexEntry(options.sessionId, store, plan);
84
84
  if (existing) {
85
- if ((0, canonical_1.canonicalRuntimeJson)(existing) !== (0, canonical_1.canonicalRuntimeJson)(entry)) {
85
+ if (!sameCutoverEntry(existing, entry, plan.projection)) {
86
86
  throw new LegacyThreadMaterializationError('ORION_THREAD_CUTOVER_CONFLICT', `Session ${options.sessionId} already points at a different v2 Thread`);
87
87
  }
88
88
  mode = 'already_cutover';
@@ -131,7 +131,10 @@ function openSessionCheckpointStorageV1(projectPathInput, sessionId) {
131
131
  head.cursor < entry.cursor ||
132
132
  !head.verifiedPrefixes.some(prefix => prefix.cursor === entry.cursor &&
133
133
  prefix.eventDigest === entry.eventDigest &&
134
- prefix.projectionDigest === entry.projectionDigest)) {
134
+ prefix.projectionDigest === entry.projectionDigest &&
135
+ (entry.projectionDigestVersion === undefined ||
136
+ prefix.projectionDigestVersion === undefined ||
137
+ prefix.projectionDigestVersion === entry.projectionDigestVersion))) {
135
138
  return undefined;
136
139
  }
137
140
  return Object.freeze({
@@ -179,7 +182,7 @@ function openSessionStorageV1(projectPathInput, sessionId) {
179
182
  const store = new thread_event_store_1.ThreadEventStore((0, paths_1.getProjectThreadsV2Dir)(projectPath), entry.threadId, {
180
183
  maxReplayEvents: Math.max(10000, entry.cursor),
181
184
  });
182
- const prefixVerified = store.verifyDurablePrefix(entry.cursor, entry.eventDigest, entry.projectionDigest);
185
+ const prefixVerified = store.verifyDurablePrefix(entry.cursor, entry.eventDigest, entry.projectionDigest, entry.projectionDigestVersion);
183
186
  // The cutover receipt seals the imported prefix, not the forever-changing
184
187
  // head of a live Thread. loadProjection() independently verifies/rebuilds the
185
188
  // current cache against the complete hash-chained log.
@@ -523,6 +526,7 @@ function createIndexEntry(sessionId, store, plan) {
523
526
  importDigest: plan.receipt.importDigest,
524
527
  eventDigest: plan.eventDigest,
525
528
  projectionDigest: plan.projection.digest,
529
+ projectionDigestVersion: thread_projection_1.CURRENT_THREAD_PROJECTION_DIGEST_VERSION_V1,
526
530
  cursor: plan.events.length,
527
531
  eventLogFile: (0, path_1.basename)(store.logPath),
528
532
  projectionFile: (0, path_1.basename)(store.projectionPath),
@@ -530,6 +534,14 @@ function createIndexEntry(sessionId, store, plan) {
530
534
  cutoverAt: plan.receipt.sourceTimestamp,
531
535
  };
532
536
  }
537
+ function sameCutoverEntry(existing, next, projection) {
538
+ const { projectionDigest: existingProjectionDigest, projectionDigestVersion: existingProjectionDigestVersion, ...existingIdentity } = existing;
539
+ const { projectionDigest: _nextProjectionDigest, projectionDigestVersion: _nextProjectionDigestVersion, ...nextIdentity } = next;
540
+ void _nextProjectionDigest;
541
+ void _nextProjectionDigestVersion;
542
+ return ((0, canonical_1.canonicalRuntimeJson)(existingIdentity) === (0, canonical_1.canonicalRuntimeJson)(nextIdentity) &&
543
+ (0, thread_projection_1.resolveThreadProjectionDigestVersionV1)(projection, existingProjectionDigest, existingProjectionDigestVersion) !== undefined);
544
+ }
533
545
  function assertSourceUnchanged(projectPath, sessionId, expectedImportDigest) {
534
546
  const current = (0, legacy_session_importer_1.readLegacySessionMaterializationSnapshotV1)(projectPath, sessionId).receipt;
535
547
  if (current.importDigest !== expectedImportDigest) {
@@ -5,6 +5,18 @@ import type { ThreadSessionRuntimeActivationV1 } from './thread-session-view';
5
5
  import type { OrionCodeUiRuntime } from './ui-events';
6
6
  import type { WorkspaceMutationCoordinatorV1 } from './step-snapshot';
7
7
  import { WorkspaceRuntimeKernelV1 } from './workspace-runtime-kernel';
8
+ export interface CreateWorkspaceRuntimeKernelOptionsV1 {
9
+ readonly cwd: string;
10
+ readonly uiRenderer?: UIRenderer;
11
+ readonly onSettingsInvalidated?: (event: SettingsInvalidationV1) => void;
12
+ }
13
+ /**
14
+ * Composition root for one workspace-owned kernel. Web Hosts pool kernels per
15
+ * canonical Workspace so a Context switch can keep running Session actors on
16
+ * the config/Tool/MCP/Settings services they were created with, instead of
17
+ * rebuilding them against whatever Workspace is active at actor-start time.
18
+ */
19
+ export declare function createWorkspaceRuntimeKernelV1(options: CreateWorkspaceRuntimeKernelOptionsV1): WorkspaceRuntimeKernelV1;
8
20
  export interface ProductUiRuntimeBootstrapOptions {
9
21
  readonly cwd: string;
10
22
  readonly uiRenderer?: UIRenderer;
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createWorkspaceRuntimeKernelV1 = createWorkspaceRuntimeKernelV1;
3
4
  exports.createProductUiRuntime = createProductUiRuntime;
4
5
  const path_1 = require("path");
5
6
  const prompt_context_1 = require("../memory/prompt-context");
@@ -25,6 +26,40 @@ const orion_session_runner_1 = require("./orion-session-runner");
25
26
  const product_orion_runtime_1 = require("./product-orion-runtime");
26
27
  const skills_1 = require("./skills");
27
28
  const workspace_runtime_kernel_1 = require("./workspace-runtime-kernel");
29
+ /**
30
+ * Composition root for one workspace-owned kernel. Web Hosts pool kernels per
31
+ * canonical Workspace so a Context switch can keep running Session actors on
32
+ * the config/Tool/MCP/Settings services they were created with, instead of
33
+ * rebuilding them against whatever Workspace is active at actor-start time.
34
+ */
35
+ function createWorkspaceRuntimeKernelV1(options) {
36
+ const canonicalCwd = (0, path_1.resolve)(options.cwd);
37
+ (0, config_dir_1.ensureConfigDir)();
38
+ (0, global_config_1.recordFirstStartTime)();
39
+ const kernelConfig = (0, config_1.loadConfig)(options.uiRenderer ? { ui: { renderer: options.uiRenderer } } : {});
40
+ const kernelTools = (0, first_party_tool_universe_1.createProductionFirstPartyToolUniverseV1)({
41
+ context: {
42
+ cwd: canonicalCwd,
43
+ config: { name: kernelConfig.name, mode: kernelConfig.mode },
44
+ },
45
+ });
46
+ const kernelSkillProvider = (0, skills_1.createProductionFilesystemSkillProviderV1)({
47
+ cwd: canonicalCwd,
48
+ configuredPaths: kernelConfig.skills?.paths,
49
+ });
50
+ const kernelMcpAdapter = (0, mcp_1.createFirstPartyMcpAdapterV1)({
51
+ config: (0, mcp_1.loadFirstPartyMcpConfigurationV1)(),
52
+ baseDirectory: canonicalCwd,
53
+ });
54
+ return new workspace_runtime_kernel_1.WorkspaceRuntimeKernelV1({
55
+ cwd: canonicalCwd,
56
+ config: kernelConfig,
57
+ toolUniverse: kernelTools,
58
+ skillProvider: kernelSkillProvider,
59
+ mcpAdapter: kernelMcpAdapter,
60
+ onSettingsInvalidated: options.onSettingsInvalidated,
61
+ });
62
+ }
28
63
  /**
29
64
  * Shared product composition root for TUI, terminal, print and Web surfaces.
30
65
  *
@@ -37,29 +72,9 @@ async function createProductUiRuntime(options) {
37
72
  const ownsWorkspaceRuntimeKernel = options.workspaceRuntimeKernel === undefined;
38
73
  let workspaceRuntimeKernel = options.workspaceRuntimeKernel;
39
74
  if (!workspaceRuntimeKernel) {
40
- (0, config_dir_1.ensureConfigDir)();
41
- (0, global_config_1.recordFirstStartTime)();
42
- const kernelConfig = (0, config_1.loadConfig)(options.uiRenderer ? { ui: { renderer: options.uiRenderer } } : {});
43
- const kernelTools = (0, first_party_tool_universe_1.createProductionFirstPartyToolUniverseV1)({
44
- context: {
45
- cwd: canonicalCwd,
46
- config: { name: kernelConfig.name, mode: kernelConfig.mode },
47
- },
48
- });
49
- const kernelSkillProvider = (0, skills_1.createProductionFilesystemSkillProviderV1)({
50
- cwd: canonicalCwd,
51
- configuredPaths: kernelConfig.skills?.paths,
52
- });
53
- const kernelMcpAdapter = (0, mcp_1.createFirstPartyMcpAdapterV1)({
54
- config: (0, mcp_1.loadFirstPartyMcpConfigurationV1)(),
55
- baseDirectory: canonicalCwd,
56
- });
57
- workspaceRuntimeKernel = new workspace_runtime_kernel_1.WorkspaceRuntimeKernelV1({
75
+ workspaceRuntimeKernel = createWorkspaceRuntimeKernelV1({
58
76
  cwd: canonicalCwd,
59
- config: kernelConfig,
60
- toolUniverse: kernelTools,
61
- skillProvider: kernelSkillProvider,
62
- mcpAdapter: kernelMcpAdapter,
77
+ uiRenderer: options.uiRenderer,
63
78
  onSettingsInvalidated: options.onSettingsInvalidated,
64
79
  });
65
80
  }
@@ -204,14 +204,24 @@ function parseSgrParams(params) {
204
204
  setForeground(change, named('white'));
205
205
  break;
206
206
  // Unrecognised SGR codes are ignored (they are still valid SGR).
207
- default: break;
207
+ default:
208
+ break;
208
209
  }
209
210
  }
210
211
  if (!hasAny)
211
212
  return null;
212
213
  return change;
213
214
  }
214
- const VALID_NAMED_COLORS = ['black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white'];
215
+ const VALID_NAMED_COLORS = [
216
+ 'black',
217
+ 'red',
218
+ 'green',
219
+ 'yellow',
220
+ 'blue',
221
+ 'magenta',
222
+ 'cyan',
223
+ 'white',
224
+ ];
215
225
  function named(name) {
216
226
  // The SGR code mapping only produces valid named colors, but guard anyway.
217
227
  const value = VALID_NAMED_COLORS.includes(name)
@@ -32,10 +32,12 @@ function layoutBlock(block, width, theme, indent) {
32
32
  return layoutTable(block, width, theme, indent);
33
33
  case 'rule': {
34
34
  const available = Math.max(1, width - indent);
35
- return [[
35
+ return [
36
+ [
36
37
  { text: ' '.repeat(indent), style: theme('muted') },
37
38
  { text: '─'.repeat(available), style: theme('muted') },
38
- ]];
39
+ ],
40
+ ];
39
41
  }
40
42
  }
41
43
  }
@@ -80,10 +82,12 @@ function layoutList(block, width, theme, indent) {
80
82
  return rows;
81
83
  }
82
84
  function layoutQuote(block, width, theme, indent) {
83
- const prefix = [{
85
+ const prefix = [
86
+ {
84
87
  text: `${' '.repeat(normalizeIndent(indent, width))}> `,
85
88
  style: theme('muted'),
86
- }];
89
+ },
90
+ ];
87
91
  const prefixWidth = rowWidth(prefix);
88
92
  const childWidth = Math.max(1, width - prefixWidth);
89
93
  const rows = [];
@@ -105,14 +109,9 @@ function layoutCode(lines, width, theme, indent, language) {
105
109
  const rows = [];
106
110
  if (language) {
107
111
  const labelStyle = mergeStyles(style, theme('muted'));
108
- const labelPrefix = safeIndent > 0
109
- ? [{ text: ' '.repeat(safeIndent), style: labelStyle }]
110
- : [];
112
+ const labelPrefix = safeIndent > 0 ? [{ text: ' '.repeat(safeIndent), style: labelStyle }] : [];
111
113
  const label = truncateToWidth(language, available);
112
- rows.push(clampRow([
113
- ...labelPrefix,
114
- ...padRow([{ text: label, style: labelStyle }], available, labelStyle),
115
- ], width));
114
+ rows.push(clampRow([...labelPrefix, ...padRow([{ text: label, style: labelStyle }], available, labelStyle)], width));
116
115
  }
117
116
  for (const line of lines.length > 0 ? lines : ['']) {
118
117
  const wrapped = wrapStyledSpans([{ text: line, style }], available, false);
@@ -157,9 +156,7 @@ function layoutTable(block, width, theme, indent) {
157
156
  if (columnWidth < 4)
158
157
  return layoutTableAsKeyValue(block, width, theme, safeIndent);
159
158
  const rows = [];
160
- const indentSpan = safeIndent > 0
161
- ? [{ text: ' '.repeat(safeIndent), style: theme('assistantText') }]
162
- : [];
159
+ const indentSpan = safeIndent > 0 ? [{ text: ' '.repeat(safeIndent), style: theme('assistantText') }] : [];
163
160
  const header = [...indentSpan];
164
161
  for (let column = 0; column < columnCount; column++) {
165
162
  const text = block.headers[column]?.map(span => span.text).join('') ?? '';
@@ -195,7 +192,9 @@ function layoutTableAsKeyValue(block, width, theme, indent) {
195
192
  const value = sourceRow[column] ?? [];
196
193
  const prefix = [{ text: `${prefixIndent}${key}: `, style: theme('heading') }];
197
194
  const safePrefix = clampRow(prefix, Math.max(1, width - 1));
198
- const continuation = [{ text: ' '.repeat(rowWidth(safePrefix)), style: theme('muted') }];
195
+ const continuation = [
196
+ { text: ' '.repeat(rowWidth(safePrefix)), style: theme('muted') },
197
+ ];
199
198
  const styledValue = value.map(span => ({
200
199
  text: span.text,
201
200
  style: resolveSpanStyle(span, theme('assistantText'), theme),
@@ -214,10 +213,7 @@ function layoutPrefixedSpans(spans, width, firstPrefix, continuationPrefix, soft
214
213
  return hasContent ? [prefixRow, ...body.map(row => clampRow(row, width))] : [prefixRow];
215
214
  }
216
215
  const bodyRows = wrapStyledSpans(spans, width - prefixWidth, softWrap);
217
- return bodyRows.map((row, index) => clampRow([
218
- ...(index === 0 ? firstPrefix : continuationPrefix),
219
- ...row,
220
- ], width));
216
+ return bodyRows.map((row, index) => clampRow([...(index === 0 ? firstPrefix : continuationPrefix), ...row], width));
221
217
  }
222
218
  function wrapStyledSpans(spans, width, softWrap) {
223
219
  const safeWidth = normalizeWidth(width);
@@ -122,7 +122,10 @@ class SubagentBudgetLedger {
122
122
  }
123
123
  snapshot() {
124
124
  const used = (0, types_1.sumSubtaskUsage)(this.reconciled);
125
- const reservedModelRequests = (0, types_1.sumSubtaskUsage)(Array.from(this.reserved.entries()).map(([, n]) => ({ ...types_1.EMPTY_SUBTASK_USAGE, modelRequests: n }))).modelRequests;
125
+ const reservedModelRequests = (0, types_1.sumSubtaskUsage)(Array.from(this.reserved.entries()).map(([, n]) => ({
126
+ ...types_1.EMPTY_SUBTASK_USAGE,
127
+ modelRequests: n,
128
+ }))).modelRequests;
126
129
  const availableModelRequests = this.availableModelRequests();
127
130
  return {
128
131
  reservedModelRequests,
@@ -162,7 +162,9 @@ function clampSubagentConfig(config) {
162
162
  const clamp = (value, bounds) => Math.max(bounds.min, Math.min(bounds.max, Math.floor(value) || bounds.min));
163
163
  const roles = Array.from(new Set(config.roles)).filter(r => ['research', 'review', 'test-investigate'].includes(r));
164
164
  const clampedRoles = roles.length > 0 ? roles : DEFAULT_ROLES;
165
- const mode = ['off', 'explicit', 'auto'].includes(config.mode) ? config.mode : 'auto';
165
+ const mode = ['off', 'explicit', 'auto'].includes(config.mode)
166
+ ? config.mode
167
+ : 'auto';
166
168
  return {
167
169
  mode,
168
170
  maxParallel: clamp(config.maxParallel, types_1.SUBAGENT_LIMITS.maxParallel),
@@ -217,9 +219,14 @@ function hasMultipleInvestigationDirections(objective) {
217
219
  const lower = objective.toLowerCase();
218
220
  // Conjunctions that signal multiple independent directions.
219
221
  const multiMarkers = [
220
- / and /, /, and /, / or /,
221
- / both /, / each /,
222
- / parallel/, /separately/, /independently/,
222
+ / and /,
223
+ /, and /,
224
+ / or /,
225
+ / both /,
226
+ / each /,
227
+ / parallel/,
228
+ /separately/,
229
+ /independently/,
223
230
  / respectively/,
224
231
  // CJK: multiple items or separation
225
232
  /(和|与|以及|分别|并行|独立|同时)/,
@@ -40,9 +40,8 @@ class SubagentProviderGate {
40
40
  this.cooldownTimer = null;
41
41
  const maxConcurrent = Number(options.maxConcurrent);
42
42
  // N12: guard against NaN/undefined/negative — clamp to 1 as safe minimum.
43
- this.maxConcurrent = Number.isFinite(maxConcurrent) && maxConcurrent >= 1
44
- ? Math.floor(maxConcurrent)
45
- : 1;
43
+ this.maxConcurrent =
44
+ Number.isFinite(maxConcurrent) && maxConcurrent >= 1 ? Math.floor(maxConcurrent) : 1;
46
45
  this.now = options.now ?? (() => Date.now());
47
46
  this.scheduleTimer = options.scheduleTimer ?? defaultScheduleTimer;
48
47
  this.sharedGate = options.sharedGate ?? null;
@@ -179,7 +178,10 @@ exports.SubagentProviderGate = SubagentProviderGate;
179
178
  function defaultScheduleTimer(fn, ms) {
180
179
  const handle = setTimeout(fn, ms);
181
180
  // Don't keep the event loop alive solely for a cooldown wake.
182
- if (typeof handle === 'object' && handle && 'unref' in handle && typeof handle.unref === 'function') {
181
+ if (typeof handle === 'object' &&
182
+ handle &&
183
+ 'unref' in handle &&
184
+ typeof handle.unref === 'function') {
183
185
  handle.unref();
184
186
  }
185
187
  return {
@@ -87,7 +87,9 @@ function researchCostSummary(packet, opts = {}) {
87
87
  if (packet.sources.length > packet.request.maxSources) {
88
88
  notes.push(`source count exceeded request budget (${packet.sources.length} > ${packet.request.maxSources})`);
89
89
  }
90
- if (opts.fetchedBytes !== undefined && packet.request.maxFetchBytes > 0 && opts.fetchedBytes > packet.request.maxFetchBytes) {
90
+ if (opts.fetchedBytes !== undefined &&
91
+ packet.request.maxFetchBytes > 0 &&
92
+ opts.fetchedBytes > packet.request.maxFetchBytes) {
91
93
  notes.push(`fetched bytes exceeded budget (${opts.fetchedBytes} > ${packet.request.maxFetchBytes})`);
92
94
  }
93
95
  return {