@nextclaw/kernel 0.12.3 → 0.13.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
@@ -10,7 +10,7 @@ import { catchError, filter, from, lastValueFrom, tap } from "rxjs";
10
10
  import { DefaultNcpAgentConversationStateManager, insertMessageByTimeline, mergeToolExecutionTiming } from "@nextclaw/ncp-toolkit";
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
- import { access, appendFile, mkdir, mkdtemp, open, readFile, readdir, realpath, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
13
+ import { access, appendFile, chmod, mkdir, mkdtemp, open, readFile, readdir, realpath, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
14
14
  import { 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";
@@ -19,7 +19,6 @@ import { McpRegistryService, McpServerLifecycleManager, normalizeMcpServerName }
19
19
  import { McpNcpToolRegistryAdapter } from "@nextclaw/ncp-mcp";
20
20
  import { parse } from "yaml";
21
21
  import { createInterface } from "node:readline";
22
- import Database from "better-sqlite3";
23
22
  import { HttpRuntimeConfigResolver, HttpRuntimeNcpAgentRuntime } from "@nextclaw/nextclaw-ncp-runtime-http-client";
24
23
  import { StdioRuntimeConfigResolver, StdioRuntimeNcpAgentRuntime, probeStdioRuntime } from "@nextclaw/nextclaw-ncp-runtime-stdio-client";
25
24
  import { DefaultNcpAgentRuntime } from "@nextclaw/ncp-agent-runtime-next";
@@ -1394,7 +1393,7 @@ const SESSION_ACTIVITY_PREVIEW_STATUS_KINDS = new Set([
1394
1393
  "run-failed",
1395
1394
  "run-interrupted"
1396
1395
  ]);
1397
- function isRecord$18(value) {
1396
+ function isRecord$19(value) {
1398
1397
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
1399
1398
  }
1400
1399
  function readOptionalString$14(value) {
@@ -1403,7 +1402,7 @@ function readOptionalString$14(value) {
1403
1402
  return trimmed.length > 0 ? trimmed : void 0;
1404
1403
  }
1405
1404
  function readSessionActivityPreviewMetadata(value) {
1406
- if (!isRecord$18(value)) return null;
1405
+ if (!isRecord$19(value)) return null;
1407
1406
  const state = value.state;
1408
1407
  const timestamp = readOptionalString$14(value.timestamp);
1409
1408
  if (!SESSION_ACTIVITY_PREVIEW_STATES.has(state) || !timestamp) return null;
@@ -1497,7 +1496,7 @@ var SessionActivityPreviewEventService = class {
1497
1496
  //#endregion
1498
1497
  //#region src/managers/agent-run-session-command.manager.ts
1499
1498
  const CONTINUATION_PROMPT = "Continue from where you stopped. Preserve completed work and avoid repeating it.";
1500
- function isRecord$17(value) {
1499
+ function isRecord$18(value) {
1501
1500
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
1502
1501
  }
1503
1502
  function hasSendableMessageContent(message) {
@@ -1505,7 +1504,7 @@ function hasSendableMessageContent(message) {
1505
1504
  }
1506
1505
  function readSessionActivityState(metadata) {
1507
1506
  const preview = metadata?.[SESSION_ACTIVITY_PREVIEW_METADATA_KEY];
1508
- return isRecord$17(preview) && typeof preview.state === "string" ? preview.state : null;
1507
+ return isRecord$18(preview) && typeof preview.state === "string" ? preview.state : null;
1509
1508
  }
1510
1509
  var AgentRunSessionCommandManager = class {
1511
1510
  pendingCommands = /* @__PURE__ */ new Map();
@@ -2186,7 +2185,7 @@ var AgentRuntimeRunObserverService = class {
2186
2185
  };
2187
2186
  //#endregion
2188
2187
  //#region src/utils/agent-run-trigger.utils.ts
2189
- function isRecord$16(value) {
2188
+ function isRecord$17(value) {
2190
2189
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
2191
2190
  }
2192
2191
  function readOptionalString$12(value) {
@@ -2194,7 +2193,7 @@ function readOptionalString$12(value) {
2194
2193
  }
2195
2194
  function readSourceRunSpec(message) {
2196
2195
  const value = message.metadata?.[AGENT_RUN_MESSAGE_RUN_SPEC_METADATA_KEY];
2197
- return isRecord$16(value) ? value : {};
2196
+ return isRecord$17(value) ? value : {};
2198
2197
  }
2199
2198
  const SOURCE_CONTEXT_KEYS = [
2200
2199
  "channel",
@@ -2699,7 +2698,7 @@ const BUILTIN_RUNTIME_PRESENTATION = {
2699
2698
  }
2700
2699
  }
2701
2700
  };
2702
- function readString$9(value) {
2701
+ function readString$10(value) {
2703
2702
  if (typeof value !== "string") return;
2704
2703
  return value.trim() || void 0;
2705
2704
  }
@@ -2708,7 +2707,7 @@ function readRecord$3(value) {
2708
2707
  return value;
2709
2708
  }
2710
2709
  function resolveBuiltinRuntimePresentation(value) {
2711
- const normalized = readString$9(value)?.toLowerCase();
2710
+ const normalized = readString$10(value)?.toLowerCase();
2712
2711
  if (!normalized) return null;
2713
2712
  if (!(normalized in BUILTIN_RUNTIME_PRESENTATION)) return null;
2714
2713
  return BUILTIN_RUNTIME_PRESENTATION[normalized] ?? null;
@@ -2729,13 +2728,13 @@ function resolveAgentRuntimeEntries(params) {
2729
2728
  entries.set(DEFAULT_AGENT_RUNTIME_ENTRY_ID, buildNativeRuntimeEntry(params.config));
2730
2729
  for (const [rawId, rawEntry] of Object.entries(params.config.agents.runtimes.entries ?? {})) {
2731
2730
  const id = rawId.trim().toLowerCase();
2732
- const type = readString$9(rawEntry.type)?.toLowerCase();
2731
+ const type = readString$10(rawEntry.type)?.toLowerCase();
2733
2732
  if (!id || !type) continue;
2734
2733
  const explicitIcon = normalizeAgentRuntimeSessionTypeIcon(rawEntry.icon);
2735
2734
  const builtinPresentation = resolveBuiltinRuntimePresentation(id) ?? resolveBuiltinRuntimePresentation(type);
2736
2735
  entries.set(id, {
2737
2736
  id,
2738
- label: readString$9(rawEntry.label) ?? builtinPresentation?.label ?? id,
2737
+ label: readString$10(rawEntry.label) ?? builtinPresentation?.label ?? id,
2739
2738
  ...explicitIcon ?? builtinPresentation?.icon ? { icon: explicitIcon ?? builtinPresentation?.icon } : {},
2740
2739
  type,
2741
2740
  enabled: rawEntry.enabled !== false,
@@ -3535,6 +3534,570 @@ var AppPackageOperationManager = class {
3535
3534
  isMissingFileError = (error) => typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
3536
3535
  };
3537
3536
  //#endregion
3537
+ //#region src/types/app-package.types.ts
3538
+ var AppPackageError = class extends Error {
3539
+ constructor(code, message) {
3540
+ super(message);
3541
+ this.code = code;
3542
+ this.name = "AppPackageError";
3543
+ }
3544
+ };
3545
+ function isAppPackageError(error) {
3546
+ return error instanceof AppPackageError;
3547
+ }
3548
+ //#endregion
3549
+ //#region src/utils/service-app-manifest.utils.ts
3550
+ const SERVICE_APP_ID_PATTERN$1 = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
3551
+ const SERVICE_ACTION_RISKS = new Set([
3552
+ "read",
3553
+ "write",
3554
+ "external",
3555
+ "dangerous"
3556
+ ]);
3557
+ const SERVICE_APP_MANIFEST_FILE_NAME = "service-app.json";
3558
+ function getServiceAppManifestPath(dirPath) {
3559
+ return join(dirPath, SERVICE_APP_MANIFEST_FILE_NAME);
3560
+ }
3561
+ async function readServiceAppManifest(dirPath, options) {
3562
+ return parseServiceAppManifest(await readFile(getServiceAppManifestPath(dirPath), "utf8"), options);
3563
+ }
3564
+ function parseServiceAppManifest(raw, options = {}) {
3565
+ let parsed;
3566
+ try {
3567
+ parsed = JSON.parse(raw);
3568
+ } catch (error) {
3569
+ throw new Error(`service-app.json is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
3570
+ }
3571
+ if (!isRecord$16(parsed)) throw new Error("service-app.json must contain an object.");
3572
+ const id = readRequiredString$8(parsed, "id");
3573
+ if (!SERVICE_APP_ID_PATTERN$1.test(id)) throw new Error("service app id must be kebab-case.");
3574
+ const protocol = readOptionalString$11(parsed, "protocol") ?? "mcp";
3575
+ if (protocol !== "mcp" && protocol !== "wasi-component") throw new Error("service app protocol must be mcp or wasi-component.");
3576
+ const launch = protocol === "mcp" ? new AppServiceLaunchService(options.platformTargetService).resolve(parsed, options.target) : void 0;
3577
+ const componentEntry = protocol === "wasi-component" ? readPortableComponentEntry(parsed) : void 0;
3578
+ const lifecycle = readServiceAppLifecycle(parsed.lifecycle);
3579
+ if (lifecycle.mode !== "action" && protocol !== "wasi-component") throw new Error(`${lifecycle.mode} service app lifecycle requires wasi-component protocol.`);
3580
+ const provides = readServiceAppProvides(parsed.provides);
3581
+ if (provides && lifecycle.mode !== "provider") throw new Error("service app provides requires provider lifecycle.");
3582
+ return {
3583
+ id,
3584
+ title: readRequiredString$8(parsed, "title"),
3585
+ description: readOptionalString$11(parsed, "description"),
3586
+ enabled: readOptionalBoolean$2(parsed, "enabled") ?? true,
3587
+ protocol,
3588
+ command: launch?.command,
3589
+ args: launch?.args,
3590
+ componentEntry,
3591
+ providerIds: readProviderIds(parsed.providers),
3592
+ lifecycle,
3593
+ requires: readServiceAppRequirements(parsed.requires),
3594
+ provides,
3595
+ actions: readManifestActions(parsed.actions)
3596
+ };
3597
+ }
3598
+ function readServiceAppProvides(value) {
3599
+ if (value === void 0) return void 0;
3600
+ if (!isRecord$16(value)) throw new Error("service app provides must be an object.");
3601
+ if (value.capabilities === void 0) return void 0;
3602
+ if (!Array.isArray(value.capabilities)) throw new Error("service app provides.capabilities must be an array.");
3603
+ const seen = /* @__PURE__ */ new Set();
3604
+ const capabilities = value.capabilities.map((entry, index) => {
3605
+ if (!isRecord$16(entry)) throw new Error(`service app provides.capabilities[${index}] must be an object.`);
3606
+ const id = readRequirementIdentifier(entry, "id", `provides.capabilities[${index}].id`);
3607
+ const version = readOptionalString$11(entry, "version");
3608
+ if (!version) throw new Error(`service app provides.capabilities[${index}].version is required.`);
3609
+ const key = `${id}@${version}`;
3610
+ if (seen.has(key)) throw new Error(`service app provides.capabilities contains duplicate ${key}.`);
3611
+ seen.add(key);
3612
+ return {
3613
+ id,
3614
+ version,
3615
+ resourceTypes: readCapabilityResourceTypes(entry.resourceTypes, index)
3616
+ };
3617
+ });
3618
+ return capabilities.length > 0 ? { capabilities } : void 0;
3619
+ }
3620
+ function readCapabilityResourceTypes(value, index) {
3621
+ if (value === void 0) return void 0;
3622
+ if (!Array.isArray(value)) throw new Error(`service app provides.capabilities[${index}].resourceTypes must be an array.`);
3623
+ const resourceTypes = value.map((entry, resourceIndex) => {
3624
+ if (typeof entry !== "string" || !entry.trim()) throw new Error(`service app provides.capabilities[${index}].resourceTypes[${resourceIndex}] must be a lowercase identifier.`);
3625
+ return entry.trim();
3626
+ });
3627
+ if (resourceTypes.some((entry) => !/^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)*$/.test(entry))) throw new Error(`service app provides.capabilities[${index}].resourceTypes must contain lowercase identifiers.`);
3628
+ return Array.from(new Set(resourceTypes));
3629
+ }
3630
+ function readServiceAppRequirements(value) {
3631
+ if (value === void 0) return void 0;
3632
+ if (!isRecord$16(value)) throw new Error("service app requires must be an object.");
3633
+ const capabilities = readCapabilityRequirements(value.capabilities);
3634
+ const resources = readResourceRequirements(value.resources);
3635
+ return capabilities || resources ? {
3636
+ capabilities,
3637
+ resources
3638
+ } : void 0;
3639
+ }
3640
+ function readCapabilityRequirements(value) {
3641
+ if (value === void 0) return void 0;
3642
+ if (!Array.isArray(value)) throw new Error("service app requires.capabilities must be an array.");
3643
+ const seen = /* @__PURE__ */ new Set();
3644
+ return value.map((entry, index) => {
3645
+ if (!isRecord$16(entry)) throw new Error(`service app requires.capabilities[${index}] must be an object.`);
3646
+ const id = readRequirementIdentifier(entry, "id", `requires.capabilities[${index}].id`);
3647
+ const version = readOptionalString$11(entry, "version");
3648
+ const key = `${id}@${version ?? "*"}`;
3649
+ if (seen.has(key)) throw new Error(`service app requires.capabilities contains duplicate ${key}.`);
3650
+ seen.add(key);
3651
+ return {
3652
+ id,
3653
+ version,
3654
+ title: readOptionalString$11(entry, "title"),
3655
+ description: readOptionalString$11(entry, "description"),
3656
+ remediation: readExternalRemediation(entry.remediation, `requires.capabilities[${index}]`)
3657
+ };
3658
+ });
3659
+ }
3660
+ function readResourceRequirements(value) {
3661
+ if (value === void 0) return void 0;
3662
+ if (!Array.isArray(value)) throw new Error("service app requires.resources must be an array.");
3663
+ const seen = /* @__PURE__ */ new Set();
3664
+ return value.map((entry, index) => {
3665
+ if (!isRecord$16(entry)) throw new Error(`service app requires.resources[${index}] must be an object.`);
3666
+ const binding = readRequirementIdentifier(entry, "binding", `requires.resources[${index}].binding`);
3667
+ if (seen.has(binding)) throw new Error(`service app requires.resources contains duplicate binding ${binding}.`);
3668
+ seen.add(binding);
3669
+ return {
3670
+ binding,
3671
+ type: readRequirementIdentifier(entry, "type", `requires.resources[${index}].type`),
3672
+ required: readOptionalBoolean$2(entry, "required") ?? true,
3673
+ title: readOptionalString$11(entry, "title"),
3674
+ description: readOptionalString$11(entry, "description"),
3675
+ remediation: readExternalRemediation(entry.remediation, `requires.resources[${index}]`)
3676
+ };
3677
+ });
3678
+ }
3679
+ function readRequirementIdentifier(record, key, fieldName) {
3680
+ const value = readOptionalString$11(record, key);
3681
+ if (!value || !/^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)*$/.test(value)) throw new Error(`service app ${fieldName} must be a lowercase identifier.`);
3682
+ return value;
3683
+ }
3684
+ function readExternalRemediation(value, fieldPrefix) {
3685
+ if (value === void 0) return void 0;
3686
+ if (!isRecord$16(value) || value.kind !== "agent-setup") throw new Error(`service app ${fieldPrefix}.remediation.kind must be agent-setup.`);
3687
+ const summary = readOptionalString$11(value, "summary");
3688
+ if (!summary) throw new Error(`service app ${fieldPrefix}.remediation.summary is required.`);
3689
+ return {
3690
+ kind: "agent-setup",
3691
+ summary,
3692
+ requiresUserAction: readOptionalBoolean$2(value, "requiresUserAction")
3693
+ };
3694
+ }
3695
+ function readServiceAppLifecycle(value) {
3696
+ if (value === void 0) return { mode: "action" };
3697
+ if (!isRecord$16(value)) throw new Error("service app lifecycle must be an object.");
3698
+ const mode = readOptionalString$11(value, "mode") ?? "action";
3699
+ if (mode === "action") return { mode };
3700
+ if (mode === "provider") return { mode };
3701
+ if (mode !== "resident") throw new Error("service app lifecycle.mode must be action, resident or provider.");
3702
+ const eventIntervalMs = readOptionalNumber$1(value, "eventIntervalMs");
3703
+ if (eventIntervalMs === void 0 || !Number.isInteger(eventIntervalMs) || eventIntervalMs < 250 || eventIntervalMs > 6e4) throw new Error("resident service app lifecycle.eventIntervalMs must be an integer between 250 and 60000.");
3704
+ return {
3705
+ mode,
3706
+ eventIntervalMs
3707
+ };
3708
+ }
3709
+ function readProviderIds(value) {
3710
+ if (value === void 0) return [];
3711
+ if (!Array.isArray(value)) throw new Error("service app providers must be an array.");
3712
+ const providerIds = value.map((entry) => {
3713
+ if (typeof entry !== "string" || !SERVICE_APP_ID_PATTERN$1.test(entry.trim())) throw new Error("service app providers must contain kebab-case service app ids.");
3714
+ return entry.trim();
3715
+ });
3716
+ return Array.from(new Set(providerIds));
3717
+ }
3718
+ function readPortableComponentEntry(record) {
3719
+ const component = record.component;
3720
+ if (!isRecord$16(component)) throw new Error("service app component is required for wasi-component.");
3721
+ const entry = readRequiredString$8(component, "entry").replace(/\\/g, "/");
3722
+ if (path.isAbsolute(entry) || entry.includes("\0") || entry.split("/").includes("..")) throw new Error("service app component.entry must be a package-relative path.");
3723
+ if (!entry.endsWith(".wasm")) throw new Error("service app component.entry must reference a .wasm file.");
3724
+ return entry;
3725
+ }
3726
+ function readManifestActions(value) {
3727
+ if (value === void 0) throw new Error("service app actions are required.");
3728
+ if (!isRecord$16(value)) throw new Error("service app actions must be an object.");
3729
+ if (Object.keys(value).length === 0) throw new Error("service app actions cannot be empty.");
3730
+ const actions = {};
3731
+ for (const [name, action] of Object.entries(value)) {
3732
+ if (!name.trim()) throw new Error("service app action name cannot be empty.");
3733
+ if (!isRecord$16(action)) throw new Error(`service app action ${name} must be an object.`);
3734
+ const risk = readOptionalString$11(action, "risk");
3735
+ if (risk !== void 0 && !SERVICE_ACTION_RISKS.has(risk)) throw new Error(`service app action ${name} has invalid risk.`);
3736
+ const inputSchema = action.inputSchema;
3737
+ if (inputSchema !== void 0 && !isRecord$16(inputSchema)) throw new Error(`service app action ${name} inputSchema must be an object.`);
3738
+ const timeoutMs = readOptionalNumber$1(action, "timeoutMs");
3739
+ if (timeoutMs !== void 0 && (!Number.isInteger(timeoutMs) || timeoutMs < 100 || timeoutMs > 3e5)) throw new Error(`service app action ${name} timeoutMs must be an integer between 100 and 300000.`);
3740
+ actions[name] = {
3741
+ risk,
3742
+ title: readOptionalString$11(action, "title"),
3743
+ description: readOptionalString$11(action, "description"),
3744
+ inputSchema,
3745
+ timeoutMs
3746
+ };
3747
+ }
3748
+ return actions;
3749
+ }
3750
+ function readRequiredString$8(record, key) {
3751
+ const value = readOptionalString$11(record, key);
3752
+ if (!value) throw new Error(`service app ${key} is required.`);
3753
+ return value;
3754
+ }
3755
+ function readOptionalString$11(record, key) {
3756
+ return typeof record[key] === "string" && record[key].trim() ? record[key].trim() : void 0;
3757
+ }
3758
+ function readOptionalBoolean$2(record, key) {
3759
+ if (record[key] === void 0) return;
3760
+ if (typeof record[key] !== "boolean") throw new Error(`service app ${key} must be boolean.`);
3761
+ return record[key];
3762
+ }
3763
+ function readOptionalNumber$1(record, key) {
3764
+ if (record[key] === void 0) return;
3765
+ if (typeof record[key] !== "number") throw new Error(`service app ${key} must be number.`);
3766
+ return record[key];
3767
+ }
3768
+ function isRecord$16(value) {
3769
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
3770
+ }
3771
+ //#endregion
3772
+ //#region src/services/app-package-dependency.service.ts
3773
+ const STORE_FILE_NAME = "dependencies.json";
3774
+ const LOCK_FILE_NAME = ".dependencies.lock";
3775
+ const IDENTIFIER_PATTERN = /^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)*$/;
3776
+ /**
3777
+ * Owns non-sensitive Consumer-to-Provider bindings. The Provider catalog stays
3778
+ * outside this service, so activation and runtime ownership remain unchanged.
3779
+ */
3780
+ var AppPackageDependencyService = class {
3781
+ fileLockService = new FileLockService();
3782
+ inspect = async (params) => {
3783
+ const [requirements, bindings] = await Promise.all([this.readRequirements(params.target.components), this.readBindings(params.target.storage)]);
3784
+ return this.toView(requirements, bindings, params.providers);
3785
+ };
3786
+ resolveStoredProviderIds = async (target) => {
3787
+ const [requirements, bindings] = await Promise.all([this.readRequirements(target.components), this.readBindings(target.storage)]);
3788
+ const result = {};
3789
+ for (const requirement of requirements) {
3790
+ const binding = this.findBinding(bindings, requirement);
3791
+ if (!binding) continue;
3792
+ const providerIds = result[requirement.componentId] ?? [];
3793
+ if (!providerIds.includes(binding.providerId)) providerIds.push(binding.providerId);
3794
+ result[requirement.componentId] = providerIds;
3795
+ }
3796
+ return result;
3797
+ };
3798
+ listBindings = async (target) => await this.readBindings(target.storage);
3799
+ setup = async (params) => await this.update(params, (requirements, bindings) => {
3800
+ const updated = [...bindings];
3801
+ for (const requirement of requirements) {
3802
+ if (this.findBinding(updated, requirement)) continue;
3803
+ const candidates = this.findCandidates(requirement, params.providers);
3804
+ if (candidates.length === 1) updated.push(this.toBinding(requirement, candidates[0].providerId));
3805
+ }
3806
+ return updated;
3807
+ });
3808
+ bind = async (params) => await this.update(params, (requirements, bindings) => {
3809
+ const requirement = this.requireRequirement(requirements, params.input);
3810
+ const provider = params.providers.find((entry) => entry.providerId === params.input.providerId);
3811
+ if (!provider || !this.providerMatches(requirement, provider)) throw new AppPackageError("APP_PACKAGE_NOT_READY", `Provider ${params.input.providerId} cannot satisfy ${this.requirementKey(requirement)}.`);
3812
+ return [...bindings.filter((entry) => !this.isBindingForRequirement(entry, requirement)), this.toBinding(requirement, provider.providerId)];
3813
+ });
3814
+ unbind = async (params) => await this.update(params, (requirements, bindings) => {
3815
+ const requirement = this.requireRequirement(requirements, params.input);
3816
+ return bindings.filter((entry) => !this.isBindingForRequirement(entry, requirement));
3817
+ });
3818
+ update = async (params, mutate) => await this.fileLockService.withLock(this.lockPath(params.target.storage), async () => {
3819
+ const [requirements, bindings] = await Promise.all([this.readRequirements(params.target.components), this.readBindings(params.target.storage)]);
3820
+ const updated = this.normalizeBindings(mutate(requirements, bindings));
3821
+ await this.writeBindings(params.target.storage, updated);
3822
+ return this.toView(requirements, updated, params.providers);
3823
+ });
3824
+ readRequirements = async (components) => (await Promise.all(components.filter((component) => component.kind === "service").map(async (component) => this.readComponentRequirements(component)))).flat();
3825
+ readComponentRequirements = async (component) => {
3826
+ const manifest = await readServiceAppManifest(component.componentDirectory);
3827
+ return this.toRequirements(component.id, manifest);
3828
+ };
3829
+ toRequirements = (componentId, manifest) => [...(manifest.requires?.capabilities ?? []).map((requirement) => ({
3830
+ componentId,
3831
+ kind: "capability",
3832
+ id: requirement.version ? `${requirement.id}@${requirement.version}` : requirement.id,
3833
+ title: requirement.title ?? requirement.id,
3834
+ description: requirement.description,
3835
+ remediation: requirement.remediation,
3836
+ capabilityId: requirement.id,
3837
+ capabilityVersion: requirement.version
3838
+ })), ...(manifest.requires?.resources ?? []).filter((requirement) => requirement.required !== false).map((requirement) => ({
3839
+ componentId,
3840
+ kind: "resource",
3841
+ id: requirement.binding,
3842
+ title: requirement.title ?? requirement.type,
3843
+ description: requirement.description,
3844
+ remediation: requirement.remediation,
3845
+ resourceType: requirement.type
3846
+ }))];
3847
+ toView = (requirements, bindings, providers) => {
3848
+ const candidates = requirements.map((requirement) => ({
3849
+ requirement: this.toReadinessRequirement(requirement),
3850
+ providers: this.findCandidates(requirement, providers)
3851
+ }));
3852
+ const readinessRequirements = candidates.filter(({ requirement, providers: candidatesForRequirement }) => {
3853
+ const source = this.toRequirement(requirement);
3854
+ const binding = this.findBinding(bindings, source);
3855
+ return !binding || !candidatesForRequirement.some((provider) => provider.providerId === binding.providerId);
3856
+ }).map(({ requirement }) => requirement);
3857
+ return {
3858
+ readiness: this.toReadiness(readinessRequirements),
3859
+ bindings,
3860
+ candidates,
3861
+ resolvedProviderIds: this.toResolvedProviderIds(requirements, bindings, providers)
3862
+ };
3863
+ };
3864
+ toReadiness = (requirements) => ({
3865
+ status: requirements.some((requirement) => requirement.kind === "capability") ? "needs-capability" : requirements.some((requirement) => requirement.kind === "configuration") ? "needs-configuration" : "ready",
3866
+ requirements
3867
+ });
3868
+ toResolvedProviderIds = (requirements, bindings, providers) => {
3869
+ const availableProviderIds = new Set(providers.map((provider) => provider.providerId));
3870
+ const result = {};
3871
+ for (const requirement of requirements) {
3872
+ const binding = this.findBinding(bindings, requirement);
3873
+ if (!binding || !availableProviderIds.has(binding.providerId)) continue;
3874
+ const providerIds = result[requirement.componentId] ?? [];
3875
+ if (!providerIds.includes(binding.providerId)) providerIds.push(binding.providerId);
3876
+ result[requirement.componentId] = providerIds;
3877
+ }
3878
+ return result;
3879
+ };
3880
+ findCandidates = (requirement, providers) => providers.filter((provider) => this.providerMatches(requirement, provider));
3881
+ providerMatches = (requirement, provider) => provider.capabilities.some((capability) => {
3882
+ if (requirement.kind === "capability") return capability.id === requirement.capabilityId && (!requirement.capabilityVersion || capability.version === requirement.capabilityVersion);
3883
+ return capability.resourceTypes?.includes(requirement.resourceType) ?? false;
3884
+ });
3885
+ requireRequirement = (requirements, input) => {
3886
+ const requirement = requirements.find((entry) => entry.componentId === input.componentId && entry.kind === input.requirementKind && entry.id === input.requirementId);
3887
+ if (!requirement) throw new AppPackageError("APP_PACKAGE_NOT_FOUND", `Dependency ${input.componentId}/${input.requirementKind}/${input.requirementId} was not declared by this App.`);
3888
+ return requirement;
3889
+ };
3890
+ toBinding = (requirement, providerId) => ({
3891
+ componentId: requirement.componentId,
3892
+ requirementKind: requirement.kind,
3893
+ requirementId: requirement.id,
3894
+ providerId
3895
+ });
3896
+ findBinding = (bindings, requirement) => bindings.find((entry) => this.isBindingForRequirement(entry, requirement));
3897
+ isBindingForRequirement = (binding, requirement) => binding.componentId === requirement.componentId && binding.requirementKind === requirement.kind && binding.requirementId === requirement.id;
3898
+ requirementKey = (requirement) => `${requirement.componentId}/${requirement.kind}/${requirement.id}`;
3899
+ toRequirement = (requirement) => ({
3900
+ componentId: requirement.componentId,
3901
+ kind: requirement.kind === "capability" ? "capability" : "resource",
3902
+ id: requirement.id,
3903
+ title: requirement.title,
3904
+ description: requirement.description,
3905
+ remediation: requirement.remediation
3906
+ });
3907
+ toReadinessRequirement = (requirement) => ({
3908
+ componentId: requirement.componentId,
3909
+ kind: requirement.kind === "capability" ? "capability" : "configuration",
3910
+ id: requirement.id,
3911
+ title: requirement.title,
3912
+ description: requirement.description,
3913
+ remediation: requirement.remediation
3914
+ });
3915
+ readBindings = async (storage) => {
3916
+ try {
3917
+ const parsed = JSON.parse(await readFile(this.storePath(storage), "utf8"));
3918
+ if (!this.isStore(parsed)) throw new Error("schema");
3919
+ return this.normalizeBindings(parsed.bindings);
3920
+ } catch (error) {
3921
+ if (this.isMissingFileError(error)) return [];
3922
+ throw new AppPackageError("APP_PACKAGE_OPERATION_FAILED", `App dependency bindings are invalid: ${error instanceof Error ? error.message : String(error)}.`);
3923
+ }
3924
+ };
3925
+ writeBindings = async (storage, bindings) => {
3926
+ await mkdir(storage.configDirectory, {
3927
+ recursive: true,
3928
+ mode: 448
3929
+ });
3930
+ const filePath = this.storePath(storage);
3931
+ const stagedPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
3932
+ await writeFile(stagedPath, `${JSON.stringify({
3933
+ schemaVersion: 1,
3934
+ bindings
3935
+ }, null, 2)}\n`, {
3936
+ encoding: "utf8",
3937
+ mode: 384
3938
+ });
3939
+ await rename(stagedPath, filePath);
3940
+ await chmod(filePath, 384);
3941
+ };
3942
+ normalizeBindings = (bindings) => {
3943
+ const unique = /* @__PURE__ */ new Map();
3944
+ for (const binding of bindings) {
3945
+ if (!this.isBinding(binding)) throw new Error("invalid binding");
3946
+ unique.set(`${binding.componentId}/${binding.requirementKind}/${binding.requirementId}`, binding);
3947
+ }
3948
+ return Array.from(unique.values()).sort((left, right) => this.bindingKey(left).localeCompare(this.bindingKey(right)));
3949
+ };
3950
+ bindingKey = (binding) => `${binding.componentId}/${binding.requirementKind}/${binding.requirementId}`;
3951
+ isStore = (value) => Boolean(value) && typeof value === "object" && value.schemaVersion === 1 && Array.isArray(value.bindings);
3952
+ isBinding = (value) => Boolean(value) && typeof value === "object" && IDENTIFIER_PATTERN.test(value.componentId) && (value.requirementKind === "capability" || value.requirementKind === "resource") && typeof value.requirementId === "string" && value.requirementId.length > 0 && IDENTIFIER_PATTERN.test(value.providerId);
3953
+ storePath = (storage) => path.join(storage.configDirectory, STORE_FILE_NAME);
3954
+ lockPath = (storage) => path.join(storage.configDirectory, LOCK_FILE_NAME);
3955
+ isMissingFileError = (error) => typeof error === "object" && error !== null && error.code === "ENOENT";
3956
+ };
3957
+ //#endregion
3958
+ //#region src/services/app-package-dependency-coordinator.service.ts
3959
+ /**
3960
+ * Bridges the package lifecycle owner to dependency persistence. It keeps all
3961
+ * Provider queries and binding mutations on the one dependency service path.
3962
+ */
3963
+ var AppPackageDependencyCoordinator = class {
3964
+ dependencyService;
3965
+ constructor(params) {
3966
+ this.params = params;
3967
+ this.dependencyService = params.dependencyService ?? new AppPackageDependencyService();
3968
+ }
3969
+ inspect = async (appId) => await this.inspectTarget(await this.getTarget(appId));
3970
+ verify = async (appId) => await this.inspect(appId);
3971
+ setup = async (appId) => await this.params.installationService.withAppOperation(appId, async () => {
3972
+ const info = await this.getMutableTargetInfo(appId);
3973
+ return await this.dependencyService.setup({
3974
+ target: this.targetForInfo(info),
3975
+ providers: await this.params.listCapabilityProviders()
3976
+ });
3977
+ });
3978
+ bind = async (appId, input) => await this.params.installationService.withAppOperation(appId, async () => {
3979
+ const info = await this.getMutableTargetInfo(appId);
3980
+ return await this.dependencyService.bind({
3981
+ target: this.targetForInfo(info),
3982
+ providers: await this.params.listCapabilityProviders(),
3983
+ input
3984
+ });
3985
+ });
3986
+ unbind = async (appId, input) => await this.params.installationService.withAppOperation(appId, async () => {
3987
+ const info = await this.getMutableTargetInfo(appId);
3988
+ return await this.dependencyService.unbind({
3989
+ target: this.targetForInfo(info),
3990
+ providers: await this.params.listCapabilityProviders(),
3991
+ input
3992
+ });
3993
+ });
3994
+ inspectTarget = async (target, providers) => await this.dependencyService.inspect({
3995
+ target,
3996
+ providers: providers ?? await this.params.listCapabilityProviders()
3997
+ });
3998
+ resolveStoredProviderIds = async (target) => await this.dependencyService.resolveStoredProviderIds(target);
3999
+ listBindings = async (target) => await this.dependencyService.listBindings(target);
4000
+ listActiveComponentSourcesWithDiagnostics = async () => {
4001
+ const records = await this.params.registryService.listApps();
4002
+ const results = await Promise.all(records.filter((record) => record.enabled).map(async (record) => {
4003
+ try {
4004
+ await this.params.installationService.assertVersionIntegrity(record.appId, record.activeVersion);
4005
+ const version = record.installedVersions[record.activeVersion];
4006
+ if (!version || version.manifestSchemaVersion !== 2) return { sources: [] };
4007
+ const target = this.target({
4008
+ appId: record.appId,
4009
+ storage: record.defaultInstance.storage,
4010
+ components: version.components ?? []
4011
+ });
4012
+ const resolvedProviderIds = await this.resolveStoredProviderIds(target);
4013
+ return { sources: (version.components ?? []).map((component) => ({
4014
+ kind: component.kind,
4015
+ id: component.id,
4016
+ packageId: record.appId,
4017
+ packageVersion: record.activeVersion,
4018
+ sourcePath: component.componentDirectory,
4019
+ manifestPath: component.manifestPath,
4020
+ dataDirectory: record.dataDirectory,
4021
+ instanceId: record.defaultInstance.id,
4022
+ storage: record.defaultInstance.storage,
4023
+ resolvedProviderIds: resolvedProviderIds[component.id],
4024
+ ...this.params.resolveSecurity(version, record.appId)
4025
+ })) };
4026
+ } catch (error) {
4027
+ return {
4028
+ sources: [],
4029
+ unavailablePackage: {
4030
+ appId: record.appId,
4031
+ message: error instanceof Error ? error.message : String(error)
4032
+ }
4033
+ };
4034
+ }
4035
+ }));
4036
+ return {
4037
+ sources: results.flatMap((result) => result.sources),
4038
+ unavailablePackages: results.flatMap((result) => result.unavailablePackage ?? [])
4039
+ };
4040
+ };
4041
+ assertNoEnabledDependents = async (provider) => {
4042
+ const providerIds = new Set(provider.components.filter((component) => component.kind === "service").map((component) => component.id));
4043
+ if (providerIds.size === 0) return;
4044
+ const records = await this.params.registryService.listApps();
4045
+ const dependentAppIds = (await Promise.all(records.filter((record) => record.enabled && record.appId !== provider.id).map(async (record) => {
4046
+ return (await this.listBindings(this.targetForInfo(await this.params.installationService.info(record.appId)))).some((binding) => providerIds.has(binding.providerId)) ? record.appId : void 0;
4047
+ }))).filter((entry) => Boolean(entry));
4048
+ if (dependentAppIds.length === 0) return;
4049
+ throw new AppPackageError("APP_PACKAGE_CONFLICT", `App ${provider.id} is required by enabled App${dependentAppIds.length === 1 ? "" : "s"}: ${dependentAppIds.join(", ")}. Unbind or disable those Apps first.`);
4050
+ };
4051
+ targetForInfo = (info, selectedVersion = info.activeVersion) => {
4052
+ const version = info.installedVersions.find((entry) => entry.version === selectedVersion);
4053
+ if (!version) throw new AppPackageError("APP_PACKAGE_OPERATION_FAILED", `应用 ${info.appId} 缺少版本 ${selectedVersion}。`);
4054
+ return this.target({
4055
+ appId: info.appId,
4056
+ storage: info.storage,
4057
+ components: version.components ?? []
4058
+ });
4059
+ };
4060
+ target = (params) => ({
4061
+ appId: params.appId,
4062
+ storage: params.storage,
4063
+ components: params.components.map((component) => ({
4064
+ id: component.id,
4065
+ kind: component.kind,
4066
+ componentDirectory: component.componentDirectory
4067
+ }))
4068
+ });
4069
+ assertReadyToEnable = (app) => {
4070
+ if (app.readiness.status === "ready") return;
4071
+ const required = app.readiness.requirements.map((requirement) => requirement.title).join(", ");
4072
+ const reason = app.readiness.status === "needs-capability" ? "an additional capability is required" : "external service setup is required";
4073
+ throw new AppPackageError("APP_PACKAGE_NOT_READY", `App ${app.id} is not ready to enable: ${reason}${required ? ` (${required})` : ""}.`);
4074
+ };
4075
+ getTarget = async (appId) => this.targetForInfo(await this.params.installationService.info(appId));
4076
+ getMutableTargetInfo = async (appId) => {
4077
+ const info = await this.params.installationService.info(appId);
4078
+ if (info.enabled) throw new AppPackageError("APP_PACKAGE_CONFLICT", `App ${appId} must be disabled before changing dependency bindings.`);
4079
+ return info;
4080
+ };
4081
+ };
4082
+ //#endregion
4083
+ //#region src/services/app-package-host-target.service.ts
4084
+ var AppPackageHostTargetService = class {
4085
+ platformTargetService = new AppPlatformTargetService();
4086
+ read = () => {
4087
+ try {
4088
+ const target = this.platformTargetService.readHostTarget();
4089
+ return {
4090
+ key: this.platformTargetService.toTargetKey(target),
4091
+ operatingSystem: target.os,
4092
+ architecture: target.arch,
4093
+ ...target.os === "darwin" ? {} : { abi: target.abi }
4094
+ };
4095
+ } catch {
4096
+ return;
4097
+ }
4098
+ };
4099
+ };
4100
+ //#endregion
3538
4101
  //#region src/services/app-package-presentation.service.ts
3539
4102
  var AppPackagePresentationService = class {
3540
4103
  readManifest = async (manifestPath) => {
@@ -3583,6 +4146,7 @@ var AppPackagePresentationService = class {
3583
4146
  //#endregion
3584
4147
  //#region src/services/app-package-runtime-activation.service.ts
3585
4148
  const EMPTY_APP_PACKAGE_RUNTIME_HOOKS = {
4149
+ listCapabilityProviders: async () => [],
3586
4150
  assertCanActivate: async () => void 0,
3587
4151
  afterActivate: async () => void 0,
3588
4152
  beforeDeactivate: async () => void 0,
@@ -3605,18 +4169,6 @@ var AppPackageRuntimeActivationService = class {
3605
4169
  };
3606
4170
  };
3607
4171
  //#endregion
3608
- //#region src/types/app-package.types.ts
3609
- var AppPackageError = class extends Error {
3610
- constructor(code, message) {
3611
- super(message);
3612
- this.code = code;
3613
- this.name = "AppPackageError";
3614
- }
3615
- };
3616
- function isAppPackageError(error) {
3617
- return error instanceof AppPackageError;
3618
- }
3619
- //#endregion
3620
4172
  //#region src/utils/app-engine-version.utils.ts
3621
4173
  function satisfiesAppEngineVersion(version, range) {
3622
4174
  const parsedVersion = parseVersion(version);
@@ -3726,8 +4278,9 @@ var AppPackageManager = class {
3726
4278
  installationService;
3727
4279
  manifestService = new AppManifestService();
3728
4280
  operationManager;
3729
- platformTargetService = new AppPlatformTargetService();
4281
+ hostTargetService = new AppPackageHostTargetService();
3730
4282
  presentationService = new AppPackagePresentationService();
4283
+ dependencyCoordinator;
3731
4284
  runtimeActivationService = new AppPackageRuntimeActivationService();
3732
4285
  registryService;
3733
4286
  runtimeHooks = EMPTY_APP_PACKAGE_RUNTIME_HOOKS;
@@ -3738,82 +4291,51 @@ var AppPackageManager = class {
3738
4291
  this.appHomeService = new AppHomeService(params.appHomeDirectory);
3739
4292
  this.installationService = new AppInstallationService(this.appHomeService);
3740
4293
  this.registryService = new AppRegistryService(this.appHomeService);
4294
+ this.dependencyCoordinator = new AppPackageDependencyCoordinator({
4295
+ installationService: this.installationService,
4296
+ registryService: this.registryService,
4297
+ listCapabilityProviders: async () => await this.runtimeHooks.listCapabilityProviders(),
4298
+ resolveSecurity: this.resolveSecurity
4299
+ });
3741
4300
  this.operationManager = new AppPackageOperationManager({
3742
4301
  storePath: this.appHomeService.getOperationsPath(),
3743
4302
  execute: this.executeOperation
3744
4303
  });
3745
4304
  }
3746
4305
  installRuntimeHooks = (hooks) => {
3747
- this.runtimeHooks = hooks;
4306
+ this.runtimeHooks = {
4307
+ ...EMPTY_APP_PACKAGE_RUNTIME_HOOKS,
4308
+ ...hooks
4309
+ };
3748
4310
  };
3749
4311
  start = async () => await this.ensureBuiltInPackages();
3750
4312
  listPackages = async (options = {}) => {
3751
- const records = await this.registryService.listApps();
4313
+ const [records, providers] = await Promise.all([this.registryService.listApps(), this.runtimeHooks.listCapabilityProviders()]);
4314
+ const entries = await Promise.all(records.map(async (record) => await this.toPackageView(await this.installationService.info(record.appId, { measureStorageUsage: options.includeStorageUsage !== false }), void 0, providers)));
3752
4315
  return {
3753
- hostTarget: this.readHostTarget(),
3754
- entries: await Promise.all(records.map(async (record) => await this.toPackageView(await this.installationService.info(record.appId, { measureStorageUsage: options.includeStorageUsage !== false }))))
4316
+ hostTarget: this.hostTargetService.read(),
4317
+ entries
3755
4318
  };
3756
4319
  };
3757
- readHostTarget = () => {
3758
- try {
3759
- const target = this.platformTargetService.readHostTarget();
3760
- return {
3761
- key: this.platformTargetService.toTargetKey(target),
3762
- operatingSystem: target.os,
3763
- architecture: target.arch,
3764
- ...target.os === "darwin" ? {} : { abi: target.abi }
3765
- };
3766
- } catch {
3767
- return;
3768
- }
3769
- };
3770
4320
  listInstalledDataOwners = async () => (await this.registryService.listApps()).map((record) => ({
3771
4321
  id: record.appId,
3772
4322
  name: record.name
3773
- }));
3774
- getPackage = async (appId) => {
3775
- try {
3776
- return await this.toPackageView(await this.installationService.info(appId));
3777
- } catch (error) {
3778
- if (error instanceof Error && error.message.includes("未找到已安装应用")) throw new AppPackageError("APP_PACKAGE_NOT_FOUND", error.message);
3779
- throw error;
3780
- }
3781
- };
3782
- listActiveComponentSources = async () => (await this.listActiveComponentSourcesWithDiagnostics()).sources;
3783
- listActiveComponentSourcesWithDiagnostics = async () => {
3784
- const records = await this.registryService.listApps();
3785
- const results = await Promise.all(records.filter((record) => record.enabled).map(async (record) => {
3786
- try {
3787
- await this.installationService.assertVersionIntegrity(record.appId, record.activeVersion);
3788
- const version = record.installedVersions[record.activeVersion];
3789
- if (!version || version.manifestSchemaVersion !== 2) return { sources: [] };
3790
- return { sources: (version.components ?? []).map((component) => ({
3791
- kind: component.kind,
3792
- id: component.id,
3793
- packageId: record.appId,
3794
- packageVersion: record.activeVersion,
3795
- sourcePath: component.componentDirectory,
3796
- manifestPath: component.manifestPath,
3797
- dataDirectory: record.dataDirectory,
3798
- instanceId: record.defaultInstance.id,
3799
- storage: record.defaultInstance.storage,
3800
- ...this.resolveSecurity(version, record.appId)
3801
- })) };
3802
- } catch (error) {
3803
- return {
3804
- sources: [],
3805
- unavailablePackage: {
3806
- appId: record.appId,
3807
- message: error instanceof Error ? error.message : String(error)
3808
- }
3809
- };
3810
- }
3811
- }));
3812
- return {
3813
- sources: results.flatMap((result) => result.sources),
3814
- unavailablePackages: results.flatMap((result) => result.unavailablePackage ?? [])
3815
- };
4323
+ }));
4324
+ getPackage = async (appId) => {
4325
+ try {
4326
+ return await this.toPackageView(await this.installationService.info(appId));
4327
+ } catch (error) {
4328
+ if (error instanceof Error && error.message.includes("未找到已安装应用")) throw new AppPackageError("APP_PACKAGE_NOT_FOUND", error.message);
4329
+ throw error;
4330
+ }
3816
4331
  };
4332
+ inspectDependencies = async (appId) => await this.dependencyCoordinator.inspect(appId);
4333
+ verifyDependencies = async (appId) => await this.dependencyCoordinator.verify(appId);
4334
+ setupDependencies = async (appId) => await this.dependencyCoordinator.setup(appId);
4335
+ bindDependency = async (appId, input) => await this.dependencyCoordinator.bind(appId, input);
4336
+ unbindDependency = async (appId, input) => await this.dependencyCoordinator.unbind(appId, input);
4337
+ listActiveComponentSources = async () => (await this.listActiveComponentSourcesWithDiagnostics()).sources;
4338
+ listActiveComponentSourcesWithDiagnostics = async () => await this.dependencyCoordinator.listActiveComponentSourcesWithDiagnostics();
3817
4339
  listOperations = async () => await this.operationManager.list();
3818
4340
  startOperation = async (input) => {
3819
4341
  await this.ensureBuiltInPackages();
@@ -3832,6 +4354,7 @@ var AppPackageManager = class {
3832
4354
  const app = await this.getPackage(appId);
3833
4355
  await this.installationService.assertVersionIntegrity(appId, app.activeVersion);
3834
4356
  if (app.enabled) return app;
4357
+ this.dependencyCoordinator.assertReadyToEnable(app);
3835
4358
  await this.assertEngineCompatibility(appId);
3836
4359
  const sources = this.toComponentSources(app);
3837
4360
  await this.runtimeHooks.assertCanActivate(sources);
@@ -3857,6 +4380,7 @@ var AppPackageManager = class {
3857
4380
  return await this.installationService.withAppOperation(appId, async () => {
3858
4381
  const app = await this.getPackage(appId);
3859
4382
  if (!app.enabled) return app;
4383
+ await this.dependencyCoordinator.assertNoEnabledDependents(app);
3860
4384
  await this.runtimeHooks.beforeDeactivate(this.toComponentSources(app));
3861
4385
  await this.installationService.setEnabled(appId, false);
3862
4386
  return await this.getPackage(appId);
@@ -3873,12 +4397,14 @@ var AppPackageManager = class {
3873
4397
  package: current,
3874
4398
  result
3875
4399
  };
4400
+ await this.dependencyCoordinator.assertNoEnabledDependents(current);
3876
4401
  let activated = false;
3877
4402
  let deactivated = false;
3878
4403
  try {
3879
4404
  await this.assertEngineCompatibility(appId, result.version);
3880
4405
  const candidate = await this.toPackageView(await this.installationService.info(appId), result.version);
3881
4406
  if (current.enabled) {
4407
+ this.dependencyCoordinator.assertReadyToEnable(candidate);
3882
4408
  await this.runtimeHooks.beforeDeactivate(this.toComponentSources(current));
3883
4409
  deactivated = true;
3884
4410
  await this.runtimeHooks.assertCanActivate(this.toComponentSources(candidate));
@@ -3914,12 +4440,14 @@ var AppPackageManager = class {
3914
4440
  rolledBack: false
3915
4441
  }
3916
4442
  };
4443
+ await this.dependencyCoordinator.assertNoEnabledDependents(current);
3917
4444
  await this.assertEngineCompatibility(appId, version);
3918
4445
  const candidate = await this.toPackageView(await this.installationService.info(appId), version);
3919
4446
  let deactivated = false;
3920
4447
  let result;
3921
4448
  try {
3922
4449
  if (current.enabled) {
4450
+ this.dependencyCoordinator.assertReadyToEnable(candidate);
3923
4451
  await this.runtimeHooks.beforeDeactivate(this.toComponentSources(current));
3924
4452
  deactivated = true;
3925
4453
  await this.runtimeHooks.assertCanActivate(this.toComponentSources(candidate));
@@ -3945,6 +4473,7 @@ var AppPackageManager = class {
3945
4473
  uninstall = async (appId, purgeData) => {
3946
4474
  return await this.installationService.withAppOperation(appId, async () => {
3947
4475
  const current = await this.getPackage(appId);
4476
+ await this.dependencyCoordinator.assertNoEnabledDependents(current);
3948
4477
  if (current.builtIn) await this.registryService.setBuiltInSuppressed(appId, true);
3949
4478
  let rollbackRuntimeState;
3950
4479
  try {
@@ -3985,7 +4514,7 @@ var AppPackageManager = class {
3985
4514
  } });
3986
4515
  }
3987
4516
  };
3988
- toPackageView = async (info, selectedVersion = info.activeVersion) => {
4517
+ toPackageView = async (info, selectedVersion = info.activeVersion, providers) => {
3989
4518
  const activeVersion = info.installedVersions.find((version) => version.version === selectedVersion);
3990
4519
  if (!activeVersion) throw new AppPackageError("APP_PACKAGE_OPERATION_FAILED", `应用 ${info.appId} 缺少版本 ${selectedVersion}。`);
3991
4520
  const packagePresentation = await this.presentationService.readManifest(path.join(activeVersion.installDirectory, "manifest.json"));
@@ -3995,6 +4524,7 @@ var AppPackageManager = class {
3995
4524
  isolation: manifestBundle.manifest.main.kind === "wasi-http-component" ? "host-mediated" : "sandboxed",
3996
4525
  permissions: manifestBundle.manifest.permissions ?? {}
3997
4526
  };
4527
+ const dependencies = await this.dependencyCoordinator.inspectTarget(this.dependencyCoordinator.targetForInfo(info, selectedVersion), providers);
3998
4528
  return {
3999
4529
  id: info.appId,
4000
4530
  name: info.name,
@@ -4020,6 +4550,7 @@ var AppPackageManager = class {
4020
4550
  runtimeProfile: security.runtimeProfile,
4021
4551
  isolation: security.isolation,
4022
4552
  permissions: security.permissions,
4553
+ resolvedProviderIds: dependencies.resolvedProviderIds[component.id],
4023
4554
  ...await this.presentationService.readManifest(component.manifestPath)
4024
4555
  }))),
4025
4556
  dataDirectory: info.dataDirectory,
@@ -4027,7 +4558,9 @@ var AppPackageManager = class {
4027
4558
  storage: info.storage,
4028
4559
  storageUsage: info.storageUsage,
4029
4560
  runtimeProfile: security.runtimeProfile,
4030
- isolation: security.isolation
4561
+ isolation: security.isolation,
4562
+ readiness: dependencies.readiness,
4563
+ dependencies
4031
4564
  };
4032
4565
  };
4033
4566
  resolveSecurity = (version, appId) => {
@@ -4450,6 +4983,18 @@ function readExtensionProcessMemory(pid) {
4450
4983
  pssBytes: toBytes(readMemoryKilobytes(rollup, "Pss"))
4451
4984
  };
4452
4985
  }
4986
+ if (process.platform === "win32") {
4987
+ const rssBytes = Number(execFileSync("powershell.exe", [
4988
+ "-NoProfile",
4989
+ "-NonInteractive",
4990
+ "-Command",
4991
+ `(Get-Process -Id ${pid}).WorkingSet64`
4992
+ ], { encoding: "utf8" }).trim());
4993
+ return {
4994
+ rssBytes: Number.isFinite(rssBytes) ? rssBytes : null,
4995
+ pssBytes: null
4996
+ };
4997
+ }
4453
4998
  const rssKilobytes = Number(execFileSync("ps", [
4454
4999
  "-o",
4455
5000
  "rss=",
@@ -4975,18 +5520,18 @@ var ExtensionLifecycleService = class {
4975
5520
  };
4976
5521
  //#endregion
4977
5522
  //#region src/features/extension-runtime/utils/extension-runtime-payload.utils.ts
4978
- function readString$8(value) {
5523
+ function readString$9(value) {
4979
5524
  return typeof value === "string" && value.trim() ? value.trim() : void 0;
4980
5525
  }
4981
5526
  function readRecord$2(value) {
4982
5527
  return value && typeof value === "object" && !Array.isArray(value) ? value : {};
4983
5528
  }
4984
- function readRequiredString$8(value, name) {
4985
- const trimmed = readString$8(value);
5529
+ function readRequiredString$7(value, name) {
5530
+ const trimmed = readString$9(value);
4986
5531
  if (!trimmed) throw new Error(`${name} is required`);
4987
5532
  return trimmed;
4988
5533
  }
4989
- function readOptionalNumber$1(value) {
5534
+ function readOptionalNumber(value) {
4990
5535
  return typeof value === "number" && Number.isFinite(value) ? value : void 0;
4991
5536
  }
4992
5537
  function readTextContent(value) {
@@ -4997,24 +5542,24 @@ function readTextContent(value) {
4997
5542
  function readInboundAttachments(value) {
4998
5543
  if (!Array.isArray(value)) return [];
4999
5544
  return value.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))).map((entry) => ({
5000
- ...readString$8(entry.id) ? { id: readString$8(entry.id) } : {},
5001
- ...readString$8(entry.name) ? { name: readString$8(entry.name) } : {},
5002
- ...readString$8(entry.path) ? { path: readString$8(entry.path) } : {},
5003
- ...readString$8(entry.url) ? { url: readString$8(entry.url) } : {},
5004
- ...readString$8(entry.assetUri) ? { assetUri: readString$8(entry.assetUri) } : {},
5005
- ...readString$8(entry.mimeType) ? { mimeType: readString$8(entry.mimeType) } : {},
5006
- ...readOptionalNumber$1(entry.size) !== void 0 ? { size: readOptionalNumber$1(entry.size) } : {},
5007
- ...readString$8(entry.source) ? { source: readString$8(entry.source) } : {},
5545
+ ...readString$9(entry.id) ? { id: readString$9(entry.id) } : {},
5546
+ ...readString$9(entry.name) ? { name: readString$9(entry.name) } : {},
5547
+ ...readString$9(entry.path) ? { path: readString$9(entry.path) } : {},
5548
+ ...readString$9(entry.url) ? { url: readString$9(entry.url) } : {},
5549
+ ...readString$9(entry.assetUri) ? { assetUri: readString$9(entry.assetUri) } : {},
5550
+ ...readString$9(entry.mimeType) ? { mimeType: readString$9(entry.mimeType) } : {},
5551
+ ...readOptionalNumber(entry.size) !== void 0 ? { size: readOptionalNumber(entry.size) } : {},
5552
+ ...readString$9(entry.source) ? { source: readString$9(entry.source) } : {},
5008
5553
  ...entry.status === "ready" || entry.status === "remote-only" ? { status: entry.status } : {},
5009
- ...readString$8(entry.errorCode) ? { errorCode: readString$8(entry.errorCode) } : {}
5554
+ ...readString$9(entry.errorCode) ? { errorCode: readString$9(entry.errorCode) } : {}
5010
5555
  }));
5011
5556
  }
5012
5557
  function toInboundMessage(value) {
5013
5558
  const payload = readRecord$2(value);
5014
5559
  return {
5015
- channel: readRequiredString$8(payload.channelId, "channelId"),
5016
- chatId: readRequiredString$8(payload.conversationId, "conversationId"),
5017
- senderId: readRequiredString$8(payload.senderId, "senderId"),
5560
+ channel: readRequiredString$7(payload.channelId, "channelId"),
5561
+ chatId: readRequiredString$7(payload.conversationId, "conversationId"),
5562
+ senderId: readRequiredString$7(payload.senderId, "senderId"),
5018
5563
  content: readTextContent(payload.content),
5019
5564
  timestamp: /* @__PURE__ */ new Date(),
5020
5565
  attachments: readInboundAttachments(payload.attachments),
@@ -5036,10 +5581,10 @@ function normalizeAuthPollResult(value) {
5036
5581
  if (!value) return null;
5037
5582
  const record = readRecord$2(value);
5038
5583
  return {
5039
- channel: readRequiredString$8(record.channel, "channel"),
5584
+ channel: readRequiredString$7(record.channel, "channel"),
5040
5585
  status: record.status,
5041
- message: readString$8(record.message),
5042
- nextPollMs: readOptionalNumber$1(record.nextPollMs),
5586
+ message: readString$9(record.message),
5587
+ nextPollMs: readOptionalNumber(record.nextPollMs),
5043
5588
  accountId: typeof record.accountId === "string" ? record.accountId : null,
5044
5589
  notes: Array.isArray(record.notes) ? record.notes.filter((note) => typeof note === "string") : [],
5045
5590
  channelConfig: normalizeChannelConfigResult(record)
@@ -5068,16 +5613,16 @@ var ExtensionIngressDiagnosticsService = class {
5068
5613
  recordExtensionEvent = (input, credential) => {
5069
5614
  const payload = readRecord$2(input);
5070
5615
  this.diagnostics.record({
5071
- domain: readRequiredString$8(payload.domain, "domain"),
5072
- event: readRequiredString$8(payload.event, "event"),
5073
- component: readRequiredString$8(payload.component, "component"),
5074
- outcome: readRequiredString$8(payload.outcome, "outcome"),
5075
- correlationId: readString$8(payload.correlationId),
5076
- parentCorrelationId: readString$8(payload.parentCorrelationId),
5077
- reasonCode: readString$8(payload.reasonCode),
5078
- providerCode: readString$8(payload.providerCode),
5079
- durationMs: readOptionalNumber$1(payload.durationMs),
5080
- attempt: readOptionalNumber$1(payload.attempt),
5616
+ domain: readRequiredString$7(payload.domain, "domain"),
5617
+ event: readRequiredString$7(payload.event, "event"),
5618
+ component: readRequiredString$7(payload.component, "component"),
5619
+ outcome: readRequiredString$7(payload.outcome, "outcome"),
5620
+ correlationId: readString$9(payload.correlationId),
5621
+ parentCorrelationId: readString$9(payload.parentCorrelationId),
5622
+ reasonCode: readString$9(payload.reasonCode),
5623
+ providerCode: readString$9(payload.providerCode),
5624
+ durationMs: readOptionalNumber(payload.durationMs),
5625
+ attempt: readOptionalNumber(payload.attempt),
5081
5626
  facts: {
5082
5627
  ...readRecord$2(payload.facts),
5083
5628
  extensionId: credential.extensionId,
@@ -5207,7 +5752,7 @@ var ExtensionAuthLeaseService = class {
5207
5752
  }
5208
5753
  updateAfterRequest = async (params, value, generation) => {
5209
5754
  const { extensionId, kind, payload } = params;
5210
- const channelId = readRequiredString$8(payload.channelId, "channelId");
5755
+ const channelId = readRequiredString$7(payload.channelId, "channelId");
5211
5756
  if (kind === "channel.auth.start") {
5212
5757
  await this.retainSession({
5213
5758
  extensionId,
@@ -5217,14 +5762,14 @@ var ExtensionAuthLeaseService = class {
5217
5762
  return;
5218
5763
  }
5219
5764
  if (kind === "channel.auth.poll") {
5220
- const sessionId = readRequiredString$8(payload.sessionId, "sessionId");
5765
+ const sessionId = readRequiredString$7(payload.sessionId, "sessionId");
5221
5766
  const result = value ? readRecord$2(value) : null;
5222
- const status = result ? readString$8(result.status) : null;
5767
+ const status = result ? readString$9(result.status) : null;
5223
5768
  if (status === "authorized") await this.retainHandoff(extensionId, channelId, generation);
5224
5769
  if (!result || status === "authorized" || status === "expired" || status === "error") this.releaseSession(extensionId, sessionId);
5225
5770
  return;
5226
5771
  }
5227
- if (kind === "channel.auth.connect" && readString$8(readRecord$2(value).status) === "authorized") {
5772
+ if (kind === "channel.auth.connect" && readString$9(readRecord$2(value).status) === "authorized") {
5228
5773
  await this.retainHandoff(extensionId, channelId, generation);
5229
5774
  return;
5230
5775
  }
@@ -5253,8 +5798,8 @@ var ExtensionAuthLeaseService = class {
5253
5798
  retainSession = async (params) => {
5254
5799
  const { extensionId, generation } = params;
5255
5800
  const result = readRecord$2(params.result);
5256
- const sessionId = readRequiredString$8(result.sessionId, "sessionId");
5257
- const expiresAt = readRequiredString$8(result.expiresAt, "expiresAt");
5801
+ const sessionId = readRequiredString$7(result.sessionId, "sessionId");
5802
+ const expiresAt = readRequiredString$7(result.expiresAt, "expiresAt");
5258
5803
  const expiresAtMs = Date.parse(expiresAt);
5259
5804
  if (!Number.isFinite(expiresAtMs) || expiresAtMs <= Date.now()) throw new Error("Extension auth session has an invalid expiresAt");
5260
5805
  const endpoint = this.requireEndpoint("auth start");
@@ -5343,7 +5888,7 @@ const BUILTIN_EXTENSION_PACKAGES = [
5343
5888
  "@nextclaw/channel-extension-weixin"
5344
5889
  ];
5345
5890
  const runtimeRequire = createRequire(import.meta.url);
5346
- function readString$7(value) {
5891
+ function readString$8(value) {
5347
5892
  return typeof value === "string" && value.trim() ? value.trim() : void 0;
5348
5893
  }
5349
5894
  function readStringArray$1(value) {
@@ -5392,16 +5937,16 @@ function toManifest(value, rootDir) {
5392
5937
  if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("extension manifest must be an object");
5393
5938
  const record = value;
5394
5939
  const server = readRecord$1(record.server);
5395
- const id = readString$7(record.id);
5396
- const command = readString$7(server.command);
5940
+ const id = readString$8(record.id);
5941
+ const command = readString$8(server.command);
5397
5942
  if (!id) throw new Error("extension manifest id is required");
5398
5943
  if (server.type !== "stdio") throw new Error("extension server.type must be stdio");
5399
5944
  if (!command) throw new Error("extension server.command is required");
5400
5945
  return {
5401
5946
  id,
5402
5947
  rootDir,
5403
- ...readString$7(record.name) ? { name: readString$7(record.name) } : {},
5404
- ...readString$7(record.version) ? { version: readString$7(record.version) } : {},
5948
+ ...readString$8(record.name) ? { name: readString$8(record.name) } : {},
5949
+ ...readString$8(record.version) ? { version: readString$8(record.version) } : {},
5405
5950
  server: {
5406
5951
  type: "stdio",
5407
5952
  command,
@@ -5585,11 +6130,11 @@ var ExtensionObservationRuntimeService = class {
5585
6130
  };
5586
6131
  handleEvent = async (input) => {
5587
6132
  const payload = readRecord$2(input.payload);
5588
- const subscriptionId = readRequiredString$8(payload.subscriptionId, "subscriptionId");
6133
+ const subscriptionId = readRequiredString$7(payload.subscriptionId, "subscriptionId");
5589
6134
  const event = readRecord$2(payload.event);
5590
- const eventId = readRequiredString$8(event.eventId, "event.eventId");
5591
- const eventType = readRequiredString$8(event.eventType, "event.eventType");
5592
- const occurredAt = readRequiredString$8(event.occurredAt, "event.occurredAt");
6135
+ const eventId = readRequiredString$7(event.eventId, "event.eventId");
6136
+ const eventType = readRequiredString$7(event.eventType, "event.eventType");
6137
+ const occurredAt = readRequiredString$7(event.occurredAt, "event.occurredAt");
5593
6138
  return await this.options.onEvent?.({
5594
6139
  extensionId: input.extensionId,
5595
6140
  subscriptionId,
@@ -5597,13 +6142,13 @@ var ExtensionObservationRuntimeService = class {
5597
6142
  eventId,
5598
6143
  eventType,
5599
6144
  occurredAt,
5600
- observedAt: readString$8(event.observedAt) ?? (/* @__PURE__ */ new Date()).toISOString(),
5601
- ...readString$8(event.cursor) ? { cursor: readString$8(event.cursor) } : {},
5602
- ...readString$8(event.dedupeKey) ? { dedupeKey: readString$8(event.dedupeKey) } : {},
6145
+ observedAt: readString$9(event.observedAt) ?? (/* @__PURE__ */ new Date()).toISOString(),
6146
+ ...readString$9(event.cursor) ? { cursor: readString$9(event.cursor) } : {},
6147
+ ...readString$9(event.dedupeKey) ? { dedupeKey: readString$9(event.dedupeKey) } : {},
5603
6148
  payload: event.payload,
5604
6149
  ...Array.isArray(event.sourceRefs) && event.sourceRefs.every((value) => typeof value === "string") ? { sourceRefs: event.sourceRefs } : {},
5605
- ...readString$8(event.causationId) ? { causationId: readString$8(event.causationId) } : {},
5606
- ...readString$8(event.correlationId) ? { correlationId: readString$8(event.correlationId) } : {}
6150
+ ...readString$9(event.causationId) ? { causationId: readString$9(event.causationId) } : {},
6151
+ ...readString$9(event.correlationId) ? { correlationId: readString$9(event.correlationId) } : {}
5607
6152
  }
5608
6153
  }) ?? { accepted: false };
5609
6154
  };
@@ -5717,16 +6262,32 @@ const SERVICE_APP_ERROR_CODES = new Set([
5717
6262
  "SERVICE_APP_MANAGED_SOURCE",
5718
6263
  "SERVICE_APP_NOT_FOUND",
5719
6264
  "SERVICE_APP_READ_FAILED",
5720
- "SERVICE_APP_RUNTIME_FAILED"
6265
+ "SERVICE_APP_RUNTIME_FAILED",
6266
+ "WASI_ABI_VERSION_MISMATCH",
6267
+ "WASI_CAPABILITY_DENIED",
6268
+ "WASI_COMPONENT_FAILED",
6269
+ "WASI_COMPONENT_TRAP",
6270
+ "WASI_GUEST_EXPORT_MISSING",
6271
+ "WASI_INPUT_SCHEMA_MISMATCH"
5721
6272
  ]);
5722
6273
  var ServiceAppError = class extends Error {
5723
- constructor(code, message) {
6274
+ constructor(code, message, details) {
5724
6275
  super(message);
5725
6276
  this.code = code;
6277
+ this.details = details;
5726
6278
  this.name = "ServiceAppError";
5727
6279
  }
5728
6280
  };
5729
6281
  const isServiceAppError = (error) => error instanceof ServiceAppError || typeof error === "object" && error !== null && error.name === "ServiceAppError" && typeof error.message === "string" && SERVICE_APP_ERROR_CODES.has(error.code);
6282
+ const toServiceAppRuntimeError = (error, appId, actionName) => {
6283
+ if (isPortableWasiError(error)) return new ServiceAppError(error.code, error.message, error.details);
6284
+ return new ServiceAppError("SERVICE_APP_RUNTIME_FAILED", `Service App ${appId} action ${actionName} failed: ${error instanceof Error ? error.message : String(error)}`);
6285
+ };
6286
+ const isPortableWasiError = (error) => {
6287
+ if (typeof error !== "object" || error === null) return false;
6288
+ const candidate = error;
6289
+ return candidate.name === "PortableServiceRunnerError" && typeof candidate.message === "string" && typeof candidate.code === "string" && candidate.code.startsWith("WASI_") && SERVICE_APP_ERROR_CODES.has(candidate.code);
6290
+ };
5730
6291
  //#endregion
5731
6292
  //#region src/utils/service-action.utils.ts
5732
6293
  const DEFAULT_SERVICE_ACTION_RISK = "dangerous";
@@ -6650,10 +7211,10 @@ var DesktopNodeReplService = class {
6650
7211
  }
6651
7212
  async handleWorkerMessage(key, worker, value) {
6652
7213
  const message = asRecord$1(value);
6653
- const executionId = readString$6(message.executionId);
7214
+ const executionId = readString$7(message.executionId);
6654
7215
  if (!executionId || !worker.pending.has(executionId)) return;
6655
7216
  if (message.type === "desktop.request") {
6656
- const requestId = readString$6(message.requestId);
7217
+ const requestId = readString$7(message.requestId);
6657
7218
  if (!requestId) return;
6658
7219
  try {
6659
7220
  const result = await this.invokeDesktop(worker.pending.get(executionId).caller, message.operation, message.args);
@@ -6684,7 +7245,7 @@ var DesktopNodeReplService = class {
6684
7245
  }
6685
7246
  if (message.type === "error") {
6686
7247
  const error = asRecord$1(message.error);
6687
- this.reject(worker, executionId, replError(readString$6(error.code) ?? "repl_execution_failed", readString$6(error.message) ?? "node_repl execution failed."));
7248
+ this.reject(worker, executionId, replError(readString$7(error.code) ?? "repl_execution_failed", readString$7(error.message) ?? "node_repl execution failed."));
6688
7249
  return;
6689
7250
  }
6690
7251
  this.stopWorker(key, worker);
@@ -6830,7 +7391,7 @@ function readKeyboardModifiers(value) {
6830
7391
  if (new Set(value).size !== value.length) throw replError("invalid_tool_arguments", "modifiers cannot contain duplicates.");
6831
7392
  return value;
6832
7393
  }
6833
- function readString$6(value) {
7394
+ function readString$7(value) {
6834
7395
  return typeof value === "string" && value.trim() ? value : void 0;
6835
7396
  }
6836
7397
  function trimOutput(value) {
@@ -7298,12 +7859,12 @@ var ExtensionDesktopRuntimeService = class {
7298
7859
  return await this.capabilities.invoke({
7299
7860
  extensionId: credential.extensionId,
7300
7861
  generation: credential.generation,
7301
- method: readRequiredString$8(payload.method, "method"),
7862
+ method: readRequiredString$7(payload.method, "method"),
7302
7863
  payload: readRecord$2(payload.payload),
7303
7864
  caller: {
7304
- ...readString$8(caller.sessionId) ? { sessionId: readString$8(caller.sessionId) } : {},
7305
- ...readString$8(caller.agentRunId) ? { agentRunId: readString$8(caller.agentRunId) } : {},
7306
- ...readString$8(caller.subscriptionId) ? { subscriptionId: readString$8(caller.subscriptionId) } : {}
7865
+ ...readString$9(caller.sessionId) ? { sessionId: readString$9(caller.sessionId) } : {},
7866
+ ...readString$9(caller.agentRunId) ? { agentRunId: readString$9(caller.agentRunId) } : {},
7867
+ ...readString$9(caller.subscriptionId) ? { subscriptionId: readString$9(caller.subscriptionId) } : {}
7307
7868
  }
7308
7869
  });
7309
7870
  };
@@ -7444,7 +8005,7 @@ var ExtensionRuntimeService = class {
7444
8005
  const desired = /* @__PURE__ */ new Map();
7445
8006
  const channelConfig = readRecord$2(config.channels);
7446
8007
  for (const manifest of this.manifests) for (const channel of manifest.contributes?.channels ?? []) {
7447
- const channelId = readString$8(channel.id);
8008
+ const channelId = readString$9(channel.id);
7448
8009
  if (!channelId || readRecord$2(channelConfig[channelId]).enabled !== true) continue;
7449
8010
  desired.set(this.persistentLeaseKey(manifest.id, channelId), {
7450
8011
  manifest,
@@ -7488,7 +8049,7 @@ var ExtensionRuntimeService = class {
7488
8049
  };
7489
8050
  handleChannelConfigGet = (envelope, context) => {
7490
8051
  this.assertAuthorized(envelope, context);
7491
- const channelId = readRequiredString$8(readRecord$2(envelope.payload).channelId, "channelId");
8052
+ const channelId = readRequiredString$7(readRecord$2(envelope.payload).channelId, "channelId");
7492
8053
  return { config: this.options.getConfig().channels[channelId] ?? {} };
7493
8054
  };
7494
8055
  handleChannelMessageSubmit = async (envelope, context) => {
@@ -7510,9 +8071,9 @@ var ExtensionRuntimeService = class {
7510
8071
  handleChannelCommandExecute = async (envelope, context) => {
7511
8072
  this.assertAuthorized(envelope, context);
7512
8073
  const payload = readRecord$2(envelope.payload);
7513
- const channel = readRequiredString$8(payload.channelId, "channelId");
7514
- const chatId = readRequiredString$8(payload.conversationId, "conversationId");
7515
- const senderId = readRequiredString$8(payload.senderId, "senderId");
8074
+ const channel = readRequiredString$7(payload.channelId, "channelId");
8075
+ const chatId = readRequiredString$7(payload.conversationId, "conversationId");
8076
+ const senderId = readRequiredString$7(payload.senderId, "senderId");
7516
8077
  const metadata = readRecord$2(payload.metadata);
7517
8078
  const config = this.options.getConfig();
7518
8079
  const registry = new CommandRegistry(config, this.options.sessionManager);
@@ -7521,15 +8082,15 @@ var ExtensionRuntimeService = class {
7521
8082
  channel,
7522
8083
  chatId,
7523
8084
  senderId,
7524
- content: readString$8(payload.rawText) ?? readString$8(payload.commandName) ?? "",
8085
+ content: readString$9(payload.rawText) ?? readString$9(payload.commandName) ?? "",
7525
8086
  timestamp: /* @__PURE__ */ new Date(),
7526
8087
  attachments: [],
7527
8088
  metadata
7528
8089
  },
7529
- forcedAgentId: readString$8(metadata.target_agent_id),
7530
- sessionKeyOverride: readString$8(metadata.session_key_override)
8090
+ forcedAgentId: readString$9(metadata.target_agent_id),
8091
+ sessionKeyOverride: readString$9(metadata.session_key_override)
7531
8092
  });
7532
- const rawText = readString$8(payload.rawText);
8093
+ const rawText = readString$9(payload.rawText);
7533
8094
  if (rawText) return await registry.executeText(rawText, {
7534
8095
  channel,
7535
8096
  chatId,
@@ -7539,7 +8100,7 @@ var ExtensionRuntimeService = class {
7539
8100
  content: "",
7540
8101
  ephemeral: true
7541
8102
  };
7542
- return await registry.execute(readRequiredString$8(payload.commandName, "commandName"), readRecord$2(payload.args), {
8103
+ return await registry.execute(readRequiredString$7(payload.commandName, "commandName"), readRecord$2(payload.args), {
7543
8104
  channel,
7544
8105
  chatId,
7545
8106
  senderId,
@@ -7556,13 +8117,13 @@ var ExtensionRuntimeService = class {
7556
8117
  handleExtensionResponse = (envelope, context) => {
7557
8118
  const credential = this.assertAuthorized(envelope, context);
7558
8119
  const payload = readRecord$2(envelope.payload);
7559
- const requestId = readRequiredString$8(payload.requestId, "requestId");
8120
+ const requestId = readRequiredString$7(payload.requestId, "requestId");
7560
8121
  const pending = this.pendingRequests.get(requestId);
7561
8122
  if (!pending || pending.extensionId !== credential.extensionId || pending.generation !== credential.generation) return { accepted: false };
7562
8123
  this.pendingRequests.delete(requestId);
7563
8124
  pending.cleanup();
7564
8125
  if (payload.ok === false) {
7565
- const message = readString$8(readRecord$2(payload.error).message) ?? "Extension request failed";
8126
+ const message = readString$9(readRecord$2(payload.error).message) ?? "Extension request failed";
7566
8127
  pending.reject(new Error(message));
7567
8128
  return { accepted: true };
7568
8129
  }
@@ -7572,8 +8133,8 @@ var ExtensionRuntimeService = class {
7572
8133
  handleRuntimeReady = async (envelope, context) => {
7573
8134
  const credential = this.assertAuthorized(envelope, context);
7574
8135
  const payload = readRecord$2(envelope.payload);
7575
- const generation = readRequiredString$8(payload.generation, "generation");
7576
- const pid = readOptionalNumber$1(payload.pid);
8136
+ const generation = readRequiredString$7(payload.generation, "generation");
8137
+ const pid = readOptionalNumber(payload.pid);
7577
8138
  if (generation !== credential.generation || pid === void 0 || !Number.isInteger(pid) || pid <= 0) throw new Error("Invalid extension runtime ready payload");
7578
8139
  this.lifecycle.markReady({
7579
8140
  extensionId: credential.extensionId,
@@ -7591,12 +8152,12 @@ var ExtensionRuntimeService = class {
7591
8152
  const endpoint = this.endpoint;
7592
8153
  if (!endpoint) throw new Error("Extension runtime is not started");
7593
8154
  if (kind === "channel.outbound.sendText") {
7594
- const channelId = readRequiredString$8(payload.channelId, "channelId");
8155
+ const channelId = readRequiredString$7(payload.channelId, "channelId");
7595
8156
  if (!this.isChannelEnabled(channelId)) throw new Error(`Channel is disabled: ${channelId}`);
7596
8157
  }
7597
8158
  const manifest = this.findManifest(extensionId);
7598
8159
  const requestId = randomUUID();
7599
- const authSession = kind === "channel.auth.poll" ? this.authLeases.requireSession(extensionId, readRequiredString$8(payload.sessionId, "sessionId")) : null;
8160
+ const authSession = kind === "channel.auth.poll" ? this.authLeases.requireSession(extensionId, readRequiredString$7(payload.sessionId, "sessionId")) : null;
7600
8161
  const requestLease = await this.lifecycle.acquire(manifest, {
7601
8162
  endpoint,
7602
8163
  ...authSession ? { expectedGeneration: authSession.generation } : {},
@@ -7673,9 +8234,9 @@ var ExtensionRuntimeService = class {
7673
8234
  };
7674
8235
  assertAuthorized = (envelope, context) => {
7675
8236
  const credential = this.lifecycle.authenticateCredential({
7676
- extensionId: readString$8(envelope.extensionId) ?? null,
7677
- generation: readString$8(envelope.generation) ?? null,
7678
- token: readString$8(context.token) ?? null
8237
+ extensionId: readString$9(envelope.extensionId) ?? null,
8238
+ generation: readString$9(envelope.generation) ?? null,
8239
+ token: readString$9(context.token) ?? null
7679
8240
  });
7680
8241
  if (!credential) throw new Error("Unauthorized ingress token");
7681
8242
  return credential;
@@ -9147,16 +9708,16 @@ function parsePanelAppFolderManifest(raw) {
9147
9708
  throw new Error(`panel-app.json is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
9148
9709
  }
9149
9710
  if (!isRecord$12(parsed)) throw new Error("panel-app.json must contain an object.");
9150
- const id = readOptionalString$11(parsed, "id");
9711
+ const id = readOptionalString$10(parsed, "id");
9151
9712
  if (id && !PANEL_APP_ID_PATTERN.test(id)) throw new Error("panel app id must be kebab-case.");
9152
9713
  return {
9153
9714
  id,
9154
- title: readRequiredString$7(parsed, "title"),
9155
- description: readOptionalString$11(parsed, "description"),
9156
- icon: readOptionalString$11(parsed, "icon"),
9157
- entry: readRequiredString$7(parsed, "entry"),
9715
+ title: readRequiredString$6(parsed, "title"),
9716
+ description: readOptionalString$10(parsed, "description"),
9717
+ icon: readOptionalString$10(parsed, "icon"),
9718
+ entry: readRequiredString$6(parsed, "entry"),
9158
9719
  capabilities: readStringArray(parsed.capabilities, "capabilities"),
9159
- client: readOptionalBoolean$2(parsed, "client"),
9720
+ client: readOptionalBoolean$1(parsed, "client"),
9160
9721
  serviceActions: readStringArray(parsed.actions, "actions")
9161
9722
  };
9162
9723
  }
@@ -9214,15 +9775,15 @@ function parseTokenList(content) {
9214
9775
  if (!content) return [];
9215
9776
  return [...new Set(content.split(/[,\s]+/).map((entry) => entry.trim()).filter(Boolean))];
9216
9777
  }
9217
- function readRequiredString$7(record, key) {
9218
- const value = readOptionalString$11(record, key);
9778
+ function readRequiredString$6(record, key) {
9779
+ const value = readOptionalString$10(record, key);
9219
9780
  if (!value) throw new Error(`panel app ${key} is required.`);
9220
9781
  return value;
9221
9782
  }
9222
- function readOptionalString$11(record, key) {
9783
+ function readOptionalString$10(record, key) {
9223
9784
  return typeof record[key] === "string" && record[key].trim() ? record[key].trim() : void 0;
9224
9785
  }
9225
- function readOptionalBoolean$2(record, key) {
9786
+ function readOptionalBoolean$1(record, key) {
9226
9787
  const value = record[key];
9227
9788
  if (value === void 0) return false;
9228
9789
  if (typeof value !== "boolean") throw new Error(`panel app ${key} must be a boolean.`);
@@ -11554,12 +12115,12 @@ function parseSkillFrontmatter(raw) {
11554
12115
  const summaryI18n = readLocalizedTextMap(parsed, "summaryi18n", "summary_i18n");
11555
12116
  const descriptionI18n = readLocalizedTextMap(parsed, "descriptioni18n", "description_i18n");
11556
12117
  return {
11557
- name: readString$5(parsed, "name"),
11558
- summary: readString$5(parsed, "summary"),
11559
- summaryI18n: mergeLocalizedTextMap(summaryI18n, { zh: readString$5(parsed, "summaryzh", "summary_zh") }),
11560
- description: readString$5(parsed, "description"),
11561
- descriptionI18n: mergeLocalizedTextMap(descriptionI18n, { zh: readString$5(parsed, "descriptionzh", "description_zh") }),
11562
- author: readString$5(parsed, "author"),
12118
+ name: readString$6(parsed, "name"),
12119
+ summary: readString$6(parsed, "summary"),
12120
+ summaryI18n: mergeLocalizedTextMap(summaryI18n, { zh: readString$6(parsed, "summaryzh", "summary_zh") }),
12121
+ description: readString$6(parsed, "description"),
12122
+ descriptionI18n: mergeLocalizedTextMap(descriptionI18n, { zh: readString$6(parsed, "descriptionzh", "description_zh") }),
12123
+ author: readString$6(parsed, "author"),
11563
12124
  tags: readTags(parsed)
11564
12125
  };
11565
12126
  }
@@ -11586,7 +12147,7 @@ function parseYamlFrontmatter(raw) {
11586
12147
  throw new Error(`Invalid SKILL.md frontmatter: ${message}`);
11587
12148
  }
11588
12149
  }
11589
- function readString$5(record, ...names) {
12150
+ function readString$6(record, ...names) {
11590
12151
  const value = readValue(record, names);
11591
12152
  return typeof value === "string" && value.trim() ? value.trim() : void 0;
11592
12153
  }
@@ -11836,11 +12397,11 @@ function createAgentPeerSessionIdentity(params) {
11836
12397
  }
11837
12398
  function resolveAgentPeerScope(params) {
11838
12399
  const metadata = params.metadata ?? {};
11839
- const explicitScope = readOptionalString$10(metadata["agent_peer_scope"]) ?? readOptionalString$10(metadata.agentPeerScope);
12400
+ const explicitScope = readOptionalString$9(metadata["agent_peer_scope"]) ?? readOptionalString$9(metadata.agentPeerScope);
11840
12401
  if (explicitScope) return explicitScope;
11841
- return `agent:${readOptionalString$10(params.agentId) ?? BUILTIN_MAIN_AGENT_ID}:${readOptionalString$10(params.channel) ?? readOptionalString$10(metadata.channel) ?? "agent-run"}:${readOptionalString$10(metadata.accountId) ?? readOptionalString$10(metadata.account_id) ?? "default"}`;
12402
+ return `agent:${readOptionalString$9(params.agentId) ?? BUILTIN_MAIN_AGENT_ID}:${readOptionalString$9(params.channel) ?? readOptionalString$9(metadata.channel) ?? "agent-run"}:${readOptionalString$9(metadata.accountId) ?? readOptionalString$9(metadata.account_id) ?? "default"}`;
11842
12403
  }
11843
- function readOptionalString$10(value) {
12404
+ function readOptionalString$9(value) {
11844
12405
  if (typeof value !== "string") return;
11845
12406
  return value.trim() || void 0;
11846
12407
  }
@@ -12730,7 +13291,7 @@ function applyLimit(items, limit) {
12730
13291
  if (!Number.isFinite(limit) || typeof limit !== "number" || limit <= 0) return items;
12731
13292
  return items.slice(0, Math.trunc(limit));
12732
13293
  }
12733
- function readOptionalString$9(value) {
13294
+ function readOptionalString$8(value) {
12734
13295
  if (typeof value !== "string") return null;
12735
13296
  const trimmed = value.trim();
12736
13297
  return trimmed.length > 0 ? trimmed : null;
@@ -12791,7 +13352,7 @@ function mergeMetadataOverrides(metadata, overrides) {
12791
13352
  }
12792
13353
  function resolveSessionType(params) {
12793
13354
  const { metadata, runtime, sessionType } = params;
12794
- return readOptionalString$9(runtime) ?? readOptionalString$9(metadata.runtime) ?? readOptionalString$9(sessionType) ?? readOptionalString$9(metadata.session_type) ?? "native";
13355
+ return readOptionalString$8(runtime) ?? readOptionalString$8(metadata.runtime) ?? readOptionalString$8(sessionType) ?? readOptionalString$8(metadata.session_type) ?? "native";
12795
13356
  }
12796
13357
  function applySessionOverrides(params) {
12797
13358
  const { lifecycle, metadata, model, parentSessionId, projectRoot, requestId, sessionType, thinkingLevel, title } = params;
@@ -12801,15 +13362,15 @@ function applySessionOverrides(params) {
12801
13362
  metadata[CHILD_SESSION_LIFECYCLE_METADATA_KEY] = lifecycle;
12802
13363
  if (parentSessionId) metadata[CHILD_SESSION_PARENT_METADATA_KEY] = parentSessionId;
12803
13364
  if (requestId) metadata[CHILD_SESSION_REQUEST_METADATA_KEY] = requestId;
12804
- if (readOptionalString$9(model)) {
13365
+ if (readOptionalString$8(model)) {
12805
13366
  metadata.model = model?.trim();
12806
13367
  metadata.preferred_model = model?.trim();
12807
13368
  }
12808
- if (readOptionalString$9(thinkingLevel)) {
13369
+ if (readOptionalString$8(thinkingLevel)) {
12809
13370
  metadata.thinking = thinkingLevel?.trim();
12810
13371
  metadata.preferred_thinking = thinkingLevel?.trim();
12811
13372
  }
12812
- if (readOptionalString$9(projectRoot)) metadata.project_root = projectRoot?.trim();
13373
+ if (readOptionalString$8(projectRoot)) metadata.project_root = projectRoot?.trim();
12813
13374
  }
12814
13375
  //#endregion
12815
13376
  //#region src/app/kernel-storage-paths.ts
@@ -13651,9 +14212,10 @@ var McpServiceAppRuntimeService = class {
13651
14212
  //#region src/services/portable-service-runner-client.service.ts
13652
14213
  const PORTABLE_RUNNER_PROTOCOL_VERSION = "0.1.0";
13653
14214
  var PortableServiceRunnerError = class extends Error {
13654
- constructor(code, message) {
14215
+ constructor(code, message, details) {
13655
14216
  super(message);
13656
14217
  this.code = code;
14218
+ this.details = details;
13657
14219
  this.name = "PortableServiceRunnerError";
13658
14220
  }
13659
14221
  };
@@ -13661,6 +14223,7 @@ var PortableServiceRunnerClientService = class {
13661
14223
  child;
13662
14224
  pending = /* @__PURE__ */ new Map();
13663
14225
  stderrTail = [];
14226
+ lastObservation;
13664
14227
  constructor(params = {}) {
13665
14228
  this.params = params;
13666
14229
  }
@@ -13690,6 +14253,7 @@ var PortableServiceRunnerClientService = class {
13690
14253
  input: event
13691
14254
  }, 7e3);
13692
14255
  stats = async () => this.request({ operation: "stats" }, 2e3);
14256
+ getLastObservation = () => this.lastObservation;
13693
14257
  stop = async (app) => {
13694
14258
  await this.request({
13695
14259
  operation: "stop",
@@ -13736,7 +14300,10 @@ var PortableServiceRunnerClientService = class {
13736
14300
  this.pending.set(requestId, {
13737
14301
  resolve: (value) => resolve(value),
13738
14302
  reject,
13739
- timeout
14303
+ timeout,
14304
+ startedAt: Date.now(),
14305
+ operation: request.operation,
14306
+ appId: request.app?.id
13740
14307
  });
13741
14308
  child.stdin.write(`${JSON.stringify(payload)}\n`, (error) => {
13742
14309
  if (!error) return;
@@ -13799,11 +14366,23 @@ var PortableServiceRunnerClientService = class {
13799
14366
  if (!pending) return;
13800
14367
  clearTimeout(pending.timeout);
13801
14368
  this.pending.delete(response.requestId);
14369
+ const childPid = this.child?.pid ?? null;
14370
+ this.lastObservation = {
14371
+ operation: pending.operation,
14372
+ appId: pending.appId,
14373
+ durationMs: Date.now() - pending.startedAt,
14374
+ runnerPid: childPid,
14375
+ memory: childPid === null ? {
14376
+ rssBytes: null,
14377
+ pssBytes: null
14378
+ } : readExtensionProcessMemory(childPid),
14379
+ logs: [...this.stderrTail]
14380
+ };
13802
14381
  if (response.ok) {
13803
14382
  pending.resolve(response.result);
13804
14383
  return;
13805
14384
  }
13806
- pending.reject(new PortableServiceRunnerError(response.error?.code ?? "PORTABLE_RUNTIME_FAILED", response.error?.message ?? "Portable runner request failed."));
14385
+ pending.reject(new PortableServiceRunnerError(response.error?.code ?? "PORTABLE_RUNTIME_FAILED", response.error?.message ?? "Portable runner request failed.", this.stderrTail.length > 0 ? { logs: [...this.stderrTail] } : void 0));
13807
14386
  };
13808
14387
  handleChildFailure = (error) => {
13809
14388
  this.failAll(error);
@@ -13855,13 +14434,14 @@ var PortableServiceAppRuntimeService = class {
13855
14434
  });
13856
14435
  }
13857
14436
  getStatus = (appId) => this.states.get(appId) ?? { status: "idle" };
14437
+ getLastObservation = () => this.runner.getLastObservation();
13858
14438
  start = async ({ app, manifest }) => {
13859
14439
  if (!app.enabled || !manifest.lifecycle || manifest.lifecycle.mode === "action") return;
13860
14440
  this.persistentRegistrations.set(app.id, {
13861
14441
  app,
13862
14442
  manifest
13863
14443
  });
13864
- const runnerApp = this.toRunnerApp(app, manifest.providerIds);
14444
+ const runnerApp = this.toRunnerApp(app, app.providerIds);
13865
14445
  this.apps.set(app.id, runnerApp);
13866
14446
  if (manifest.lifecycle.mode === "provider") {
13867
14447
  if (this.providers.has(app.id)) return;
@@ -13918,7 +14498,7 @@ var PortableServiceAppRuntimeService = class {
13918
14498
  };
13919
14499
  listActions = async ({ app, manifest }) => {
13920
14500
  if (!app.enabled) return [];
13921
- const runnerApp = this.toRunnerApp(app, manifest.providerIds);
14501
+ const runnerApp = this.toRunnerApp(app, app.providerIds);
13922
14502
  this.apps.set(app.id, runnerApp);
13923
14503
  const persistent = manifest.lifecycle?.mode === "resident" || manifest.lifecycle?.mode === "provider";
13924
14504
  const lastStartedAt = persistent ? this.states.get(app.id)?.lastStartedAt ?? (/* @__PURE__ */ new Date()).toISOString() : (/* @__PURE__ */ new Date()).toISOString();
@@ -13956,7 +14536,7 @@ var PortableServiceAppRuntimeService = class {
13956
14536
  app,
13957
14537
  manifest
13958
14538
  });
13959
- const runnerApp = this.toRunnerApp(app, manifest.providerIds);
14539
+ const runnerApp = this.toRunnerApp(app, app.providerIds);
13960
14540
  this.apps.set(app.id, runnerApp);
13961
14541
  if (manifest.lifecycle?.mode === "resident") try {
13962
14542
  return await this.runner.invoke(runnerApp, actionName, input, manifest.actions[actionName]?.timeoutMs ?? 7e3);
@@ -14083,6 +14663,7 @@ var ServiceAppRuntimeService = class {
14083
14663
  getStatus = (appId) => {
14084
14664
  return this.protocols.get(appId) === "wasi-component" ? this.portable.getStatus(appId) : this.mcp.getStatus(appId);
14085
14665
  };
14666
+ getLastObservation = () => this.portable.getLastObservation();
14086
14667
  start = async (call) => {
14087
14668
  this.protocols.set(call.app.id, call.manifest.protocol);
14088
14669
  if (call.manifest.protocol === "wasi-component") await this.portable.start(call);
@@ -14108,128 +14689,6 @@ var ServiceAppRuntimeService = class {
14108
14689
  };
14109
14690
  };
14110
14691
  //#endregion
14111
- //#region src/utils/service-app-manifest.utils.ts
14112
- const SERVICE_APP_ID_PATTERN$1 = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
14113
- const SERVICE_ACTION_RISKS = new Set([
14114
- "read",
14115
- "write",
14116
- "external",
14117
- "dangerous"
14118
- ]);
14119
- const SERVICE_APP_MANIFEST_FILE_NAME = "service-app.json";
14120
- function getServiceAppManifestPath(dirPath) {
14121
- return join(dirPath, SERVICE_APP_MANIFEST_FILE_NAME);
14122
- }
14123
- async function readServiceAppManifest(dirPath, options) {
14124
- return parseServiceAppManifest(await readFile(getServiceAppManifestPath(dirPath), "utf8"), options);
14125
- }
14126
- function parseServiceAppManifest(raw, options = {}) {
14127
- let parsed;
14128
- try {
14129
- parsed = JSON.parse(raw);
14130
- } catch (error) {
14131
- throw new Error(`service-app.json is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
14132
- }
14133
- if (!isRecord$7(parsed)) throw new Error("service-app.json must contain an object.");
14134
- const id = readRequiredString$6(parsed, "id");
14135
- if (!SERVICE_APP_ID_PATTERN$1.test(id)) throw new Error("service app id must be kebab-case.");
14136
- const protocol = readOptionalString$8(parsed, "protocol") ?? "mcp";
14137
- if (protocol !== "mcp" && protocol !== "wasi-component") throw new Error("service app protocol must be mcp or wasi-component.");
14138
- const launch = protocol === "mcp" ? new AppServiceLaunchService(options.platformTargetService).resolve(parsed, options.target) : void 0;
14139
- const componentEntry = protocol === "wasi-component" ? readPortableComponentEntry(parsed) : void 0;
14140
- const lifecycle = readServiceAppLifecycle(parsed.lifecycle);
14141
- if (lifecycle.mode !== "action" && protocol !== "wasi-component") throw new Error(`${lifecycle.mode} service app lifecycle requires wasi-component protocol.`);
14142
- return {
14143
- id,
14144
- title: readRequiredString$6(parsed, "title"),
14145
- description: readOptionalString$8(parsed, "description"),
14146
- enabled: readOptionalBoolean$1(parsed, "enabled") ?? true,
14147
- protocol,
14148
- command: launch?.command,
14149
- args: launch?.args,
14150
- componentEntry,
14151
- providerIds: readProviderIds(parsed.providers),
14152
- lifecycle,
14153
- actions: readManifestActions(parsed.actions)
14154
- };
14155
- }
14156
- function readServiceAppLifecycle(value) {
14157
- if (value === void 0) return { mode: "action" };
14158
- if (!isRecord$7(value)) throw new Error("service app lifecycle must be an object.");
14159
- const mode = readOptionalString$8(value, "mode") ?? "action";
14160
- if (mode === "action") return { mode };
14161
- if (mode === "provider") return { mode };
14162
- if (mode !== "resident") throw new Error("service app lifecycle.mode must be action, resident or provider.");
14163
- const eventIntervalMs = readOptionalNumber(value, "eventIntervalMs");
14164
- if (eventIntervalMs === void 0 || !Number.isInteger(eventIntervalMs) || eventIntervalMs < 250 || eventIntervalMs > 6e4) throw new Error("resident service app lifecycle.eventIntervalMs must be an integer between 250 and 60000.");
14165
- return {
14166
- mode,
14167
- eventIntervalMs
14168
- };
14169
- }
14170
- function readProviderIds(value) {
14171
- if (value === void 0) return [];
14172
- if (!Array.isArray(value)) throw new Error("service app providers must be an array.");
14173
- const providerIds = value.map((entry) => {
14174
- if (typeof entry !== "string" || !SERVICE_APP_ID_PATTERN$1.test(entry.trim())) throw new Error("service app providers must contain kebab-case service app ids.");
14175
- return entry.trim();
14176
- });
14177
- return Array.from(new Set(providerIds));
14178
- }
14179
- function readPortableComponentEntry(record) {
14180
- const component = record.component;
14181
- if (!isRecord$7(component)) throw new Error("service app component is required for wasi-component.");
14182
- const entry = readRequiredString$6(component, "entry").replace(/\\/g, "/");
14183
- if (path.isAbsolute(entry) || entry.includes("\0") || entry.split("/").includes("..")) throw new Error("service app component.entry must be a package-relative path.");
14184
- if (!entry.endsWith(".wasm")) throw new Error("service app component.entry must reference a .wasm file.");
14185
- return entry;
14186
- }
14187
- function readManifestActions(value) {
14188
- if (value === void 0) throw new Error("service app actions are required.");
14189
- if (!isRecord$7(value)) throw new Error("service app actions must be an object.");
14190
- if (Object.keys(value).length === 0) throw new Error("service app actions cannot be empty.");
14191
- const actions = {};
14192
- for (const [name, action] of Object.entries(value)) {
14193
- if (!name.trim()) throw new Error("service app action name cannot be empty.");
14194
- if (!isRecord$7(action)) throw new Error(`service app action ${name} must be an object.`);
14195
- const risk = readOptionalString$8(action, "risk");
14196
- if (risk !== void 0 && !SERVICE_ACTION_RISKS.has(risk)) throw new Error(`service app action ${name} has invalid risk.`);
14197
- const inputSchema = action.inputSchema;
14198
- if (inputSchema !== void 0 && !isRecord$7(inputSchema)) throw new Error(`service app action ${name} inputSchema must be an object.`);
14199
- const timeoutMs = readOptionalNumber(action, "timeoutMs");
14200
- if (timeoutMs !== void 0 && (!Number.isInteger(timeoutMs) || timeoutMs < 100 || timeoutMs > 3e5)) throw new Error(`service app action ${name} timeoutMs must be an integer between 100 and 300000.`);
14201
- actions[name] = {
14202
- risk,
14203
- title: readOptionalString$8(action, "title"),
14204
- description: readOptionalString$8(action, "description"),
14205
- inputSchema,
14206
- timeoutMs
14207
- };
14208
- }
14209
- return actions;
14210
- }
14211
- function readRequiredString$6(record, key) {
14212
- const value = readOptionalString$8(record, key);
14213
- if (!value) throw new Error(`service app ${key} is required.`);
14214
- return value;
14215
- }
14216
- function readOptionalString$8(record, key) {
14217
- return typeof record[key] === "string" && record[key].trim() ? record[key].trim() : void 0;
14218
- }
14219
- function readOptionalBoolean$1(record, key) {
14220
- if (record[key] === void 0) return;
14221
- if (typeof record[key] !== "boolean") throw new Error(`service app ${key} must be boolean.`);
14222
- return record[key];
14223
- }
14224
- function readOptionalNumber(record, key) {
14225
- if (record[key] === void 0) return;
14226
- if (typeof record[key] !== "number") throw new Error(`service app ${key} must be number.`);
14227
- return record[key];
14228
- }
14229
- function isRecord$7(value) {
14230
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
14231
- }
14232
- //#endregion
14233
14692
  //#region src/services/service-app-lifecycle.service.ts
14234
14693
  var ServiceAppLifecycleService = class {
14235
14694
  constructor(params) {
@@ -14349,7 +14808,7 @@ var ServiceAppRecordService = class {
14349
14808
  runtimeProfile: packageSource?.runtimeProfile ?? "native-process",
14350
14809
  permissions: packageSource?.permissions ?? {},
14351
14810
  componentPath: manifest.componentEntry ? join(dirPath, manifest.componentEntry) : void 0,
14352
- providerIds: manifest.providerIds,
14811
+ providerIds: Array.from(new Set([...manifest.providerIds ?? [], ...packageSource?.resolvedProviderIds ?? []])),
14353
14812
  lifecycle: manifest.lifecycle
14354
14813
  };
14355
14814
  };
@@ -14646,6 +15105,24 @@ var ServiceAppPackageRuntimeService = class {
14646
15105
  };
14647
15106
  };
14648
15107
  //#endregion
15108
+ //#region src/services/service-app-capability-provider.service.ts
15109
+ function projectCapabilityProviders(entries) {
15110
+ return entries.flatMap(({ manifest, record }) => {
15111
+ if (manifest.lifecycle?.mode !== "provider" || !record.enabled || record.status !== "running") return [];
15112
+ const capabilities = manifest.provides?.capabilities ?? [];
15113
+ return capabilities.length === 0 ? [] : [{
15114
+ providerId: record.id,
15115
+ appId: record.packageId ?? record.id,
15116
+ componentId: record.id,
15117
+ capabilities: capabilities.map(({ id, version, resourceTypes }) => ({
15118
+ id,
15119
+ version,
15120
+ resourceTypes
15121
+ }))
15122
+ }];
15123
+ });
15124
+ }
15125
+ //#endregion
14649
15126
  //#region src/utils/service-app-runtime-action.utils.ts
14650
15127
  function listServiceAppManifestActions(record, manifest) {
14651
15128
  if (!record.enabled) return [];
@@ -14736,6 +15213,7 @@ var ServiceAppManager = class {
14736
15213
  entries: [...workspaceEntries, ...packageEntries].filter((entry) => Boolean(entry)).sort((left, right) => left.title.localeCompare(right.title))
14737
15214
  };
14738
15215
  };
15216
+ listCapabilityProviders = async () => projectCapabilityProviders(await this.listValidServiceApps());
14739
15217
  getServiceApp = async (appId) => {
14740
15218
  const { record } = await this.requireServiceApp(appId);
14741
15219
  return record;
@@ -14775,7 +15253,7 @@ var ServiceAppManager = class {
14775
15253
  })
14776
15254
  };
14777
15255
  } catch (error) {
14778
- throw new ServiceAppError("SERVICE_APP_RUNTIME_FAILED", `Service App ${record.id} action ${actionName} failed: ${error instanceof Error ? error.message : String(error)}`);
15256
+ throw toServiceAppRuntimeError(error, record.id, actionName);
14779
15257
  }
14780
15258
  };
14781
15259
  grantServiceAction = async (actionId, request) => {
@@ -14995,7 +15473,7 @@ function hasToolCall(message, toolCallId) {
14995
15473
  return message.parts.some((part) => part.type === "tool-invocation" && part.toolCallId === toolCallId);
14996
15474
  }
14997
15475
  function findContextInheritanceAnchor(params) {
14998
- const anchorToolCallId = readOptionalString$9(params.anchorToolCallId);
15476
+ const anchorToolCallId = readOptionalString$8(params.anchorToolCallId);
14999
15477
  if (!anchorToolCallId) return null;
15000
15478
  const index = params.messages.findIndex((message) => hasToolCall(message, anchorToolCallId));
15001
15479
  const message = index >= 0 ? params.messages[index] : void 0;
@@ -15035,7 +15513,7 @@ function createInheritedContextSnapshot(params) {
15035
15513
  enabled: true,
15036
15514
  sourceSessionId: sourceRecord.sessionId,
15037
15515
  anchorKind: anchor ? "tool_call" : "latest_persisted",
15038
- anchorToolCallId: readOptionalString$9(anchorToolCallId),
15516
+ anchorToolCallId: readOptionalString$8(anchorToolCallId),
15039
15517
  anchorMessageId: anchor?.message.id,
15040
15518
  inheritedMessageCount: messages.length
15041
15519
  }
@@ -15323,7 +15801,7 @@ var SessionWorkingDirResolver = class {
15323
15801
  return this.resolveContext(params).effectiveWorkspace;
15324
15802
  };
15325
15803
  resolveContext = (params) => {
15326
- const profile = this.agentManager.resolveAgentProfile(readOptionalString$9(params.agentId));
15804
+ const profile = this.agentManager.resolveAgentProfile(readOptionalString$8(params.agentId));
15327
15805
  return resolveSessionProjectContext({
15328
15806
  sessionMetadata: params.metadata,
15329
15807
  workspace: profile.workspace
@@ -15371,10 +15849,10 @@ var SessionManager = class {
15371
15849
  const { agentId: requestedAgentId, contextInheritance, metadataOverrides, model, parentSessionId: rawParentSessionId, projectRoot, requestId: rawRequestId, runtime, sessionId: requestedSessionId, sessionType: requestedSessionType, sourceSessionId, sourceSessionMetadata, task, thinkingLevel, title: requestedTitle } = params;
15372
15850
  const sourceRecord = sourceSessionId ? await this.getSessionRecord(sourceSessionId) : null;
15373
15851
  const metadata = cloneInheritedMetadata(sourceSessionMetadata);
15374
- const title = readOptionalString$9(requestedTitle) ?? summarizeTask(task);
15375
- const parentSessionId = readOptionalString$9(rawParentSessionId);
15852
+ const title = readOptionalString$8(requestedTitle) ?? summarizeTask(task);
15853
+ const parentSessionId = readOptionalString$8(rawParentSessionId);
15376
15854
  await assertCanCreateSessionFromLineage(parentSessionId, sourceRecord, metadataOverrides, this.getSessionRecord);
15377
- const requestId = readOptionalString$9(rawRequestId);
15855
+ const requestId = readOptionalString$8(rawRequestId);
15378
15856
  const sessionType = resolveSessionType({
15379
15857
  runtime,
15380
15858
  sessionType: requestedSessionType,
@@ -15400,8 +15878,8 @@ var SessionManager = class {
15400
15878
  if (normalizedProjectRoot) nextMetadata.project_root = normalizedProjectRoot;
15401
15879
  else delete nextMetadata.project_root;
15402
15880
  }
15403
- const agentId = readOptionalString$9(requestedAgentId) ?? readOptionalString$9(sourceRecord?.agentId) ?? BUILTIN_MAIN_AGENT_ID;
15404
- const sessionId = readOptionalString$9(requestedSessionId) ?? buildSessionId();
15881
+ const agentId = readOptionalString$8(requestedAgentId) ?? readOptionalString$8(sourceRecord?.agentId) ?? BUILTIN_MAIN_AGENT_ID;
15882
+ const sessionId = readOptionalString$8(requestedSessionId) ?? buildSessionId();
15405
15883
  const inheritedContext = createSessionContextInheritance({
15406
15884
  childSessionId: sessionId,
15407
15885
  contextInheritance,
@@ -15487,7 +15965,7 @@ var SessionManager = class {
15487
15965
  listSessions = async (options) => {
15488
15966
  const { limit, peerId: rawPeerId } = options ?? {};
15489
15967
  const { journalStore } = this.options;
15490
- const peerId = readOptionalString$9(rawPeerId);
15968
+ const peerId = readOptionalString$8(rawPeerId);
15491
15969
  const summaryReadOptions = !peerId && limit !== void 0 ? { limit } : void 0;
15492
15970
  return applyLimit((await journalStore.listSessionSummaries(summaryReadOptions)).filter((summary) => !peerId || summary.peerId === peerId).map(this.workingDirResolver.withWorkingDir), limit);
15493
15971
  };
@@ -15546,9 +16024,9 @@ var SessionManager = class {
15546
16024
  };
15547
16025
  createAgentRunSession = async (params) => {
15548
16026
  const { agentId, agentRuntimeId: requestedAgentRuntimeId, channel, contextInheritance, metadata, model, parentSessionId: rawParentSessionId, peerId: rawPeerId, projectRoot, sessionId, sourceSessionId: rawSourceSessionId, sourceSessionMetadata: requestedSourceSessionMetadata, task, thinkingEffort } = params;
15549
- const peerId = readOptionalString$9(rawPeerId);
15550
- const parentSessionId = readOptionalString$9(rawParentSessionId);
15551
- const sourceSessionId = readOptionalString$9(rawSourceSessionId);
16027
+ const peerId = readOptionalString$8(rawPeerId);
16028
+ const parentSessionId = readOptionalString$8(rawParentSessionId);
16029
+ const sourceSessionId = readOptionalString$8(rawSourceSessionId);
15552
16030
  const sourceRecord = sourceSessionId ? await this.getSessionRecord(sourceSessionId) : null;
15553
16031
  const sourceSessionMetadata = requestedSourceSessionMetadata ?? sourceRecord?.metadata ?? {};
15554
16032
  const agentRuntimeId = requestedAgentRuntimeId ?? readAgentRuntimeId(sourceSessionMetadata) ?? "native";
@@ -15558,7 +16036,7 @@ var SessionManager = class {
15558
16036
  metadata,
15559
16037
  peerId
15560
16038
  }) : void 0;
15561
- const requestedSessionId = readOptionalString$9(sessionId);
16039
+ const requestedSessionId = readOptionalString$8(sessionId);
15562
16040
  const created = await this.createSession({
15563
16041
  contextInheritance,
15564
16042
  parentSessionId: parentSessionId ?? void 0,
@@ -17149,7 +17627,7 @@ function deduplicateNcpAgentSessionTailMessages(messages) {
17149
17627
  function isNcpAgentSessionMessageProjectionMeta(value, sessionId) {
17150
17628
  if (!value || typeof value !== "object" || Array.isArray(value)) return false;
17151
17629
  const meta = value;
17152
- 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));
17630
+ 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));
17153
17631
  }
17154
17632
  function readActiveAssistantMessageId(messages) {
17155
17633
  for (let index = messages.length - 1; index >= 0; index -= 1) {
@@ -17172,12 +17650,12 @@ function mergePendingCompactionMessageIds(current, messages) {
17172
17650
  }
17173
17651
  return pending;
17174
17652
  }
17175
- function isRecord$6(value) {
17653
+ function isRecord$7(value) {
17176
17654
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
17177
17655
  }
17178
17656
  function readCompactionStatus(message) {
17179
17657
  const checkpoint = message.metadata?.checkpoint;
17180
- if (message.metadata?.nextclaw_timeline_kind !== "context_compaction" || !isRecord$6(checkpoint)) return null;
17658
+ if (message.metadata?.nextclaw_timeline_kind !== "context_compaction" || !isRecord$7(checkpoint)) return null;
17181
17659
  return typeof checkpoint.status === "string" ? checkpoint.status : null;
17182
17660
  }
17183
17661
  //#endregion
@@ -17685,6 +18163,151 @@ async function readLegacyRecords(journalDir, diagnostics) {
17685
18163
  return /* @__PURE__ */ new Map();
17686
18164
  }
17687
18165
  //#endregion
18166
+ //#region src/stores/ncp-session-catalog-sqlite-driver.store.ts
18167
+ const require = createRequire(import.meta.url);
18168
+ const portableDatabases = /* @__PURE__ */ new Map();
18169
+ async function openSessionCatalogSqliteDatabase(databasePath) {
18170
+ try {
18171
+ return new (require("node:sqlite")).DatabaseSync(databasePath);
18172
+ } catch (error) {
18173
+ if (!isMissingNodeSqlite(error)) throw error;
18174
+ return await openPortableDatabase(databasePath);
18175
+ }
18176
+ }
18177
+ function runSessionCatalogSqliteTransaction(database, operation, mode = "DEFERRED") {
18178
+ database.exec(`BEGIN ${mode}`);
18179
+ try {
18180
+ const result = operation();
18181
+ database.exec("COMMIT");
18182
+ return result;
18183
+ } catch (error) {
18184
+ database.exec("ROLLBACK");
18185
+ throw error;
18186
+ }
18187
+ }
18188
+ function isMissingNodeSqlite(error) {
18189
+ const code = typeof error === "object" && error !== null ? error.code : void 0;
18190
+ return code === "ERR_UNKNOWN_BUILTIN_MODULE" || code === "MODULE_NOT_FOUND";
18191
+ }
18192
+ async function openPortableDatabase(databasePath) {
18193
+ const existing = portableDatabases.get(databasePath);
18194
+ if (existing) return (await existing).acquire();
18195
+ const opening = createPortableDatabase(databasePath).catch((error) => {
18196
+ portableDatabases.delete(databasePath);
18197
+ throw error;
18198
+ });
18199
+ portableDatabases.set(databasePath, opening);
18200
+ return (await opening).acquire();
18201
+ }
18202
+ async function createPortableDatabase(databasePath) {
18203
+ const initialize = (await import("sql.js")).default;
18204
+ const sqlite = await initialize({ locateFile: (file) => require.resolve(`sql.js/dist/${file}`) });
18205
+ return new PortableSessionCatalogSqliteDatabase(databasePath, existsSync(databasePath) ? new sqlite.Database(readFileSync(databasePath)) : new sqlite.Database());
18206
+ }
18207
+ var PortableSessionCatalogSqliteDatabase = class {
18208
+ inTransaction = false;
18209
+ dirty = false;
18210
+ leaseCount = 0;
18211
+ constructor(databasePath, database) {
18212
+ this.databasePath = databasePath;
18213
+ this.database = database;
18214
+ }
18215
+ acquire = () => {
18216
+ this.leaseCount += 1;
18217
+ return new PortableSessionCatalogSqliteLease(this);
18218
+ };
18219
+ exec = (sql) => {
18220
+ const command = sql.trim().toUpperCase();
18221
+ this.database.run(sql);
18222
+ if (command.startsWith("BEGIN")) {
18223
+ this.inTransaction = true;
18224
+ return;
18225
+ }
18226
+ if (command.startsWith("ROLLBACK")) {
18227
+ this.inTransaction = false;
18228
+ this.dirty = false;
18229
+ return;
18230
+ }
18231
+ if (command.startsWith("COMMIT")) {
18232
+ this.inTransaction = false;
18233
+ this.persistIfDirty();
18234
+ return;
18235
+ }
18236
+ if (!command.startsWith("PRAGMA")) this.markDirty();
18237
+ };
18238
+ prepare = (sql) => new PortableSessionCatalogSqliteStatement(this, sql);
18239
+ release = () => {
18240
+ this.leaseCount -= 1;
18241
+ if (this.leaseCount > 0) return;
18242
+ this.persistIfDirty();
18243
+ this.database.close();
18244
+ portableDatabases.delete(this.databasePath);
18245
+ };
18246
+ run = (sql, params) => {
18247
+ this.database.run(sql, normalizeParams(params));
18248
+ this.markDirty();
18249
+ };
18250
+ readRows = (sql, params) => {
18251
+ const statement = this.database.prepare(sql);
18252
+ try {
18253
+ bindStatement(statement, params);
18254
+ const rows = [];
18255
+ while (statement.step()) rows.push(statement.getAsObject());
18256
+ return rows;
18257
+ } finally {
18258
+ statement.free();
18259
+ }
18260
+ };
18261
+ markDirty = () => {
18262
+ this.dirty = true;
18263
+ if (!this.inTransaction) this.persistIfDirty();
18264
+ };
18265
+ persistIfDirty = () => {
18266
+ if (!this.dirty) return;
18267
+ const temporaryPath = `${this.databasePath}.tmp`;
18268
+ writeFileSync(temporaryPath, this.database.export());
18269
+ renameSync(temporaryPath, this.databasePath);
18270
+ this.dirty = false;
18271
+ };
18272
+ };
18273
+ var PortableSessionCatalogSqliteLease = class {
18274
+ closed = false;
18275
+ constructor(owner) {
18276
+ this.owner = owner;
18277
+ }
18278
+ close = () => {
18279
+ if (this.closed) return;
18280
+ this.closed = true;
18281
+ this.owner.release();
18282
+ };
18283
+ exec = (sql) => this.owner.exec(sql);
18284
+ prepare = (sql) => this.owner.prepare(sql);
18285
+ };
18286
+ var PortableSessionCatalogSqliteStatement = class {
18287
+ constructor(database, sql) {
18288
+ this.database = database;
18289
+ this.sql = sql;
18290
+ }
18291
+ all = (...params) => this.database.readRows(this.sql, params);
18292
+ get = (...params) => this.all(...params)[0];
18293
+ run = (...params) => {
18294
+ this.database.run(this.sql, params);
18295
+ return {};
18296
+ };
18297
+ };
18298
+ function bindStatement(statement, params) {
18299
+ const normalized = normalizeParams(params);
18300
+ if (Array.isArray(normalized) && normalized.length === 0) return;
18301
+ statement.bind(normalized);
18302
+ }
18303
+ function normalizeParams(params) {
18304
+ if (params.length !== 1 || !isRecord$6(params[0])) return params;
18305
+ return Object.fromEntries(Object.entries(params[0]).map(([key, value]) => [`@${key}`, value]));
18306
+ }
18307
+ function isRecord$6(value) {
18308
+ return typeof value === "object" && value !== null && !Array.isArray(value);
18309
+ }
18310
+ //#endregion
17688
18311
  //#region src/stores/ncp-agent-session-summary-index.store.ts
17689
18312
  const SQLITE_DATABASE_FILE = ".ncp-agent-session-catalog.sqlite";
17690
18313
  const CATALOG_SCHEMA_VERSION = 1;
@@ -17786,16 +18409,17 @@ var NcpAgentSessionSummaryIndexStore = class {
17786
18409
  };
17787
18410
  upsertForEvent = async (params) => {
17788
18411
  await this.ensureReady();
17789
- const sessionId = normalizeNcpSessionId(params.sessionId);
18412
+ const { sessionId: rawSessionId, event, updatedAt } = params;
18413
+ const sessionId = normalizeNcpSessionId(rawSessionId);
17790
18414
  const currentRow = this.db().prepare("SELECT * FROM sessions WHERE session_id = ? LIMIT 1").get(sessionId);
17791
18415
  const row = summaryToRow({
17792
18416
  ...upsertNcpAgentSessionSummaryEvent({
17793
18417
  current: currentRow && currentRow.deleted_at === null ? rowToSummary(currentRow) : void 0,
17794
18418
  sessionId,
17795
- event: params.event,
17796
- updatedAt: params.updatedAt
18419
+ event,
18420
+ updatedAt
17797
18421
  }),
17798
- messageCount: readEventMessage(params.event) ? 1 : 0
18422
+ messageCount: readEventMessage(event) ? 1 : 0
17799
18423
  });
17800
18424
  this.db().prepare(`INSERT INTO sessions (
17801
18425
  session_id, peer_id, agent_id, created_at, updated_at,
@@ -17823,7 +18447,7 @@ var NcpAgentSessionSummaryIndexStore = class {
17823
18447
  await this.ensureReady();
17824
18448
  const normalizedSessionId = normalizeNcpSessionId(sessionId);
17825
18449
  const deletedAt = (/* @__PURE__ */ new Date()).toISOString();
17826
- this.db().transaction(() => {
18450
+ runSessionCatalogSqliteTransaction(this.db(), () => {
17827
18451
  if (this.db().prepare("SELECT session_id FROM sessions WHERE session_id = ? LIMIT 1").get(normalizedSessionId)) {
17828
18452
  this.db().prepare("UPDATE sessions SET deleted_at = ? WHERE session_id = ?").run(deletedAt, normalizedSessionId);
17829
18453
  return;
@@ -17832,7 +18456,7 @@ var NcpAgentSessionSummaryIndexStore = class {
17832
18456
  session_id, created_at, updated_at, message_count,
17833
18457
  status, metadata_json, deleted_at
17834
18458
  ) VALUES (?, ?, ?, 0, 'idle', '{}', ?)`).run(normalizedSessionId, deletedAt, deletedAt, deletedAt);
17835
- })();
18459
+ }, "IMMEDIATE");
17836
18460
  };
17837
18461
  close = () => {
17838
18462
  this.database?.close();
@@ -17848,10 +18472,13 @@ var NcpAgentSessionSummaryIndexStore = class {
17848
18472
  };
17849
18473
  initializeCatalog = async () => {
17850
18474
  await mkdir(this.journalDir, { recursive: true });
17851
- this.database = new Database(resolve(this.journalDir, SQLITE_DATABASE_FILE), { timeout: 1e4 });
17852
- this.database.pragma("journal_mode = WAL");
17853
- this.database.pragma("synchronous = NORMAL");
17854
- this.database.pragma("foreign_keys = ON");
18475
+ this.database = await openSessionCatalogSqliteDatabase(resolve(this.journalDir, SQLITE_DATABASE_FILE));
18476
+ this.database.exec(`
18477
+ PRAGMA busy_timeout = 10000;
18478
+ PRAGMA journal_mode = WAL;
18479
+ PRAGMA synchronous = NORMAL;
18480
+ PRAGMA foreign_keys = ON;
18481
+ `);
17855
18482
  this.database.exec(`
17856
18483
  CREATE TABLE IF NOT EXISTS sessions (
17857
18484
  session_id TEXT PRIMARY KEY,
@@ -17885,22 +18512,22 @@ var NcpAgentSessionSummaryIndexStore = class {
17885
18512
  loadSession: (sessionId) => this.loadSession(sessionId),
17886
18513
  loadSessionSummary: this.loadSessionSummary
17887
18514
  });
17888
- this.db().transaction((result) => {
18515
+ runSessionCatalogSqliteTransaction(this.db(), () => {
17889
18516
  if (this.db().prepare("SELECT value FROM storage_meta WHERE key = ? LIMIT 1").get(MIGRATION_STATUS_KEY)?.value === MIGRATION_COMPLETE) {
17890
- this.reconcileRecords(result.records);
18517
+ this.reconcileRecords(scan.records);
17891
18518
  return;
17892
18519
  }
17893
- for (const record of result.records) {
18520
+ for (const record of scan.records) {
17894
18521
  if (this.db().prepare("SELECT deleted_at FROM sessions WHERE session_id = ? LIMIT 1").get(record.sessionId)?.deleted_at) continue;
17895
18522
  this.writeSummary(summaryToRow(record), false);
17896
18523
  }
17897
- for (const diagnostic of result.diagnostics) this.db().prepare(`INSERT INTO migration_diagnostics (session_id, kind, detail_json, created_at)
18524
+ for (const diagnostic of scan.diagnostics) this.db().prepare(`INSERT INTO migration_diagnostics (session_id, kind, detail_json, created_at)
17898
18525
  VALUES (?, ?, ?, ?)`).run(diagnostic.sessionId ?? null, diagnostic.kind, JSON.stringify(diagnostic.detail), (/* @__PURE__ */ new Date()).toISOString());
17899
18526
  this.db().prepare(`INSERT INTO storage_meta (key, value) VALUES (?, ?)
17900
18527
  ON CONFLICT(key) DO UPDATE SET value = excluded.value`).run(MIGRATION_STATUS_KEY, MIGRATION_COMPLETE);
17901
18528
  this.db().prepare(`INSERT INTO storage_meta (key, value) VALUES ('schema_version', ?)
17902
18529
  ON CONFLICT(key) DO UPDATE SET value = excluded.value`).run(String(CATALOG_SCHEMA_VERSION));
17903
- }).immediate(scan);
18530
+ }, "IMMEDIATE");
17904
18531
  };
17905
18532
  reconcileRecords = (records) => {
17906
18533
  for (const record of records) if (!this.db().prepare("SELECT deleted_at FROM sessions WHERE session_id = ? LIMIT 1").get(record.sessionId)?.deleted_at) this.writeSummary(summaryToRow(record), false);
@@ -18293,14 +18920,14 @@ function readRecord(value) {
18293
18920
  if (!value || typeof value !== "object" || Array.isArray(value)) return;
18294
18921
  return value;
18295
18922
  }
18296
- function readString$4(value) {
18923
+ function readString$5(value) {
18297
18924
  if (typeof value !== "string") return;
18298
18925
  return value.trim() || void 0;
18299
18926
  }
18300
18927
  function resolveRequestedModel(params) {
18301
18928
  const { configuredModel, defaultModel, input, modelSelectionMode, sessionMetadata } = params;
18302
18929
  if (modelSelectionMode === "runtime-default") return;
18303
- const requestedModel = readString$4(readRecord(input.metadata)?.preferred_model) ?? readString$4(readRecord(input.metadata)?.preferredModel) ?? readString$4(readRecord(input.metadata)?.model) ?? readString$4(sessionMetadata.preferred_model) ?? readString$4(sessionMetadata.preferredModel) ?? readString$4(sessionMetadata.model);
18930
+ const requestedModel = readString$5(readRecord(input.metadata)?.preferred_model) ?? readString$5(readRecord(input.metadata)?.preferredModel) ?? readString$5(readRecord(input.metadata)?.model) ?? readString$5(sessionMetadata.preferred_model) ?? readString$5(sessionMetadata.preferredModel) ?? readString$5(sessionMetadata.model);
18304
18931
  if (isRuntimeDefaultModelValue(requestedModel)) return;
18305
18932
  return requestedModel ?? configuredModel ?? (modelSelectionMode === "optional" ? void 0 : defaultModel);
18306
18933
  }
@@ -18546,7 +19173,7 @@ var BuiltinNarpRuntimeProviderService = class {
18546
19173
  input,
18547
19174
  sessionMetadata: runtimeParams.sessionMetadata,
18548
19175
  defaultModel: this.configManager.loadConfig().agents.defaults.model,
18549
- configuredModel: readString$4(config.model),
19176
+ configuredModel: readString$5(config.model),
18550
19177
  modelSelectionMode: normalizeRuntimeModelSelectionMode(config.modelSelectionMode)
18551
19178
  })
18552
19179
  });
@@ -18563,7 +19190,7 @@ var BuiltinNarpRuntimeProviderService = class {
18563
19190
  input,
18564
19191
  sessionMetadata: runtimeParams.sessionMetadata,
18565
19192
  defaultModel: this.configManager.loadConfig().agents.defaults.model,
18566
- configuredModel: readString$4(config.model),
19193
+ configuredModel: readString$5(config.model),
18567
19194
  modelSelectionMode: normalizeRuntimeModelSelectionMode(config.modelSelectionMode)
18568
19195
  })
18569
19196
  });
@@ -19329,7 +19956,7 @@ const MAX_TOTAL_CONTEXT_CHARACTERS$1 = 96e3;
19329
19956
  function isRecord$5(value) {
19330
19957
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
19331
19958
  }
19332
- function readString$3(value) {
19959
+ function readString$4(value) {
19333
19960
  return typeof value === "string" && value.trim() ? value.trim() : null;
19334
19961
  }
19335
19962
  function escapeAttribute$1(value) {
@@ -19342,10 +19969,10 @@ function readConversationExcerpts(metadata) {
19342
19969
  const seen = /* @__PURE__ */ new Set();
19343
19970
  for (const entry of entries) {
19344
19971
  if (!isRecord$5(entry) || entry.kind !== CHAT_CONVERSATION_EXCERPT_TOKEN_KIND) continue;
19345
- const key = readString$3(entry.key);
19346
- const messageId = readString$3(entry.messageId);
19347
- const label = readString$3(entry.label);
19348
- const excerpt = readString$3(entry.excerpt);
19972
+ const key = readString$4(entry.key);
19973
+ const messageId = readString$4(entry.messageId);
19974
+ const label = readString$4(entry.label);
19975
+ const excerpt = readString$4(entry.excerpt);
19349
19976
  const role = entry.role;
19350
19977
  if (!key || !messageId || !label || !excerpt || role !== "assistant" && role !== "user" || excerpt.length > MAX_EXCERPT_CHARACTERS$1 || seen.has(key)) continue;
19351
19978
  seen.add(key);
@@ -20124,7 +20751,7 @@ var WorkspaceReferenceMaterializerService = class {
20124
20751
  function isRecord$2(value) {
20125
20752
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
20126
20753
  }
20127
- function readString$2(value) {
20754
+ function readString$3(value) {
20128
20755
  return typeof value === "string" && value.trim() ? value.trim() : null;
20129
20756
  }
20130
20757
  function readLine(value) {
@@ -20139,19 +20766,19 @@ function readWorkspaceReferences(metadata) {
20139
20766
  for (const rawToken of rawTokens) {
20140
20767
  if (!isRecord$2(rawToken)) continue;
20141
20768
  const kind = rawToken.kind === CHAT_WORKSPACE_FILE_TOKEN_KIND ? CHAT_WORKSPACE_FILE_TOKEN_KIND : rawToken.kind === CHAT_WORKSPACE_DIRECTORY_TOKEN_KIND ? CHAT_WORKSPACE_DIRECTORY_TOKEN_KIND : rawToken.kind === CHAT_WORKSPACE_EXCERPT_TOKEN_KIND ? CHAT_WORKSPACE_EXCERPT_TOKEN_KIND : rawToken.kind === CHAT_PROJECT_TOKEN_KIND ? CHAT_PROJECT_TOKEN_KIND : null;
20142
- const key = readString$2(rawToken.key);
20769
+ const key = readString$3(rawToken.key);
20143
20770
  if (!kind || !key || seen.has(`${kind}:${key}`)) continue;
20144
20771
  seen.add(`${kind}:${key}`);
20145
20772
  if (kind === CHAT_WORKSPACE_EXCERPT_TOKEN_KIND) {
20146
- const path = readString$2(rawToken.path);
20147
- const excerpt = readString$2(rawToken.excerpt);
20773
+ const path = readString$3(rawToken.path);
20774
+ const excerpt = readString$3(rawToken.excerpt);
20148
20775
  if (!path || !excerpt) continue;
20149
20776
  references.push({
20150
20777
  kind,
20151
20778
  key,
20152
20779
  path,
20153
20780
  excerpt,
20154
- label: readString$2(rawToken.label) ?? path,
20781
+ label: readString$3(rawToken.label) ?? path,
20155
20782
  startLine: readLine(rawToken.startLine),
20156
20783
  endLine: readLine(rawToken.endLine)
20157
20784
  });
@@ -20160,7 +20787,7 @@ function readWorkspaceReferences(metadata) {
20160
20787
  references.push({
20161
20788
  kind,
20162
20789
  key,
20163
- label: readString$2(rawToken.label) ?? key
20790
+ label: readString$3(rawToken.label) ?? key
20164
20791
  });
20165
20792
  }
20166
20793
  return references;
@@ -20763,7 +21390,7 @@ function toInt(value, fallback) {
20763
21390
  const parsed = typeof value === "number" || typeof value === "string" ? Number(value) : NaN;
20764
21391
  return Number.isFinite(parsed) ? Math.max(0, Math.trunc(parsed)) : fallback;
20765
21392
  }
20766
- function readString$1(value) {
21393
+ function readString$2(value) {
20767
21394
  return typeof value === "string" && value.trim() ? value.trim() : void 0;
20768
21395
  }
20769
21396
  function messageText(message) {
@@ -20791,7 +21418,7 @@ function capHistory(items) {
20791
21418
  }];
20792
21419
  }
20793
21420
  function sessionLabel(summary) {
20794
- return readString$1(summary.metadata?.label) ?? readString$1(summary.metadata?.session_label);
21421
+ return readString$2(summary.metadata?.label) ?? readString$2(summary.metadata?.session_label);
20795
21422
  }
20796
21423
  var SessionsListTool = class {
20797
21424
  name = "sessions_list";
@@ -20820,7 +21447,7 @@ var SessionsListTool = class {
20820
21447
  }
20821
21448
  execute = async (args) => {
20822
21449
  const { limit, messageLimit, sessionKey } = normalizeToolParams(args);
20823
- const exactSessionKey = readString$1(sessionKey);
21450
+ const exactSessionKey = readString$2(sessionKey);
20824
21451
  const summaries = exactSessionKey ? [await this.sessions.getSession(exactSessionKey)].filter((summary) => Boolean(summary)) : await this.sessions.listSessions();
20825
21452
  const maxSessions = toInt(limit, DEFAULT_LIMIT);
20826
21453
  const maxMessages = Math.min(toInt(messageLimit, 0), MAX_MESSAGE_LIMIT);
@@ -20869,7 +21496,7 @@ var SessionsHistoryTool = class {
20869
21496
  }
20870
21497
  execute = async (args) => {
20871
21498
  const { includeTools, limit, sessionKey: rawSessionKey } = normalizeToolParams(args);
20872
- const sessionKey = readString$1(rawSessionKey);
21499
+ const sessionKey = readString$2(rawSessionKey);
20873
21500
  if (!sessionKey) return "Error: sessionKey is required";
20874
21501
  const session = await this.resolveSession(sessionKey);
20875
21502
  if (!session) return `Error: session '${sessionKey}' not found`;
@@ -22069,6 +22696,178 @@ function readInput(value) {
22069
22696
  return value;
22070
22697
  }
22071
22698
  //#endregion
22699
+ //#region src/contributions/tool-provider/providers/app-package-dependency-tool.provider.ts
22700
+ const MUTATION_CONFIRMATION = {
22701
+ type: "object",
22702
+ properties: {
22703
+ appId: {
22704
+ type: "string",
22705
+ description: "Installed App id."
22706
+ },
22707
+ confirm: {
22708
+ type: "boolean",
22709
+ description: "Set true only after the user approves this local configuration change."
22710
+ }
22711
+ },
22712
+ required: ["appId"],
22713
+ additionalProperties: false
22714
+ };
22715
+ var AppPackageDependencyToolProvider = class {
22716
+ constructor(manager) {
22717
+ this.manager = manager;
22718
+ }
22719
+ provide = (_request) => [
22720
+ this.inspectTool(),
22721
+ this.verifyTool(),
22722
+ this.setupTool(),
22723
+ this.bindTool(),
22724
+ this.unbindTool()
22725
+ ];
22726
+ inspectTool = () => ({
22727
+ name: "app_dependencies_inspect",
22728
+ description: "Inspect an installed App's declared dependencies, compatible trusted Providers, and non-sensitive bindings.",
22729
+ parameters: {
22730
+ type: "object",
22731
+ properties: { appId: { type: "string" } },
22732
+ required: ["appId"],
22733
+ additionalProperties: false
22734
+ },
22735
+ execute: async (args) => JSON.stringify(await this.manager.inspectDependencies(readAppId(args)), null, 2)
22736
+ });
22737
+ verifyTool = () => ({
22738
+ name: "app_dependencies_verify",
22739
+ description: "Verify whether an installed App's current capability and resource dependencies are satisfied.",
22740
+ parameters: {
22741
+ type: "object",
22742
+ properties: { appId: { type: "string" } },
22743
+ required: ["appId"],
22744
+ additionalProperties: false
22745
+ },
22746
+ execute: async (args) => JSON.stringify(await this.manager.verifyDependencies(readAppId(args)), null, 2)
22747
+ });
22748
+ setupTool = () => ({
22749
+ name: "app_dependencies_setup",
22750
+ description: "Set up dependency bindings only when each requirement has exactly one compatible trusted Provider. Run this only after the user has explicitly approved the local configuration change.",
22751
+ parameters: MUTATION_CONFIRMATION,
22752
+ execute: async (args) => await this.executeWithConfirmation(args, "setup", (appId) => this.manager.setupDependencies(appId))
22753
+ });
22754
+ bindTool = () => ({
22755
+ name: "app_dependency_bind",
22756
+ description: "Bind one declared dependency to an installed trusted Provider. Run this only after the user has explicitly approved the local configuration change.",
22757
+ parameters: {
22758
+ type: "object",
22759
+ properties: {
22760
+ appId: { type: "string" },
22761
+ componentId: { type: "string" },
22762
+ requirementKind: {
22763
+ type: "string",
22764
+ enum: ["capability", "resource"]
22765
+ },
22766
+ requirementId: { type: "string" },
22767
+ providerId: { type: "string" },
22768
+ confirm: { type: "boolean" }
22769
+ },
22770
+ required: [
22771
+ "appId",
22772
+ "componentId",
22773
+ "requirementKind",
22774
+ "requirementId",
22775
+ "providerId"
22776
+ ],
22777
+ additionalProperties: false
22778
+ },
22779
+ execute: async (args) => {
22780
+ const input = readBinding(args);
22781
+ if (!readConfirmation(args)) return confirmationResult("bind", input.appId);
22782
+ return JSON.stringify(await this.manager.bindDependency(input.appId, input), null, 2);
22783
+ }
22784
+ });
22785
+ unbindTool = () => ({
22786
+ name: "app_dependency_unbind",
22787
+ description: "Remove one local dependency binding. Run this only after the user has explicitly approved the local configuration change.",
22788
+ parameters: {
22789
+ type: "object",
22790
+ properties: {
22791
+ appId: { type: "string" },
22792
+ componentId: { type: "string" },
22793
+ requirementKind: {
22794
+ type: "string",
22795
+ enum: ["capability", "resource"]
22796
+ },
22797
+ requirementId: { type: "string" },
22798
+ confirm: { type: "boolean" }
22799
+ },
22800
+ required: [
22801
+ "appId",
22802
+ "componentId",
22803
+ "requirementKind",
22804
+ "requirementId"
22805
+ ],
22806
+ additionalProperties: false
22807
+ },
22808
+ execute: async (args) => {
22809
+ const input = readUnbind(args);
22810
+ if (!readConfirmation(args)) return confirmationResult("unbind", input.appId);
22811
+ return JSON.stringify(await this.manager.unbindDependency(input.appId, input), null, 2);
22812
+ }
22813
+ });
22814
+ executeWithConfirmation = async (args, action, execute) => {
22815
+ const params = normalizeToolParams(args);
22816
+ const appId = readAppId(params);
22817
+ if (!readConfirmation(params)) return confirmationResult(action, appId);
22818
+ return JSON.stringify(await execute(appId), null, 2);
22819
+ };
22820
+ };
22821
+ function readAppId(value) {
22822
+ const params = normalizeToolParams(value);
22823
+ if (typeof params.appId !== "string" || !params.appId.trim()) throw new Error("appId must be a non-empty string.");
22824
+ return params.appId.trim();
22825
+ }
22826
+ function readConfirmation(value) {
22827
+ return normalizeToolParams(value).confirm === true;
22828
+ }
22829
+ function readBinding(value) {
22830
+ const params = normalizeToolParams(value);
22831
+ const appId = readAppId(params);
22832
+ const componentId = readString$1(params.componentId, "componentId");
22833
+ const requirementId = readString$1(params.requirementId, "requirementId");
22834
+ const providerId = readString$1(params.providerId, "providerId");
22835
+ if (params.requirementKind !== "capability" && params.requirementKind !== "resource") throw new Error("requirementKind must be capability or resource.");
22836
+ return {
22837
+ appId,
22838
+ componentId,
22839
+ requirementKind: params.requirementKind,
22840
+ requirementId,
22841
+ providerId,
22842
+ confirm: params.confirm === true
22843
+ };
22844
+ }
22845
+ function readUnbind(value) {
22846
+ const binding = readBinding({
22847
+ ...normalizeToolParams(value),
22848
+ providerId: "unused"
22849
+ });
22850
+ return {
22851
+ appId: binding.appId,
22852
+ componentId: binding.componentId,
22853
+ requirementKind: binding.requirementKind,
22854
+ requirementId: binding.requirementId,
22855
+ confirm: binding.confirm
22856
+ };
22857
+ }
22858
+ function readString$1(value, name) {
22859
+ if (typeof value !== "string" || !value.trim()) throw new Error(`${name} must be a non-empty string.`);
22860
+ return value.trim();
22861
+ }
22862
+ function confirmationResult(action, appId) {
22863
+ return JSON.stringify({
22864
+ status: "requires_user_authorization",
22865
+ action,
22866
+ appId,
22867
+ message: "This changes local dependency configuration. Confirm that the user has approved it, then retry with confirm=true."
22868
+ }, null, 2);
22869
+ }
22870
+ //#endregion
22072
22871
  //#region src/contributions/tool-provider/services/tool-provider-run-context.service.ts
22073
22872
  var ToolProviderRunContextService = class {
22074
22873
  constructor(sessionManager, agentManager, configManager) {
@@ -22126,6 +22925,7 @@ var ToolProviderContribution = class extends Contribution$1 {
22126
22925
  new SessionToolProvider(runContextService, this.kernel.sessionManager, this.kernel.sessionRequests, this.kernel.sessionSearch),
22127
22926
  new AssetToolProvider(this.kernel.assetStore),
22128
22927
  new ServiceActionToolProvider(runContextService, this.kernel.serviceAppManager),
22928
+ new AppPackageDependencyToolProvider(this.kernel.appPackageManager),
22129
22929
  new McpToolProvider(runContextService, this.kernel.mcpManager)
22130
22930
  ];
22131
22931
  };
@@ -22225,6 +23025,7 @@ function createKernelContributions(kernel) {
22225
23025
  function installKernelAppPackageRuntimeHooks(params) {
22226
23026
  const { appPackageManager, panelAppManager, serviceAppManager } = params;
22227
23027
  appPackageManager.installRuntimeHooks({
23028
+ listCapabilityProviders: serviceAppManager.listCapabilityProviders,
22228
23029
  assertCanActivate: async (sources) => {
22229
23030
  await panelAppManager.assertCanActivatePackageComponents(sources);
22230
23031
  await serviceAppManager.assertCanActivatePackageComponents(sources);