@lincoln504/pi-research 0.1.12 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +64 -115
- package/dist/cli.mjs +23211 -0
- package/dist/prompts/research-knowledge-relevance-triage.md +35 -0
- package/dist/prompts/research-knowledge-search-extractor.md +41 -0
- package/dist/prompts/research-tool-usage.md +92 -0
- package/dist/prompts/researcher.md +89 -0
- package/dist/prompts/system-coordinator.md +75 -0
- package/dist/prompts/system-lead-evaluator.md +118 -0
- package/dist/thread-worker.mjs +2611 -0
- package/dist/webgpu-probe.mjs +80 -0
- package/docs/AGENT-SKILL.md +102 -0
- package/docs/ARCHITECTURE.md +306 -80
- package/docs/CONFIGURATION.md +249 -0
- package/docs/KNOWLEDGE-STORE.md +213 -0
- package/docs/PI-EXTENSION.md +91 -0
- package/docs/SDK.md +113 -0
- package/docs/THIRD-PARTY-NOTICES.md +64 -0
- package/package.json +102 -36
- package/scripts/build.cjs +140 -0
- package/scripts/cleanup.cjs +149 -0
- package/scripts/prepare.cjs +28 -0
- package/scripts/setup.cjs +134 -0
- package/skills/pi-research/README.md +90 -0
- package/skills/pi-research/SKILL.md +102 -0
- package/skills/pi-research/references/configuration.md +77 -0
- package/skills/pi-research/scripts/run.mjs +237 -0
- package/src/cleanup/research-cleanup.ts +140 -0
- package/src/cli.ts +1044 -0
- package/src/config.ts +1036 -147
- package/src/constants.ts +115 -84
- package/src/core/health-check-service.ts +265 -0
- package/src/core/interfaces/health-check-interfaces.ts +50 -0
- package/src/core/interfaces/knowledge-interfaces.ts +190 -0
- package/src/core/interfaces/observer-interfaces.ts +67 -0
- package/src/core/interfaces/orchestration-interfaces.ts +86 -0
- package/src/core/interfaces/planning-interfaces.ts +100 -0
- package/src/core/interfaces/process-interfaces.ts +37 -0
- package/src/core/interfaces/research-plan-types.ts +52 -0
- package/src/core/interfaces/scheduler-interfaces.ts +91 -0
- package/src/core/interfaces/service-names.ts +39 -0
- package/src/core/interfaces/state-manager-interfaces.ts +29 -0
- package/src/core/interfaces/steering-interfaces.ts +31 -0
- package/src/core/llm/agentic-repair.ts +169 -0
- package/src/core/llm/inject-date.ts +52 -0
- package/src/core/llm/llm-timeout.ts +64 -0
- package/src/core/llm/llm-utils.ts +86 -0
- package/src/core/llm/model-registry-factory.ts +287 -0
- package/src/core/llm/pi-ai-completion.ts +29 -0
- package/src/core/llm/prompts.ts +50 -0
- package/src/core/llm/research-model-resolver.ts +91 -0
- package/src/core/planning-constants.ts +13 -0
- package/src/core/planning-service.ts +649 -0
- package/src/core/planning-utils.ts +299 -0
- package/src/core/scheduler-factory.ts +48 -0
- package/src/core/scheduler-service.ts +123 -0
- package/src/core/service-initialization.ts +194 -0
- package/src/core/service-interfaces.ts +48 -0
- package/src/core/service-registry.ts +659 -0
- package/src/healthcheck/index.ts +271 -265
- package/src/healthcheck/registry.ts +130 -0
- package/src/index.ts +639 -50
- package/src/infrastructure/browser/browser-cleanup.ts +309 -0
- package/src/infrastructure/browser/browser-client.ts +199 -0
- package/src/infrastructure/browser/browser-error-utils.ts +160 -0
- package/src/infrastructure/browser/browser-lifecycle.ts +120 -0
- package/src/infrastructure/browser/browser-server.ts +174 -0
- package/src/infrastructure/browser/browser-task-scheduler.ts +461 -0
- package/src/infrastructure/browser/cleanup-utils.ts +155 -0
- package/src/infrastructure/browser/client-agent.ts +31 -0
- package/src/infrastructure/browser/config.ts +288 -0
- package/src/infrastructure/browser/ensure-browser.ts +205 -0
- package/src/infrastructure/browser/index.ts +37 -0
- package/src/infrastructure/browser/priority-task-queue.ts +226 -0
- package/src/infrastructure/browser/scheduler-factory.ts +354 -0
- package/src/infrastructure/browser/task-execution-service.ts +209 -0
- package/src/infrastructure/browser/thread-worker-browser.ts +282 -0
- package/src/infrastructure/browser/thread-worker-lifecycle.ts +323 -0
- package/src/infrastructure/browser/thread-worker-messaging.ts +662 -0
- package/src/infrastructure/browser/thread-worker-types.ts +25 -0
- package/src/infrastructure/browser/thread-worker.mjs +2611 -0
- package/src/infrastructure/browser/thread-worker.ts +168 -0
- package/src/infrastructure/browser/worker-pool-manager.ts +449 -0
- package/src/infrastructure/embedding/embedding-client.ts +205 -0
- package/src/infrastructure/embedding/embedding-factory.ts +324 -0
- package/src/infrastructure/embedding/embedding-server.ts +535 -0
- package/src/infrastructure/file-lock-service.ts +592 -0
- package/src/infrastructure/gpu-resource-service.ts +191 -0
- package/src/infrastructure/knowledge-store-service.ts +475 -0
- package/src/infrastructure/metrics-service.ts +129 -0
- package/src/infrastructure/process-lifecycle-service.ts +253 -0
- package/src/infrastructure/scheduler-factory-service.ts +58 -0
- package/src/infrastructure/service-initialization.ts +292 -0
- package/src/infrastructure/state/state-backup-manager.ts +290 -0
- package/src/infrastructure/state/state-browser-api.ts +62 -0
- package/src/infrastructure/state/state-browser-manager.ts +70 -0
- package/src/infrastructure/state/state-manager-service.ts +264 -0
- package/src/infrastructure/state/state-manager.ts +547 -0
- package/src/infrastructure/state/state-metrics.ts +92 -0
- package/src/infrastructure/state/state-migration.ts +103 -0
- package/src/infrastructure/state/state-path-configuration.ts +119 -0
- package/src/infrastructure/state/state-session-api.ts +94 -0
- package/src/infrastructure/state/state-session-manager.ts +127 -0
- package/src/infrastructure/state/state-validator.ts +93 -0
- package/src/infrastructure/types/state-types.ts +85 -0
- package/src/knowledge/chunker.ts +157 -0
- package/src/knowledge/embedder-init.ts +413 -0
- package/src/knowledge/embedder-utils.ts +296 -0
- package/src/knowledge/embedder.ts +592 -0
- package/src/knowledge/index.ts +126 -0
- package/src/knowledge/lancedb-loader.ts +35 -0
- package/src/knowledge/migration.ts +17 -0
- package/src/knowledge/model-config.ts +132 -0
- package/src/knowledge/onnx-env.ts +48 -0
- package/src/knowledge/store-operations.ts +291 -0
- package/src/knowledge/store-schema.ts +79 -0
- package/src/knowledge/store.ts +1251 -0
- package/src/knowledge/transformers-loader.ts +31 -0
- package/src/knowledge/webgpu-probe.mjs +80 -0
- package/src/knowledge/webgpu-viability.ts +255 -0
- package/src/knowledge/writer-queue.ts +252 -0
- package/src/logger.ts +316 -122
- package/src/observers/research-observer-impl.ts +459 -0
- package/src/orchestration/deep-research-orchestrator.ts +524 -773
- package/src/orchestration/deep-research-types.ts +47 -35
- package/src/orchestration/headless-observer.ts +114 -0
- package/src/orchestration/orchestration-types.ts +33 -0
- package/src/orchestration/quick-research-orchestrator.ts +463 -0
- package/src/orchestration/research-orchestration-service.ts +549 -0
- package/src/orchestration/research-session-service.ts +178 -0
- package/src/orchestration/research-synthesis-service.ts +348 -0
- package/src/orchestration/researcher-executor.ts +394 -0
- package/src/orchestration/researcher.ts +115 -42
- package/src/orchestration/service-initialization.ts +109 -0
- package/src/orchestration/session-context.ts +7 -3
- package/src/orchestration/session-state.ts +672 -0
- package/src/prompts/research-knowledge-relevance-triage.md +35 -0
- package/src/prompts/research-knowledge-search-extractor.md +41 -0
- package/src/prompts/research-tool-usage.md +63 -16
- package/src/prompts/researcher.md +59 -136
- package/src/prompts/system-coordinator.md +64 -13
- package/src/prompts/system-lead-evaluator.md +82 -189
- package/src/research-config.ts +872 -0
- package/src/sdk.ts +847 -0
- package/src/security/cisa-kev.ts +72 -9
- package/src/security/cvss.ts +92 -0
- package/src/security/ecosystem.ts +45 -0
- package/src/security/github-advisories.ts +168 -428
- package/src/security/github-advisory-types.ts +282 -0
- package/src/security/index.ts +63 -177
- package/src/security/nvd-client.ts +550 -0
- package/src/security/nvd-types.ts +179 -0
- package/src/security/osv-client.ts +246 -0
- package/src/security/osv-types.ts +285 -0
- package/src/security/types.ts +67 -53
- package/src/skill-install/skill-installer.ts +519 -0
- package/src/stackexchange/index.ts +81 -27
- package/src/stackexchange/output/compact.ts +7 -6
- package/src/stackexchange/output/table.ts +11 -7
- package/src/stackexchange/queries.ts +0 -10
- package/src/stackexchange/rest-client.ts +141 -66
- package/src/stackexchange/types.ts +0 -17
- package/src/tool.ts +15 -423
- package/src/tools/grep.ts +32 -310
- package/src/tools/health-tool-definition.ts +101 -0
- package/src/tools/index.ts +38 -9
- package/src/tools/research-knowledge-search.ts +974 -0
- package/src/tools/research-knowledge-types.ts +77 -0
- package/src/tools/research-tool-definition.ts +494 -0
- package/src/tools/scrape.ts +286 -388
- package/src/tools/search.ts +98 -153
- package/src/tools/security.ts +107 -80
- package/src/tools/stackexchange.ts +58 -35
- package/src/tools/youtube-transcript.ts +163 -0
- package/src/tui/knowledge-search-panel.ts +86 -0
- package/src/tui/pulse.ts +86 -0
- package/src/tui/research-health.ts +124 -0
- package/src/tui/research-panel-color-utils.ts +340 -0
- package/src/tui/research-panel-format.ts +54 -0
- package/src/tui/research-panel-state.ts +404 -0
- package/src/tui/research-panel-wave.ts +280 -0
- package/src/tui/research-panel.ts +339 -435
- package/src/tui/research-tui-manager.ts +233 -0
- package/src/tui/tui-controller.ts +137 -0
- package/src/types/extension-context.ts +18 -49
- package/src/types/index.ts +61 -0
- package/src/types/llm.ts +68 -29
- package/src/types/research-panel-types.ts +61 -0
- package/src/utils/circuit-breaker.ts +146 -0
- package/src/utils/citation-utils.ts +97 -0
- package/src/utils/disk-space-checker.ts +55 -0
- package/src/utils/error-tracker.ts +265 -0
- package/src/utils/host-config.ts +45 -0
- package/src/utils/input-validation.ts +8 -10
- package/src/utils/json-utils.ts +150 -16
- package/src/utils/llm-usage.ts +54 -0
- package/src/utils/log-rotation.ts +125 -0
- package/src/utils/log-utils.ts +159 -0
- package/src/utils/make-resource-loader.ts +3 -1
- package/src/utils/metrics-summary.ts +459 -0
- package/src/utils/metrics.ts +302 -0
- package/src/utils/research-export.ts +28 -12
- package/src/utils/safe-unref.ts +31 -0
- package/src/utils/shared-links.ts +195 -90
- package/src/utils/shutdown-manager.ts +65 -0
- package/src/utils/stdio-capture.ts +407 -0
- package/src/utils/text-utils.ts +264 -7
- package/src/utils/tool-usage-tracker.ts +83 -16
- package/src/utils/url-utils.ts +255 -0
- package/src/utils/user-agent.ts +28 -0
- package/src/web-research/browser-search.ts +179 -0
- package/src/web-research/retry-utils.ts +173 -77
- package/src/web-research/scraper-types.ts +72 -0
- package/src/web-research/scraper-utils.ts +434 -0
- package/src/web-research/search.ts +78 -218
- package/src/web-research/types.ts +9 -24
- package/src/web-research/utils.ts +2 -143
- package/src/web-research/web-scraper.ts +477 -0
- package/src/youtube/index.ts +154 -0
- package/src/youtube/potoken.ts +259 -0
- package/src/youtube/transcript-client.ts +390 -0
- package/src/youtube/video-id.ts +118 -0
- package/config/searxng/default-settings.yml +0 -189
- package/config/searxng/limiter.toml +0 -34
- package/config/tooling/eslint.config.js +0 -73
- package/config/tooling/vitest.config.integration.ts +0 -17
- package/config/tooling/vitest.config.ts +0 -40
- package/config/tooling/vitest.config.unit.ts +0 -12
- package/docs/assets/README-banner.jpg +0 -0
- package/scripts/setup.js +0 -174
- package/src/infrastructure/searxng-lifecycle.ts +0 -577
- package/src/infrastructure/searxng-manager.ts +0 -1183
- package/src/infrastructure/state-manager.ts +0 -918
- package/src/orchestration/deep-research-reducer.ts +0 -129
- package/src/orchestration/id-utils.ts +0 -63
- package/src/security/nvd.ts +0 -532
- package/src/security/osv.ts +0 -487
- package/src/stackexchange/cache.ts +0 -111
- package/src/stackexchange/output/index.ts +0 -7
- package/src/stackexchange/output/json.ts +0 -21
- package/src/tui/searxng-status.ts +0 -80
- package/src/utils/inject-date.ts +0 -29
- package/src/utils/research-error.ts +0 -205
- package/src/utils/searxng-config.ts +0 -94
- package/src/utils/session-state.ts +0 -295
- package/src/web-research/dependencies.ts +0 -20
- package/src/web-research/scrapers.ts +0 -935
- package/tsconfig.json +0 -40
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Infrastructure Service Initialization Module
|
|
3
|
+
*
|
|
4
|
+
* This module registers all infrastructure-level services with the service registry.
|
|
5
|
+
* Infrastructure services are registered by this module to avoid Core layer
|
|
6
|
+
* depending on Infrastructure layer.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { registerService, getService, disposeAllServices, type IService, getServiceContainer } from '../core/service-registry.ts';
|
|
10
|
+
import type { ServiceContainer } from '../core/service-registry.ts';
|
|
11
|
+
import { ServiceNames } from '../core/service-interfaces.ts';
|
|
12
|
+
import type { IKnowledgeStoreService } from '../core/service-interfaces.ts';
|
|
13
|
+
import { SchedulerFactoryService } from './scheduler-factory-service.ts';
|
|
14
|
+
import { StateManagerService } from './state/state-manager-service.ts';
|
|
15
|
+
import { MetricsService } from './metrics-service.ts';
|
|
16
|
+
import { ProcessLifecycleService } from './process-lifecycle-service.ts';
|
|
17
|
+
import { GPUResourceService } from './gpu-resource-service.ts';
|
|
18
|
+
import { StateSessionManager } from './state/state-session-manager.ts';
|
|
19
|
+
import { StateBrowserManager } from './state/state-browser-manager.ts';
|
|
20
|
+
import { StateMetricsCollector } from './state/state-metrics.ts';
|
|
21
|
+
import { StateValidator } from './state/state-validator.ts';
|
|
22
|
+
import { WorkerPoolManager } from './browser/worker-pool-manager.ts';
|
|
23
|
+
import { StatePathConfiguration } from './state/state-path-configuration.ts';
|
|
24
|
+
import { FileLockService } from './file-lock-service.ts';
|
|
25
|
+
import { StateBackupManager } from './state/state-backup-manager.ts';
|
|
26
|
+
import { logger } from '../logger.ts';
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Register all infrastructure services with the service registry
|
|
30
|
+
*/
|
|
31
|
+
export function registerInfrastructureServices(container: ServiceContainer = getServiceContainer()): void {
|
|
32
|
+
logger.debug('[InfrastructureServiceInit] Registering infrastructure services...');
|
|
33
|
+
|
|
34
|
+
// Register Process Lifecycle Service
|
|
35
|
+
registerService(
|
|
36
|
+
ServiceNames.PROCESS_LIFECYCLE,
|
|
37
|
+
() => new ProcessLifecycleService(),
|
|
38
|
+
{
|
|
39
|
+
allowOverwrite: false,
|
|
40
|
+
enableLogging: true,
|
|
41
|
+
},
|
|
42
|
+
container
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
// Register State Path Configuration Service
|
|
46
|
+
// Must be initialized before FileLockService and StateBackupManager
|
|
47
|
+
registerService(
|
|
48
|
+
ServiceNames.STATE_PATH_CONFIGURATION,
|
|
49
|
+
() => new StatePathConfiguration(process.env['PI_RESEARCH_STATE_DIR']),
|
|
50
|
+
{
|
|
51
|
+
allowOverwrite: false,
|
|
52
|
+
enableLogging: true,
|
|
53
|
+
},
|
|
54
|
+
container
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
// Register File Lock Service
|
|
58
|
+
// Depends on StatePathConfiguration
|
|
59
|
+
registerService(
|
|
60
|
+
ServiceNames.FILE_LOCK_SERVICE,
|
|
61
|
+
async () => {
|
|
62
|
+
const pathConfig = await getService<StatePathConfiguration>(ServiceNames.STATE_PATH_CONFIGURATION, undefined, container);
|
|
63
|
+
const processLifecycle = await getService<ProcessLifecycleService>(ServiceNames.PROCESS_LIFECYCLE, undefined, container);
|
|
64
|
+
return new FileLockService({
|
|
65
|
+
lockFilePath: pathConfig.getLockFilePath(),
|
|
66
|
+
processLifecycle,
|
|
67
|
+
});
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
allowOverwrite: false,
|
|
71
|
+
enableLogging: true,
|
|
72
|
+
},
|
|
73
|
+
container
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
// Register State Backup Manager Service
|
|
77
|
+
// Depends on StatePathConfiguration
|
|
78
|
+
registerService(
|
|
79
|
+
ServiceNames.STATE_BACKUP_MANAGER,
|
|
80
|
+
async () => {
|
|
81
|
+
const pathConfig = await getService<StatePathConfiguration>(ServiceNames.STATE_PATH_CONFIGURATION, undefined, container);
|
|
82
|
+
return new StateBackupManager(
|
|
83
|
+
pathConfig.getStateFilePath(),
|
|
84
|
+
pathConfig.getBackupDirPath(),
|
|
85
|
+
10 // maxBackups
|
|
86
|
+
);
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
allowOverwrite: false,
|
|
90
|
+
enableLogging: true,
|
|
91
|
+
},
|
|
92
|
+
container
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
// Register Scheduler Factory Service
|
|
96
|
+
// This is the factory that creates scheduler instances
|
|
97
|
+
registerService(
|
|
98
|
+
ServiceNames.SCHEDULER_FACTORY,
|
|
99
|
+
() => new SchedulerFactoryService(),
|
|
100
|
+
{
|
|
101
|
+
allowOverwrite: false,
|
|
102
|
+
enableLogging: true,
|
|
103
|
+
},
|
|
104
|
+
container
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
// Register State Manager Service
|
|
108
|
+
registerService(
|
|
109
|
+
ServiceNames.STATE_MANAGER,
|
|
110
|
+
() => new StateManagerService(),
|
|
111
|
+
{
|
|
112
|
+
allowOverwrite: false,
|
|
113
|
+
enableLogging: true,
|
|
114
|
+
},
|
|
115
|
+
container
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
// Register Knowledge Store Service.
|
|
119
|
+
// Loaded via dynamic import so the native ML/vector stack
|
|
120
|
+
// (@huggingface/transformers / onnxruntime-node, @lancedb/lancedb) is only
|
|
121
|
+
// pulled in when the store is actually requested — never at extension load.
|
|
122
|
+
// The default mode is 'global', but this factory is still fully lazy: it only
|
|
123
|
+
// runs the first time the store is actually accessed (a scrape cache-check or
|
|
124
|
+
// write), so startup stays native-free. When KNOWLEDGE_STORE_MODE is 'none'
|
|
125
|
+
// this factory never runs at all.
|
|
126
|
+
registerService(
|
|
127
|
+
ServiceNames.KNOWLEDGE_STORE,
|
|
128
|
+
async () => {
|
|
129
|
+
const { KnowledgeStoreService } = await import('./knowledge-store-service.ts');
|
|
130
|
+
return new KnowledgeStoreService();
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
allowOverwrite: false,
|
|
134
|
+
enableLogging: true,
|
|
135
|
+
},
|
|
136
|
+
container
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
// Register Metrics Service
|
|
140
|
+
registerService(
|
|
141
|
+
ServiceNames.METRICS,
|
|
142
|
+
() => new MetricsService(),
|
|
143
|
+
{
|
|
144
|
+
allowOverwrite: false,
|
|
145
|
+
enableLogging: true,
|
|
146
|
+
},
|
|
147
|
+
container
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
// Register Writer Queue Service
|
|
151
|
+
// This depends on KnowledgeStoreService being initialized
|
|
152
|
+
registerService(
|
|
153
|
+
ServiceNames.WRITER_QUEUE,
|
|
154
|
+
async () => {
|
|
155
|
+
const storeService = await getService<IKnowledgeStoreService>(ServiceNames.KNOWLEDGE_STORE, undefined, container);
|
|
156
|
+
const queue = await storeService.getWriterQueue();
|
|
157
|
+
if (!queue) {
|
|
158
|
+
throw new Error('Writer queue is disabled in configuration');
|
|
159
|
+
}
|
|
160
|
+
return queue;
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
allowOverwrite: false,
|
|
164
|
+
enableLogging: true,
|
|
165
|
+
},
|
|
166
|
+
container
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
// Register GPU Resource Service
|
|
170
|
+
// Depends on ProcessLifecycleService
|
|
171
|
+
registerService(
|
|
172
|
+
ServiceNames.GPU_RESOURCE_SERVICE,
|
|
173
|
+
async () => {
|
|
174
|
+
const processLifecycle = await getService<ProcessLifecycleService>(ServiceNames.PROCESS_LIFECYCLE, undefined, container);
|
|
175
|
+
return new GPUResourceService({ processLifecycle });
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
allowOverwrite: false,
|
|
179
|
+
enableLogging: true,
|
|
180
|
+
},
|
|
181
|
+
container
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
// Register State Session Manager Service
|
|
185
|
+
// Depends on ProcessLifecycleService
|
|
186
|
+
registerService(
|
|
187
|
+
ServiceNames.STATE_SESSION_MANAGER,
|
|
188
|
+
async () => {
|
|
189
|
+
const processLifecycle = await getService<ProcessLifecycleService>(ServiceNames.PROCESS_LIFECYCLE, undefined, container);
|
|
190
|
+
return new StateSessionManager(processLifecycle);
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
allowOverwrite: false,
|
|
194
|
+
enableLogging: true,
|
|
195
|
+
},
|
|
196
|
+
container
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
// Register State Browser Manager Service
|
|
200
|
+
registerService(
|
|
201
|
+
ServiceNames.STATE_BROWSER_MANAGER,
|
|
202
|
+
() => new StateBrowserManager(),
|
|
203
|
+
{
|
|
204
|
+
allowOverwrite: false,
|
|
205
|
+
enableLogging: true,
|
|
206
|
+
},
|
|
207
|
+
container
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
// Register State Metrics Collector Service
|
|
211
|
+
registerService(
|
|
212
|
+
ServiceNames.STATE_METRICS_COLLECTOR,
|
|
213
|
+
() => new StateMetricsCollector(),
|
|
214
|
+
{
|
|
215
|
+
allowOverwrite: false,
|
|
216
|
+
enableLogging: true,
|
|
217
|
+
},
|
|
218
|
+
container
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
// Register State Validator Service
|
|
222
|
+
registerService(
|
|
223
|
+
ServiceNames.STATE_VALIDATOR,
|
|
224
|
+
() => new StateValidator(),
|
|
225
|
+
{
|
|
226
|
+
allowOverwrite: false,
|
|
227
|
+
enableLogging: true,
|
|
228
|
+
},
|
|
229
|
+
container
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
// Register Worker Pool Manager Service
|
|
233
|
+
registerService(
|
|
234
|
+
ServiceNames.WORKER_POOL_MANAGER,
|
|
235
|
+
() => new WorkerPoolManager(),
|
|
236
|
+
{
|
|
237
|
+
allowOverwrite: false,
|
|
238
|
+
enableLogging: true,
|
|
239
|
+
},
|
|
240
|
+
container
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
logger.debug('[InfrastructureServiceInit] Infrastructure services registered');
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Initialize all infrastructure services
|
|
248
|
+
*
|
|
249
|
+
* @param ctx - Optional extension context
|
|
250
|
+
* @param container - Service container instance
|
|
251
|
+
*/
|
|
252
|
+
export async function initializeInfrastructureServices(ctx?: any, container: ServiceContainer = getServiceContainer()): Promise<{ success: boolean; initialized: string[]; failed: string[] }> {
|
|
253
|
+
logger.log('[InfrastructureServiceInit] Initializing infrastructure services...');
|
|
254
|
+
|
|
255
|
+
const initialized: string[] = [];
|
|
256
|
+
const failed: string[] = [];
|
|
257
|
+
|
|
258
|
+
// Infrastructure services that should be eagerly initialized
|
|
259
|
+
const eagerServices = [
|
|
260
|
+
{ name: ServiceNames.PROCESS_LIFECYCLE, label: 'Process Lifecycle' },
|
|
261
|
+
{ name: ServiceNames.STATE_PATH_CONFIGURATION, label: 'Path Configuration' },
|
|
262
|
+
{ name: ServiceNames.STATE_MANAGER, label: 'State Manager' },
|
|
263
|
+
{ name: ServiceNames.METRICS, label: 'Metrics' },
|
|
264
|
+
];
|
|
265
|
+
|
|
266
|
+
for (const service of eagerServices) {
|
|
267
|
+
try {
|
|
268
|
+
await getService<IService>(service.name, ctx, container);
|
|
269
|
+
initialized.push(service.label);
|
|
270
|
+
} catch (err) {
|
|
271
|
+
const msg = `${service.label} init failed`;
|
|
272
|
+
logger.error(`[InfrastructureServiceInit] ${msg}:`, err);
|
|
273
|
+
failed.push(msg);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return {
|
|
278
|
+
success: failed.length === 0,
|
|
279
|
+
initialized,
|
|
280
|
+
failed
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Shutdown all infrastructure services
|
|
286
|
+
*
|
|
287
|
+
* @param container - Service container instance
|
|
288
|
+
*/
|
|
289
|
+
export async function shutdownInfrastructureServices(container: ServiceContainer = getServiceContainer()): Promise<void> {
|
|
290
|
+
logger.log('[InfrastructureServiceInit] Shutting down infrastructure services...');
|
|
291
|
+
await disposeAllServices(container);
|
|
292
|
+
}
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* State Backup Manager
|
|
3
|
+
*
|
|
4
|
+
* Handles backup creation, cleanup, and recovery for state files.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import * as fs from 'node:fs/promises';
|
|
8
|
+
import * as crypto from 'node:crypto';
|
|
9
|
+
import * as path from 'node:path';
|
|
10
|
+
import { logger } from '../../logger.ts';
|
|
11
|
+
import type { IService } from '../../core/service-registry.ts';
|
|
12
|
+
import { ServiceLifecycle } from '../../core/service-registry.ts';
|
|
13
|
+
import { ServiceNames } from '../../core/interfaces/service-names.ts';
|
|
14
|
+
import { StateValidator } from './state-validator.ts';
|
|
15
|
+
import { CURRENT_STATE_VERSION } from '../types/state-types.ts';
|
|
16
|
+
|
|
17
|
+
// How many newest .quarantine copies to retain. Small: they exist only for forensic
|
|
18
|
+
// inspection of a newer-build/older-reader collision, never for restore.
|
|
19
|
+
const QUARANTINE_KEEP = 5;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Manages backup and recovery for state files
|
|
23
|
+
*/
|
|
24
|
+
export class StateBackupManager implements IService {
|
|
25
|
+
readonly name = ServiceNames.STATE_BACKUP_MANAGER;
|
|
26
|
+
lifecycle = ServiceLifecycle.UNINITIALIZED;
|
|
27
|
+
private _initialized = false;
|
|
28
|
+
|
|
29
|
+
private readonly stateFilePath: string;
|
|
30
|
+
private readonly backupDirPath: string;
|
|
31
|
+
private readonly maxBackups: number;
|
|
32
|
+
// Stateless schema validation/migration; constructed inline (no DI plumbing
|
|
33
|
+
// needed since validateState/migrateAndValidate don't depend on initialize()).
|
|
34
|
+
private readonly validator = new StateValidator();
|
|
35
|
+
|
|
36
|
+
constructor(stateFilePath: string, backupDirPath: string, maxBackups: number = 5) {
|
|
37
|
+
this.stateFilePath = stateFilePath;
|
|
38
|
+
this.backupDirPath = backupDirPath;
|
|
39
|
+
this.maxBackups = maxBackups;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async initialize(): Promise<void> {
|
|
43
|
+
if (this._initialized) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
this.lifecycle = ServiceLifecycle.INITIALIZING;
|
|
47
|
+
// Ensure backup directory exists
|
|
48
|
+
await fs.mkdir(this.backupDirPath, { recursive: true, mode: 0o700 });
|
|
49
|
+
this._initialized = true;
|
|
50
|
+
this.lifecycle = ServiceLifecycle.INITIALIZED;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async dispose(): Promise<void> {
|
|
54
|
+
// Nothing to dispose
|
|
55
|
+
this.lifecycle = ServiceLifecycle.DISPOSED;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Create a backup of the current state file
|
|
60
|
+
* @throws Error if unable to create backup
|
|
61
|
+
*/
|
|
62
|
+
async createBackup(): Promise<void> {
|
|
63
|
+
try {
|
|
64
|
+
// Check if state file exists
|
|
65
|
+
try {
|
|
66
|
+
await fs.access(this.stateFilePath);
|
|
67
|
+
} catch {
|
|
68
|
+
// No state file to backup
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Ensure backup directory exists
|
|
73
|
+
await fs.mkdir(this.backupDirPath, { recursive: true, mode: 0o700 });
|
|
74
|
+
|
|
75
|
+
// Timestamp + random suffix: two writers in the same millisecond would
|
|
76
|
+
// otherwise produce the same filename and silently overwrite one backup
|
|
77
|
+
// generation. The random suffix keeps both. Prefix/suffix still match the
|
|
78
|
+
// `research-state-*.json` patterns used by cleanup and recovery.
|
|
79
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
80
|
+
const backupFileName = `research-state-${timestamp}-${crypto.randomBytes(4).toString('hex')}.json`;
|
|
81
|
+
const backupFilePath = path.join(this.backupDirPath, backupFileName);
|
|
82
|
+
|
|
83
|
+
await fs.copyFile(this.stateFilePath, backupFilePath);
|
|
84
|
+
} catch (error: unknown) {
|
|
85
|
+
throw new Error(`Failed to create backup: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Clean up old backups, keeping only the most recent maxBackups
|
|
91
|
+
*/
|
|
92
|
+
async cleanupOldBackups(): Promise<void> {
|
|
93
|
+
try {
|
|
94
|
+
const entries = await fs.readdir(this.backupDirPath);
|
|
95
|
+
|
|
96
|
+
if (entries.length <= this.maxBackups) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Get file stats and sort by modification time (newest first)
|
|
101
|
+
const backupFiles: Array<{ name: string; mtime: Date }> = [];
|
|
102
|
+
|
|
103
|
+
for (const entry of entries) {
|
|
104
|
+
const filePath = path.join(this.backupDirPath, entry);
|
|
105
|
+
const stats = await fs.stat(filePath);
|
|
106
|
+
|
|
107
|
+
if (stats.isFile() && entry.startsWith('research-state-') && entry.endsWith('.json')) {
|
|
108
|
+
backupFiles.push({ name: entry, mtime: stats.mtime });
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Sort by mtime, newest first
|
|
113
|
+
backupFiles.sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
|
|
114
|
+
|
|
115
|
+
// Remove backups beyond the max count
|
|
116
|
+
const backupsToRemove = backupFiles.slice(this.maxBackups);
|
|
117
|
+
for (const backupFile of backupsToRemove) {
|
|
118
|
+
const filePath = path.join(this.backupDirPath, backupFile.name);
|
|
119
|
+
await fs.unlink(filePath);
|
|
120
|
+
}
|
|
121
|
+
} catch (error: unknown) {
|
|
122
|
+
logger.error(`Failed to cleanup old backups: ${error instanceof Error ? error.message : String(error)}`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Recover from corruption by restoring a backup or writing a default state.
|
|
128
|
+
*/
|
|
129
|
+
async recoverFromCorruption(): Promise<void> {
|
|
130
|
+
try {
|
|
131
|
+
// Collect candidate backups, newest first. The newest backup can itself be a
|
|
132
|
+
// partial write (e.g. a crash during createBackup) OR well-formed JSON that
|
|
133
|
+
// nonetheless fails the state schema, so we do NOT blindly restore it. We
|
|
134
|
+
// parse, migrate, and schema-validate each, and restore the first that
|
|
135
|
+
// passes. JSON-parse alone is insufficient: a parseable-but-invalid backup
|
|
136
|
+
// would be restored, fail validation on the next read, and trigger recovery
|
|
137
|
+
// again — a loop that never converges and never persists a usable state.
|
|
138
|
+
let backups: Array<{ name: string; mtime: Date }> = [];
|
|
139
|
+
try {
|
|
140
|
+
const entries = await fs.readdir(this.backupDirPath);
|
|
141
|
+
for (const entry of entries) {
|
|
142
|
+
const filePath = path.join(this.backupDirPath, entry);
|
|
143
|
+
const stats = await fs.stat(filePath);
|
|
144
|
+
if (stats.isFile() && entry.startsWith('research-state-') && entry.endsWith('.json')) {
|
|
145
|
+
backups.push({ name: entry, mtime: stats.mtime });
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
} catch {
|
|
149
|
+
// Backup directory unreadable — fall through to default state
|
|
150
|
+
}
|
|
151
|
+
backups.sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
|
|
152
|
+
|
|
153
|
+
for (const backup of backups) {
|
|
154
|
+
const backupPath = path.join(this.backupDirPath, backup.name);
|
|
155
|
+
let restoreContent: string;
|
|
156
|
+
try {
|
|
157
|
+
const content = await fs.readFile(backupPath, 'utf-8');
|
|
158
|
+
// Parse + migrate-forward + schema-validate. Persist the validated
|
|
159
|
+
// (and possibly migrated) object so the restored file is a current,
|
|
160
|
+
// well-formed state — never a raw older/odd backup. A future-version
|
|
161
|
+
// or schema-invalid backup throws here and is skipped.
|
|
162
|
+
const validated = this.validator.migrateAndValidate(JSON.parse(content));
|
|
163
|
+
restoreContent = JSON.stringify(validated, null, 2);
|
|
164
|
+
} catch (err) {
|
|
165
|
+
logger.warn(`[StateManager] Skipping invalid backup ${backup.name}: ${err instanceof Error ? err.message : String(err)}`);
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
// Atomic restore so a crash mid-recovery can't leave a half-written state file.
|
|
169
|
+
await this.atomicWriteStateFile(restoreContent);
|
|
170
|
+
logger.log(`[StateManager] Recovered state from backup: ${backup.name}`);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// No backups (or none valid) — write default state directly (atomic, no lock needed)
|
|
175
|
+
await this.writeDefaultState();
|
|
176
|
+
logger.log('[StateManager] Recovered with default state (no valid backups available)');
|
|
177
|
+
} catch (error: unknown) {
|
|
178
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
179
|
+
logger.error(`[StateManager] Failed to recover from corruption: ${message}`);
|
|
180
|
+
throw error;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Preserve a copy of a state file written by a NEWER build before this (older)
|
|
186
|
+
* process continues on an in-memory default. The original is left untouched —
|
|
187
|
+
* the newer build still owns and reads it. The quarantine copy is named so it
|
|
188
|
+
* matches neither the backup-recovery nor cleanup glob (`research-state-*.json`):
|
|
189
|
+
* it must never be restored into an older reader or pruned as an old backup.
|
|
190
|
+
*/
|
|
191
|
+
async quarantineFutureState(fileVersion: number): Promise<void> {
|
|
192
|
+
try {
|
|
193
|
+
await fs.access(this.stateFilePath);
|
|
194
|
+
} catch {
|
|
195
|
+
return; // nothing on disk to preserve
|
|
196
|
+
}
|
|
197
|
+
await fs.mkdir(this.backupDirPath, { recursive: true, mode: 0o700 });
|
|
198
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
199
|
+
const name = `research-state-future-v${fileVersion}-${timestamp}.quarantine`;
|
|
200
|
+
const dest = path.join(this.backupDirPath, name);
|
|
201
|
+
await fs.copyFile(this.stateFilePath, dest);
|
|
202
|
+
logger.warn(`[StateManager] Quarantined newer-build state (v${fileVersion}) copy at ${dest}`);
|
|
203
|
+
// Quarantine files are deliberately excluded from cleanupOldBackups (they must never be
|
|
204
|
+
// restored into an older reader), so nothing else prunes them. Cap retention here so a
|
|
205
|
+
// machine that repeatedly runs an older build against newer state (rollback, bisect,
|
|
206
|
+
// flip-flopping installs) can't accumulate them without bound.
|
|
207
|
+
await this.pruneQuarantineFiles(QUARANTINE_KEEP);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** Keep only the newest `keep` *.quarantine files; delete the rest. Best-effort. */
|
|
211
|
+
private async pruneQuarantineFiles(keep: number): Promise<void> {
|
|
212
|
+
try {
|
|
213
|
+
const entries = await fs.readdir(this.backupDirPath);
|
|
214
|
+
const quarantines: Array<{ name: string; mtime: number }> = [];
|
|
215
|
+
for (const entry of entries) {
|
|
216
|
+
if (!entry.endsWith('.quarantine')) continue;
|
|
217
|
+
try {
|
|
218
|
+
const stats = await fs.stat(path.join(this.backupDirPath, entry));
|
|
219
|
+
if (stats.isFile()) quarantines.push({ name: entry, mtime: stats.mtimeMs });
|
|
220
|
+
} catch { /* raced away — ignore */ }
|
|
221
|
+
}
|
|
222
|
+
if (quarantines.length <= keep) return;
|
|
223
|
+
quarantines.sort((a, b) => b.mtime - a.mtime);
|
|
224
|
+
for (const stale of quarantines.slice(keep)) {
|
|
225
|
+
try {
|
|
226
|
+
await fs.unlink(path.join(this.backupDirPath, stale.name));
|
|
227
|
+
} catch { /* already gone — ignore */ }
|
|
228
|
+
}
|
|
229
|
+
} catch { /* backup dir unreadable — nothing to prune */ }
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Atomically write the given content to the state file (temp file + rename, with
|
|
234
|
+
* a Windows copy+delete fallback since rename fails on NTFS when the target exists).
|
|
235
|
+
*/
|
|
236
|
+
private async atomicWriteStateFile(content: string): Promise<void> {
|
|
237
|
+
const stateDir = path.dirname(this.stateFilePath);
|
|
238
|
+
await fs.mkdir(stateDir, { recursive: true, mode: 0o700 });
|
|
239
|
+
const tempFile = `research-state-${crypto.randomBytes(16).toString('hex')}.tmp`;
|
|
240
|
+
const tempPath = path.join(stateDir, tempFile);
|
|
241
|
+
// 0o600: the state file holds browserServer.authSecret. The primary writer
|
|
242
|
+
// (state-manager) forces owner-only; this recovery/default path must match so a
|
|
243
|
+
// corruption-recovery write never lands world-readable.
|
|
244
|
+
// Open + write + fsync + close before rename, mirroring the primary writer's
|
|
245
|
+
// durability: a crash immediately after recovery must not resurrect the very
|
|
246
|
+
// corruption we just recovered from (an un-synced rename can expose a
|
|
247
|
+
// zero-length file after a power loss).
|
|
248
|
+
const fh = await fs.open(tempPath, 'w', 0o600);
|
|
249
|
+
try {
|
|
250
|
+
await fh.writeFile(content, { encoding: 'utf-8' });
|
|
251
|
+
await fh.sync();
|
|
252
|
+
} finally {
|
|
253
|
+
await fh.close();
|
|
254
|
+
}
|
|
255
|
+
try {
|
|
256
|
+
await fs.rename(tempPath, this.stateFilePath);
|
|
257
|
+
} catch (renameErr) {
|
|
258
|
+
if (process.platform === 'win32') {
|
|
259
|
+
await fs.copyFile(tempPath, this.stateFilePath);
|
|
260
|
+
await fs.unlink(tempPath).catch(() => { /* best-effort cleanup */ });
|
|
261
|
+
} else {
|
|
262
|
+
await fs.unlink(tempPath).catch(() => { /* best-effort cleanup */ });
|
|
263
|
+
throw renameErr;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Write a default state to the state file
|
|
270
|
+
*/
|
|
271
|
+
async writeDefaultState(): Promise<void> {
|
|
272
|
+
const defaultState = this.getDefaultState();
|
|
273
|
+
defaultState.lastUpdated = Date.now();
|
|
274
|
+
await this.atomicWriteStateFile(JSON.stringify(defaultState, null, 2));
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Get the default state object
|
|
279
|
+
*/
|
|
280
|
+
private getDefaultState() {
|
|
281
|
+
return {
|
|
282
|
+
version: CURRENT_STATE_VERSION,
|
|
283
|
+
containerId: '',
|
|
284
|
+
containerName: '',
|
|
285
|
+
port: 0,
|
|
286
|
+
sessions: {},
|
|
287
|
+
lastUpdated: Date.now(),
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* State Browser API
|
|
3
|
+
*
|
|
4
|
+
* Browser server management API that delegates to StateBrowserManager.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { StateBrowserManager } from './state-browser-manager.ts';
|
|
8
|
+
import type { SingletonState } from '../types/state-types.ts';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Provides browser server management operations
|
|
12
|
+
*/
|
|
13
|
+
export class StateBrowserApi {
|
|
14
|
+
constructor(private readonly browserManager: StateBrowserManager) {}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Get the current browser server information
|
|
18
|
+
* @param readState Function to read state
|
|
19
|
+
* @returns Browser server info or null if not set
|
|
20
|
+
*/
|
|
21
|
+
async getBrowserServer(
|
|
22
|
+
readState: () => Promise<SingletonState>
|
|
23
|
+
): Promise<{ port: number; pid: number; schedulerId?: string; startTime?: number | null; authSecret?: string } | null> {
|
|
24
|
+
const state = await readState();
|
|
25
|
+
return this.browserManager.getBrowserServer(state);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Set the current browser server information (atomic: only overwrites if no live server exists)
|
|
30
|
+
* @param port The browser server port
|
|
31
|
+
* @param pid The browser server process ID
|
|
32
|
+
* @param schedulerId Optional scheduler ID
|
|
33
|
+
* @param updateState Function to update state atomically
|
|
34
|
+
* @param getStartTime Function to get process start time
|
|
35
|
+
*/
|
|
36
|
+
async setBrowserServer(
|
|
37
|
+
port: number,
|
|
38
|
+
pid: number,
|
|
39
|
+
schedulerId: string | undefined,
|
|
40
|
+
updateState: (updater: (state: SingletonState) => SingletonState | Promise<SingletonState>) => Promise<void>,
|
|
41
|
+
getStartTime: (pid: number) => Promise<number | null>,
|
|
42
|
+
authSecret?: string
|
|
43
|
+
): Promise<void> {
|
|
44
|
+
const startTime = await getStartTime(pid);
|
|
45
|
+
await updateState((state) => {
|
|
46
|
+
return this.browserManager.setBrowserServer(state, port, pid, schedulerId, startTime, authSecret);
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Clear the browser server information
|
|
52
|
+
* @param updateState Function to update state atomically
|
|
53
|
+
*/
|
|
54
|
+
async clearBrowserServer(
|
|
55
|
+
updateState: (updater: (state: SingletonState) => SingletonState | Promise<SingletonState>) => Promise<void>
|
|
56
|
+
): Promise<void> {
|
|
57
|
+
await updateState((state) => {
|
|
58
|
+
return this.browserManager.clearBrowserServer(state);
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
}
|