@nextclaw/kernel 0.15.0 → 0.15.2
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/dist/index.d.ts +40 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +456 -85
- package/dist/index.js.map +1 -1
- package/package.json +10 -10
package/dist/index.js
CHANGED
|
@@ -4404,7 +4404,7 @@ var AppPackageDependencyCoordinator = class {
|
|
|
4404
4404
|
});
|
|
4405
4405
|
resolveStoredProviderIds = async (target) => await this.dependencyService.resolveStoredProviderIds(target);
|
|
4406
4406
|
listBindings = async (target) => await this.dependencyService.listBindings(target);
|
|
4407
|
-
|
|
4407
|
+
loadActiveComponentSourcesWithDiagnostics = async () => {
|
|
4408
4408
|
const records = await this.params.registryService.listApps();
|
|
4409
4409
|
const results = await Promise.all(records.filter((record) => record.enabled).map(async (record) => {
|
|
4410
4410
|
try {
|
|
@@ -4542,6 +4542,54 @@ var AppPackageDependencyCoordinator = class {
|
|
|
4542
4542
|
};
|
|
4543
4543
|
};
|
|
4544
4544
|
//#endregion
|
|
4545
|
+
//#region src/services/app-package-component-catalog.service.ts
|
|
4546
|
+
const EMPTY_COMPONENT_CATALOG = {
|
|
4547
|
+
sources: [],
|
|
4548
|
+
unavailablePackages: []
|
|
4549
|
+
};
|
|
4550
|
+
/**
|
|
4551
|
+
* Owns the verified projection of active package components.
|
|
4552
|
+
*
|
|
4553
|
+
* Expensive package integrity checks happen while refreshing this catalog at
|
|
4554
|
+
* lifecycle boundaries. Product reads consume the last complete snapshot and
|
|
4555
|
+
* never trigger a package-tree scan after initialization.
|
|
4556
|
+
*/
|
|
4557
|
+
var AppPackageComponentCatalogService = class {
|
|
4558
|
+
snapshot = EMPTY_COMPONENT_CATALOG;
|
|
4559
|
+
requestedRevision = 0;
|
|
4560
|
+
appliedRevision = 0;
|
|
4561
|
+
refreshPromise;
|
|
4562
|
+
constructor(load) {
|
|
4563
|
+
this.load = load;
|
|
4564
|
+
}
|
|
4565
|
+
read = async () => {
|
|
4566
|
+
if (this.appliedRevision === 0) await this.refresh();
|
|
4567
|
+
return cloneCatalog(this.snapshot);
|
|
4568
|
+
};
|
|
4569
|
+
refresh = async () => {
|
|
4570
|
+
this.requestedRevision += 1;
|
|
4571
|
+
this.refreshPromise ??= this.runRefreshLoop();
|
|
4572
|
+
await this.refreshPromise;
|
|
4573
|
+
};
|
|
4574
|
+
runRefreshLoop = async () => {
|
|
4575
|
+
try {
|
|
4576
|
+
while (this.appliedRevision < this.requestedRevision) {
|
|
4577
|
+
const targetRevision = this.requestedRevision;
|
|
4578
|
+
this.snapshot = cloneCatalog(await this.load());
|
|
4579
|
+
this.appliedRevision = targetRevision;
|
|
4580
|
+
}
|
|
4581
|
+
} finally {
|
|
4582
|
+
this.refreshPromise = void 0;
|
|
4583
|
+
}
|
|
4584
|
+
};
|
|
4585
|
+
};
|
|
4586
|
+
function cloneCatalog(catalog) {
|
|
4587
|
+
return {
|
|
4588
|
+
sources: catalog.sources.map((source) => ({ ...source })),
|
|
4589
|
+
unavailablePackages: catalog.unavailablePackages.map((diagnostic) => ({ ...diagnostic }))
|
|
4590
|
+
};
|
|
4591
|
+
}
|
|
4592
|
+
//#endregion
|
|
4545
4593
|
//#region src/services/app-package-document-access.service.ts
|
|
4546
4594
|
var AppPackageDocumentAccessService = class {
|
|
4547
4595
|
constructor(params) {
|
|
@@ -4709,6 +4757,7 @@ var AppPackageManager = class {
|
|
|
4709
4757
|
hostTargetService = new AppPackageHostTargetService();
|
|
4710
4758
|
presentationService = new AppPackagePresentationService();
|
|
4711
4759
|
dependencyCoordinator;
|
|
4760
|
+
componentCatalog;
|
|
4712
4761
|
runtimeActivationService = new AppPackageRuntimeActivationService();
|
|
4713
4762
|
registryService;
|
|
4714
4763
|
grantService;
|
|
@@ -4736,6 +4785,7 @@ var AppPackageManager = class {
|
|
|
4736
4785
|
listCapabilityProviders: async () => await this.runtimeHooks.listCapabilityProviders(),
|
|
4737
4786
|
resolveSecurity: this.resolveSecurity
|
|
4738
4787
|
});
|
|
4788
|
+
this.componentCatalog = new AppPackageComponentCatalogService(this.dependencyCoordinator.loadActiveComponentSourcesWithDiagnostics);
|
|
4739
4789
|
this.readinessManager = new AppPackageReadinessManager({
|
|
4740
4790
|
manifestService: this.manifestService,
|
|
4741
4791
|
installationService: this.installationService,
|
|
@@ -4759,7 +4809,10 @@ var AppPackageManager = class {
|
|
|
4759
4809
|
...hooks
|
|
4760
4810
|
};
|
|
4761
4811
|
};
|
|
4762
|
-
start = async () =>
|
|
4812
|
+
start = async () => {
|
|
4813
|
+
await this.ensureBuiltInPackages();
|
|
4814
|
+
await this.componentCatalog.refresh();
|
|
4815
|
+
};
|
|
4763
4816
|
listPackages = async (options = {}) => {
|
|
4764
4817
|
const [records, providers] = await Promise.all([this.registryService.listApps(), this.runtimeHooks.listCapabilityProviders()]);
|
|
4765
4818
|
const entries = (await Promise.all(records.map(async (record) => await this.installationService.withAppOperation(record.appId, async () => await this.readListedPackageView(record.appId, options.includeStorageUsage !== false, providers))))).filter((entry) => entry !== void 0);
|
|
@@ -4784,9 +4837,21 @@ var AppPackageManager = class {
|
|
|
4784
4837
|
};
|
|
4785
4838
|
inspectDependencies = async (appId) => await this.installationService.withAppOperation(appId, async () => await this.dependencyCoordinator.inspect(appId));
|
|
4786
4839
|
verifyDependencies = async (appId) => await this.installationService.withAppOperation(appId, async () => await this.dependencyCoordinator.verify(appId));
|
|
4787
|
-
setupDependencies = async (appId) =>
|
|
4788
|
-
|
|
4789
|
-
|
|
4840
|
+
setupDependencies = async (appId) => {
|
|
4841
|
+
const result = await this.dependencyCoordinator.setup(appId);
|
|
4842
|
+
await this.componentCatalog.refresh();
|
|
4843
|
+
return result;
|
|
4844
|
+
};
|
|
4845
|
+
bindDependency = async (appId, input) => {
|
|
4846
|
+
const result = await this.dependencyCoordinator.bind(appId, input);
|
|
4847
|
+
await this.componentCatalog.refresh();
|
|
4848
|
+
return result;
|
|
4849
|
+
};
|
|
4850
|
+
unbindDependency = async (appId, input) => {
|
|
4851
|
+
const result = await this.dependencyCoordinator.unbind(appId, input);
|
|
4852
|
+
await this.componentCatalog.refresh();
|
|
4853
|
+
return result;
|
|
4854
|
+
};
|
|
4790
4855
|
inspectSecrets = async (appId) => await this.installationService.withAppOperation(appId, async () => await this.readinessManager.inspectSecrets(await this.installationService.info(appId), false));
|
|
4791
4856
|
verifySecrets = async (appId) => await this.installationService.withAppOperation(appId, async () => await this.readinessManager.inspectSecrets(await this.installationService.info(appId), true));
|
|
4792
4857
|
bindSecret = async (appId, input) => await this.installationService.withAppOperation(appId, async () => {
|
|
@@ -4810,7 +4875,7 @@ var AppPackageManager = class {
|
|
|
4810
4875
|
grantDocumentAccess = async (appId, input) => await this.documentAccessService.grant(appId, input);
|
|
4811
4876
|
revokeDocumentAccess = async (appId, scopeId) => await this.documentAccessService.revoke(appId, scopeId);
|
|
4812
4877
|
listActiveComponentSources = async () => (await this.listActiveComponentSourcesWithDiagnostics()).sources;
|
|
4813
|
-
listActiveComponentSourcesWithDiagnostics = async () => await this.
|
|
4878
|
+
listActiveComponentSourcesWithDiagnostics = async () => await this.componentCatalog.read();
|
|
4814
4879
|
listOperations = async () => await this.operationManager.list();
|
|
4815
4880
|
startOperation = async (input) => {
|
|
4816
4881
|
await this.ensureBuiltInPackages();
|
|
@@ -4822,6 +4887,7 @@ var AppPackageManager = class {
|
|
|
4822
4887
|
onProgress
|
|
4823
4888
|
});
|
|
4824
4889
|
if (await this.isBuiltInAppId(result.appId)) await this.registryService.setBuiltInSuppressed(result.appId, false);
|
|
4890
|
+
await this.componentCatalog.refresh();
|
|
4825
4891
|
return await this.getPackage(result.appId);
|
|
4826
4892
|
};
|
|
4827
4893
|
enable = async (appId) => {
|
|
@@ -4849,6 +4915,7 @@ var AppPackageManager = class {
|
|
|
4849
4915
|
});
|
|
4850
4916
|
throw error;
|
|
4851
4917
|
}
|
|
4918
|
+
await this.componentCatalog.refresh();
|
|
4852
4919
|
return await this.getPackage(appId);
|
|
4853
4920
|
});
|
|
4854
4921
|
};
|
|
@@ -4859,6 +4926,7 @@ var AppPackageManager = class {
|
|
|
4859
4926
|
await this.dependencyCoordinator.assertNoEnabledDependents(app);
|
|
4860
4927
|
await this.runtimeHooks.beforeDeactivate(this.toComponentSources(app));
|
|
4861
4928
|
await this.installationService.setEnabled(appId, false);
|
|
4929
|
+
await this.componentCatalog.refresh();
|
|
4862
4930
|
return await this.getPackage(appId);
|
|
4863
4931
|
});
|
|
4864
4932
|
};
|
|
@@ -4888,6 +4956,7 @@ var AppPackageManager = class {
|
|
|
4888
4956
|
}
|
|
4889
4957
|
activated = (await this.installationService.rollback(appId, result.version)).rolledBack;
|
|
4890
4958
|
if (current.enabled) await this.runtimeHooks.afterActivate(this.toComponentSources(candidate));
|
|
4959
|
+
await this.componentCatalog.refresh();
|
|
4891
4960
|
return {
|
|
4892
4961
|
package: await this.getPackage(appId),
|
|
4893
4962
|
result
|
|
@@ -4932,6 +5001,7 @@ var AppPackageManager = class {
|
|
|
4932
5001
|
}
|
|
4933
5002
|
result = await this.installationService.rollback(appId, version);
|
|
4934
5003
|
if (current.enabled) await this.runtimeHooks.afterActivate(this.toComponentSources(candidate));
|
|
5004
|
+
await this.componentCatalog.refresh();
|
|
4935
5005
|
return {
|
|
4936
5006
|
package: await this.getPackage(appId),
|
|
4937
5007
|
result
|
|
@@ -4957,7 +5027,9 @@ var AppPackageManager = class {
|
|
|
4957
5027
|
try {
|
|
4958
5028
|
const sources = this.toComponentSources(current);
|
|
4959
5029
|
rollbackRuntimeState = await this.runtimeHooks.beforeUninstall(sources) || void 0;
|
|
4960
|
-
|
|
5030
|
+
const result = await this.installationService.uninstall(appId, purgeData);
|
|
5031
|
+
await this.componentCatalog.refresh();
|
|
5032
|
+
return result;
|
|
4961
5033
|
} catch (error) {
|
|
4962
5034
|
const recoveryErrors = [];
|
|
4963
5035
|
if (rollbackRuntimeState) try {
|
|
@@ -10696,7 +10768,7 @@ var PanelAppPackageStateManager = class {
|
|
|
10696
10768
|
try {
|
|
10697
10769
|
return await this.params.sourceService.resolveSource(this.params.getPanelsPath(), id);
|
|
10698
10770
|
} catch (error) {
|
|
10699
|
-
if (!isPanelAppError(error) || error.code !== "PANEL_APP_NOT_FOUND") throw error;
|
|
10771
|
+
if (!isPanelAppError(error) || error.code !== "PANEL_APP_NOT_FOUND" && error.code !== "PANEL_APP_INVALID_ID") throw error;
|
|
10700
10772
|
}
|
|
10701
10773
|
const match = (await this.listSources()).find(({ source, packageSource }) => packageSource && (encodePanelAppId(source.sourceName) === id || packageSource.id === id));
|
|
10702
10774
|
if (!match) throw new PanelAppError("PANEL_APP_NOT_FOUND", "panel app not found");
|
|
@@ -10707,16 +10779,34 @@ var PanelAppPackageStateManager = class {
|
|
|
10707
10779
|
};
|
|
10708
10780
|
readContentSourceByIdOrAppId = async (id) => {
|
|
10709
10781
|
const panelsPath = this.params.getPanelsPath();
|
|
10710
|
-
|
|
10711
|
-
|
|
10712
|
-
|
|
10713
|
-
|
|
10714
|
-
|
|
10715
|
-
|
|
10716
|
-
|
|
10717
|
-
|
|
10718
|
-
|
|
10719
|
-
|
|
10782
|
+
const { source } = await this.resolveSourceByIdOrAppId(id);
|
|
10783
|
+
return await readPanelAppContentSourceByIdOrPath({
|
|
10784
|
+
createAssetBaseHref: this.params.createAssetBaseHref,
|
|
10785
|
+
id,
|
|
10786
|
+
panelsPath,
|
|
10787
|
+
sourcePath: source.sourcePath,
|
|
10788
|
+
sourceService: this.params.sourceService
|
|
10789
|
+
});
|
|
10790
|
+
};
|
|
10791
|
+
resolveSourceByIdOrAppId = async (id) => {
|
|
10792
|
+
const panelsPath = this.params.getPanelsPath();
|
|
10793
|
+
try {
|
|
10794
|
+
return await this.toResolvedTarget(await this.params.sourceService.resolveSource(panelsPath, id));
|
|
10795
|
+
} catch (error) {
|
|
10796
|
+
if (!isPanelAppError(error) || error.code !== "PANEL_APP_NOT_FOUND" && error.code !== "PANEL_APP_INVALID_ID") throw error;
|
|
10797
|
+
}
|
|
10798
|
+
const packageSources = (await this.listPackageComponentSources()).filter((component) => component.kind === "panel");
|
|
10799
|
+
const exactPackageSource = packageSources.find((component) => component.id === id);
|
|
10800
|
+
if (exactPackageSource) return await this.toResolvedTarget(await this.params.sourceService.resolveSourcePath(exactPackageSource.sourcePath), exactPackageSource);
|
|
10801
|
+
const workspaceSources = await this.params.sourceService.listSources(panelsPath);
|
|
10802
|
+
for (const source of workspaceSources) {
|
|
10803
|
+
const target = await this.toResolvedTarget(source);
|
|
10804
|
+
if (resolvePanelAppAppId(source, target.manifest) === id) return target;
|
|
10805
|
+
}
|
|
10806
|
+
for (const packageSource of packageSources) {
|
|
10807
|
+
const source = await this.params.sourceService.resolveSourcePath(packageSource.sourcePath);
|
|
10808
|
+
const target = await this.toResolvedTarget(source, packageSource);
|
|
10809
|
+
if (encodePanelAppId(source.sourceName) === id || resolvePanelAppAppId(source, target.manifest) === id) return target;
|
|
10720
10810
|
}
|
|
10721
10811
|
throw new PanelAppError("PANEL_APP_NOT_FOUND", "panel app not found");
|
|
10722
10812
|
};
|
|
@@ -10795,6 +10885,11 @@ var PanelAppPackageStateManager = class {
|
|
|
10795
10885
|
return errors;
|
|
10796
10886
|
};
|
|
10797
10887
|
listPackageComponentSources = async () => await this.params.listPackageComponentSources?.() ?? [];
|
|
10888
|
+
toResolvedTarget = async (source, packageSource) => ({
|
|
10889
|
+
source,
|
|
10890
|
+
packageSource,
|
|
10891
|
+
manifest: source.manifest ?? parsePanelAppManifest(await readFile(source.entryPath, "utf8"))
|
|
10892
|
+
});
|
|
10798
10893
|
};
|
|
10799
10894
|
//#endregion
|
|
10800
10895
|
//#region src/utils/panel-app-time.utils.ts
|
|
@@ -11964,6 +12059,12 @@ var PanelAppManager = class {
|
|
|
11964
12059
|
unavailablePackages: await this.params.listPackageComponentDiagnostics?.() ?? []
|
|
11965
12060
|
};
|
|
11966
12061
|
};
|
|
12062
|
+
getPanelApp = async (id) => {
|
|
12063
|
+
const panelsPath = this.getPanelsPath(this.getWorkspacePath());
|
|
12064
|
+
const resolved = await this.packageStateManager.resolveSourceByIdOrAppId(id);
|
|
12065
|
+
const appState = await this.createStateStore(panelsPath).load();
|
|
12066
|
+
return await this.entryPresenter.build(resolved.source, appState.apps[encodePanelAppId(resolved.source.sourceName)] ?? {}, resolved.packageSource, appState.mainSidebarAppIds);
|
|
12067
|
+
};
|
|
11967
12068
|
getPanelAppContent = async (id, sourcePath) => {
|
|
11968
12069
|
try {
|
|
11969
12070
|
const resolved = sourcePath ? await readPanelAppContentSourceByIdOrPath({
|
|
@@ -12101,19 +12202,19 @@ var PanelAppManager = class {
|
|
|
12101
12202
|
return await this.agentBridgeService.grantAgentCapability(bridgeSession, capability);
|
|
12102
12203
|
};
|
|
12103
12204
|
updatePanelAppPreferences = async (id, preferences) => {
|
|
12104
|
-
const fileName = await this.resolvePanelAppFileName(id);
|
|
12105
12205
|
const panelsPath = this.getPanelsPath(this.getWorkspacePath());
|
|
12106
|
-
const
|
|
12107
|
-
const
|
|
12108
|
-
const appId = resolvePanelAppAppId(source,
|
|
12206
|
+
const resolved = await this.packageStateManager.resolveSourceByIdOrAppId(id);
|
|
12207
|
+
const fileName = resolved.source.sourceName;
|
|
12208
|
+
const appId = resolvePanelAppAppId(resolved.source, resolved.manifest);
|
|
12109
12209
|
const result = await this.createStateStore(panelsPath).updatePreferences(encodePanelAppId(fileName), appId, preferences);
|
|
12110
|
-
return await this.entryPresenter.build(source, result.entry, packageSource, result.mainSidebarAppIds);
|
|
12210
|
+
return await this.entryPresenter.build(resolved.source, result.entry, resolved.packageSource, result.mainSidebarAppIds);
|
|
12111
12211
|
};
|
|
12112
12212
|
recordPanelAppOpened = async (id) => {
|
|
12113
|
-
const fileName = await this.resolvePanelAppFileName(id);
|
|
12114
12213
|
const panelsPath = this.getPanelsPath(this.getWorkspacePath());
|
|
12214
|
+
const resolved = await this.packageStateManager.resolveSourceByIdOrAppId(id);
|
|
12215
|
+
const fileName = resolved.source.sourceName;
|
|
12115
12216
|
const result = await this.createStateStore(panelsPath).recordOpened(encodePanelAppId(fileName));
|
|
12116
|
-
return await this.entryPresenter.build(
|
|
12217
|
+
return await this.entryPresenter.build(resolved.source, result.entry, resolved.packageSource, result.mainSidebarAppIds);
|
|
12117
12218
|
};
|
|
12118
12219
|
deletePanelApp = async (id) => {
|
|
12119
12220
|
const panelsPath = this.getPanelsPath(this.getWorkspacePath());
|
|
@@ -12146,9 +12247,6 @@ var PanelAppManager = class {
|
|
|
12146
12247
|
return `${PANEL_APP_TOKENIZED_ASSET_BASE_PATH}/${encodeURIComponent(token)}/`;
|
|
12147
12248
|
};
|
|
12148
12249
|
createStateStore = (panelsPath) => new PanelAppStateStore(panelsPath);
|
|
12149
|
-
resolvePanelAppFileName = async (id) => {
|
|
12150
|
-
return (await this.packageStateManager.resolveSource(id)).sourceName;
|
|
12151
|
-
};
|
|
12152
12250
|
assertCanActivatePackageComponents = async (components) => await this.packageStateManager.assertCanActivate(components);
|
|
12153
12251
|
deactivatePackageComponents = (components) => this.packageStateManager.deactivate(components);
|
|
12154
12252
|
preparePackageComponentDeactivation = (components) => {
|
|
@@ -20329,7 +20427,7 @@ var ObservationManager = class {
|
|
|
20329
20427
|
};
|
|
20330
20428
|
//#endregion
|
|
20331
20429
|
//#region src/features/projects/stores/project.store.ts
|
|
20332
|
-
const PROJECT_STORE_VERSION =
|
|
20430
|
+
const PROJECT_STORE_VERSION = 3;
|
|
20333
20431
|
function createProjectId() {
|
|
20334
20432
|
return randomBytes(9).toString("base64url");
|
|
20335
20433
|
}
|
|
@@ -20368,17 +20466,18 @@ var ProjectStore = class {
|
|
|
20368
20466
|
throw error;
|
|
20369
20467
|
}
|
|
20370
20468
|
if (storeFile.version === PROJECT_STORE_VERSION) return false;
|
|
20371
|
-
await this.save(storeFile.projects.map((project) => ({
|
|
20469
|
+
await this.save(storeFile.version === 1 ? storeFile.projects.map((project) => ({
|
|
20372
20470
|
id: createProjectId(),
|
|
20373
20471
|
...project
|
|
20374
|
-
})));
|
|
20472
|
+
})) : storeFile.projects);
|
|
20375
20473
|
return true;
|
|
20376
20474
|
};
|
|
20377
|
-
save = async (projects) => {
|
|
20475
|
+
save = async (projects, removedProjects = []) => {
|
|
20378
20476
|
const tempPath = `${this.storePath}.${randomUUID()}.tmp`;
|
|
20379
20477
|
const storeFile = {
|
|
20380
20478
|
version: PROJECT_STORE_VERSION,
|
|
20381
|
-
projects
|
|
20479
|
+
projects,
|
|
20480
|
+
removedProjects
|
|
20382
20481
|
};
|
|
20383
20482
|
await mkdir(dirname(this.storePath), { recursive: true });
|
|
20384
20483
|
try {
|
|
@@ -20389,19 +20488,57 @@ var ProjectStore = class {
|
|
|
20389
20488
|
throw error;
|
|
20390
20489
|
}
|
|
20391
20490
|
};
|
|
20491
|
+
remove = async (projectId) => {
|
|
20492
|
+
const storeFile = await this.readStoreFile();
|
|
20493
|
+
const project = storeFile.projects.find((entry) => entry.id === projectId);
|
|
20494
|
+
if (!project) return null;
|
|
20495
|
+
await this.save(storeFile.projects.filter((entry) => entry.id !== projectId), [...storeFile.removedProjects.filter((entry) => entry.id !== projectId), project]);
|
|
20496
|
+
return structuredClone(project);
|
|
20497
|
+
};
|
|
20498
|
+
restoreByRootPath = async (rootPath, updatedAt) => {
|
|
20499
|
+
const storeFile = await this.readStoreFile();
|
|
20500
|
+
const project = storeFile.removedProjects.find((entry) => entry.rootPath === rootPath);
|
|
20501
|
+
if (!project) return null;
|
|
20502
|
+
const restored = {
|
|
20503
|
+
...project,
|
|
20504
|
+
updatedAt
|
|
20505
|
+
};
|
|
20506
|
+
await this.save([...storeFile.projects, restored], storeFile.removedProjects.filter((entry) => entry.id !== project.id));
|
|
20507
|
+
return structuredClone(restored);
|
|
20508
|
+
};
|
|
20509
|
+
isRemovedRootPath = async (rootPath) => (await this.readStoreFile()).removedProjects.some((entry) => entry.rootPath === rootPath);
|
|
20510
|
+
readStoreFile = async () => {
|
|
20511
|
+
try {
|
|
20512
|
+
return this.parseStoreFile(await readFile(this.storePath, "utf8"));
|
|
20513
|
+
} catch (error) {
|
|
20514
|
+
if (this.isMissingFileError(error)) return {
|
|
20515
|
+
version: PROJECT_STORE_VERSION,
|
|
20516
|
+
projects: [],
|
|
20517
|
+
removedProjects: []
|
|
20518
|
+
};
|
|
20519
|
+
if (error instanceof SyntaxError) throw new ProjectStoreError("project registry contains invalid JSON");
|
|
20520
|
+
throw error;
|
|
20521
|
+
}
|
|
20522
|
+
};
|
|
20392
20523
|
parseStoreFile = (source) => {
|
|
20393
20524
|
const value = this.parseStoredFile(source);
|
|
20394
|
-
if (value.version !== PROJECT_STORE_VERSION || !value.projects.every(this.isProjectRecord)) throw new ProjectStoreError("project registry has an unsupported structure");
|
|
20525
|
+
if (value.version !== PROJECT_STORE_VERSION || !value.projects.every(this.isProjectRecord) || !value.removedProjects.every(this.isProjectRecord)) throw new ProjectStoreError("project registry has an unsupported structure");
|
|
20395
20526
|
return {
|
|
20396
20527
|
version: PROJECT_STORE_VERSION,
|
|
20397
|
-
projects: value.projects.map((project) => structuredClone(project))
|
|
20528
|
+
projects: value.projects.map((project) => structuredClone(project)),
|
|
20529
|
+
removedProjects: value.removedProjects.map((project) => structuredClone(project))
|
|
20398
20530
|
};
|
|
20399
20531
|
};
|
|
20400
20532
|
parseStoredFile = (source) => {
|
|
20401
20533
|
const value = JSON.parse(source);
|
|
20402
20534
|
if (!this.isRecord(value) || !Array.isArray(value.projects)) throw new ProjectStoreError("project registry has an unsupported structure");
|
|
20403
|
-
if (value.version === PROJECT_STORE_VERSION && value.projects.every(this.isProjectRecord)) return {
|
|
20535
|
+
if (value.version === PROJECT_STORE_VERSION && Array.isArray(value.removedProjects) && value.projects.every(this.isProjectRecord) && value.removedProjects.every(this.isProjectRecord)) return {
|
|
20404
20536
|
version: PROJECT_STORE_VERSION,
|
|
20537
|
+
projects: value.projects.map((project) => structuredClone(project)),
|
|
20538
|
+
removedProjects: value.removedProjects.map((project) => structuredClone(project))
|
|
20539
|
+
};
|
|
20540
|
+
if (value.version === 2 && value.projects.every(this.isProjectRecord)) return {
|
|
20541
|
+
version: 2,
|
|
20405
20542
|
projects: value.projects.map((project) => structuredClone(project))
|
|
20406
20543
|
};
|
|
20407
20544
|
if (value.version === 1 && value.projects.every(this.isLegacyProjectRecord)) return {
|
|
@@ -20487,7 +20624,15 @@ var ProjectManager = class {
|
|
|
20487
20624
|
name,
|
|
20488
20625
|
rootPath,
|
|
20489
20626
|
template
|
|
20490
|
-
});
|
|
20627
|
+
}, { restoreRemoved: true });
|
|
20628
|
+
};
|
|
20629
|
+
addExistingProject = async (rootPath, name) => {
|
|
20630
|
+
const canonicalPath = await this.resolveExistingProjectRoot(rootPath);
|
|
20631
|
+
if (!canonicalPath) return null;
|
|
20632
|
+
return await this.upsertProject({
|
|
20633
|
+
name: name === void 0 ? basename(canonicalPath) : this.normalizeName(name),
|
|
20634
|
+
rootPath: canonicalPath
|
|
20635
|
+
}, { restoreRemoved: true });
|
|
20491
20636
|
};
|
|
20492
20637
|
registerExistingProject = async (rootPath, name) => {
|
|
20493
20638
|
const canonicalPath = await this.resolveExistingProjectRoot(rootPath);
|
|
@@ -20495,7 +20640,13 @@ var ProjectManager = class {
|
|
|
20495
20640
|
return await this.upsertProject({
|
|
20496
20641
|
name: name === void 0 ? basename(canonicalPath) : this.normalizeName(name),
|
|
20497
20642
|
rootPath: canonicalPath
|
|
20498
|
-
});
|
|
20643
|
+
}, { restoreRemoved: true });
|
|
20644
|
+
};
|
|
20645
|
+
removeProject = async (projectId, confirmProjectId) => {
|
|
20646
|
+
if (confirmProjectId !== projectId) throw new ProjectError("PROJECT_REMOVE_CONFIRMATION_MISMATCH", "project removal confirmation must exactly match the project id");
|
|
20647
|
+
const removed = await this.store.remove(projectId);
|
|
20648
|
+
if (!removed) throw new ProjectError("PROJECT_NOT_FOUND", "project was not found");
|
|
20649
|
+
return removed;
|
|
20499
20650
|
};
|
|
20500
20651
|
normalizeSessionProjectRoot = async (value) => {
|
|
20501
20652
|
return (await this.normalizeSessionProjectContext(value))?.rootPath ?? null;
|
|
@@ -20529,18 +20680,30 @@ var ProjectManager = class {
|
|
|
20529
20680
|
for (const projectRoot of projectRoots) {
|
|
20530
20681
|
if (projectRoot == null || typeof projectRoot === "string" && !projectRoot.trim()) continue;
|
|
20531
20682
|
try {
|
|
20532
|
-
await this.
|
|
20683
|
+
const canonicalPath = await this.resolveExistingProjectRoot(projectRoot);
|
|
20684
|
+
if (!canonicalPath || await this.store.isRemovedRootPath(canonicalPath)) continue;
|
|
20685
|
+
await this.upsertProject({
|
|
20686
|
+
name: basename(canonicalPath),
|
|
20687
|
+
rootPath: canonicalPath
|
|
20688
|
+
});
|
|
20533
20689
|
} catch (error) {
|
|
20534
20690
|
const message = error instanceof Error ? error.message : String(error);
|
|
20535
20691
|
console.warn(`[project-manager] skipped historical project root: ${message}`);
|
|
20536
20692
|
}
|
|
20537
20693
|
}
|
|
20538
20694
|
};
|
|
20539
|
-
upsertProject = async (input) => {
|
|
20695
|
+
upsertProject = async (input, options = {}) => {
|
|
20540
20696
|
const projects = await this.store.list();
|
|
20541
20697
|
const existing = projects.find((project) => project.rootPath === input.rootPath);
|
|
20542
20698
|
if (existing) return existing;
|
|
20543
20699
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
20700
|
+
if (options.restoreRemoved) {
|
|
20701
|
+
const restored = await this.store.restoreByRootPath(input.rootPath, now);
|
|
20702
|
+
if (restored) {
|
|
20703
|
+
await this.options.onProjectRegistered?.(structuredClone(restored));
|
|
20704
|
+
return restored;
|
|
20705
|
+
}
|
|
20706
|
+
}
|
|
20544
20707
|
const project = {
|
|
20545
20708
|
id: createProjectId(),
|
|
20546
20709
|
name: input.name,
|
|
@@ -20646,9 +20809,18 @@ async function openPortableDatabase(databasePath) {
|
|
|
20646
20809
|
}
|
|
20647
20810
|
async function createPortableDatabase(databasePath) {
|
|
20648
20811
|
const initialize = (await import("sql.js")).default;
|
|
20649
|
-
const sqlite = await initialize({ locateFile:
|
|
20812
|
+
const sqlite = await initialize({ locateFile: resolveSqlJsAsset });
|
|
20650
20813
|
return new PortableSqliteDatabase(databasePath, existsSync(databasePath) ? new sqlite.Database(readFileSync(databasePath)) : new sqlite.Database());
|
|
20651
20814
|
}
|
|
20815
|
+
function resolveSqlJsAsset(file) {
|
|
20816
|
+
try {
|
|
20817
|
+
return require.resolve(`sql.js/dist/${file}`);
|
|
20818
|
+
} catch (error) {
|
|
20819
|
+
const bundledAssetPath = fileURLToPath(new URL(file, import.meta.url));
|
|
20820
|
+
if (file === "sql-wasm.wasm" && existsSync(bundledAssetPath)) return bundledAssetPath;
|
|
20821
|
+
throw error;
|
|
20822
|
+
}
|
|
20823
|
+
}
|
|
20652
20824
|
var PortableSqliteDatabase = class {
|
|
20653
20825
|
inTransaction = false;
|
|
20654
20826
|
dirty = false;
|
|
@@ -20845,6 +21017,95 @@ function toArtifactLink(row) {
|
|
|
20845
21017
|
};
|
|
20846
21018
|
}
|
|
20847
21019
|
//#endregion
|
|
21020
|
+
//#region src/features/projects/stores/project-work-query.store.ts
|
|
21021
|
+
var ProjectWorkQueryStore = class {
|
|
21022
|
+
constructor(db, toItem) {
|
|
21023
|
+
this.db = db;
|
|
21024
|
+
this.toItem = toItem;
|
|
21025
|
+
}
|
|
21026
|
+
listItems = (params) => {
|
|
21027
|
+
const { cursor, includeDeleted, limit, projectId, stateId } = params;
|
|
21028
|
+
const conditions = ["items.project_id = ?"];
|
|
21029
|
+
const values = [projectId];
|
|
21030
|
+
if (!includeDeleted) conditions.push("items.deleted_at IS NULL");
|
|
21031
|
+
if (stateId) {
|
|
21032
|
+
conditions.push("items.state_id = ?");
|
|
21033
|
+
values.push(stateId);
|
|
21034
|
+
}
|
|
21035
|
+
const total = Number(this.db().prepare(`SELECT COUNT(*) AS count FROM project_work_items AS items
|
|
21036
|
+
WHERE ${conditions.join(" AND ")}`).get(...values).count);
|
|
21037
|
+
if (cursor) {
|
|
21038
|
+
conditions.push("(items.updated_at < ? OR (items.updated_at = ? AND items.id < ?))");
|
|
21039
|
+
values.push(cursor.updatedAt, cursor.updatedAt, cursor.id);
|
|
21040
|
+
}
|
|
21041
|
+
const rows = this.db().prepare(`SELECT items.*,
|
|
21042
|
+
(SELECT COUNT(*) FROM project_work_artifact_links AS links
|
|
21043
|
+
WHERE links.project_id = items.project_id AND links.work_item_id = items.id) AS artifact_count
|
|
21044
|
+
FROM project_work_items AS items
|
|
21045
|
+
WHERE ${conditions.join(" AND ")}
|
|
21046
|
+
ORDER BY items.updated_at DESC, items.id DESC
|
|
21047
|
+
LIMIT ?`).all(...values, limit + 1);
|
|
21048
|
+
return {
|
|
21049
|
+
items: rows.slice(0, limit).map((row) => ({
|
|
21050
|
+
...this.toItem(row),
|
|
21051
|
+
artifactCount: Number(row.artifact_count)
|
|
21052
|
+
})),
|
|
21053
|
+
hasMore: rows.length > limit,
|
|
21054
|
+
total
|
|
21055
|
+
};
|
|
21056
|
+
};
|
|
21057
|
+
summarizeItems = (projectId) => {
|
|
21058
|
+
const row = this.db().prepare(`SELECT
|
|
21059
|
+
COUNT(*) AS total,
|
|
21060
|
+
SUM(CASE WHEN states.category NOT IN ('completed', 'canceled') THEN 1 ELSE 0 END) AS active,
|
|
21061
|
+
SUM(CASE WHEN states.category = 'completed' THEN 1 ELSE 0 END) AS completed,
|
|
21062
|
+
SUM(CASE WHEN items.attention != 'none' THEN 1 ELSE 0 END) AS attention,
|
|
21063
|
+
MAX(items.updated_at) AS updated_at
|
|
21064
|
+
FROM project_work_items AS items
|
|
21065
|
+
JOIN project_work_states AS states
|
|
21066
|
+
ON states.project_id = items.project_id AND states.id = items.state_id
|
|
21067
|
+
WHERE items.project_id = ? AND items.deleted_at IS NULL`).get(projectId);
|
|
21068
|
+
return {
|
|
21069
|
+
total: Number(row.total),
|
|
21070
|
+
active: Number(row.active ?? 0),
|
|
21071
|
+
completed: Number(row.completed ?? 0),
|
|
21072
|
+
attention: Number(row.attention ?? 0),
|
|
21073
|
+
updatedAt: row.updated_at
|
|
21074
|
+
};
|
|
21075
|
+
};
|
|
21076
|
+
listRecentArtifacts = (params) => {
|
|
21077
|
+
const { cursor, limit, projectId } = params;
|
|
21078
|
+
const total = Number(this.db().prepare("SELECT COUNT(DISTINCT path) AS count FROM project_work_artifact_links WHERE project_id = ?").get(projectId).count);
|
|
21079
|
+
const cursorCondition = cursor ? "AND (created_at < ? OR (created_at = ? AND id < ?))" : "";
|
|
21080
|
+
const rows = this.db().prepare(`WITH ranked AS (
|
|
21081
|
+
SELECT links.id, links.path, links.label, links.work_item_id,
|
|
21082
|
+
items.title AS work_item_title, links.created_at,
|
|
21083
|
+
ROW_NUMBER() OVER (
|
|
21084
|
+
PARTITION BY links.path
|
|
21085
|
+
ORDER BY links.created_at DESC, links.id DESC
|
|
21086
|
+
) AS path_rank
|
|
21087
|
+
FROM project_work_artifact_links AS links
|
|
21088
|
+
JOIN project_work_items AS items
|
|
21089
|
+
ON items.project_id = links.project_id AND items.id = links.work_item_id
|
|
21090
|
+
WHERE links.project_id = ?
|
|
21091
|
+
)
|
|
21092
|
+
SELECT id, path, label, work_item_id, work_item_title, created_at
|
|
21093
|
+
FROM ranked
|
|
21094
|
+
WHERE path_rank = 1 ${cursorCondition}
|
|
21095
|
+
ORDER BY created_at DESC, id DESC
|
|
21096
|
+
LIMIT ?`).all(projectId, ...cursor ? [
|
|
21097
|
+
cursor.createdAt,
|
|
21098
|
+
cursor.createdAt,
|
|
21099
|
+
cursor.id
|
|
21100
|
+
] : [], limit + 1);
|
|
21101
|
+
return {
|
|
21102
|
+
artifacts: rows.slice(0, limit),
|
|
21103
|
+
hasMore: rows.length > limit,
|
|
21104
|
+
total
|
|
21105
|
+
};
|
|
21106
|
+
};
|
|
21107
|
+
};
|
|
21108
|
+
//#endregion
|
|
20848
21109
|
//#region src/features/projects/stores/project-work-state.store.ts
|
|
20849
21110
|
const DEFAULT_PROJECT_WORK_STATES = [
|
|
20850
21111
|
{
|
|
@@ -20998,6 +21259,7 @@ var ProjectWorkStore = class {
|
|
|
20998
21259
|
database = null;
|
|
20999
21260
|
readyPromise = null;
|
|
21000
21261
|
activities = new ProjectWorkActivityStore(() => this.db());
|
|
21262
|
+
queries = new ProjectWorkQueryStore(() => this.db(), toItem);
|
|
21001
21263
|
states = new ProjectWorkStateStore(() => this.db(), this.activities.insertActivity);
|
|
21002
21264
|
constructor(databasePath) {
|
|
21003
21265
|
this.databasePath = databasePath;
|
|
@@ -21032,12 +21294,6 @@ var ProjectWorkStore = class {
|
|
|
21032
21294
|
await this.ensureReady();
|
|
21033
21295
|
return await this.states.deleteState(projectId, stateId, migrateToStateId, actor);
|
|
21034
21296
|
};
|
|
21035
|
-
listItems = async (projectId, includeDeleted = false) => {
|
|
21036
|
-
await this.ensureReady();
|
|
21037
|
-
return this.db().prepare(`SELECT * FROM project_work_items
|
|
21038
|
-
WHERE project_id = ? ${includeDeleted ? "" : "AND deleted_at IS NULL"}
|
|
21039
|
-
ORDER BY updated_at DESC, created_at DESC`).all(projectId).map(toItem);
|
|
21040
|
-
};
|
|
21041
21297
|
getItem = async (projectId, workItemId) => {
|
|
21042
21298
|
await this.ensureReady();
|
|
21043
21299
|
const row = this.db().prepare("SELECT * FROM project_work_items WHERE project_id = ? AND id = ? LIMIT 1").get(projectId, workItemId);
|
|
@@ -21184,8 +21440,12 @@ var ProjectWorkStore = class {
|
|
|
21184
21440
|
);
|
|
21185
21441
|
CREATE INDEX IF NOT EXISTS project_work_items_list_idx
|
|
21186
21442
|
ON project_work_items(project_id, deleted_at, updated_at);
|
|
21443
|
+
CREATE INDEX IF NOT EXISTS project_work_items_state_list_idx
|
|
21444
|
+
ON project_work_items(project_id, state_id, deleted_at, updated_at, id);
|
|
21187
21445
|
CREATE INDEX IF NOT EXISTS project_work_activity_timeline_idx
|
|
21188
21446
|
ON project_work_activities(project_id, work_item_id, created_at);
|
|
21447
|
+
CREATE INDEX IF NOT EXISTS project_work_artifacts_recent_idx
|
|
21448
|
+
ON project_work_artifact_links(project_id, created_at, id);
|
|
21189
21449
|
`);
|
|
21190
21450
|
};
|
|
21191
21451
|
db = () => {
|
|
@@ -21233,6 +21493,112 @@ function isProjectWorkError(error) {
|
|
|
21233
21493
|
return error instanceof ProjectWorkError;
|
|
21234
21494
|
}
|
|
21235
21495
|
//#endregion
|
|
21496
|
+
//#region src/features/projects/services/project-work-query.service.ts
|
|
21497
|
+
var ProjectWorkQueryService = class {
|
|
21498
|
+
constructor(store, projectManager) {
|
|
21499
|
+
this.store = store;
|
|
21500
|
+
this.projectManager = projectManager;
|
|
21501
|
+
}
|
|
21502
|
+
list = async (projectId, input = {}) => {
|
|
21503
|
+
await this.ensureProject(projectId);
|
|
21504
|
+
const limit = this.requirePageLimit(input.limit);
|
|
21505
|
+
const cursor = input.cursor ? this.decodeCursor(input.cursor, "work item", "updatedAt") : void 0;
|
|
21506
|
+
const states = await this.store.listStates(projectId);
|
|
21507
|
+
if (input.stateId && !states.some((state) => state.id === input.stateId)) throw new ProjectWorkError("PROJECT_WORK_STATE_NOT_FOUND", "work item state was not found");
|
|
21508
|
+
const page = this.store.queries.listItems({
|
|
21509
|
+
projectId,
|
|
21510
|
+
includeDeleted: input.includeDeleted ?? false,
|
|
21511
|
+
limit,
|
|
21512
|
+
...input.stateId ? { stateId: input.stateId } : {},
|
|
21513
|
+
...cursor ? { cursor } : {}
|
|
21514
|
+
});
|
|
21515
|
+
const stateById = new Map(states.map((state) => [state.id, state]));
|
|
21516
|
+
const items = page.items.map((item) => ({
|
|
21517
|
+
...item,
|
|
21518
|
+
state: this.requireMappedState(stateById, item.stateId)
|
|
21519
|
+
}));
|
|
21520
|
+
const last = items.at(-1);
|
|
21521
|
+
return {
|
|
21522
|
+
items,
|
|
21523
|
+
total: page.total,
|
|
21524
|
+
nextCursor: page.hasMore && last ? this.encodeCursor({
|
|
21525
|
+
updatedAt: last.updatedAt,
|
|
21526
|
+
id: last.id
|
|
21527
|
+
}) : null
|
|
21528
|
+
};
|
|
21529
|
+
};
|
|
21530
|
+
summary = async (projectId) => {
|
|
21531
|
+
await this.ensureProject(projectId);
|
|
21532
|
+
return this.store.queries.summarizeItems(projectId);
|
|
21533
|
+
};
|
|
21534
|
+
listRecentArtifacts = async (projectId, input = {}) => {
|
|
21535
|
+
const project = await this.requireProject(projectId);
|
|
21536
|
+
await this.store.ensureProject(projectId);
|
|
21537
|
+
const limit = this.requirePageLimit(input.limit);
|
|
21538
|
+
const cursor = input.cursor ? this.decodeCursor(input.cursor, "project artifact", "createdAt") : void 0;
|
|
21539
|
+
const page = this.store.queries.listRecentArtifacts({
|
|
21540
|
+
projectId,
|
|
21541
|
+
limit,
|
|
21542
|
+
...cursor ? { cursor } : {}
|
|
21543
|
+
});
|
|
21544
|
+
const artifacts = await Promise.all(page.artifacts.map(async (artifact) => ({
|
|
21545
|
+
id: artifact.id,
|
|
21546
|
+
path: artifact.path,
|
|
21547
|
+
label: artifact.label,
|
|
21548
|
+
workItemId: artifact.work_item_id,
|
|
21549
|
+
workItemTitle: artifact.work_item_title,
|
|
21550
|
+
createdAt: artifact.created_at,
|
|
21551
|
+
exists: await this.isArtifactAvailable(project.rootPath, artifact.path)
|
|
21552
|
+
})));
|
|
21553
|
+
const last = artifacts.at(-1);
|
|
21554
|
+
return {
|
|
21555
|
+
artifacts,
|
|
21556
|
+
total: page.total,
|
|
21557
|
+
nextCursor: page.hasMore && last ? this.encodeCursor({
|
|
21558
|
+
createdAt: last.createdAt,
|
|
21559
|
+
id: last.id
|
|
21560
|
+
}) : null
|
|
21561
|
+
};
|
|
21562
|
+
};
|
|
21563
|
+
ensureProject = async (projectId) => {
|
|
21564
|
+
await this.requireProject(projectId);
|
|
21565
|
+
await this.store.ensureProject(projectId);
|
|
21566
|
+
};
|
|
21567
|
+
requireProject = async (projectId) => {
|
|
21568
|
+
const normalized = projectId.trim();
|
|
21569
|
+
const project = normalized ? await this.projectManager.getProjectById(normalized) : null;
|
|
21570
|
+
if (!project) throw new ProjectWorkError("PROJECT_NOT_FOUND", "project was not found");
|
|
21571
|
+
return project;
|
|
21572
|
+
};
|
|
21573
|
+
requireMappedState = (states, stateId) => {
|
|
21574
|
+
const state = states.get(stateId);
|
|
21575
|
+
if (!state) throw new ProjectWorkError("PROJECT_WORK_STATE_NOT_FOUND", "work item state was not found");
|
|
21576
|
+
return state;
|
|
21577
|
+
};
|
|
21578
|
+
requirePageLimit = (value) => {
|
|
21579
|
+
const limit = value ?? 20;
|
|
21580
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > 100) throw new ProjectWorkError("PROJECT_WORK_VALIDATION_FAILED", "page limit must be an integer between 1 and 100");
|
|
21581
|
+
return limit;
|
|
21582
|
+
};
|
|
21583
|
+
encodeCursor = (value) => Buffer.from(JSON.stringify(value), "utf8").toString("base64url");
|
|
21584
|
+
decodeCursor = (value, label, timestampKey) => {
|
|
21585
|
+
try {
|
|
21586
|
+
const decoded = JSON.parse(Buffer.from(value, "base64url").toString("utf8"));
|
|
21587
|
+
if (!decoded || typeof decoded !== "object" || typeof decoded.id !== "string" || typeof decoded[timestampKey] !== "string" || Object.keys(decoded).some((key) => key !== "id" && key !== timestampKey) || Object.values(decoded).some((entry) => typeof entry !== "string" || !entry)) throw new Error("invalid cursor shape");
|
|
21588
|
+
return decoded;
|
|
21589
|
+
} catch {
|
|
21590
|
+
throw new ProjectWorkError("PROJECT_WORK_VALIDATION_FAILED", `${label} cursor is invalid`);
|
|
21591
|
+
}
|
|
21592
|
+
};
|
|
21593
|
+
isArtifactAvailable = async (projectRoot, projectRelativePath) => {
|
|
21594
|
+
try {
|
|
21595
|
+
return (await stat(resolve(projectRoot, projectRelativePath))).isFile();
|
|
21596
|
+
} catch {
|
|
21597
|
+
return false;
|
|
21598
|
+
}
|
|
21599
|
+
};
|
|
21600
|
+
};
|
|
21601
|
+
//#endregion
|
|
21236
21602
|
//#region src/features/projects/types/project-work.types.ts
|
|
21237
21603
|
const PROJECT_WORK_STATE_CATEGORIES = [
|
|
21238
21604
|
"backlog",
|
|
@@ -21250,9 +21616,11 @@ const PROJECT_WORK_ATTENTION_VALUES = [
|
|
|
21250
21616
|
//#region src/features/projects/managers/project-work.manager.ts
|
|
21251
21617
|
var ProjectWorkManager = class {
|
|
21252
21618
|
store;
|
|
21619
|
+
queries;
|
|
21253
21620
|
constructor(options) {
|
|
21254
21621
|
this.options = options;
|
|
21255
21622
|
this.store = new ProjectWorkStore(options.databasePath);
|
|
21623
|
+
this.queries = new ProjectWorkQueryService(this.store, options.projectManager);
|
|
21256
21624
|
}
|
|
21257
21625
|
initialize = async () => {
|
|
21258
21626
|
await this.store.initialize();
|
|
@@ -21263,31 +21631,9 @@ var ProjectWorkManager = class {
|
|
|
21263
21631
|
await this.requireProject(projectId);
|
|
21264
21632
|
await this.store.ensureProject(projectId);
|
|
21265
21633
|
};
|
|
21266
|
-
list = async (projectId,
|
|
21267
|
-
|
|
21268
|
-
|
|
21269
|
-
const stateById = new Map(states.map((state) => [state.id, state]));
|
|
21270
|
-
const detailed = await Promise.all(items.map(async (item) => ({
|
|
21271
|
-
...item,
|
|
21272
|
-
state: this.requireMappedState(stateById, item.stateId),
|
|
21273
|
-
artifacts: await this.store.listArtifacts(projectId, item.id)
|
|
21274
|
-
})));
|
|
21275
|
-
return {
|
|
21276
|
-
items: detailed,
|
|
21277
|
-
states,
|
|
21278
|
-
total: detailed.length
|
|
21279
|
-
};
|
|
21280
|
-
};
|
|
21281
|
-
summary = async (projectId) => {
|
|
21282
|
-
const { items } = await this.list(projectId);
|
|
21283
|
-
return {
|
|
21284
|
-
total: items.length,
|
|
21285
|
-
active: items.filter((item) => item.state.category !== "completed" && item.state.category !== "canceled").length,
|
|
21286
|
-
completed: items.filter((item) => item.state.category === "completed").length,
|
|
21287
|
-
attention: items.filter((item) => item.attention !== "none").length,
|
|
21288
|
-
updatedAt: items[0]?.updatedAt ?? null
|
|
21289
|
-
};
|
|
21290
|
-
};
|
|
21634
|
+
list = async (projectId, input = {}) => await this.queries.list(projectId, input);
|
|
21635
|
+
summary = async (projectId) => await this.queries.summary(projectId);
|
|
21636
|
+
listRecentArtifacts = async (projectId, input = {}) => await this.queries.listRecentArtifacts(projectId, input);
|
|
21291
21637
|
get = async (projectId, workItemId) => {
|
|
21292
21638
|
await this.ensureProject(projectId);
|
|
21293
21639
|
const item = await this.store.getItem(projectId, workItemId);
|
|
@@ -21456,11 +21802,6 @@ var ProjectWorkManager = class {
|
|
|
21456
21802
|
if (!project) throw new ProjectWorkError("PROJECT_NOT_FOUND", "project was not found");
|
|
21457
21803
|
return project;
|
|
21458
21804
|
};
|
|
21459
|
-
requireMappedState = (states, stateId) => {
|
|
21460
|
-
const state = states.get(stateId);
|
|
21461
|
-
if (!state) throw new ProjectWorkError("PROJECT_WORK_STATE_NOT_FOUND", "work item state was not found");
|
|
21462
|
-
return state;
|
|
21463
|
-
};
|
|
21464
21805
|
requireName = (value, label) => {
|
|
21465
21806
|
const normalized = typeof value === "string" ? value.trim() : "";
|
|
21466
21807
|
if (!normalized) throw new ProjectWorkError("PROJECT_WORK_VALIDATION_FAILED", `${label} is required`);
|
|
@@ -23800,7 +24141,6 @@ var NcpAgentSessionSummaryIndexStore = class {
|
|
|
23800
24141
|
CREATE INDEX IF NOT EXISTS sessions_activity_idx
|
|
23801
24142
|
ON sessions (deleted_at, last_message_at, created_at, updated_at);
|
|
23802
24143
|
`);
|
|
23803
|
-
if (this.database.prepare("SELECT value FROM storage_meta WHERE key = ? LIMIT 1").get(MIGRATION_STATUS_KEY)?.value === MIGRATION_COMPLETE) return;
|
|
23804
24144
|
const scan = await scanNcpAgentSessionCatalogJournals({
|
|
23805
24145
|
journalDir: this.journalDir,
|
|
23806
24146
|
loadSession: (sessionId) => this.loadSession(sessionId),
|
|
@@ -23846,7 +24186,16 @@ var NcpAgentSessionSummaryIndexStore = class {
|
|
|
23846
24186
|
deleted_at = CASE
|
|
23847
24187
|
WHEN @restore_deleted = 1 THEN NULL
|
|
23848
24188
|
ELSE sessions.deleted_at
|
|
23849
|
-
END
|
|
24189
|
+
END
|
|
24190
|
+
WHERE sessions.peer_id IS NOT excluded.peer_id
|
|
24191
|
+
OR sessions.agent_id IS NOT excluded.agent_id
|
|
24192
|
+
OR sessions.created_at IS NOT excluded.created_at
|
|
24193
|
+
OR sessions.updated_at IS NOT excluded.updated_at
|
|
24194
|
+
OR sessions.last_message_at IS NOT excluded.last_message_at
|
|
24195
|
+
OR sessions.message_count IS NOT excluded.message_count
|
|
24196
|
+
OR sessions.status IS NOT excluded.status
|
|
24197
|
+
OR sessions.metadata_json IS NOT excluded.metadata_json
|
|
24198
|
+
OR (@restore_deleted = 1 AND sessions.deleted_at IS NOT NULL)`).run({
|
|
23850
24199
|
...row,
|
|
23851
24200
|
restore_deleted: restoreDeleted ? 1 : 0
|
|
23852
24201
|
});
|
|
@@ -23974,6 +24323,7 @@ var NcpAgentSessionJournalStore = class {
|
|
|
23974
24323
|
this.unfinishedRunStore = new NcpAgentUnfinishedRunStore(journalDir, async () => (await this.summaryIndexStore.list()).map(({ sessionId }) => sessionId));
|
|
23975
24324
|
}
|
|
23976
24325
|
initialize = async () => await this.summaryIndexStore.initialize();
|
|
24326
|
+
close = () => this.summaryIndexStore.close();
|
|
23977
24327
|
appendSessionEvent = async (params) => {
|
|
23978
24328
|
const sessionId = normalizeNcpSessionId(params.sessionId);
|
|
23979
24329
|
if (!sessionId) return;
|
|
@@ -26684,13 +27034,29 @@ function agentActor(context) {
|
|
|
26684
27034
|
}
|
|
26685
27035
|
var ProjectWorkListTool = class {
|
|
26686
27036
|
name = "project_work_list";
|
|
26687
|
-
description = "List persistent work items
|
|
27037
|
+
description = "List one bounded page of persistent work items for the current project.";
|
|
26688
27038
|
parameters = {
|
|
26689
27039
|
type: "object",
|
|
26690
|
-
properties: {
|
|
26691
|
-
|
|
26692
|
-
|
|
26693
|
-
|
|
27040
|
+
properties: {
|
|
27041
|
+
include_deleted: {
|
|
27042
|
+
type: "boolean",
|
|
27043
|
+
description: "Include deleted work items."
|
|
27044
|
+
},
|
|
27045
|
+
state_id: {
|
|
27046
|
+
type: "string",
|
|
27047
|
+
description: "Only return work items in this custom state."
|
|
27048
|
+
},
|
|
27049
|
+
cursor: {
|
|
27050
|
+
type: "string",
|
|
27051
|
+
description: "Opaque next_cursor from an earlier list response."
|
|
27052
|
+
},
|
|
27053
|
+
limit: {
|
|
27054
|
+
type: "integer",
|
|
27055
|
+
minimum: 1,
|
|
27056
|
+
maximum: 100,
|
|
27057
|
+
description: "Page size. Defaults to 20."
|
|
27058
|
+
}
|
|
27059
|
+
},
|
|
26694
27060
|
additionalProperties: false
|
|
26695
27061
|
};
|
|
26696
27062
|
constructor(work, context) {
|
|
@@ -26699,7 +27065,12 @@ var ProjectWorkListTool = class {
|
|
|
26699
27065
|
}
|
|
26700
27066
|
execute = async (args) => {
|
|
26701
27067
|
const params = normalizeToolParams(args);
|
|
26702
|
-
return JSON.stringify(await this.work.list(this.context.projectId,
|
|
27068
|
+
return JSON.stringify(await this.work.list(this.context.projectId, {
|
|
27069
|
+
includeDeleted: optionalBoolean(params.include_deleted) ?? false,
|
|
27070
|
+
...optionalString$1(params.state_id) ? { stateId: optionalString$1(params.state_id) } : {},
|
|
27071
|
+
...optionalString$1(params.cursor) ? { cursor: optionalString$1(params.cursor) } : {},
|
|
27072
|
+
...optionalNumber(params.limit) ? { limit: optionalNumber(params.limit) } : {}
|
|
27073
|
+
}), null, 2);
|
|
26703
27074
|
};
|
|
26704
27075
|
};
|
|
26705
27076
|
var ProjectWorkGetTool = class {
|