@nextclaw/kernel 0.15.0-beta.0 → 0.15.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/dist/index.js CHANGED
@@ -11,7 +11,7 @@ import { DefaultNcpAgentConversationStateManager, insertMessageByTimeline, merge
11
11
  import { accessSync, appendFileSync, chmodSync, constants, createReadStream, existsSync, mkdirSync, readFileSync, readdirSync, readlinkSync, realpathSync, renameSync, rmSync, writeFileSync } from "node:fs";
12
12
  import path, { basename, delimiter, dirname, extname, isAbsolute, join, normalize, relative, resolve, sep } from "node:path";
13
13
  import { access, appendFile, chmod, lstat, mkdir, mkdtemp, open, readFile, readdir, realpath, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
14
- import { AppHomeService, AppInstallationService, AppInstanceInventoryService, AppInstanceStorageService, AppManifestService, AppPlatformTargetService, AppRegistryService, AppServiceLaunchService, FileLockService, isAppComponentManifestBundle } from "@nextclaw/app-runtime";
14
+ import { AppGrantService, AppHomeService, AppInstallationService, AppInstanceInventoryService, AppInstanceStorageService, AppManifestService, AppPlatformTargetService, AppRegistryService, AppServiceLaunchService, FileLockService, isAppComponentManifestBundle } from "@nextclaw/app-runtime";
15
15
  import { execFileSync, spawn } from "node:child_process";
16
16
  import { fileURLToPath } from "node:url";
17
17
  import { BUILTIN_PROVIDER_PLUGINS } from "@nextclaw/runtime";
@@ -3664,7 +3664,11 @@ var AppPackageReadinessManager = class {
3664
3664
  isolation: manifestBundle.manifest.main.kind === "wasi-http-component" ? "host-mediated" : "sandboxed",
3665
3665
  permissions: manifestBundle.manifest.permissions ?? {}
3666
3666
  };
3667
- const [dependencies, secrets] = await Promise.all([this.params.dependencyCoordinator.inspectTarget(this.params.dependencyCoordinator.targetForInfo(info, selectedVersion), providers), this.inspectSecretsForInfo(info, true, selectedVersion)]);
3667
+ const [dependencies, secrets, documentAccess] = await Promise.all([
3668
+ this.params.dependencyCoordinator.inspectTarget(this.params.dependencyCoordinator.targetForInfo(info, selectedVersion), providers),
3669
+ this.inspectSecretsForInfo(info, true, selectedVersion),
3670
+ this.params.grantService.summarize(info.appId).then((state) => state.documentAccess)
3671
+ ]);
3668
3672
  return {
3669
3673
  id: info.appId,
3670
3674
  name: info.name,
@@ -3701,7 +3705,8 @@ var AppPackageReadinessManager = class {
3701
3705
  isolation: security.isolation,
3702
3706
  readiness: this.combineReadiness(dependencies.readiness, secrets.readiness),
3703
3707
  secrets,
3704
- dependencies
3708
+ dependencies,
3709
+ documentAccess
3705
3710
  };
3706
3711
  };
3707
3712
  inspectSecrets = async (info, verify, selectedVersion = info.activeVersion) => {
@@ -4537,6 +4542,73 @@ var AppPackageDependencyCoordinator = class {
4537
4542
  };
4538
4543
  };
4539
4544
  //#endregion
4545
+ //#region src/services/app-package-document-access.service.ts
4546
+ var AppPackageDocumentAccessService = class {
4547
+ constructor(params) {
4548
+ this.params = params;
4549
+ }
4550
+ inspect = async (appId) => await this.params.installationService.withAppOperation(appId, async () => {
4551
+ try {
4552
+ return await this.params.grantService.summarize(appId);
4553
+ } catch (error) {
4554
+ throw this.toError(error);
4555
+ }
4556
+ });
4557
+ assert = async (appId, scopeId, requestedMode) => {
4558
+ const scope = (await this.inspect(appId)).documentAccess.find((entry) => entry.id === scopeId);
4559
+ if (!scope) throw new AppPackageError("DOCUMENT_SCOPE_NOT_DECLARED", `App ${appId} does not declare document scope ${scopeId}.`);
4560
+ if (!scope.granted) throw new AppPackageError("DOCUMENT_SCOPE_NOT_GRANTED", `App ${appId} requires document scope ${scopeId}. Grant it and retry the action.`);
4561
+ if (scope.status === "unavailable") throw new AppPackageError("DOCUMENT_SCOPE_UNAVAILABLE", `App ${appId} document scope ${scopeId} is unavailable. Replace or revoke it.`);
4562
+ if (requestedMode === "read-write" && scope.effectiveMode !== "read-write") throw new AppPackageError("DOCUMENT_SCOPE_MODE_INSUFFICIENT", `App ${appId} document scope ${scopeId} needs read-write access.`);
4563
+ };
4564
+ grant = async (appId, input) => await this.mutate(appId, input.scopeId, async () => await this.params.grantService.grantDocumentScope({
4565
+ appId,
4566
+ ...input
4567
+ }));
4568
+ revoke = async (appId, scopeId) => await this.mutate(appId, scopeId, async () => {
4569
+ if (!(await this.params.grantService.summarize(appId)).documentAccess.some((scope) => scope.id === scopeId)) throw new AppPackageError("DOCUMENT_SCOPE_NOT_DECLARED", `App ${appId} does not declare document scope ${scopeId}.`);
4570
+ return await this.params.grantService.revokeDocumentScope({
4571
+ appId,
4572
+ scopeId
4573
+ });
4574
+ });
4575
+ mutate = async (appId, scopeId, mutation) => await this.params.installationService.withAppOperation(appId, async () => {
4576
+ const app = await this.params.getPackage(appId);
4577
+ const previousGrant = (await this.params.registryService.getApp(appId))?.grants[scopeId];
4578
+ const sources = this.toComponentSources(app);
4579
+ const runtimeHooks = this.params.getRuntimeHooks();
4580
+ const rollbackRuntime = app.enabled ? await runtimeHooks.prepareCapabilityChange(sources) : async () => void 0;
4581
+ try {
4582
+ const result = await mutation();
4583
+ if (app.enabled) await runtimeHooks.afterCapabilityChange(sources);
4584
+ return result;
4585
+ } catch (error) {
4586
+ const recoveryErrors = [];
4587
+ try {
4588
+ await this.params.registryService.restoreDocumentGrant(appId, scopeId, previousGrant);
4589
+ } catch (recoveryError) {
4590
+ recoveryErrors.push(recoveryError);
4591
+ }
4592
+ try {
4593
+ await rollbackRuntime();
4594
+ } catch (recoveryError) {
4595
+ recoveryErrors.push(recoveryError);
4596
+ }
4597
+ if (recoveryErrors.length > 0) throw new AggregateError([error, ...recoveryErrors], `App ${appId} document scope ${scopeId} mutation failed and recovery was incomplete.`);
4598
+ throw this.toError(error);
4599
+ }
4600
+ });
4601
+ toComponentSources = (app) => app.components.map((component) => ({ ...component }));
4602
+ toError = (error) => {
4603
+ if (error instanceof AppPackageError) return error;
4604
+ const message = error instanceof Error ? error.message : String(error);
4605
+ if (message.includes("未声明 documentAccess scope")) return new AppPackageError("DOCUMENT_SCOPE_NOT_DECLARED", message);
4606
+ if (message.includes("只声明了 read")) return new AppPackageError("DOCUMENT_SCOPE_MODE_INSUFFICIENT", message);
4607
+ if (message.includes("不是可用目录")) return new AppPackageError("DOCUMENT_SCOPE_UNAVAILABLE", message);
4608
+ return new AppPackageError("DOCUMENT_SCOPE_MUTATION_FAILED", message);
4609
+ };
4610
+ };
4611
+ //#endregion
4540
4612
  //#region src/services/app-package-host-target.service.ts
4541
4613
  var AppPackageHostTargetService = class {
4542
4614
  platformTargetService = new AppPlatformTargetService();
@@ -4607,6 +4679,8 @@ const EMPTY_APP_PACKAGE_RUNTIME_HOOKS = {
4607
4679
  assertCanActivate: async () => void 0,
4608
4680
  afterActivate: async () => void 0,
4609
4681
  beforeDeactivate: async () => void 0,
4682
+ prepareCapabilityChange: async () => async () => void 0,
4683
+ afterCapabilityChange: async () => void 0,
4610
4684
  beforeUninstall: async () => void 0
4611
4685
  };
4612
4686
  var AppPackageRuntimeActivationService = class {
@@ -4637,6 +4711,8 @@ var AppPackageManager = class {
4637
4711
  dependencyCoordinator;
4638
4712
  runtimeActivationService = new AppPackageRuntimeActivationService();
4639
4713
  registryService;
4714
+ grantService;
4715
+ documentAccessService;
4640
4716
  readinessManager;
4641
4717
  runtimeHooks = EMPTY_APP_PACKAGE_RUNTIME_HOOKS;
4642
4718
  builtInBootstrapPromise;
@@ -4646,6 +4722,14 @@ var AppPackageManager = class {
4646
4722
  this.appHomeService = new AppHomeService(params.appHomeDirectory);
4647
4723
  this.installationService = new AppInstallationService(this.appHomeService);
4648
4724
  this.registryService = new AppRegistryService(this.appHomeService);
4725
+ this.grantService = new AppGrantService(this.registryService, this.manifestService);
4726
+ this.documentAccessService = new AppPackageDocumentAccessService({
4727
+ grantService: this.grantService,
4728
+ installationService: this.installationService,
4729
+ registryService: this.registryService,
4730
+ getPackage: this.getPackage,
4731
+ getRuntimeHooks: () => this.runtimeHooks
4732
+ });
4649
4733
  this.dependencyCoordinator = new AppPackageDependencyCoordinator({
4650
4734
  installationService: this.installationService,
4651
4735
  registryService: this.registryService,
@@ -4658,6 +4742,7 @@ var AppPackageManager = class {
4658
4742
  presentationService: this.presentationService,
4659
4743
  dependencyCoordinator: this.dependencyCoordinator,
4660
4744
  registryService: this.registryService,
4745
+ grantService: this.grantService,
4661
4746
  productVersion: params.productVersion,
4662
4747
  getSecretConfig: params.getSecretConfig,
4663
4748
  secretConfigPath: params.secretConfigPath,
@@ -4718,6 +4803,12 @@ var AppPackageManager = class {
4718
4803
  await this.registryService.unbindSecret(appId, slotId);
4719
4804
  return await this.inspectSecrets(appId);
4720
4805
  });
4806
+ inspectDocumentAccess = async (appId) => await this.documentAccessService.inspect(appId);
4807
+ assertDocumentAccess = async (appId, scopeId, requestedMode) => {
4808
+ await this.documentAccessService.assert(appId, scopeId, requestedMode);
4809
+ };
4810
+ grantDocumentAccess = async (appId, input) => await this.documentAccessService.grant(appId, input);
4811
+ revokeDocumentAccess = async (appId, scopeId) => await this.documentAccessService.revoke(appId, scopeId);
4721
4812
  listActiveComponentSources = async () => (await this.listActiveComponentSourcesWithDiagnostics()).sources;
4722
4813
  listActiveComponentSourcesWithDiagnostics = async () => await this.dependencyCoordinator.listActiveComponentSourcesWithDiagnostics();
4723
4814
  listOperations = async () => await this.operationManager.list();
@@ -6590,6 +6681,9 @@ function isRecord$18(value) {
6590
6681
  //#region src/utils/service-app-error.utils.ts
6591
6682
  const SERVICE_APP_ERROR_CODES = new Set([
6592
6683
  "AUTHORIZATION_REQUIRED",
6684
+ "DOCUMENT_SCOPE_NOT_GRANTED",
6685
+ "DOCUMENT_SCOPE_MODE_INSUFFICIENT",
6686
+ "DOCUMENT_SCOPE_UNAVAILABLE",
6593
6687
  "SERVICE_APP_ACTION_NOT_DECLARED",
6594
6688
  "SERVICE_APP_ACTION_NOT_FOUND",
6595
6689
  "SERVICE_APP_INVALID_ACTION",
@@ -7650,23 +7744,23 @@ var DesktopNodeReplService = class {
7650
7744
  });
7651
7745
  case "setValue": return await this.sessionState.setValue(caller, {
7652
7746
  target,
7653
- stateId: requiredString$1(args.stateId, "stateId"),
7747
+ stateId: requiredString$2(args.stateId, "stateId"),
7654
7748
  elementIndex: readElementIndex(args.element),
7655
7749
  value: requiredValue$1(args.value ?? args.text, "value")
7656
7750
  });
7657
7751
  case "click": return await this.sessionState.click(caller, {
7658
7752
  target,
7659
- stateId: requiredString$1(args.stateId, "stateId"),
7753
+ stateId: requiredString$2(args.stateId, "stateId"),
7660
7754
  ...args.coordinate === void 0 ? { elementIndex: readElementIndex(args.element) } : { coordinate: readCoordinate(args.coordinate) }
7661
7755
  });
7662
7756
  case "typeText": return await this.sessionState.typeText(caller, {
7663
7757
  target,
7664
- stateId: requiredString$1(args.stateId, "stateId"),
7758
+ stateId: requiredString$2(args.stateId, "stateId"),
7665
7759
  text: requiredText(args.text, "text")
7666
7760
  });
7667
7761
  case "pressKey": return await this.sessionState.pressKey(caller, {
7668
7762
  target,
7669
- stateId: requiredString$1(args.stateId, "stateId"),
7763
+ stateId: requiredString$2(args.stateId, "stateId"),
7670
7764
  key: readKeyboardKey(args.key),
7671
7765
  ...args.modifiers === void 0 ? {} : { modifiers: readKeyboardModifiers(args.modifiers) }
7672
7766
  });
@@ -7713,7 +7807,7 @@ function pickCaller(input) {
7713
7807
  };
7714
7808
  }
7715
7809
  function readTarget(value) {
7716
- return { applicationId: requiredString$1(asRecord$1(value).applicationId, "target.applicationId") };
7810
+ return { applicationId: requiredString$2(asRecord$1(value).applicationId, "target.applicationId") };
7717
7811
  }
7718
7812
  function readElementIndex(value) {
7719
7813
  const index = asRecord$1(value).index;
@@ -7737,7 +7831,7 @@ function isFiniteNumber$1(value) {
7737
7831
  function asRecord$1(value) {
7738
7832
  return value && typeof value === "object" && !Array.isArray(value) ? value : {};
7739
7833
  }
7740
- function requiredString$1(value, field) {
7834
+ function requiredString$2(value, field) {
7741
7835
  if (typeof value !== "string" || !value.trim()) throw replError("invalid_tool_arguments", `${field} must be a non-empty string.`);
7742
7836
  return value.trim();
7743
7837
  }
@@ -7769,7 +7863,7 @@ const SUPPORTED_KEY_MODIFIERS = new Set([
7769
7863
  "shift"
7770
7864
  ]);
7771
7865
  function readKeyboardKey(value) {
7772
- const key = requiredString$1(value, "key");
7866
+ const key = requiredString$2(value, "key");
7773
7867
  if (!SUPPORTED_KEYS.has(key)) throw replError("invalid_tool_arguments", "key must be a supported named key or a lowercase letter/digit.");
7774
7868
  return key;
7775
7869
  }
@@ -13644,16 +13738,15 @@ function applySessionRuntimePatch(metadata, patch) {
13644
13738
  function applySessionSettingsMetadataPatch(currentMetadata, patch) {
13645
13739
  return applySessionRuntimePatch(applySessionPreferencePatch(structuredClone(currentMetadata), patch), patch);
13646
13740
  }
13647
- async function applySessionProjectMetadataPatch(metadata, patch, normalizeProjectRoot) {
13741
+ async function applySessionProjectMetadataPatch(metadata, patch, normalizeProjectContext) {
13648
13742
  if (!Object.prototype.hasOwnProperty.call(patch, "projectRoot")) return metadata;
13649
- const projectRoot = await normalizeProjectRoot(patch.projectRoot);
13650
- const { projectRoot: _legacyProjectRoot, ...nextMetadata } = metadata;
13651
- if (projectRoot) return {
13743
+ const project = await normalizeProjectContext(patch.projectRoot);
13744
+ const { projectRoot: _legacyProjectRoot, projectId: _legacyProjectId, project_root: _projectRoot, project_id: _projectId, ...nextMetadata } = metadata;
13745
+ return project ? {
13652
13746
  ...nextMetadata,
13653
- project_root: projectRoot
13654
- };
13655
- const { project_root: _projectRoot, ...metadataWithoutProjectRoot } = nextMetadata;
13656
- return metadataWithoutProjectRoot;
13747
+ project_id: project.projectId,
13748
+ project_root: project.rootPath
13749
+ } : nextMetadata;
13657
13750
  }
13658
13751
  function publishSessionMetadataChanged(eventBus, sessionKey, metadata, mode) {
13659
13752
  eventBus.emit(eventKeys$1.sessionMetadataChanged, {
@@ -13715,6 +13808,9 @@ function readThinkingEffort(metadata) {
13715
13808
  function readProjectRoot(metadata) {
13716
13809
  return readOptionalMetadataString(metadata?.project_root) ?? readOptionalMetadataString(metadata?.projectRoot);
13717
13810
  }
13811
+ function readProjectId(metadata) {
13812
+ return readOptionalMetadataString(metadata?.project_id) ?? readOptionalMetadataString(metadata?.projectId);
13813
+ }
13718
13814
  function readAgentRuntimeId(metadata) {
13719
13815
  return readOptionalMetadataString(metadata?.agentRuntimeId) ?? readOptionalMetadataString(metadata?.runtime) ?? readOptionalMetadataString(metadata?.session_type);
13720
13816
  }
@@ -13731,6 +13827,7 @@ function cloneInheritedMetadata(sourceMetadata) {
13731
13827
  "preferred_model",
13732
13828
  "preferred_thinking",
13733
13829
  "project_root",
13830
+ "project_id",
13734
13831
  "codex_runtime_backend",
13735
13832
  "reasoningNormalizationMode",
13736
13833
  "reasoning_normalization_mode"
@@ -13784,6 +13881,9 @@ function resolveKernelPreferenceStorePath(options) {
13784
13881
  function resolveKernelProjectStorePath(options) {
13785
13882
  return resolveKernelDataPath(options, "projects", "projects.json");
13786
13883
  }
13884
+ function resolveKernelProjectWorkStorePath(options) {
13885
+ return resolveKernelDataPath(options, "projects", "work-items.db");
13886
+ }
13787
13887
  function resolveKernelInboxDeliveryStorePath(options) {
13788
13888
  return resolveKernelDataPath(options, "inbox", "deliveries.json");
13789
13889
  }
@@ -15105,10 +15205,14 @@ var PortableServiceCapabilityResolverService = class {
15105
15205
  if (!record || app.packageVersion && record.activeVersion !== app.packageVersion) return [];
15106
15206
  const resolved = [];
15107
15207
  for (const scope of scopes) {
15108
- const grantedPath = record.grants[scope.id];
15109
- if (grantedPath) resolved.push({
15110
- scope,
15111
- hostPath: await this.requireCanonicalDirectory(grantedPath, `${app.id}:${scope.id}`)
15208
+ const grant = record.grants[scope.id];
15209
+ if (!grant || grant.mode === "read-write" && scope.mode !== "read-write") continue;
15210
+ resolved.push({
15211
+ scope: {
15212
+ ...scope,
15213
+ mode: grant.mode
15214
+ },
15215
+ hostPath: await this.requireCanonicalDirectory(grant.path, `${app.id}:${scope.id}`)
15112
15216
  });
15113
15217
  }
15114
15218
  return resolved;
@@ -17340,6 +17444,7 @@ function projectCapabilityProviders(entries) {
17340
17444
  //#endregion
17341
17445
  //#region src/managers/service-app.manager.ts
17342
17446
  const SERVICE_APP_ID_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
17447
+ const DOCUMENT_GUEST_PATH_PATTERN = /^\/documents\/([^/]+)(?:\/|$)/;
17343
17448
  var ServiceAppManager = class {
17344
17449
  removalService = new ServiceAppRemovalService();
17345
17450
  runtimeService;
@@ -17488,6 +17593,7 @@ var ServiceAppManager = class {
17488
17593
  const action = listServiceAppManifestActions(record, manifest).find((entry) => entry.id === actionId);
17489
17594
  if (!action) throw new ServiceAppError("SERVICE_APP_ACTION_NOT_FOUND", "service action not found");
17490
17595
  if (!await this.actionGrants.isGranted(request.caller, action)) throw new ServiceAppError("AUTHORIZATION_REQUIRED", `This panel app needs permission to call ${actionId}.`);
17596
+ await this.assertDocumentInputAccess(record, action.risk, request.input ?? {});
17491
17597
  return await this.jobManager.invoke({
17492
17598
  actionId,
17493
17599
  actionName,
@@ -17504,6 +17610,8 @@ var ServiceAppManager = class {
17504
17610
  */
17505
17611
  invokeInstalledServiceAction = async (appId, actionName, input = {}) => {
17506
17612
  const { manifest, record } = await this.requireInstalledServiceApp(appId, actionName);
17613
+ const action = listServiceAppManifestActions(record, manifest).find((entry) => entry.name === actionName);
17614
+ await this.assertDocumentInputAccess(record, action?.risk ?? "dangerous", input);
17507
17615
  return await this.jobManager.invoke({
17508
17616
  actionId: buildServiceActionId(record.id, actionName),
17509
17617
  actionName,
@@ -17514,6 +17622,24 @@ var ServiceAppManager = class {
17514
17622
  entrySurface: "installed-app-cli"
17515
17623
  });
17516
17624
  };
17625
+ assertDocumentInputAccess = async (record, risk, input) => {
17626
+ const match = (typeof input.path === "string" ? input.path : void 0)?.match(DOCUMENT_GUEST_PATH_PATTERN);
17627
+ if (!match || !record.packageId || !this.params.assertDocumentAccess) return;
17628
+ const scopeId = decodeURIComponent(match[1]);
17629
+ const requestedMode = risk === "read" ? "read" : "read-write";
17630
+ try {
17631
+ await this.params.assertDocumentAccess(record.packageId, scopeId, requestedMode);
17632
+ } catch (error) {
17633
+ const code = typeof error === "object" && error !== null && "code" in error ? String(error.code) : "DOCUMENT_SCOPE_NOT_GRANTED";
17634
+ if (code === "DOCUMENT_SCOPE_NOT_GRANTED" || code === "DOCUMENT_SCOPE_MODE_INSUFFICIENT" || code === "DOCUMENT_SCOPE_UNAVAILABLE") throw new ServiceAppError(code, error instanceof Error ? error.message : code, {
17635
+ appId: record.packageId,
17636
+ scopeId,
17637
+ requestedMode,
17638
+ recoveryActions: code === "DOCUMENT_SCOPE_UNAVAILABLE" ? ["replace", "revoke"] : code === "DOCUMENT_SCOPE_MODE_INSUFFICIENT" ? ["upgrade"] : ["grant"]
17639
+ });
17640
+ throw error;
17641
+ }
17642
+ };
17517
17643
  listVerificationRecords = async (filters = {}) => await this.jobManager.listVerificationRecords(filters);
17518
17644
  exportVerificationRecords = async (filters = {}) => await this.jobManager.exportVerificationRecords(filters);
17519
17645
  listServiceAppJobs = async (appId, params = {}) => await this.jobManager.list(appId, params.caller);
@@ -18870,7 +18996,7 @@ var SessionSettingsService = class {
18870
18996
  existing = await this.options.getSessionRecord(sessionId);
18871
18997
  }
18872
18998
  if (!existing) return null;
18873
- const metadata = await applySessionProjectMetadataPatch(applySessionSettingsMetadataPatch(existing.metadata ?? {}, patch), patch, this.options.normalizeProjectRoot);
18999
+ const metadata = await applySessionProjectMetadataPatch(applySessionSettingsMetadataPatch(existing.metadata ?? {}, patch), patch, this.options.normalizeProjectContext);
18874
19000
  return await this.options.setSessionMetadata(sessionId, metadata) ? await this.options.getSession(sessionId) : null;
18875
19001
  };
18876
19002
  };
@@ -18973,7 +19099,7 @@ var SessionManager = class {
18973
19099
  createSession: this.createSession,
18974
19100
  getSession: this.getSession,
18975
19101
  getSessionRecord: this.getSessionRecord,
18976
- normalizeProjectRoot: options.projectManager.normalizeSessionProjectRoot,
19102
+ normalizeProjectContext: options.projectManager.normalizeSessionProjectContext,
18977
19103
  setSessionMetadata: this.setSessionMetadata
18978
19104
  });
18979
19105
  }
@@ -19005,14 +19131,9 @@ var SessionManager = class {
19005
19131
  title
19006
19132
  });
19007
19133
  const now = (/* @__PURE__ */ new Date()).toISOString();
19008
- const nextMetadata = mergeMetadataOverrides(metadata, metadataOverrides);
19134
+ let nextMetadata = mergeMetadataOverrides(metadata, metadataOverrides);
19009
19135
  const requestedProjectRoot = projectRoot !== void 0 ? projectRoot : readProjectRoot(nextMetadata);
19010
- if (requestedProjectRoot !== void 0) {
19011
- const normalizedProjectRoot = await this.options.projectManager.normalizeSessionProjectRoot(requestedProjectRoot);
19012
- delete nextMetadata.projectRoot;
19013
- if (normalizedProjectRoot) nextMetadata.project_root = normalizedProjectRoot;
19014
- else delete nextMetadata.project_root;
19015
- }
19136
+ if (requestedProjectRoot !== void 0) nextMetadata = await applySessionProjectMetadataPatch(nextMetadata, { projectRoot: requestedProjectRoot }, this.options.projectManager.normalizeSessionProjectContext);
19016
19137
  const agentId = readOptionalString$10(requestedAgentId) ?? readOptionalString$10(sourceRecord?.agentId) ?? BUILTIN_MAIN_AGENT_ID;
19017
19138
  const sessionId = readOptionalString$10(requestedSessionId) ?? buildSessionId();
19018
19139
  const inheritedContext = createSessionContextInheritance({
@@ -19197,6 +19318,7 @@ var SessionManager = class {
19197
19318
  metadata: structuredClone(created.metadata ?? {}),
19198
19319
  model: model ?? readOptionalMetadataString(created.metadata?.model) ?? readOptionalMetadataString(created.metadata?.preferred_model),
19199
19320
  projectRoot: readProjectRoot(created.metadata),
19321
+ projectId: readProjectId(created.metadata),
19200
19322
  workingDir: this.workingDirResolver.resolve({
19201
19323
  agentId: created.agentId,
19202
19324
  metadata: created.metadata
@@ -19218,6 +19340,7 @@ var SessionManager = class {
19218
19340
  metadata: structuredClone(metadata),
19219
19341
  model: readOptionalMetadataString(metadata.model) ?? readOptionalMetadataString(metadata.preferred_model),
19220
19342
  projectRoot: readProjectRoot(metadata),
19343
+ projectId: readProjectId(metadata),
19221
19344
  workingDir: this.workingDirResolver.resolve({
19222
19345
  agentId: summary.agentId,
19223
19346
  metadata
@@ -20375,14 +20498,20 @@ var ProjectManager = class {
20375
20498
  });
20376
20499
  };
20377
20500
  normalizeSessionProjectRoot = async (value) => {
20501
+ return (await this.normalizeSessionProjectContext(value))?.rootPath ?? null;
20502
+ };
20503
+ normalizeSessionProjectContext = async (value) => {
20378
20504
  if (value == null || typeof value === "string" && !value.trim()) return null;
20379
20505
  const rootPath = await this.resolveExistingProjectRoot(value);
20380
20506
  if (!rootPath) return null;
20381
- await this.upsertProject({
20507
+ const project = await this.upsertProject({
20382
20508
  name: basename(rootPath),
20383
20509
  rootPath
20384
20510
  });
20385
- return rootPath;
20511
+ return {
20512
+ projectId: project.id,
20513
+ rootPath: project.rootPath
20514
+ };
20386
20515
  };
20387
20516
  resolveExistingProjectRoot = async (value) => {
20388
20517
  if (typeof value !== "string") throw new ProjectError("PROJECT_PATH_INVALID_TYPE", "project path must be a string or null");
@@ -20421,6 +20550,7 @@ var ProjectManager = class {
20421
20550
  updatedAt: now
20422
20551
  };
20423
20552
  await this.store.save([...projects, project]);
20553
+ await this.options.onProjectRegistered?.(structuredClone(project));
20424
20554
  return structuredClone(project);
20425
20555
  };
20426
20556
  assertNotDefaultWorkspace = async (rootPath) => {
@@ -20478,81 +20608,971 @@ var ProjectManager = class {
20478
20608
  isMissingFileError = (error) => typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
20479
20609
  };
20480
20610
  //#endregion
20481
- //#region src/features/projects/types/project-observation.types.ts
20482
- const PROJECT_OBSERVATION_PROTOCOL = "nextclaw.project/v1";
20483
- var ProjectObservationError = class extends Error {
20484
- constructor(code, message) {
20485
- super(message);
20486
- this.code = code;
20487
- this.name = "ProjectObservationError";
20611
+ //#region src/stores/sqlite-database.store.ts
20612
+ const require = createRequire(import.meta.url);
20613
+ const portableDatabases = /* @__PURE__ */ new Map();
20614
+ async function openSqliteDatabase(databasePath) {
20615
+ try {
20616
+ return new (require("node:sqlite")).DatabaseSync(databasePath);
20617
+ } catch (error) {
20618
+ if (!isMissingNodeSqlite(error)) throw error;
20619
+ return await openPortableDatabase(databasePath);
20620
+ }
20621
+ }
20622
+ function runSqliteTransaction(database, operation, mode = "DEFERRED") {
20623
+ database.exec(`BEGIN ${mode}`);
20624
+ try {
20625
+ const result = operation();
20626
+ database.exec("COMMIT");
20627
+ return result;
20628
+ } catch (error) {
20629
+ database.exec("ROLLBACK");
20630
+ throw error;
20631
+ }
20632
+ }
20633
+ function isMissingNodeSqlite(error) {
20634
+ const code = typeof error === "object" && error !== null ? error.code : void 0;
20635
+ return code === "ERR_UNKNOWN_BUILTIN_MODULE" || code === "MODULE_NOT_FOUND";
20636
+ }
20637
+ async function openPortableDatabase(databasePath) {
20638
+ const existing = portableDatabases.get(databasePath);
20639
+ if (existing) return (await existing).acquire();
20640
+ const opening = createPortableDatabase(databasePath).catch((error) => {
20641
+ portableDatabases.delete(databasePath);
20642
+ throw error;
20643
+ });
20644
+ portableDatabases.set(databasePath, opening);
20645
+ return (await opening).acquire();
20646
+ }
20647
+ async function createPortableDatabase(databasePath) {
20648
+ const initialize = (await import("sql.js")).default;
20649
+ const sqlite = await initialize({ locateFile: (file) => require.resolve(`sql.js/dist/${file}`) });
20650
+ return new PortableSqliteDatabase(databasePath, existsSync(databasePath) ? new sqlite.Database(readFileSync(databasePath)) : new sqlite.Database());
20651
+ }
20652
+ var PortableSqliteDatabase = class {
20653
+ inTransaction = false;
20654
+ dirty = false;
20655
+ leaseCount = 0;
20656
+ constructor(databasePath, database) {
20657
+ this.databasePath = databasePath;
20658
+ this.database = database;
20659
+ }
20660
+ acquire = () => {
20661
+ this.leaseCount += 1;
20662
+ return new PortableSqliteLease(this);
20663
+ };
20664
+ exec = (sql) => {
20665
+ const command = sql.trim().toUpperCase();
20666
+ this.database.run(sql);
20667
+ if (command.startsWith("BEGIN")) {
20668
+ this.inTransaction = true;
20669
+ return;
20670
+ }
20671
+ if (command.startsWith("ROLLBACK")) {
20672
+ this.inTransaction = false;
20673
+ this.dirty = false;
20674
+ return;
20675
+ }
20676
+ if (command.startsWith("COMMIT")) {
20677
+ this.inTransaction = false;
20678
+ this.persistIfDirty();
20679
+ return;
20680
+ }
20681
+ if (!command.startsWith("PRAGMA")) this.markDirty();
20682
+ };
20683
+ prepare = (sql) => new PortableSqliteStatement(this, sql);
20684
+ release = () => {
20685
+ this.leaseCount -= 1;
20686
+ if (this.leaseCount > 0) return;
20687
+ this.persistIfDirty();
20688
+ this.database.close();
20689
+ portableDatabases.delete(this.databasePath);
20690
+ };
20691
+ run = (sql, params) => {
20692
+ this.database.run(sql, normalizeParams(params));
20693
+ this.markDirty();
20694
+ };
20695
+ readRows = (sql, params) => {
20696
+ const statement = this.database.prepare(sql);
20697
+ try {
20698
+ bindStatement(statement, params);
20699
+ const rows = [];
20700
+ while (statement.step()) rows.push(statement.getAsObject());
20701
+ return rows;
20702
+ } finally {
20703
+ statement.free();
20704
+ }
20705
+ };
20706
+ markDirty = () => {
20707
+ this.dirty = true;
20708
+ if (!this.inTransaction) this.persistIfDirty();
20709
+ };
20710
+ persistIfDirty = () => {
20711
+ if (!this.dirty) return;
20712
+ const temporaryPath = `${this.databasePath}.tmp`;
20713
+ writeFileSync(temporaryPath, this.database.export());
20714
+ renameSync(temporaryPath, this.databasePath);
20715
+ this.dirty = false;
20716
+ };
20717
+ };
20718
+ var PortableSqliteLease = class {
20719
+ closed = false;
20720
+ constructor(owner) {
20721
+ this.owner = owner;
20722
+ }
20723
+ close = () => {
20724
+ if (this.closed) return;
20725
+ this.closed = true;
20726
+ this.owner.release();
20727
+ };
20728
+ exec = (sql) => this.owner.exec(sql);
20729
+ prepare = (sql) => this.owner.prepare(sql);
20730
+ };
20731
+ var PortableSqliteStatement = class {
20732
+ constructor(database, sql) {
20733
+ this.database = database;
20734
+ this.sql = sql;
20488
20735
  }
20736
+ all = (...params) => this.database.readRows(this.sql, params);
20737
+ get = (...params) => this.all(...params)[0];
20738
+ run = (...params) => {
20739
+ this.database.run(this.sql, params);
20740
+ return {};
20741
+ };
20489
20742
  };
20490
- function isProjectObservationError(error) {
20491
- return error instanceof ProjectObservationError;
20743
+ function bindStatement(statement, params) {
20744
+ const normalized = normalizeParams(params);
20745
+ if (Array.isArray(normalized) && normalized.length === 0) return;
20746
+ statement.bind(normalized);
20747
+ }
20748
+ function normalizeParams(params) {
20749
+ if (params.length !== 1 || !isRecord$9(params[0])) return params;
20750
+ return Object.fromEntries(Object.entries(params[0]).map(([key, value]) => [`@${key}`, value]));
20751
+ }
20752
+ function isRecord$9(value) {
20753
+ return typeof value === "object" && value !== null && !Array.isArray(value);
20492
20754
  }
20493
20755
  //#endregion
20494
- //#region src/features/projects/utils/project-observation-config.utils.ts
20495
- const isRecord$9 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
20496
- const readString$7 = (value) => {
20497
- if (typeof value !== "string") return null;
20498
- return value.trim() || null;
20499
- };
20500
- const collectUnknownKeyIssues = (value, allowed, owner) => Object.keys(value).flatMap((key) => allowed.includes(key) ? [] : [{
20501
- code: "PROJECT_CONFIG_UNKNOWN_FIELD",
20502
- message: `${owner} contains unknown field '${key}'.`
20503
- }]);
20504
- function parseContext(value) {
20505
- if (value === void 0) return {
20506
- value: [],
20507
- issues: []
20756
+ //#region src/features/projects/stores/project-work-activity.store.ts
20757
+ var ProjectWorkActivityStore = class {
20758
+ constructor(db) {
20759
+ this.db = db;
20760
+ }
20761
+ listActivities = async (projectId, workItemId, options) => {
20762
+ const { cursor, limit } = options;
20763
+ const cursorSequence = cursor ? Number(cursor) : null;
20764
+ if (cursorSequence !== null && !Number.isInteger(cursorSequence)) throw new Error("PROJECT_WORK_ACTIVITY_CURSOR_INVALID");
20765
+ const rows = this.db().prepare(`SELECT rowid AS sequence, * FROM project_work_activities
20766
+ WHERE project_id = ? AND work_item_id = ? ${cursorSequence !== null ? "AND rowid < ?" : ""}
20767
+ ORDER BY rowid DESC LIMIT ?`).all(projectId, workItemId, ...cursorSequence !== null ? [cursorSequence] : [], limit + 1);
20768
+ const hasMore = rows.length > limit;
20769
+ const pageRows = rows.slice(0, limit);
20770
+ return {
20771
+ activities: pageRows.map(toActivity),
20772
+ nextCursor: hasMore ? String(pageRows.at(-1)?.sequence) : null
20773
+ };
20508
20774
  };
20509
- if (!Array.isArray(value)) return {
20510
- value: [],
20511
- issues: [{
20512
- code: "PROJECT_CONFIG_CONTEXT_INVALID",
20513
- message: "project.context must be an array."
20514
- }]
20775
+ listArtifacts = async (projectId, workItemId) => this.db().prepare(`SELECT * FROM project_work_artifact_links
20776
+ WHERE project_id = ? AND work_item_id = ? ORDER BY created_at DESC`).all(projectId, workItemId).map(toArtifactLink);
20777
+ linkArtifact = async (params) => {
20778
+ const { actor, label, path, projectId, workItemId } = params;
20779
+ const now = (/* @__PURE__ */ new Date()).toISOString();
20780
+ const id = randomUUID();
20781
+ runSqliteTransaction(this.db(), () => {
20782
+ this.db().prepare(`INSERT INTO project_work_artifact_links
20783
+ (id, project_id, work_item_id, path, label, created_at) VALUES (?, ?, ?, ?, ?, ?)`).run(id, projectId, workItemId, path, label ?? null, now);
20784
+ this.insertActivity({
20785
+ projectId,
20786
+ workItemId,
20787
+ type: "artifact-linked",
20788
+ actor,
20789
+ details: {
20790
+ artifactLinkId: id,
20791
+ path
20792
+ },
20793
+ createdAt: now
20794
+ });
20795
+ }, "IMMEDIATE");
20796
+ return (await this.listArtifacts(projectId, workItemId)).find((link) => link.id === id);
20515
20797
  };
20516
- const issues = [];
20798
+ unlinkArtifact = async (params) => {
20799
+ const { actor, artifactLinkId, projectId, workItemId } = params;
20800
+ const link = this.db().prepare(`SELECT * FROM project_work_artifact_links
20801
+ WHERE project_id = ? AND work_item_id = ? AND id = ? LIMIT 1`).get(projectId, workItemId, artifactLinkId);
20802
+ if (!link) return false;
20803
+ const now = (/* @__PURE__ */ new Date()).toISOString();
20804
+ runSqliteTransaction(this.db(), () => {
20805
+ this.db().prepare("DELETE FROM project_work_artifact_links WHERE project_id = ? AND work_item_id = ? AND id = ?").run(projectId, workItemId, artifactLinkId);
20806
+ this.insertActivity({
20807
+ projectId,
20808
+ workItemId,
20809
+ type: "artifact-unlinked",
20810
+ actor,
20811
+ details: {
20812
+ artifactLinkId: link.id,
20813
+ path: link.path
20814
+ },
20815
+ createdAt: now
20816
+ });
20817
+ }, "IMMEDIATE");
20818
+ return true;
20819
+ };
20820
+ insertActivity = (activity) => {
20821
+ this.db().prepare(`INSERT INTO project_work_activities
20822
+ (id, project_id, work_item_id, type, actor_json, details_json, created_at)
20823
+ VALUES (?, ?, ?, ?, ?, ?, ?)`).run(randomUUID(), activity.projectId, activity.workItemId, activity.type, JSON.stringify(activity.actor), JSON.stringify(activity.details), activity.createdAt);
20824
+ };
20825
+ };
20826
+ function toActivity(row) {
20517
20827
  return {
20518
- value: value.flatMap((entry, index) => {
20519
- if (!isRecord$9(entry)) {
20520
- issues.push({
20521
- code: "PROJECT_CONFIG_CONTEXT_INVALID",
20522
- message: `project.context[${index}] must be an object.`
20523
- });
20524
- return [];
20525
- }
20526
- issues.push(...collectUnknownKeyIssues(entry, [
20527
- "id",
20528
- "role",
20529
- "source"
20530
- ], `project.context[${index}]`));
20531
- const id = readString$7(entry.id);
20532
- const role = readString$7(entry.role);
20533
- const source = readString$7(entry.source);
20534
- if (!id || !role || !source) {
20535
- issues.push({
20536
- code: "PROJECT_CONFIG_CONTEXT_INVALID",
20537
- message: `project.context[${index}] requires id, role and source.`
20538
- });
20539
- return [];
20540
- }
20541
- return [{
20542
- id,
20543
- role,
20544
- source
20545
- }];
20546
- }),
20547
- issues
20828
+ id: row.id,
20829
+ projectId: row.project_id,
20830
+ workItemId: row.work_item_id,
20831
+ type: row.type,
20832
+ actor: JSON.parse(row.actor_json),
20833
+ details: JSON.parse(row.details_json),
20834
+ createdAt: row.created_at
20548
20835
  };
20549
20836
  }
20550
- function parseWorkflows(value) {
20551
- if (value === void 0) return {
20552
- value: [],
20553
- issues: []
20837
+ function toArtifactLink(row) {
20838
+ return {
20839
+ id: row.id,
20840
+ projectId: row.project_id,
20841
+ workItemId: row.work_item_id,
20842
+ path: row.path,
20843
+ label: row.label,
20844
+ createdAt: row.created_at
20554
20845
  };
20555
- if (!Array.isArray(value)) return {
20846
+ }
20847
+ //#endregion
20848
+ //#region src/features/projects/stores/project-work-state.store.ts
20849
+ const DEFAULT_PROJECT_WORK_STATES = [
20850
+ {
20851
+ name: "Backlog",
20852
+ category: "backlog",
20853
+ position: 0,
20854
+ isDefault: false
20855
+ },
20856
+ {
20857
+ name: "Planned",
20858
+ category: "unstarted",
20859
+ position: 1,
20860
+ isDefault: true
20861
+ },
20862
+ {
20863
+ name: "In Progress",
20864
+ category: "started",
20865
+ position: 2,
20866
+ isDefault: false
20867
+ },
20868
+ {
20869
+ name: "In Review",
20870
+ category: "started",
20871
+ position: 3,
20872
+ isDefault: false
20873
+ },
20874
+ {
20875
+ name: "Awaiting Acceptance",
20876
+ category: "started",
20877
+ position: 4,
20878
+ isDefault: false
20879
+ },
20880
+ {
20881
+ name: "Completed",
20882
+ category: "completed",
20883
+ position: 5,
20884
+ isDefault: false
20885
+ },
20886
+ {
20887
+ name: "Canceled",
20888
+ category: "canceled",
20889
+ position: 6,
20890
+ isDefault: false
20891
+ }
20892
+ ];
20893
+ var ProjectWorkStateStore = class {
20894
+ constructor(db, insertActivity) {
20895
+ this.db = db;
20896
+ this.insertActivity = insertActivity;
20897
+ }
20898
+ ensureProject = async (projectId) => {
20899
+ if (this.db().prepare("SELECT COUNT(*) AS total FROM project_work_states WHERE project_id = ?").get(projectId).total > 0) return;
20900
+ const now = (/* @__PURE__ */ new Date()).toISOString();
20901
+ runSqliteTransaction(this.db(), () => {
20902
+ if (this.db().prepare("SELECT COUNT(*) AS total FROM project_work_states WHERE project_id = ?").get(projectId).total > 0) return;
20903
+ const insert = this.db().prepare(`INSERT INTO project_work_states
20904
+ (id, project_id, name, category, position, is_default, created_at, updated_at)
20905
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`);
20906
+ for (const state of DEFAULT_PROJECT_WORK_STATES) insert.run(randomUUID(), projectId, state.name, state.category, state.position, state.isDefault ? 1 : 0, now, now);
20907
+ }, "IMMEDIATE");
20908
+ };
20909
+ listStates = async (projectId) => this.db().prepare("SELECT * FROM project_work_states WHERE project_id = ? ORDER BY position, created_at").all(projectId).map(toState);
20910
+ getState = async (projectId, stateId) => {
20911
+ const row = this.db().prepare("SELECT * FROM project_work_states WHERE project_id = ? AND id = ? LIMIT 1").get(projectId, stateId);
20912
+ return row ? toState(row) : null;
20913
+ };
20914
+ createState = async (projectId, input) => {
20915
+ const now = (/* @__PURE__ */ new Date()).toISOString();
20916
+ const id = randomUUID();
20917
+ const position = input.position ?? this.nextStatePosition(projectId);
20918
+ runSqliteTransaction(this.db(), () => {
20919
+ if (input.isDefault) this.clearDefaultState(projectId);
20920
+ this.db().prepare(`INSERT INTO project_work_states
20921
+ (id, project_id, name, category, position, is_default, created_at, updated_at)
20922
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`).run(id, projectId, input.name, input.category, position, input.isDefault ? 1 : 0, now, now);
20923
+ }, "IMMEDIATE");
20924
+ return await this.getState(projectId, id);
20925
+ };
20926
+ updateState = async (projectId, stateId, input) => {
20927
+ const current = await this.getState(projectId, stateId);
20928
+ if (!current) return null;
20929
+ const next = {
20930
+ ...current,
20931
+ ...input,
20932
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
20933
+ };
20934
+ runSqliteTransaction(this.db(), () => {
20935
+ if (next.isDefault) this.clearDefaultState(projectId);
20936
+ this.db().prepare(`UPDATE project_work_states
20937
+ SET name = ?, category = ?, position = ?, is_default = ?, updated_at = ?
20938
+ WHERE project_id = ? AND id = ?`).run(next.name, next.category, next.position, next.isDefault ? 1 : 0, next.updatedAt, projectId, stateId);
20939
+ }, "IMMEDIATE");
20940
+ return await this.getState(projectId, stateId);
20941
+ };
20942
+ deleteState = async (projectId, stateId, migrateToStateId, actor) => {
20943
+ const current = await this.getState(projectId, stateId);
20944
+ if (!current) return false;
20945
+ runSqliteTransaction(this.db(), () => {
20946
+ if (this.db().prepare("SELECT COUNT(*) AS total FROM project_work_items WHERE project_id = ? AND state_id = ?").get(projectId, stateId).total > 0 && !migrateToStateId) throw new Error("PROJECT_WORK_STATE_IN_USE");
20947
+ if (migrateToStateId) this.migrateItems(projectId, stateId, migrateToStateId, actor);
20948
+ this.db().prepare("DELETE FROM project_work_states WHERE project_id = ? AND id = ?").run(projectId, stateId);
20949
+ if (current.isDefault) this.assignReplacementDefault(projectId, migrateToStateId);
20950
+ }, "IMMEDIATE");
20951
+ return true;
20952
+ };
20953
+ migrateItems = (projectId, stateId, migrateToStateId, actor) => {
20954
+ const affected = this.db().prepare("SELECT id FROM project_work_items WHERE project_id = ? AND state_id = ?").all(projectId, stateId);
20955
+ const now = (/* @__PURE__ */ new Date()).toISOString();
20956
+ this.db().prepare(`UPDATE project_work_items
20957
+ SET state_id = ?, version = version + 1, updated_at = ?
20958
+ WHERE project_id = ? AND state_id = ?`).run(migrateToStateId, now, projectId, stateId);
20959
+ for (const item of affected) this.insertActivity({
20960
+ projectId,
20961
+ workItemId: item.id,
20962
+ type: "state-changed",
20963
+ actor,
20964
+ details: {
20965
+ fromStateId: stateId,
20966
+ toStateId: migrateToStateId,
20967
+ reason: "state-deleted"
20968
+ },
20969
+ createdAt: now
20970
+ });
20971
+ };
20972
+ assignReplacementDefault = (projectId, migrateToStateId) => {
20973
+ const replacement = migrateToStateId ?? this.db().prepare("SELECT id FROM project_work_states WHERE project_id = ? ORDER BY position LIMIT 1").get(projectId)?.id;
20974
+ if (replacement) this.db().prepare("UPDATE project_work_states SET is_default = 1 WHERE project_id = ? AND id = ?").run(projectId, replacement);
20975
+ };
20976
+ nextStatePosition = (projectId) => {
20977
+ return this.db().prepare("SELECT COALESCE(MAX(position), -1) + 1 AS position FROM project_work_states WHERE project_id = ?").get(projectId).position;
20978
+ };
20979
+ clearDefaultState = (projectId) => {
20980
+ this.db().prepare("UPDATE project_work_states SET is_default = 0 WHERE project_id = ?").run(projectId);
20981
+ };
20982
+ };
20983
+ function toState(row) {
20984
+ return {
20985
+ id: row.id,
20986
+ projectId: row.project_id,
20987
+ name: row.name,
20988
+ category: row.category,
20989
+ position: row.position,
20990
+ isDefault: Boolean(row.is_default),
20991
+ createdAt: row.created_at,
20992
+ updatedAt: row.updated_at
20993
+ };
20994
+ }
20995
+ //#endregion
20996
+ //#region src/features/projects/stores/project-work.store.ts
20997
+ var ProjectWorkStore = class {
20998
+ database = null;
20999
+ readyPromise = null;
21000
+ activities = new ProjectWorkActivityStore(() => this.db());
21001
+ states = new ProjectWorkStateStore(() => this.db(), this.activities.insertActivity);
21002
+ constructor(databasePath) {
21003
+ this.databasePath = databasePath;
21004
+ }
21005
+ initialize = async () => await this.ensureReady();
21006
+ close = () => {
21007
+ this.database?.close();
21008
+ this.database = null;
21009
+ this.readyPromise = null;
21010
+ };
21011
+ ensureProject = async (projectId) => {
21012
+ await this.ensureReady();
21013
+ await this.states.ensureProject(projectId);
21014
+ };
21015
+ listStates = async (projectId) => {
21016
+ await this.ensureReady();
21017
+ return await this.states.listStates(projectId);
21018
+ };
21019
+ getState = async (projectId, stateId) => {
21020
+ await this.ensureReady();
21021
+ return await this.states.getState(projectId, stateId);
21022
+ };
21023
+ createState = async (projectId, input) => {
21024
+ await this.ensureReady();
21025
+ return await this.states.createState(projectId, input);
21026
+ };
21027
+ updateState = async (projectId, stateId, input) => {
21028
+ await this.ensureReady();
21029
+ return await this.states.updateState(projectId, stateId, input);
21030
+ };
21031
+ deleteState = async (projectId, stateId, migrateToStateId, actor) => {
21032
+ await this.ensureReady();
21033
+ return await this.states.deleteState(projectId, stateId, migrateToStateId, actor);
21034
+ };
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
+ getItem = async (projectId, workItemId) => {
21042
+ await this.ensureReady();
21043
+ const row = this.db().prepare("SELECT * FROM project_work_items WHERE project_id = ? AND id = ? LIMIT 1").get(projectId, workItemId);
21044
+ return row ? toItem(row) : null;
21045
+ };
21046
+ createItem = async (params) => {
21047
+ await this.ensureReady();
21048
+ const now = (/* @__PURE__ */ new Date()).toISOString();
21049
+ const id = randomUUID();
21050
+ runSqliteTransaction(this.db(), () => {
21051
+ this.db().prepare(`INSERT INTO project_work_items
21052
+ (id, project_id, title, description, state_id, attention, version, created_at, updated_at, deleted_at)
21053
+ VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?, NULL)`).run(id, params.projectId, params.input.title, params.input.description ?? "", params.stateId, params.input.attention ?? "none", now, now);
21054
+ this.activities.insertActivity({
21055
+ projectId: params.projectId,
21056
+ workItemId: id,
21057
+ type: "created",
21058
+ actor: params.actor,
21059
+ details: { stateId: params.stateId },
21060
+ createdAt: now
21061
+ });
21062
+ }, "IMMEDIATE");
21063
+ return await this.getItem(params.projectId, id);
21064
+ };
21065
+ updateItem = async (params) => {
21066
+ await this.ensureReady();
21067
+ const { actor, input, projectId, workItemId } = params;
21068
+ const current = await this.getItem(projectId, workItemId);
21069
+ if (!current) return null;
21070
+ if (input.expectedVersion !== void 0 && input.expectedVersion !== current.version) throw new Error("PROJECT_WORK_VERSION_CONFLICT");
21071
+ const now = (/* @__PURE__ */ new Date()).toISOString();
21072
+ const next = {
21073
+ ...current,
21074
+ ...input
21075
+ };
21076
+ runSqliteTransaction(this.db(), () => {
21077
+ this.db().prepare(`UPDATE project_work_items SET title = ?, description = ?, state_id = ?, attention = ?,
21078
+ version = version + 1, updated_at = ? WHERE project_id = ? AND id = ?`).run(next.title, next.description, next.stateId, next.attention, now, projectId, workItemId);
21079
+ const stateChanged = next.stateId !== current.stateId;
21080
+ this.activities.insertActivity({
21081
+ projectId,
21082
+ workItemId,
21083
+ type: stateChanged ? "state-changed" : "updated",
21084
+ actor,
21085
+ details: stateChanged ? {
21086
+ fromStateId: current.stateId,
21087
+ toStateId: next.stateId
21088
+ } : changedItemFields(current, next),
21089
+ createdAt: now
21090
+ });
21091
+ }, "IMMEDIATE");
21092
+ return await this.getItem(projectId, workItemId);
21093
+ };
21094
+ setDeleted = async (params) => {
21095
+ await this.ensureReady();
21096
+ const { actor, deleted, projectId, workItemId } = params;
21097
+ if (!await this.getItem(projectId, workItemId)) return null;
21098
+ const now = (/* @__PURE__ */ new Date()).toISOString();
21099
+ runSqliteTransaction(this.db(), () => {
21100
+ this.db().prepare(`UPDATE project_work_items SET deleted_at = ?, version = version + 1, updated_at = ?
21101
+ WHERE project_id = ? AND id = ?`).run(deleted ? now : null, now, projectId, workItemId);
21102
+ this.activities.insertActivity({
21103
+ projectId,
21104
+ workItemId,
21105
+ type: deleted ? "deleted" : "restored",
21106
+ actor,
21107
+ details: {},
21108
+ createdAt: now
21109
+ });
21110
+ }, "IMMEDIATE");
21111
+ return await this.getItem(projectId, workItemId);
21112
+ };
21113
+ listActivities = async (projectId, workItemId, options) => {
21114
+ await this.ensureReady();
21115
+ return await this.activities.listActivities(projectId, workItemId, options);
21116
+ };
21117
+ listArtifacts = async (projectId, workItemId) => {
21118
+ await this.ensureReady();
21119
+ return await this.activities.listArtifacts(projectId, workItemId);
21120
+ };
21121
+ linkArtifact = async (params) => {
21122
+ await this.ensureReady();
21123
+ return await this.activities.linkArtifact(params);
21124
+ };
21125
+ unlinkArtifact = async (params) => {
21126
+ await this.ensureReady();
21127
+ return await this.activities.unlinkArtifact(params);
21128
+ };
21129
+ ensureReady = async () => {
21130
+ this.readyPromise ??= this.open();
21131
+ await this.readyPromise;
21132
+ };
21133
+ open = async () => {
21134
+ await mkdir(dirname(this.databasePath), { recursive: true });
21135
+ this.database = await openSqliteDatabase(this.databasePath);
21136
+ this.database.exec(`
21137
+ PRAGMA busy_timeout = 10000;
21138
+ PRAGMA journal_mode = WAL;
21139
+ PRAGMA synchronous = NORMAL;
21140
+ PRAGMA foreign_keys = ON;
21141
+ CREATE TABLE IF NOT EXISTS project_work_states (
21142
+ id TEXT PRIMARY KEY,
21143
+ project_id TEXT NOT NULL,
21144
+ name TEXT NOT NULL,
21145
+ category TEXT NOT NULL,
21146
+ position INTEGER NOT NULL,
21147
+ is_default INTEGER NOT NULL DEFAULT 0,
21148
+ created_at TEXT NOT NULL,
21149
+ updated_at TEXT NOT NULL,
21150
+ UNIQUE(project_id, name)
21151
+ );
21152
+ CREATE TABLE IF NOT EXISTS project_work_items (
21153
+ id TEXT PRIMARY KEY,
21154
+ project_id TEXT NOT NULL,
21155
+ title TEXT NOT NULL,
21156
+ description TEXT NOT NULL DEFAULT '',
21157
+ state_id TEXT NOT NULL,
21158
+ attention TEXT NOT NULL DEFAULT 'none',
21159
+ version INTEGER NOT NULL DEFAULT 1,
21160
+ created_at TEXT NOT NULL,
21161
+ updated_at TEXT NOT NULL,
21162
+ deleted_at TEXT,
21163
+ FOREIGN KEY(state_id) REFERENCES project_work_states(id)
21164
+ );
21165
+ CREATE TABLE IF NOT EXISTS project_work_activities (
21166
+ id TEXT PRIMARY KEY,
21167
+ project_id TEXT NOT NULL,
21168
+ work_item_id TEXT NOT NULL,
21169
+ type TEXT NOT NULL,
21170
+ actor_json TEXT NOT NULL,
21171
+ details_json TEXT NOT NULL,
21172
+ created_at TEXT NOT NULL,
21173
+ FOREIGN KEY(work_item_id) REFERENCES project_work_items(id)
21174
+ );
21175
+ CREATE TABLE IF NOT EXISTS project_work_artifact_links (
21176
+ id TEXT PRIMARY KEY,
21177
+ project_id TEXT NOT NULL,
21178
+ work_item_id TEXT NOT NULL,
21179
+ path TEXT NOT NULL,
21180
+ label TEXT,
21181
+ created_at TEXT NOT NULL,
21182
+ UNIQUE(project_id, work_item_id, path),
21183
+ FOREIGN KEY(work_item_id) REFERENCES project_work_items(id)
21184
+ );
21185
+ CREATE INDEX IF NOT EXISTS project_work_items_list_idx
21186
+ ON project_work_items(project_id, deleted_at, updated_at);
21187
+ CREATE INDEX IF NOT EXISTS project_work_activity_timeline_idx
21188
+ ON project_work_activities(project_id, work_item_id, created_at);
21189
+ `);
21190
+ };
21191
+ db = () => {
21192
+ if (!this.database) throw new Error("Project work database is not initialized.");
21193
+ return this.database;
21194
+ };
21195
+ };
21196
+ function toItem(row) {
21197
+ return {
21198
+ id: row.id,
21199
+ projectId: row.project_id,
21200
+ title: row.title,
21201
+ description: row.description,
21202
+ stateId: row.state_id,
21203
+ attention: row.attention,
21204
+ version: row.version,
21205
+ createdAt: row.created_at,
21206
+ updatedAt: row.updated_at,
21207
+ deletedAt: row.deleted_at
21208
+ };
21209
+ }
21210
+ function changedItemFields(current, next) {
21211
+ const fields = {};
21212
+ if (current.title !== next.title) fields.title = {
21213
+ from: current.title,
21214
+ to: next.title
21215
+ };
21216
+ if (current.description !== next.description) fields.descriptionChanged = true;
21217
+ if (current.attention !== next.attention) fields.attention = {
21218
+ from: current.attention,
21219
+ to: next.attention
21220
+ };
21221
+ return fields;
21222
+ }
21223
+ //#endregion
21224
+ //#region src/features/projects/types/project-work-error.types.ts
21225
+ var ProjectWorkError = class extends Error {
21226
+ constructor(code, message) {
21227
+ super(message);
21228
+ this.code = code;
21229
+ this.name = "ProjectWorkError";
21230
+ }
21231
+ };
21232
+ function isProjectWorkError(error) {
21233
+ return error instanceof ProjectWorkError;
21234
+ }
21235
+ //#endregion
21236
+ //#region src/features/projects/types/project-work.types.ts
21237
+ const PROJECT_WORK_STATE_CATEGORIES = [
21238
+ "backlog",
21239
+ "unstarted",
21240
+ "started",
21241
+ "completed",
21242
+ "canceled"
21243
+ ];
21244
+ const PROJECT_WORK_ATTENTION_VALUES = [
21245
+ "none",
21246
+ "blocked",
21247
+ "awaiting-user"
21248
+ ];
21249
+ //#endregion
21250
+ //#region src/features/projects/managers/project-work.manager.ts
21251
+ var ProjectWorkManager = class {
21252
+ store;
21253
+ constructor(options) {
21254
+ this.options = options;
21255
+ this.store = new ProjectWorkStore(options.databasePath);
21256
+ }
21257
+ initialize = async () => {
21258
+ await this.store.initialize();
21259
+ for (const project of await this.options.projectManager.listProjects()) await this.store.ensureProject(project.id);
21260
+ };
21261
+ dispose = () => this.store.close();
21262
+ ensureProject = async (projectId) => {
21263
+ await this.requireProject(projectId);
21264
+ await this.store.ensureProject(projectId);
21265
+ };
21266
+ list = async (projectId, includeDeleted = false) => {
21267
+ await this.ensureProject(projectId);
21268
+ const [states, items] = await Promise.all([this.store.listStates(projectId), this.store.listItems(projectId, includeDeleted)]);
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
+ };
21291
+ get = async (projectId, workItemId) => {
21292
+ await this.ensureProject(projectId);
21293
+ const item = await this.store.getItem(projectId, workItemId);
21294
+ if (!item) throw new ProjectWorkError("PROJECT_WORK_ITEM_NOT_FOUND", "work item was not found");
21295
+ const state = await this.store.getState(projectId, item.stateId);
21296
+ if (!state) throw new ProjectWorkError("PROJECT_WORK_STATE_NOT_FOUND", "work item state was not found");
21297
+ return {
21298
+ ...item,
21299
+ state,
21300
+ artifacts: await this.store.listArtifacts(projectId, workItemId)
21301
+ };
21302
+ };
21303
+ create = async (projectId, input, actor) => {
21304
+ await this.ensureProject(projectId);
21305
+ const title = this.requireName(input.title, "work item title");
21306
+ const attention = input.attention ?? "none";
21307
+ this.assertAttention(attention);
21308
+ const states = await this.store.listStates(projectId);
21309
+ const state = input.stateId ? states.find((entry) => entry.id === input.stateId) : states.find((entry) => entry.isDefault);
21310
+ if (!state) throw new ProjectWorkError("PROJECT_WORK_STATE_NOT_FOUND", "work item state was not found");
21311
+ const item = await this.store.createItem({
21312
+ projectId,
21313
+ actor: this.normalizeActor(actor),
21314
+ stateId: state.id,
21315
+ input: {
21316
+ title,
21317
+ description: input.description?.trim() ?? "",
21318
+ attention
21319
+ }
21320
+ });
21321
+ await this.emit(projectId, "created", item.id);
21322
+ return await this.get(projectId, item.id);
21323
+ };
21324
+ update = async (projectId, workItemId, input, actor) => {
21325
+ await this.ensureProject(projectId);
21326
+ const current = await this.get(projectId, workItemId);
21327
+ if (input.expectedVersion !== void 0 && input.expectedVersion !== current.version) throw new ProjectWorkError("PROJECT_WORK_VERSION_CONFLICT", "work item changed since it was loaded");
21328
+ const normalizedInput = {
21329
+ ...input,
21330
+ ...input.title !== void 0 ? { title: this.requireName(input.title, "work item title") } : {},
21331
+ ...input.description !== void 0 ? { description: input.description.trim() } : {}
21332
+ };
21333
+ if (normalizedInput.attention !== void 0) this.assertAttention(normalizedInput.attention);
21334
+ if (normalizedInput.stateId !== void 0 && !await this.store.getState(projectId, normalizedInput.stateId)) throw new ProjectWorkError("PROJECT_WORK_STATE_NOT_FOUND", "work item state was not found");
21335
+ if ((normalizedInput.title === void 0 || normalizedInput.title === current.title) && (normalizedInput.description === void 0 || normalizedInput.description === current.description) && (normalizedInput.stateId === void 0 || normalizedInput.stateId === current.stateId) && (normalizedInput.attention === void 0 || normalizedInput.attention === current.attention)) return current;
21336
+ let item;
21337
+ try {
21338
+ item = await this.store.updateItem({
21339
+ projectId,
21340
+ workItemId,
21341
+ input: normalizedInput,
21342
+ actor: this.normalizeActor(actor)
21343
+ });
21344
+ } catch (error) {
21345
+ if (error instanceof Error && error.message === "PROJECT_WORK_VERSION_CONFLICT") throw new ProjectWorkError("PROJECT_WORK_VERSION_CONFLICT", "work item changed since it was loaded");
21346
+ throw error;
21347
+ }
21348
+ if (!item) throw new ProjectWorkError("PROJECT_WORK_ITEM_NOT_FOUND", "work item was not found");
21349
+ await this.emit(projectId, "updated", workItemId);
21350
+ return await this.get(projectId, workItemId);
21351
+ };
21352
+ delete = async (projectId, workItemId, actor) => {
21353
+ return await this.setDeleted(projectId, workItemId, true, actor);
21354
+ };
21355
+ restore = async (projectId, workItemId, actor) => {
21356
+ return await this.setDeleted(projectId, workItemId, false, actor);
21357
+ };
21358
+ listActivities = async (projectId, workItemId, options = {}) => {
21359
+ await this.get(projectId, workItemId);
21360
+ if (options.cursor && !/^\d+$/.test(options.cursor)) throw new ProjectWorkError("PROJECT_WORK_VALIDATION_FAILED", "work item activity cursor is invalid");
21361
+ return await this.store.listActivities(projectId, workItemId, {
21362
+ ...options.cursor ? { cursor: options.cursor } : {},
21363
+ limit: Math.min(100, Math.max(1, options.limit ?? 50))
21364
+ });
21365
+ };
21366
+ listStates = async (projectId) => {
21367
+ await this.ensureProject(projectId);
21368
+ return await this.store.listStates(projectId);
21369
+ };
21370
+ createState = async (projectId, input) => {
21371
+ await this.ensureProject(projectId);
21372
+ const normalized = {
21373
+ ...input,
21374
+ name: this.requireName(input.name, "state name")
21375
+ };
21376
+ this.assertCategory(normalized.category);
21377
+ if ((await this.store.listStates(projectId)).some((state) => state.name.toLocaleLowerCase() === normalized.name.toLocaleLowerCase())) throw new ProjectWorkError("PROJECT_WORK_STATE_INVALID", "state name is already in use");
21378
+ const state = await this.store.createState(projectId, normalized);
21379
+ await this.emit(projectId, "state-config");
21380
+ return state;
21381
+ };
21382
+ updateState = async (projectId, stateId, input) => {
21383
+ await this.ensureProject(projectId);
21384
+ const normalizedInput = {
21385
+ ...input,
21386
+ ...input.name !== void 0 ? { name: this.requireName(input.name, "state name") } : {}
21387
+ };
21388
+ if (normalizedInput.category !== void 0) this.assertCategory(normalizedInput.category);
21389
+ const current = await this.store.getState(projectId, stateId);
21390
+ if (!current) throw new ProjectWorkError("PROJECT_WORK_STATE_NOT_FOUND", "work item state was not found");
21391
+ if (current.isDefault && normalizedInput.isDefault === false) throw new ProjectWorkError("PROJECT_WORK_STATE_INVALID", "choose another default state before clearing this one");
21392
+ if (normalizedInput.name && (await this.store.listStates(projectId)).some((state) => state.id !== stateId && state.name.toLocaleLowerCase() === normalizedInput.name.toLocaleLowerCase())) throw new ProjectWorkError("PROJECT_WORK_STATE_INVALID", "state name is already in use");
21393
+ const state = await this.store.updateState(projectId, stateId, normalizedInput);
21394
+ if (!state) throw new ProjectWorkError("PROJECT_WORK_STATE_NOT_FOUND", "work item state was not found");
21395
+ await this.emit(projectId, "state-config");
21396
+ return state;
21397
+ };
21398
+ deleteState = async (projectId, stateId, migrateToStateId, actor) => {
21399
+ await this.ensureProject(projectId);
21400
+ const states = await this.store.listStates(projectId);
21401
+ if (states.length <= 1) throw new ProjectWorkError("PROJECT_WORK_STATE_INVALID", "a project must keep at least one state");
21402
+ if (migrateToStateId === stateId) throw new ProjectWorkError("PROJECT_WORK_STATE_INVALID", "migration target must be a different state");
21403
+ if (migrateToStateId && !states.some((state) => state.id === migrateToStateId)) throw new ProjectWorkError("PROJECT_WORK_STATE_NOT_FOUND", "migration target state was not found");
21404
+ try {
21405
+ if (!await this.store.deleteState(projectId, stateId, migrateToStateId, this.normalizeActor(actor))) throw new ProjectWorkError("PROJECT_WORK_STATE_NOT_FOUND", "work item state was not found");
21406
+ } catch (error) {
21407
+ if (error instanceof Error && error.message === "PROJECT_WORK_STATE_IN_USE") throw new ProjectWorkError("PROJECT_WORK_STATE_IN_USE", "state is used by work items; choose a migration target");
21408
+ throw error;
21409
+ }
21410
+ await this.emit(projectId, "state-config");
21411
+ };
21412
+ linkArtifact = async (params) => {
21413
+ const { actor, label, path, projectId, workItemId } = params;
21414
+ const project = await this.requireProject(projectId);
21415
+ await this.get(projectId, workItemId);
21416
+ const artifactPath = await this.normalizeArtifactPath(project.rootPath, path);
21417
+ const existing = (await this.store.listArtifacts(projectId, workItemId)).find((link) => link.path === artifactPath);
21418
+ if (existing) return existing;
21419
+ const link = await this.store.linkArtifact({
21420
+ projectId,
21421
+ workItemId,
21422
+ path: artifactPath,
21423
+ label: label?.trim() || void 0,
21424
+ actor: this.normalizeActor(actor)
21425
+ });
21426
+ await this.emit(projectId, "artifact", workItemId);
21427
+ return link;
21428
+ };
21429
+ unlinkArtifact = async (params) => {
21430
+ const { actor, artifactLinkId, projectId, workItemId } = params;
21431
+ await this.get(projectId, workItemId);
21432
+ if (!await this.store.unlinkArtifact({
21433
+ projectId,
21434
+ workItemId,
21435
+ artifactLinkId,
21436
+ actor: this.normalizeActor(actor)
21437
+ })) throw new ProjectWorkError("PROJECT_WORK_ARTIFACT_NOT_FOUND", "artifact link was not found");
21438
+ await this.emit(projectId, "artifact", workItemId);
21439
+ };
21440
+ setDeleted = async (projectId, workItemId, deleted, actor) => {
21441
+ await this.ensureProject(projectId);
21442
+ const current = await this.get(projectId, workItemId);
21443
+ if (current.deletedAt !== null === deleted) return current;
21444
+ if (!await this.store.setDeleted({
21445
+ projectId,
21446
+ workItemId,
21447
+ deleted,
21448
+ actor: this.normalizeActor(actor)
21449
+ })) throw new ProjectWorkError("PROJECT_WORK_ITEM_NOT_FOUND", "work item was not found");
21450
+ await this.emit(projectId, deleted ? "deleted" : "restored", workItemId);
21451
+ return await this.get(projectId, workItemId);
21452
+ };
21453
+ requireProject = async (projectId) => {
21454
+ const normalized = projectId.trim();
21455
+ const project = normalized ? await this.options.projectManager.getProjectById(normalized) : null;
21456
+ if (!project) throw new ProjectWorkError("PROJECT_NOT_FOUND", "project was not found");
21457
+ return project;
21458
+ };
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
+ requireName = (value, label) => {
21465
+ const normalized = typeof value === "string" ? value.trim() : "";
21466
+ if (!normalized) throw new ProjectWorkError("PROJECT_WORK_VALIDATION_FAILED", `${label} is required`);
21467
+ if (normalized.length > 200) throw new ProjectWorkError("PROJECT_WORK_VALIDATION_FAILED", `${label} is too long`);
21468
+ return normalized;
21469
+ };
21470
+ assertAttention = (value) => {
21471
+ if (!PROJECT_WORK_ATTENTION_VALUES.some((entry) => entry === value)) throw new ProjectWorkError("PROJECT_WORK_VALIDATION_FAILED", "work item attention is invalid");
21472
+ };
21473
+ assertCategory = (value) => {
21474
+ if (!PROJECT_WORK_STATE_CATEGORIES.some((entry) => entry === value)) throw new ProjectWorkError("PROJECT_WORK_VALIDATION_FAILED", "work item state category is invalid");
21475
+ };
21476
+ normalizeActor = (actor) => ({
21477
+ kind: actor.kind,
21478
+ ...actor.id?.trim() ? { id: actor.id.trim() } : {},
21479
+ ...actor.sessionId?.trim() ? { sessionId: actor.sessionId.trim() } : {}
21480
+ });
21481
+ normalizeArtifactPath = async (rootPath, inputPath) => {
21482
+ const requested = inputPath.trim();
21483
+ if (!requested) throw new ProjectWorkError("PROJECT_WORK_ARTIFACT_INVALID", "artifact path is required");
21484
+ const canonicalRoot = await realpath(rootPath);
21485
+ const candidate = await realpath(isAbsolute(requested) ? requested : resolve(canonicalRoot, requested)).catch(() => null);
21486
+ if (!candidate) throw new ProjectWorkError("PROJECT_WORK_ARTIFACT_NOT_FOUND", "artifact file does not exist");
21487
+ const relativePath = relative(canonicalRoot, candidate);
21488
+ if (!relativePath || relativePath === ".." || relativePath.startsWith(`..${sep}`) || isAbsolute(relativePath)) throw new ProjectWorkError("PROJECT_WORK_ARTIFACT_INVALID", "artifact must be a file inside the project root");
21489
+ if (!(await stat(candidate)).isFile()) throw new ProjectWorkError("PROJECT_WORK_ARTIFACT_INVALID", "artifact must be a file");
21490
+ return relativePath.split(sep).join("/");
21491
+ };
21492
+ emit = async (projectId, change, workItemId) => {
21493
+ await this.options.eventBus.emit(eventKeys$1.projectWorkChanged, {
21494
+ projectId,
21495
+ change,
21496
+ ...workItemId ? { workItemId } : {}
21497
+ });
21498
+ };
21499
+ };
21500
+ //#endregion
21501
+ //#region src/features/projects/types/project-observation.types.ts
21502
+ const PROJECT_OBSERVATION_PROTOCOL = "nextclaw.project/v1";
21503
+ var ProjectObservationError = class extends Error {
21504
+ constructor(code, message) {
21505
+ super(message);
21506
+ this.code = code;
21507
+ this.name = "ProjectObservationError";
21508
+ }
21509
+ };
21510
+ function isProjectObservationError(error) {
21511
+ return error instanceof ProjectObservationError;
21512
+ }
21513
+ //#endregion
21514
+ //#region src/features/projects/utils/project-observation-config.utils.ts
21515
+ const isRecord$8 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
21516
+ const readString$7 = (value) => {
21517
+ if (typeof value !== "string") return null;
21518
+ return value.trim() || null;
21519
+ };
21520
+ const collectUnknownKeyIssues = (value, allowed, owner) => Object.keys(value).flatMap((key) => allowed.includes(key) ? [] : [{
21521
+ code: "PROJECT_CONFIG_UNKNOWN_FIELD",
21522
+ message: `${owner} contains unknown field '${key}'.`
21523
+ }]);
21524
+ function parseContext(value) {
21525
+ if (value === void 0) return {
21526
+ value: [],
21527
+ issues: []
21528
+ };
21529
+ if (!Array.isArray(value)) return {
21530
+ value: [],
21531
+ issues: [{
21532
+ code: "PROJECT_CONFIG_CONTEXT_INVALID",
21533
+ message: "project.context must be an array."
21534
+ }]
21535
+ };
21536
+ const issues = [];
21537
+ return {
21538
+ value: value.flatMap((entry, index) => {
21539
+ if (!isRecord$8(entry)) {
21540
+ issues.push({
21541
+ code: "PROJECT_CONFIG_CONTEXT_INVALID",
21542
+ message: `project.context[${index}] must be an object.`
21543
+ });
21544
+ return [];
21545
+ }
21546
+ issues.push(...collectUnknownKeyIssues(entry, [
21547
+ "id",
21548
+ "role",
21549
+ "source"
21550
+ ], `project.context[${index}]`));
21551
+ const id = readString$7(entry.id);
21552
+ const role = readString$7(entry.role);
21553
+ const source = readString$7(entry.source);
21554
+ if (!id || !role || !source) {
21555
+ issues.push({
21556
+ code: "PROJECT_CONFIG_CONTEXT_INVALID",
21557
+ message: `project.context[${index}] requires id, role and source.`
21558
+ });
21559
+ return [];
21560
+ }
21561
+ return [{
21562
+ id,
21563
+ role,
21564
+ source
21565
+ }];
21566
+ }),
21567
+ issues
21568
+ };
21569
+ }
21570
+ function parseWorkflows(value) {
21571
+ if (value === void 0) return {
21572
+ value: [],
21573
+ issues: []
21574
+ };
21575
+ if (!Array.isArray(value)) return {
20556
21576
  value: [],
20557
21577
  issues: [{
20558
21578
  code: "PROJECT_CONFIG_WORKFLOWS_INVALID",
@@ -20562,7 +21582,7 @@ function parseWorkflows(value) {
20562
21582
  const issues = [];
20563
21583
  return {
20564
21584
  value: value.flatMap((entry, workflowIndex) => {
20565
- if (!isRecord$9(entry) || !Array.isArray(entry.stages)) {
21585
+ if (!isRecord$8(entry) || !Array.isArray(entry.stages)) {
20566
21586
  issues.push({
20567
21587
  code: "PROJECT_CONFIG_WORKFLOW_INVALID",
20568
21588
  message: `workflows[${workflowIndex}] requires a stages array.`
@@ -20577,7 +21597,7 @@ function parseWorkflows(value) {
20577
21597
  const id = readString$7(entry.id);
20578
21598
  const label = readString$7(entry.label);
20579
21599
  const stages = entry.stages.flatMap((stage, stageIndex) => {
20580
- if (!isRecord$9(stage)) {
21600
+ if (!isRecord$8(stage)) {
20581
21601
  issues.push({
20582
21602
  code: "PROJECT_CONFIG_STAGE_INVALID",
20583
21603
  message: `workflows[${workflowIndex}].stages[${stageIndex}] must be an object.`
@@ -20630,7 +21650,7 @@ function parseArtifactCategories(value) {
20630
21650
  const issues = [];
20631
21651
  return {
20632
21652
  value: value.flatMap((entry, index) => {
20633
- if (!isRecord$9(entry) || !Array.isArray(entry.include)) {
21653
+ if (!isRecord$8(entry) || !Array.isArray(entry.include)) {
20634
21654
  issues.push({
20635
21655
  code: "PROJECT_CONFIG_ARTIFACT_INVALID",
20636
21656
  message: `observation.artifacts[${index}] requires an include array.`
@@ -20676,7 +21696,7 @@ function parseSkillRoots(value) {
20676
21696
  const issues = [];
20677
21697
  return {
20678
21698
  value: value.flatMap((entry, index) => {
20679
- if (!isRecord$9(entry)) {
21699
+ if (!isRecord$8(entry)) {
20680
21700
  issues.push({
20681
21701
  code: "PROJECT_CONFIG_SKILL_INVALID",
20682
21702
  message: `observation.skills[${index}] must be an object.`
@@ -20711,7 +21731,7 @@ function parseProjectObservationConfig(source) {
20711
21731
  }]
20712
21732
  };
20713
21733
  }
20714
- if (!isRecord$9(parsed)) return {
21734
+ if (!isRecord$8(parsed)) return {
20715
21735
  config: null,
20716
21736
  issues: [{
20717
21737
  code: "PROJECT_CONFIG_INVALID",
@@ -20731,13 +21751,13 @@ function parseProjectObservationConfig(source) {
20731
21751
  message: "Only schema_version 1 is supported."
20732
21752
  }, ...issues]
20733
21753
  };
20734
- const project = isRecord$9(parsed.project) ? parsed.project : {};
20735
- const observation = isRecord$9(parsed.observation) ? parsed.observation : {};
20736
- if (parsed.project !== void 0 && !isRecord$9(parsed.project)) issues.push({
21754
+ const project = isRecord$8(parsed.project) ? parsed.project : {};
21755
+ const observation = isRecord$8(parsed.observation) ? parsed.observation : {};
21756
+ if (parsed.project !== void 0 && !isRecord$8(parsed.project)) issues.push({
20737
21757
  code: "PROJECT_CONFIG_PROJECT_INVALID",
20738
21758
  message: "project must be an object."
20739
21759
  });
20740
- if (parsed.observation !== void 0 && !isRecord$9(parsed.observation)) issues.push({
21760
+ if (parsed.observation !== void 0 && !isRecord$8(parsed.observation)) issues.push({
20741
21761
  code: "PROJECT_CONFIG_OBSERVATION_INVALID",
20742
21762
  message: "observation must be an object."
20743
21763
  });
@@ -20749,7 +21769,7 @@ function parseProjectObservationConfig(source) {
20749
21769
  "skills"
20750
21770
  ], "observation"));
20751
21771
  if (observation.markers !== void 0) {
20752
- if (!(Array.isArray(observation.markers) ? observation.markers : []).some((marker) => isRecord$9(marker) && marker.protocol === "nextclaw.project/v1")) issues.push({
21772
+ if (!(Array.isArray(observation.markers) ? observation.markers : []).some((marker) => isRecord$8(marker) && marker.protocol === "nextclaw.project/v1")) issues.push({
20753
21773
  code: "PROJECT_CONFIG_MARKERS_INVALID",
20754
21774
  message: `observation.markers must enable protocol '${PROJECT_OBSERVATION_PROTOCOL}'.`
20755
21775
  });
@@ -21402,10 +22422,10 @@ function parseProjectObservationMarkers(params) {
21402
22422
  issues
21403
22423
  };
21404
22424
  }
21405
- const isRecord$8 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
22425
+ const isRecord$7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
21406
22426
  function readProjectObservationResponseMetadata(metadata) {
21407
22427
  const response = metadata?.project_observation_response;
21408
- if (!isRecord$8(response) || response.protocol !== "nextclaw.project/v1" || typeof response.requestId !== "string" || response.decision !== "confirmed" && response.decision !== "rejected") return null;
22428
+ if (!isRecord$7(response) || response.protocol !== "nextclaw.project/v1" || typeof response.requestId !== "string" || response.decision !== "confirmed" && response.decision !== "rejected") return null;
21409
22429
  return {
21410
22430
  protocol: PROJECT_OBSERVATION_PROTOCOL,
21411
22431
  requestId: response.requestId,
@@ -22046,7 +23066,7 @@ function deduplicateNcpAgentSessionTailMessages(messages) {
22046
23066
  function isNcpAgentSessionMessageProjectionMeta(value, sessionId) {
22047
23067
  if (!value || typeof value !== "object" || Array.isArray(value)) return false;
22048
23068
  const meta = value;
22049
- return meta.version === 7 && meta.sessionId === sessionId && Number.isSafeInteger(meta.total) && Number.isSafeInteger(meta.projectedJournalOffset) && Number.isSafeInteger(meta.dataBytes) && (meta.activeMessageId === null || typeof meta.activeMessageId === "string") && Array.isArray(meta.pendingCompactionMessageIds) && meta.pendingCompactionMessageIds.every((id) => typeof id === "string") && (meta.contextWindow === null || isRecord$7(meta.contextWindow));
23069
+ return meta.version === 7 && meta.sessionId === sessionId && Number.isSafeInteger(meta.total) && Number.isSafeInteger(meta.projectedJournalOffset) && Number.isSafeInteger(meta.dataBytes) && (meta.activeMessageId === null || typeof meta.activeMessageId === "string") && Array.isArray(meta.pendingCompactionMessageIds) && meta.pendingCompactionMessageIds.every((id) => typeof id === "string") && (meta.contextWindow === null || isRecord$6(meta.contextWindow));
22050
23070
  }
22051
23071
  function readActiveAssistantMessageId(messages) {
22052
23072
  for (let index = messages.length - 1; index >= 0; index -= 1) {
@@ -22069,12 +23089,12 @@ function mergePendingCompactionMessageIds(current, messages) {
22069
23089
  }
22070
23090
  return pending;
22071
23091
  }
22072
- function isRecord$7(value) {
23092
+ function isRecord$6(value) {
22073
23093
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
22074
23094
  }
22075
23095
  function readCompactionStatus(message) {
22076
23096
  const checkpoint = message.metadata?.checkpoint;
22077
- if (message.metadata?.nextclaw_timeline_kind !== "context_compaction" || !isRecord$7(checkpoint)) return null;
23097
+ if (message.metadata?.nextclaw_timeline_kind !== "context_compaction" || !isRecord$6(checkpoint)) return null;
22078
23098
  return typeof checkpoint.status === "string" ? checkpoint.status : null;
22079
23099
  }
22080
23100
  //#endregion
@@ -22582,151 +23602,6 @@ async function readLegacyRecords(journalDir, diagnostics) {
22582
23602
  return /* @__PURE__ */ new Map();
22583
23603
  }
22584
23604
  //#endregion
22585
- //#region src/stores/ncp-session-catalog-sqlite-driver.store.ts
22586
- const require = createRequire(import.meta.url);
22587
- const portableDatabases = /* @__PURE__ */ new Map();
22588
- async function openSessionCatalogSqliteDatabase(databasePath) {
22589
- try {
22590
- return new (require("node:sqlite")).DatabaseSync(databasePath);
22591
- } catch (error) {
22592
- if (!isMissingNodeSqlite(error)) throw error;
22593
- return await openPortableDatabase(databasePath);
22594
- }
22595
- }
22596
- function runSessionCatalogSqliteTransaction(database, operation, mode = "DEFERRED") {
22597
- database.exec(`BEGIN ${mode}`);
22598
- try {
22599
- const result = operation();
22600
- database.exec("COMMIT");
22601
- return result;
22602
- } catch (error) {
22603
- database.exec("ROLLBACK");
22604
- throw error;
22605
- }
22606
- }
22607
- function isMissingNodeSqlite(error) {
22608
- const code = typeof error === "object" && error !== null ? error.code : void 0;
22609
- return code === "ERR_UNKNOWN_BUILTIN_MODULE" || code === "MODULE_NOT_FOUND";
22610
- }
22611
- async function openPortableDatabase(databasePath) {
22612
- const existing = portableDatabases.get(databasePath);
22613
- if (existing) return (await existing).acquire();
22614
- const opening = createPortableDatabase(databasePath).catch((error) => {
22615
- portableDatabases.delete(databasePath);
22616
- throw error;
22617
- });
22618
- portableDatabases.set(databasePath, opening);
22619
- return (await opening).acquire();
22620
- }
22621
- async function createPortableDatabase(databasePath) {
22622
- const initialize = (await import("sql.js")).default;
22623
- const sqlite = await initialize({ locateFile: (file) => require.resolve(`sql.js/dist/${file}`) });
22624
- return new PortableSessionCatalogSqliteDatabase(databasePath, existsSync(databasePath) ? new sqlite.Database(readFileSync(databasePath)) : new sqlite.Database());
22625
- }
22626
- var PortableSessionCatalogSqliteDatabase = class {
22627
- inTransaction = false;
22628
- dirty = false;
22629
- leaseCount = 0;
22630
- constructor(databasePath, database) {
22631
- this.databasePath = databasePath;
22632
- this.database = database;
22633
- }
22634
- acquire = () => {
22635
- this.leaseCount += 1;
22636
- return new PortableSessionCatalogSqliteLease(this);
22637
- };
22638
- exec = (sql) => {
22639
- const command = sql.trim().toUpperCase();
22640
- this.database.run(sql);
22641
- if (command.startsWith("BEGIN")) {
22642
- this.inTransaction = true;
22643
- return;
22644
- }
22645
- if (command.startsWith("ROLLBACK")) {
22646
- this.inTransaction = false;
22647
- this.dirty = false;
22648
- return;
22649
- }
22650
- if (command.startsWith("COMMIT")) {
22651
- this.inTransaction = false;
22652
- this.persistIfDirty();
22653
- return;
22654
- }
22655
- if (!command.startsWith("PRAGMA")) this.markDirty();
22656
- };
22657
- prepare = (sql) => new PortableSessionCatalogSqliteStatement(this, sql);
22658
- release = () => {
22659
- this.leaseCount -= 1;
22660
- if (this.leaseCount > 0) return;
22661
- this.persistIfDirty();
22662
- this.database.close();
22663
- portableDatabases.delete(this.databasePath);
22664
- };
22665
- run = (sql, params) => {
22666
- this.database.run(sql, normalizeParams(params));
22667
- this.markDirty();
22668
- };
22669
- readRows = (sql, params) => {
22670
- const statement = this.database.prepare(sql);
22671
- try {
22672
- bindStatement(statement, params);
22673
- const rows = [];
22674
- while (statement.step()) rows.push(statement.getAsObject());
22675
- return rows;
22676
- } finally {
22677
- statement.free();
22678
- }
22679
- };
22680
- markDirty = () => {
22681
- this.dirty = true;
22682
- if (!this.inTransaction) this.persistIfDirty();
22683
- };
22684
- persistIfDirty = () => {
22685
- if (!this.dirty) return;
22686
- const temporaryPath = `${this.databasePath}.tmp`;
22687
- writeFileSync(temporaryPath, this.database.export());
22688
- renameSync(temporaryPath, this.databasePath);
22689
- this.dirty = false;
22690
- };
22691
- };
22692
- var PortableSessionCatalogSqliteLease = class {
22693
- closed = false;
22694
- constructor(owner) {
22695
- this.owner = owner;
22696
- }
22697
- close = () => {
22698
- if (this.closed) return;
22699
- this.closed = true;
22700
- this.owner.release();
22701
- };
22702
- exec = (sql) => this.owner.exec(sql);
22703
- prepare = (sql) => this.owner.prepare(sql);
22704
- };
22705
- var PortableSessionCatalogSqliteStatement = class {
22706
- constructor(database, sql) {
22707
- this.database = database;
22708
- this.sql = sql;
22709
- }
22710
- all = (...params) => this.database.readRows(this.sql, params);
22711
- get = (...params) => this.all(...params)[0];
22712
- run = (...params) => {
22713
- this.database.run(this.sql, params);
22714
- return {};
22715
- };
22716
- };
22717
- function bindStatement(statement, params) {
22718
- const normalized = normalizeParams(params);
22719
- if (Array.isArray(normalized) && normalized.length === 0) return;
22720
- statement.bind(normalized);
22721
- }
22722
- function normalizeParams(params) {
22723
- if (params.length !== 1 || !isRecord$6(params[0])) return params;
22724
- return Object.fromEntries(Object.entries(params[0]).map(([key, value]) => [`@${key}`, value]));
22725
- }
22726
- function isRecord$6(value) {
22727
- return typeof value === "object" && value !== null && !Array.isArray(value);
22728
- }
22729
- //#endregion
22730
23605
  //#region src/stores/ncp-agent-session-summary-index.store.ts
22731
23606
  const SQLITE_DATABASE_FILE = ".ncp-agent-session-catalog.sqlite";
22732
23607
  const CATALOG_SCHEMA_VERSION = 1;
@@ -22866,7 +23741,7 @@ var NcpAgentSessionSummaryIndexStore = class {
22866
23741
  await this.ensureReady();
22867
23742
  const normalizedSessionId = normalizeNcpSessionId(sessionId);
22868
23743
  const deletedAt = (/* @__PURE__ */ new Date()).toISOString();
22869
- runSessionCatalogSqliteTransaction(this.db(), () => {
23744
+ runSqliteTransaction(this.db(), () => {
22870
23745
  if (this.db().prepare("SELECT session_id FROM sessions WHERE session_id = ? LIMIT 1").get(normalizedSessionId)) {
22871
23746
  this.db().prepare("UPDATE sessions SET deleted_at = ? WHERE session_id = ?").run(deletedAt, normalizedSessionId);
22872
23747
  return;
@@ -22891,7 +23766,7 @@ var NcpAgentSessionSummaryIndexStore = class {
22891
23766
  };
22892
23767
  initializeCatalog = async () => {
22893
23768
  await mkdir(this.journalDir, { recursive: true });
22894
- this.database = await openSessionCatalogSqliteDatabase(resolve(this.journalDir, SQLITE_DATABASE_FILE));
23769
+ this.database = await openSqliteDatabase(resolve(this.journalDir, SQLITE_DATABASE_FILE));
22895
23770
  this.database.exec(`
22896
23771
  PRAGMA busy_timeout = 10000;
22897
23772
  PRAGMA journal_mode = WAL;
@@ -22931,7 +23806,7 @@ var NcpAgentSessionSummaryIndexStore = class {
22931
23806
  loadSession: (sessionId) => this.loadSession(sessionId),
22932
23807
  loadSessionSummary: this.loadSessionSummary
22933
23808
  });
22934
- runSessionCatalogSqliteTransaction(this.db(), () => {
23809
+ runSqliteTransaction(this.db(), () => {
22935
23810
  if (this.db().prepare("SELECT value FROM storage_meta WHERE key = ? LIMIT 1").get(MIGRATION_STATUS_KEY)?.value === MIGRATION_COMPLETE) {
22936
23811
  this.reconcileRecords(scan.records);
22937
23812
  return;
@@ -25786,12 +26661,234 @@ var ProjectsCreateTool = class {
25786
26661
  };
25787
26662
  };
25788
26663
  //#endregion
26664
+ //#region src/tools/project-work.tools.ts
26665
+ function requiredString$1(value, key) {
26666
+ if (typeof value !== "string" || !value.trim()) throw new Error(`${key} must be a non-empty string.`);
26667
+ return value.trim();
26668
+ }
26669
+ function optionalString$1(value) {
26670
+ return typeof value === "string" && value.trim() ? value.trim() : void 0;
26671
+ }
26672
+ function optionalBoolean(value) {
26673
+ return typeof value === "boolean" ? value : void 0;
26674
+ }
26675
+ function optionalNumber(value) {
26676
+ return typeof value === "number" && Number.isInteger(value) ? value : void 0;
26677
+ }
26678
+ function agentActor(context) {
26679
+ return {
26680
+ kind: "agent",
26681
+ ...context.agentId ? { id: context.agentId } : {},
26682
+ sessionId: context.sessionId
26683
+ };
26684
+ }
26685
+ var ProjectWorkListTool = class {
26686
+ name = "project_work_list";
26687
+ description = "List persistent work items and custom states for the current project.";
26688
+ parameters = {
26689
+ type: "object",
26690
+ properties: { include_deleted: {
26691
+ type: "boolean",
26692
+ description: "Include deleted work items."
26693
+ } },
26694
+ additionalProperties: false
26695
+ };
26696
+ constructor(work, context) {
26697
+ this.work = work;
26698
+ this.context = context;
26699
+ }
26700
+ execute = async (args) => {
26701
+ const params = normalizeToolParams(args);
26702
+ return JSON.stringify(await this.work.list(this.context.projectId, optionalBoolean(params.include_deleted) ?? false), null, 2);
26703
+ };
26704
+ };
26705
+ var ProjectWorkGetTool = class {
26706
+ name = "project_work_get";
26707
+ description = "Get one current-project work item with artifact links and recent immutable activity.";
26708
+ parameters = {
26709
+ type: "object",
26710
+ properties: {
26711
+ id: {
26712
+ type: "string",
26713
+ description: "Work item id."
26714
+ },
26715
+ activity_limit: {
26716
+ type: "integer",
26717
+ minimum: 1,
26718
+ maximum: 100,
26719
+ description: "Recent activity entries. Defaults to 20."
26720
+ }
26721
+ },
26722
+ required: ["id"],
26723
+ additionalProperties: false
26724
+ };
26725
+ constructor(work, context) {
26726
+ this.work = work;
26727
+ this.context = context;
26728
+ }
26729
+ execute = async (args) => {
26730
+ const params = normalizeToolParams(args);
26731
+ const id = requiredString$1(params.id, "id");
26732
+ const [item, activity] = await Promise.all([this.work.get(this.context.projectId, id), this.work.listActivities(this.context.projectId, id, { limit: optionalNumber(params.activity_limit) ?? 20 })]);
26733
+ return JSON.stringify({
26734
+ item,
26735
+ activity
26736
+ }, null, 2);
26737
+ };
26738
+ };
26739
+ var ProjectWorkCreateTool = class {
26740
+ name = "project_work_create";
26741
+ description = "Create a persistent work item in the current project.";
26742
+ parameters = {
26743
+ type: "object",
26744
+ properties: {
26745
+ title: {
26746
+ type: "string",
26747
+ description: "Concise work item title."
26748
+ },
26749
+ description: {
26750
+ type: "string",
26751
+ description: "Optional durable context and acceptance notes."
26752
+ },
26753
+ state_id: {
26754
+ type: "string",
26755
+ description: "Optional custom state id. The project default is used when omitted."
26756
+ },
26757
+ attention: {
26758
+ type: "string",
26759
+ enum: [
26760
+ "none",
26761
+ "blocked",
26762
+ "awaiting-user"
26763
+ ]
26764
+ }
26765
+ },
26766
+ required: ["title"],
26767
+ additionalProperties: false
26768
+ };
26769
+ constructor(work, context) {
26770
+ this.work = work;
26771
+ this.context = context;
26772
+ }
26773
+ execute = async (args) => {
26774
+ const params = normalizeToolParams(args);
26775
+ return JSON.stringify(await this.work.create(this.context.projectId, {
26776
+ title: requiredString$1(params.title, "title"),
26777
+ ...optionalString$1(params.description) ? { description: optionalString$1(params.description) } : {},
26778
+ ...optionalString$1(params.state_id) ? { stateId: optionalString$1(params.state_id) } : {},
26779
+ ...optionalString$1(params.attention) ? { attention: optionalString$1(params.attention) } : {}
26780
+ }, agentActor(this.context)), null, 2);
26781
+ };
26782
+ };
26783
+ var ProjectWorkUpdateTool = class {
26784
+ name = "project_work_update";
26785
+ description = "Update, soft-delete, or restore a current-project work item, or add/remove its artifact links. Status changes use state_id; there is no separate start tool.";
26786
+ parameters = {
26787
+ type: "object",
26788
+ properties: {
26789
+ id: {
26790
+ type: "string",
26791
+ description: "Work item id."
26792
+ },
26793
+ title: { type: "string" },
26794
+ description: { type: "string" },
26795
+ state_id: { type: "string" },
26796
+ attention: {
26797
+ type: "string",
26798
+ enum: [
26799
+ "none",
26800
+ "blocked",
26801
+ "awaiting-user"
26802
+ ]
26803
+ },
26804
+ expected_version: {
26805
+ type: "integer",
26806
+ minimum: 1
26807
+ },
26808
+ deleted: {
26809
+ type: "boolean",
26810
+ description: "True to soft-delete the item; false to restore it."
26811
+ },
26812
+ add_artifact: {
26813
+ type: "object",
26814
+ properties: {
26815
+ path: { type: "string" },
26816
+ label: { type: "string" }
26817
+ },
26818
+ required: ["path"],
26819
+ additionalProperties: false
26820
+ },
26821
+ remove_artifact_id: { type: "string" }
26822
+ },
26823
+ required: ["id"],
26824
+ additionalProperties: false
26825
+ };
26826
+ constructor(work, context) {
26827
+ this.work = work;
26828
+ this.context = context;
26829
+ }
26830
+ execute = async (args) => {
26831
+ const params = normalizeToolParams(args);
26832
+ const id = requiredString$1(params.id, "id");
26833
+ const actor = agentActor(this.context);
26834
+ const patch = {
26835
+ ...params.title !== void 0 ? { title: requiredString$1(params.title, "title") } : {},
26836
+ ...typeof params.description === "string" ? { description: params.description } : {},
26837
+ ...optionalString$1(params.state_id) ? { stateId: optionalString$1(params.state_id) } : {},
26838
+ ...optionalString$1(params.attention) ? { attention: optionalString$1(params.attention) } : {},
26839
+ ...optionalNumber(params.expected_version) ? { expectedVersion: optionalNumber(params.expected_version) } : {}
26840
+ };
26841
+ if (Object.keys(patch).length > 0) await this.work.update(this.context.projectId, id, patch, actor);
26842
+ const deleted = optionalBoolean(params.deleted);
26843
+ if (deleted !== void 0) await (deleted ? this.work.delete(this.context.projectId, id, actor) : this.work.restore(this.context.projectId, id, actor));
26844
+ if (params.add_artifact && typeof params.add_artifact === "object" && !Array.isArray(params.add_artifact)) {
26845
+ const artifact = params.add_artifact;
26846
+ await this.work.linkArtifact({
26847
+ projectId: this.context.projectId,
26848
+ workItemId: id,
26849
+ path: requiredString$1(artifact.path, "add_artifact.path"),
26850
+ ...optionalString$1(artifact.label) ? { label: optionalString$1(artifact.label) } : {},
26851
+ actor
26852
+ });
26853
+ }
26854
+ const removeArtifactId = optionalString$1(params.remove_artifact_id);
26855
+ if (removeArtifactId) await this.work.unlinkArtifact({
26856
+ projectId: this.context.projectId,
26857
+ workItemId: id,
26858
+ artifactLinkId: removeArtifactId,
26859
+ actor
26860
+ });
26861
+ return JSON.stringify(await this.work.get(this.context.projectId, id), null, 2);
26862
+ };
26863
+ };
26864
+ //#endregion
25789
26865
  //#region src/contributions/tool-provider/providers/project-tool.provider.ts
25790
26866
  var ProjectToolProvider = class {
25791
- constructor(projectManager) {
26867
+ constructor(runContext, projectManager, projectWork) {
26868
+ this.runContext = runContext;
25792
26869
  this.projectManager = projectManager;
26870
+ this.projectWork = projectWork;
25793
26871
  }
25794
- provide = (_request) => [new ProjectsListTool(this.projectManager), new ProjectsCreateTool(this.projectManager)];
26872
+ provide = async (request) => {
26873
+ const globalTools = [new ProjectsListTool(this.projectManager), new ProjectsCreateTool(this.projectManager)];
26874
+ const resolved = await this.runContext.resolve(request);
26875
+ const projectId = readProjectId(resolved.session?.metadata);
26876
+ const projectRoot = readProjectRoot(resolved.session?.metadata);
26877
+ const project = projectId ? await this.projectManager.getProjectById(projectId) : projectRoot ? await this.projectManager.getRegisteredProject(projectRoot) : null;
26878
+ if (!project || !resolved.sessionId) return globalTools;
26879
+ const context = {
26880
+ projectId: project.id,
26881
+ sessionId: resolved.sessionId,
26882
+ ...resolved.session?.agentId ? { agentId: resolved.session.agentId } : {}
26883
+ };
26884
+ return [
26885
+ ...globalTools,
26886
+ new ProjectWorkListTool(this.projectWork, context),
26887
+ new ProjectWorkGetTool(this.projectWork, context),
26888
+ new ProjectWorkCreateTool(this.projectWork, context),
26889
+ new ProjectWorkUpdateTool(this.projectWork, context)
26890
+ ];
26891
+ };
25795
26892
  };
25796
26893
  //#endregion
25797
26894
  //#region src/tools/session-history.tools.ts
@@ -27628,7 +28725,7 @@ var ToolProviderContribution = class extends Contribution$1 {
27628
28725
  new DesktopToolProvider(runContextService, this.kernel.extensions.getDesktopHost()),
27629
28726
  new CoreToolProvider(runContextService, this.kernel.getGatewayController),
27630
28727
  new MessagingToolProvider(runContextService, this.kernel.channels, this.kernel.automation, this.kernel.extensions),
27631
- new ProjectToolProvider(this.kernel.projectManager),
28728
+ new ProjectToolProvider(runContextService, this.kernel.projectManager, this.kernel.projectWorkManager),
27632
28729
  new SessionToolProvider(runContextService, this.kernel.sessionManager, this.kernel.sessionRequests, this.kernel.sessionSearch),
27633
28730
  new AssetToolProvider(this.kernel.assetStore),
27634
28731
  new ServiceActionToolProvider(runContextService, this.kernel.serviceAppManager),
@@ -27668,6 +28765,7 @@ function createKernelServiceAppManagers(params) {
27668
28765
  appHomeDirectory,
27669
28766
  configManager,
27670
28767
  listPackageComponentSources: appPackageManager.listActiveComponentSources,
28768
+ assertDocumentAccess: appPackageManager.assertDocumentAccess,
27671
28769
  capabilityGrantManager,
27672
28770
  hasAgent,
27673
28771
  providerManager,
@@ -27724,16 +28822,26 @@ function createPortableRuntimeAcceptanceServices(params) {
27724
28822
  };
27725
28823
  }
27726
28824
  function createKernelSessionManagers(params) {
27727
- const { agentContextWindowManager, agentManager, configManager, eventBus, ingress, observationStorePath, projectStorePath, sessionsDir } = params;
28825
+ const { agentContextWindowManager, agentManager, configManager, eventBus, ingress, observationStorePath, projectStorePath, projectWorkStorePath, sessionsDir } = params;
27728
28826
  const sessionSearch = new SessionSearchService({
27729
28827
  databasePath: resolve(getDataDir(), "session-search.db"),
27730
28828
  sessionsDir
27731
28829
  });
27732
28830
  const journalStore = new NcpAgentSessionJournalStore(resolve(sessionsDir, ".ncp-agent-journal"));
28831
+ const projectWorkOwner = { current: null };
27733
28832
  const projectManager = new ProjectManager({
27734
28833
  storePath: projectStorePath,
27735
- getDefaultWorkspacePath: () => getWorkspacePathFromConfig(configManager.config)
28834
+ getDefaultWorkspacePath: () => getWorkspacePathFromConfig(configManager.config),
28835
+ onProjectRegistered: async (project) => {
28836
+ await projectWorkOwner.current?.ensureProject(project.id);
28837
+ }
27736
28838
  });
28839
+ const projectWorkManager = new ProjectWorkManager({
28840
+ databasePath: projectWorkStorePath,
28841
+ eventBus,
28842
+ projectManager
28843
+ });
28844
+ projectWorkOwner.current = projectWorkManager;
27737
28845
  const observationOwner = { current: null };
27738
28846
  const sessionManager = new SessionManager({
27739
28847
  agentContextWindowManager,
@@ -27766,6 +28874,7 @@ function createKernelSessionManagers(params) {
27766
28874
  observations,
27767
28875
  projectManager,
27768
28876
  projectObservation,
28877
+ projectWorkManager,
27769
28878
  sessionManager,
27770
28879
  sessionSearch
27771
28880
  };
@@ -27794,6 +28903,10 @@ function installKernelAppPackageRuntimeHooks(params) {
27794
28903
  panelAppManager.deactivatePackageComponents(sources);
27795
28904
  await serviceAppManager.deactivatePackageComponents(sources);
27796
28905
  },
28906
+ prepareCapabilityChange: async (sources) => await serviceAppManager.preparePackageComponentDeactivation(sources),
28907
+ afterCapabilityChange: async (sources) => {
28908
+ await serviceAppManager.activatePackageComponents(sources);
28909
+ },
27797
28910
  beforeUninstall: async (sources) => {
27798
28911
  const rollbacks = [];
27799
28912
  try {
@@ -27862,6 +28975,7 @@ var NextclawKernel = class {
27862
28975
  preferenceManager;
27863
28976
  projectManager;
27864
28977
  projectObservation;
28978
+ projectWorkManager;
27865
28979
  serviceAppManager;
27866
28980
  extensions;
27867
28981
  agentRuntimeManager = new AgentRuntimeManager();
@@ -27907,7 +29021,7 @@ var NextclawKernel = class {
27907
29021
  homeDir: options.homeDir
27908
29022
  });
27909
29023
  this.capabilityGrantLegacyMigration = this.createCapabilityGrantLegacyMigration(options);
27910
- ({journalStore: this.ncpAgentSessionJournalStore, observations: this.observations, projectManager: this.projectManager, projectObservation: this.projectObservation, sessionManager: this.sessionManager, sessionSearch: this.sessionSearch} = createKernelSessionManagers({
29024
+ ({journalStore: this.ncpAgentSessionJournalStore, observations: this.observations, projectManager: this.projectManager, projectObservation: this.projectObservation, projectWorkManager: this.projectWorkManager, sessionManager: this.sessionManager, sessionSearch: this.sessionSearch} = createKernelSessionManagers({
27911
29025
  agentContextWindowManager: this.agentContextWindowManager,
27912
29026
  agentManager: this.agents,
27913
29027
  configManager: this.configManager,
@@ -27915,6 +29029,7 @@ var NextclawKernel = class {
27915
29029
  ingress: this.ingress,
27916
29030
  observationStorePath: resolveKernelObservationStorePath(options),
27917
29031
  projectStorePath: resolveKernelProjectStorePath(options),
29032
+ projectWorkStorePath: resolveKernelProjectWorkStorePath(options),
27918
29033
  sessionsDir
27919
29034
  }));
27920
29035
  this.inboxDeliveryManager = new InboxDeliveryManager({
@@ -28016,6 +29131,7 @@ var NextclawKernel = class {
28016
29131
  this.providerModelCatalog.start();
28017
29132
  await this.projectManager.migrateLegacyProjects();
28018
29133
  await this.projectManager.importSessionProjects((await this.sessionManager.listSessions()).map((session) => readProjectRoot(session.metadata)));
29134
+ await this.projectWorkManager.initialize();
28019
29135
  await this.sessionManager.start();
28020
29136
  for (const contribution of this.contributions) await contribution.start();
28021
29137
  this.agentRunRequestManager.start();
@@ -28035,6 +29151,7 @@ var NextclawKernel = class {
28035
29151
  await this.mcpManager.dispose();
28036
29152
  await this.serviceAppManager.dispose();
28037
29153
  await this.sessionSearch.dispose();
29154
+ this.projectWorkManager.dispose();
28038
29155
  };
28039
29156
  };
28040
29157
  //#endregion
@@ -29177,6 +30294,6 @@ function resolveLegacyEventType(message) {
29177
30294
  return `message.${role || "other"}`;
29178
30295
  }
29179
30296
  //#endregion
29180
- export { AUTOMATIC_UPDATE_CHECK_INTERVAL_MS, AccessManager, AgentManager, AgentRunClient, AppDataError, AppDataManager, AppPackageError, AppPackageManager, AutomationManager, BuiltinNarpRuntimeProviderService, CONTEXT_COMPACTION_CONTINUATION_TEXT, CONTEXT_COMPACTION_PROJECTION_KIND, CONTEXT_COMPACTION_PROJECTION_METADATA_KEY, CONTEXT_COMPACTION_SYSTEM_PREAMBLE, CONTEXT_COMPACTION_TIMELINE_KIND, CapabilityGrantLegacyMigrationService, CapabilityGrantManager, CapabilityGrantStore, ChannelManager, CommandRegistry, ConfigManager, ContextCompactionJournalRecoveryService, ContextCompactionPreflightService, Contribution, DEFAULT_AGENT_RUNTIME_ENTRY_ID, DEFAULT_SERVICE_ACTION_RISK, DESKTOP_HOST_ACCESS, DESKTOP_HOST_PROTOCOL_VERSION, DesktopHostCapabilityManager, DesktopNodeReplService, DesktopSessionStateService, EventBus, ExtensionManager, FeatureControlsService, GatewayInboundProcessor, InboxDeliveryError, InboxDeliveryManager, Ingress, LlmProviderManager, LlmUsageManager, LlmUsageStore, MAX_INBOX_DELIVERY_CONTENT_LENGTH, McpManager, McpServiceAppRuntimeService, NARP_HTTP_RUNTIME_KIND, NARP_STDIO_RUNTIME_KIND, NEXTCLAW_TIMELINE_KIND_METADATA_KEY, NcpAgentSessionJournalStore, NextclawHarness, NextclawHarnessError, NextclawKernel, ObservationManager, PANEL_APP_AGENT_CAPABILITIES, PORTABLE_RUNTIME_ACCEPTANCE_CONTRACT, PORTABLE_RUNTIME_ACCEPTANCE_CONTRACT_FINGERPRINT, PORTABLE_RUNTIME_ACCEPTANCE_LOCALES, PORTABLE_RUNTIME_ACCEPTANCE_PLATFORMS, PORTABLE_RUNTIME_ACCEPTANCE_PRESENTATION, PORTABLE_RUNTIME_ACCEPTANCE_REFERENCE_APP_ID, PROJECT_OBSERVATION_PROTOCOL, PROJECT_TEMPLATE_IDS, PROVIDER_MODEL_CATALOG_REFRESH_INTERVAL_MS, PanelAppAssetTokenService, PanelAppError, PanelAppManager, PortableRuntimeAcceptanceIdentityService, PortableRuntimeAcceptanceManager, PreferenceError, PreferenceManager, ProjectError, ProjectManager, ProjectObservationError, ProjectObservationService, ProviderManagerNcpLLMApi, ProviderModelCatalogManager, SERVICE_APP_MANIFEST_FILE_NAME, ServiceAppAiCapabilityService, ServiceAppError, ServiceAppJobJournalService, ServiceAppManager, ServiceAppResidentEventInboxService, ServiceAppRuntimeService, SessionContextCompactionError, SessionContextCompactionManager, SessionManager, SessionMessageCursorError, SessionRequestManager, SessionSettingsError, SkillManager, SystemObjectReferenceError, SystemObjectReferenceManager, UnavailableDesktopHost, UpdateManifestReader, VerificationRecordService, assertObservationJsonValue, assertObservationPredicate, buildAgentRunSendPayload, buildContextCompactionModelProjection, buildContextCompactionTimelineNcpMessage, buildLlmUsageSummary, buildLocalizedTextMap, buildNextclawNcpRunContext, buildObservationEventModelMessage, buildServiceActionId, buildSessionRequestCompletionMessage, capabilityGrantCovers, createAgentRuntimeSessionRequestDispatcher, createAgentRuntimeSessionRequestSourceNotifier, createAssetTools, createCapabilityDeclarationFingerprint, createContextCompactionMessageId, createContextWindowSignature, createCronJobSystemObjectProvider, createDesktopHostError, createInboxDeliverySystemObjectProvider, createLlmUsageRecord, createPanelAppAgentGrantRequest, createPanelAppClientGrantRequest, createServiceActionGrantRequest, createServiceAppAgentSlotGrantRequest, createServiceAppModelSlotGrantRequest, createTypedKey, describeAgentRuntimeSessionTypes, dispatchAgentRuntimeSessionRequest, dispatchChannelReplyRoute, dispatchPromptOverNcp, dispatchPromptOverNcpResult, evaluateObservationEventAdmission, evaluatePortableRuntimeAcceptance, evaluatePortableRuntimeAcceptanceArtifact, eventKeys, getAutomaticUpdateCheckDelay, getCapabilityGrantKey, getServiceActionName, getServiceAppManifestPath, getUiContentParamsBootstrapScript, getUnsignedUpdateManifest, hasLlmUsageTelemetry, ingressKeys, injectUiContentParamsBootstrap, isAppDataError, isAppPackageError, isContextCompactionProjectionMessage, isContextCompactionTimelineMessage, isContextWindowSnapshot, isInboxDeliveryError, isPanelAppAgentCapability, isPanelAppError, isPreferenceError, isProjectError, isProjectObservationError, isReplyCapableChannel, isServiceAppError, isSessionContextCompactionError, isSessionMessageCursorError, isSessionSettingsError, isSystemObjectReferenceError, listExtensionChannelIds, listServiceAppManifestActions, matchesCapabilityGrantFilter, matchesObservationPredicate, mergeServiceAppRuntimeActions, normalizeAgentRuntimeSessionTypeIcon, normalizeCapabilityGrantRequest, normalizeLlmUsageModel, normalizeOptionalString, normalizePortableRuntimeEnvironment, parseObservationDuration, parsePortableRuntimeAcceptanceEvidenceArtifact, parseServiceAppManifest, parseSkillFrontmatter, presentPortableRuntimeAcceptanceDefinition, readContextCompactionCheckpoint, readContextWindowEventSessionId, readJsonPointer, readLatestContextCompactionCheckpoint, readLearningLoopRuntimeConfig, readMetadataModel, readMetadataThinking, readServiceActionTargetId, readServiceAppManifest, readServiceAppSlotTarget, resolveAgentRuntimeEntries, resolveAutomaticUpdateCheckIntervalMs, resolveChannelReplyRoute, resolveEffectiveModel, resolveLegacyEventType, resolvePortableRuntimeAcceptanceLocale, resolveSessionChannelContext, runGatewayInboundLoop, runNextclawTask, sanitizeLlmUsage, serializeContextTail, serializeUnsignedUpdateManifest, shouldRefreshContextWindowDuringStream, shouldRefreshContextWindowImmediately, startPromptOverNcpExecution, stripSkillFrontmatter, syncSessionThinkingPreference, toBoundedJson, toNcpMessages, waitForAgentRuntimeSessionReply };
30297
+ export { AUTOMATIC_UPDATE_CHECK_INTERVAL_MS, AccessManager, AgentManager, AgentRunClient, AppDataError, AppDataManager, AppPackageError, AppPackageManager, AutomationManager, BuiltinNarpRuntimeProviderService, CONTEXT_COMPACTION_CONTINUATION_TEXT, CONTEXT_COMPACTION_PROJECTION_KIND, CONTEXT_COMPACTION_PROJECTION_METADATA_KEY, CONTEXT_COMPACTION_SYSTEM_PREAMBLE, CONTEXT_COMPACTION_TIMELINE_KIND, CapabilityGrantLegacyMigrationService, CapabilityGrantManager, CapabilityGrantStore, ChannelManager, CommandRegistry, ConfigManager, ContextCompactionJournalRecoveryService, ContextCompactionPreflightService, Contribution, DEFAULT_AGENT_RUNTIME_ENTRY_ID, DEFAULT_SERVICE_ACTION_RISK, DESKTOP_HOST_ACCESS, DESKTOP_HOST_PROTOCOL_VERSION, DesktopHostCapabilityManager, DesktopNodeReplService, DesktopSessionStateService, EventBus, ExtensionManager, FeatureControlsService, GatewayInboundProcessor, InboxDeliveryError, InboxDeliveryManager, Ingress, LlmProviderManager, LlmUsageManager, LlmUsageStore, MAX_INBOX_DELIVERY_CONTENT_LENGTH, McpManager, McpServiceAppRuntimeService, NARP_HTTP_RUNTIME_KIND, NARP_STDIO_RUNTIME_KIND, NEXTCLAW_TIMELINE_KIND_METADATA_KEY, NcpAgentSessionJournalStore, NextclawHarness, NextclawHarnessError, NextclawKernel, ObservationManager, PANEL_APP_AGENT_CAPABILITIES, PORTABLE_RUNTIME_ACCEPTANCE_CONTRACT, PORTABLE_RUNTIME_ACCEPTANCE_CONTRACT_FINGERPRINT, PORTABLE_RUNTIME_ACCEPTANCE_LOCALES, PORTABLE_RUNTIME_ACCEPTANCE_PLATFORMS, PORTABLE_RUNTIME_ACCEPTANCE_PRESENTATION, PORTABLE_RUNTIME_ACCEPTANCE_REFERENCE_APP_ID, PROJECT_OBSERVATION_PROTOCOL, PROJECT_TEMPLATE_IDS, PROJECT_WORK_ATTENTION_VALUES, PROJECT_WORK_STATE_CATEGORIES, PROVIDER_MODEL_CATALOG_REFRESH_INTERVAL_MS, PanelAppAssetTokenService, PanelAppError, PanelAppManager, PortableRuntimeAcceptanceIdentityService, PortableRuntimeAcceptanceManager, PreferenceError, PreferenceManager, ProjectError, ProjectManager, ProjectObservationError, ProjectObservationService, ProjectWorkError, ProjectWorkManager, ProviderManagerNcpLLMApi, ProviderModelCatalogManager, SERVICE_APP_MANIFEST_FILE_NAME, ServiceAppAiCapabilityService, ServiceAppError, ServiceAppJobJournalService, ServiceAppManager, ServiceAppResidentEventInboxService, ServiceAppRuntimeService, SessionContextCompactionError, SessionContextCompactionManager, SessionManager, SessionMessageCursorError, SessionRequestManager, SessionSettingsError, SkillManager, SystemObjectReferenceError, SystemObjectReferenceManager, UnavailableDesktopHost, UpdateManifestReader, VerificationRecordService, assertObservationJsonValue, assertObservationPredicate, buildAgentRunSendPayload, buildContextCompactionModelProjection, buildContextCompactionTimelineNcpMessage, buildLlmUsageSummary, buildLocalizedTextMap, buildNextclawNcpRunContext, buildObservationEventModelMessage, buildServiceActionId, buildSessionRequestCompletionMessage, capabilityGrantCovers, createAgentRuntimeSessionRequestDispatcher, createAgentRuntimeSessionRequestSourceNotifier, createAssetTools, createCapabilityDeclarationFingerprint, createContextCompactionMessageId, createContextWindowSignature, createCronJobSystemObjectProvider, createDesktopHostError, createInboxDeliverySystemObjectProvider, createLlmUsageRecord, createPanelAppAgentGrantRequest, createPanelAppClientGrantRequest, createServiceActionGrantRequest, createServiceAppAgentSlotGrantRequest, createServiceAppModelSlotGrantRequest, createTypedKey, describeAgentRuntimeSessionTypes, dispatchAgentRuntimeSessionRequest, dispatchChannelReplyRoute, dispatchPromptOverNcp, dispatchPromptOverNcpResult, evaluateObservationEventAdmission, evaluatePortableRuntimeAcceptance, evaluatePortableRuntimeAcceptanceArtifact, eventKeys, getAutomaticUpdateCheckDelay, getCapabilityGrantKey, getServiceActionName, getServiceAppManifestPath, getUiContentParamsBootstrapScript, getUnsignedUpdateManifest, hasLlmUsageTelemetry, ingressKeys, injectUiContentParamsBootstrap, isAppDataError, isAppPackageError, isContextCompactionProjectionMessage, isContextCompactionTimelineMessage, isContextWindowSnapshot, isInboxDeliveryError, isPanelAppAgentCapability, isPanelAppError, isPreferenceError, isProjectError, isProjectObservationError, isProjectWorkError, isReplyCapableChannel, isServiceAppError, isSessionContextCompactionError, isSessionMessageCursorError, isSessionSettingsError, isSystemObjectReferenceError, listExtensionChannelIds, listServiceAppManifestActions, matchesCapabilityGrantFilter, matchesObservationPredicate, mergeServiceAppRuntimeActions, normalizeAgentRuntimeSessionTypeIcon, normalizeCapabilityGrantRequest, normalizeLlmUsageModel, normalizeOptionalString, normalizePortableRuntimeEnvironment, parseObservationDuration, parsePortableRuntimeAcceptanceEvidenceArtifact, parseServiceAppManifest, parseSkillFrontmatter, presentPortableRuntimeAcceptanceDefinition, readContextCompactionCheckpoint, readContextWindowEventSessionId, readJsonPointer, readLatestContextCompactionCheckpoint, readLearningLoopRuntimeConfig, readMetadataModel, readMetadataThinking, readServiceActionTargetId, readServiceAppManifest, readServiceAppSlotTarget, resolveAgentRuntimeEntries, resolveAutomaticUpdateCheckIntervalMs, resolveChannelReplyRoute, resolveEffectiveModel, resolveLegacyEventType, resolvePortableRuntimeAcceptanceLocale, resolveSessionChannelContext, runGatewayInboundLoop, runNextclawTask, sanitizeLlmUsage, serializeContextTail, serializeUnsignedUpdateManifest, shouldRefreshContextWindowDuringStream, shouldRefreshContextWindowImmediately, startPromptOverNcpExecution, stripSkillFrontmatter, syncSessionThinkingPreference, toBoundedJson, toNcpMessages, waitForAgentRuntimeSessionReply };
29181
30298
 
29182
30299
  //# sourceMappingURL=index.js.map