@nextclaw/kernel 0.11.0 → 0.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +103 -43
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +772 -99
- package/dist/index.js.map +1 -1
- package/package.json +12 -12
package/dist/index.js
CHANGED
|
@@ -8,7 +8,7 @@ import { CHAT_CONTINUATION_TARGET_MESSAGE_METADATA_KEY, CHAT_CONVERSATION_EXCERP
|
|
|
8
8
|
import { createHash, createHmac, randomBytes, randomUUID, scryptSync, timingSafeEqual } from "node:crypto";
|
|
9
9
|
import { catchError, filter, from, lastValueFrom, tap } from "rxjs";
|
|
10
10
|
import { DefaultNcpAgentConversationStateManager, insertMessageByTimeline, mergeToolExecutionTiming } from "@nextclaw/ncp-toolkit";
|
|
11
|
-
import { appendFileSync, chmodSync, constants, createReadStream, existsSync, mkdirSync, readFileSync, readdirSync, readlinkSync, realpathSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
11
|
+
import { accessSync, appendFileSync, chmodSync, constants, createReadStream, existsSync, mkdirSync, readFileSync, readdirSync, readlinkSync, realpathSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
12
12
|
import path, { basename, delimiter, dirname, extname, isAbsolute, join, normalize, relative, resolve, sep } from "node:path";
|
|
13
13
|
import { access, appendFile, 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";
|
|
@@ -3592,6 +3592,30 @@ var AppPackagePresentationService = class {
|
|
|
3592
3592
|
};
|
|
3593
3593
|
};
|
|
3594
3594
|
//#endregion
|
|
3595
|
+
//#region src/services/app-package-runtime-activation.service.ts
|
|
3596
|
+
const EMPTY_APP_PACKAGE_RUNTIME_HOOKS = {
|
|
3597
|
+
assertCanActivate: async () => void 0,
|
|
3598
|
+
afterActivate: async () => void 0,
|
|
3599
|
+
beforeDeactivate: async () => void 0,
|
|
3600
|
+
beforeUninstall: async () => void 0
|
|
3601
|
+
};
|
|
3602
|
+
var AppPackageRuntimeActivationService = class {
|
|
3603
|
+
recoverFailedActivation = async (params) => {
|
|
3604
|
+
const recoveryErrors = [];
|
|
3605
|
+
try {
|
|
3606
|
+
await params.beforeDeactivate(params.sources);
|
|
3607
|
+
} catch (recoveryError) {
|
|
3608
|
+
recoveryErrors.push(recoveryError);
|
|
3609
|
+
}
|
|
3610
|
+
try {
|
|
3611
|
+
await params.disable();
|
|
3612
|
+
} catch (recoveryError) {
|
|
3613
|
+
recoveryErrors.push(recoveryError);
|
|
3614
|
+
}
|
|
3615
|
+
if (recoveryErrors.length > 0) throw new AggregateError([params.error, ...recoveryErrors], `应用 ${params.appId} 启用失败,且 runtime 状态恢复未完整完成。`);
|
|
3616
|
+
};
|
|
3617
|
+
};
|
|
3618
|
+
//#endregion
|
|
3595
3619
|
//#region src/types/app-package.types.ts
|
|
3596
3620
|
var AppPackageError = class extends Error {
|
|
3597
3621
|
constructor(code, message) {
|
|
@@ -3708,11 +3732,6 @@ function compareVersions(left, right) {
|
|
|
3708
3732
|
}
|
|
3709
3733
|
//#endregion
|
|
3710
3734
|
//#region src/managers/app-package.manager.ts
|
|
3711
|
-
const EMPTY_RUNTIME_HOOKS = {
|
|
3712
|
-
assertCanActivate: async () => void 0,
|
|
3713
|
-
beforeDeactivate: async () => void 0,
|
|
3714
|
-
beforeUninstall: async () => void 0
|
|
3715
|
-
};
|
|
3716
3735
|
var AppPackageManager = class {
|
|
3717
3736
|
appHomeService;
|
|
3718
3737
|
installationService;
|
|
@@ -3720,8 +3739,9 @@ var AppPackageManager = class {
|
|
|
3720
3739
|
operationManager;
|
|
3721
3740
|
platformTargetService = new AppPlatformTargetService();
|
|
3722
3741
|
presentationService = new AppPackagePresentationService();
|
|
3742
|
+
runtimeActivationService = new AppPackageRuntimeActivationService();
|
|
3723
3743
|
registryService;
|
|
3724
|
-
runtimeHooks =
|
|
3744
|
+
runtimeHooks = EMPTY_APP_PACKAGE_RUNTIME_HOOKS;
|
|
3725
3745
|
builtInBootstrapPromise;
|
|
3726
3746
|
builtInDefinitionsPromise;
|
|
3727
3747
|
constructor(params) {
|
|
@@ -3827,6 +3847,20 @@ var AppPackageManager = class {
|
|
|
3827
3847
|
const sources = this.toComponentSources(app);
|
|
3828
3848
|
await this.runtimeHooks.assertCanActivate(sources);
|
|
3829
3849
|
await this.installationService.setEnabled(appId, true);
|
|
3850
|
+
try {
|
|
3851
|
+
await this.runtimeHooks.afterActivate(sources);
|
|
3852
|
+
} catch (error) {
|
|
3853
|
+
await this.runtimeActivationService.recoverFailedActivation({
|
|
3854
|
+
appId,
|
|
3855
|
+
error,
|
|
3856
|
+
sources,
|
|
3857
|
+
beforeDeactivate: this.runtimeHooks.beforeDeactivate,
|
|
3858
|
+
disable: async () => {
|
|
3859
|
+
await this.installationService.setEnabled(appId, false);
|
|
3860
|
+
}
|
|
3861
|
+
});
|
|
3862
|
+
throw error;
|
|
3863
|
+
}
|
|
3830
3864
|
return await this.getPackage(appId);
|
|
3831
3865
|
});
|
|
3832
3866
|
};
|
|
@@ -3861,6 +3895,7 @@ var AppPackageManager = class {
|
|
|
3861
3895
|
await this.runtimeHooks.assertCanActivate(this.toComponentSources(candidate));
|
|
3862
3896
|
}
|
|
3863
3897
|
activated = (await this.installationService.rollback(appId, result.version)).rolledBack;
|
|
3898
|
+
if (current.enabled) await this.runtimeHooks.afterActivate(this.toComponentSources(candidate));
|
|
3864
3899
|
return {
|
|
3865
3900
|
package: await this.getPackage(appId),
|
|
3866
3901
|
result
|
|
@@ -3869,6 +3904,7 @@ var AppPackageManager = class {
|
|
|
3869
3904
|
if (activated) await this.installationService.rollback(appId, result.previousVersion);
|
|
3870
3905
|
if (deactivated) try {
|
|
3871
3906
|
await this.runtimeHooks.assertCanActivate(this.toComponentSources(current));
|
|
3907
|
+
await this.runtimeHooks.afterActivate(this.toComponentSources(current));
|
|
3872
3908
|
} catch (recoveryError) {
|
|
3873
3909
|
throw new AggregateError([error, recoveryError], `应用 ${appId} 更新失败,且旧 runtime 恢复探测失败。`);
|
|
3874
3910
|
}
|
|
@@ -3900,6 +3936,7 @@ var AppPackageManager = class {
|
|
|
3900
3936
|
await this.runtimeHooks.assertCanActivate(this.toComponentSources(candidate));
|
|
3901
3937
|
}
|
|
3902
3938
|
result = await this.installationService.rollback(appId, version);
|
|
3939
|
+
if (current.enabled) await this.runtimeHooks.afterActivate(this.toComponentSources(candidate));
|
|
3903
3940
|
return {
|
|
3904
3941
|
package: await this.getPackage(appId),
|
|
3905
3942
|
result
|
|
@@ -3908,6 +3945,7 @@ var AppPackageManager = class {
|
|
|
3908
3945
|
if (result?.rolledBack) await this.installationService.rollback(appId, result.previousVersion);
|
|
3909
3946
|
if (deactivated) try {
|
|
3910
3947
|
await this.runtimeHooks.assertCanActivate(this.toComponentSources(current));
|
|
3948
|
+
await this.runtimeHooks.afterActivate(this.toComponentSources(current));
|
|
3911
3949
|
} catch (recoveryError) {
|
|
3912
3950
|
throw new AggregateError([error, recoveryError], `应用 ${appId} 回滚失败,且旧 runtime 恢复探测失败。`);
|
|
3913
3951
|
}
|
|
@@ -3965,7 +4003,8 @@ var AppPackageManager = class {
|
|
|
3965
4003
|
const manifestBundle = await this.manifestService.load(activeVersion.installDirectory);
|
|
3966
4004
|
const security = manifestBundle.manifest.schemaVersion === 2 ? this.manifestService.resolvePlatformSecurity(manifestBundle.manifest) : {
|
|
3967
4005
|
runtimeProfile: "wasi",
|
|
3968
|
-
isolation: manifestBundle.manifest.main.kind === "wasi-http-component" ? "host-mediated" : "sandboxed"
|
|
4006
|
+
isolation: manifestBundle.manifest.main.kind === "wasi-http-component" ? "host-mediated" : "sandboxed",
|
|
4007
|
+
permissions: manifestBundle.manifest.permissions ?? {}
|
|
3969
4008
|
};
|
|
3970
4009
|
return {
|
|
3971
4010
|
id: info.appId,
|
|
@@ -3991,6 +4030,7 @@ var AppPackageManager = class {
|
|
|
3991
4030
|
storage: info.storage,
|
|
3992
4031
|
runtimeProfile: security.runtimeProfile,
|
|
3993
4032
|
isolation: security.isolation,
|
|
4033
|
+
permissions: security.permissions,
|
|
3994
4034
|
...await this.presentationService.readManifest(component.manifestPath)
|
|
3995
4035
|
}))),
|
|
3996
4036
|
dataDirectory: info.dataDirectory,
|
|
@@ -4004,16 +4044,19 @@ var AppPackageManager = class {
|
|
|
4004
4044
|
resolveSecurity = (version, appId) => {
|
|
4005
4045
|
if (version.security) return {
|
|
4006
4046
|
runtimeProfile: version.security.runtimeProfile,
|
|
4007
|
-
isolation: version.security.isolation
|
|
4047
|
+
isolation: version.security.isolation,
|
|
4048
|
+
permissions: version.security.permissions
|
|
4008
4049
|
};
|
|
4009
4050
|
const hasService = version.components?.some((component) => component.kind === "service") ?? false;
|
|
4010
4051
|
if (version.manifestSchemaVersion !== 2) throw new AppPackageError("APP_PACKAGE_INCOMPATIBLE", `应用 ${appId} 仍使用 legacy schema,不能投影组件。`);
|
|
4011
4052
|
return hasService ? {
|
|
4012
4053
|
runtimeProfile: "native-process",
|
|
4013
|
-
isolation: "full-user"
|
|
4054
|
+
isolation: "full-user",
|
|
4055
|
+
permissions: {}
|
|
4014
4056
|
} : {
|
|
4015
4057
|
runtimeProfile: "panel-only",
|
|
4016
|
-
isolation: "sandboxed"
|
|
4058
|
+
isolation: "sandboxed",
|
|
4059
|
+
permissions: {}
|
|
4017
4060
|
};
|
|
4018
4061
|
};
|
|
4019
4062
|
toComponentSources = (app) => app.components.map((component) => ({ ...component }));
|
|
@@ -4954,7 +4997,7 @@ function readRequiredString$8(value, name) {
|
|
|
4954
4997
|
if (!trimmed) throw new Error(`${name} is required`);
|
|
4955
4998
|
return trimmed;
|
|
4956
4999
|
}
|
|
4957
|
-
function readOptionalNumber(value) {
|
|
5000
|
+
function readOptionalNumber$1(value) {
|
|
4958
5001
|
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
4959
5002
|
}
|
|
4960
5003
|
function readTextContent(value) {
|
|
@@ -4971,7 +5014,7 @@ function readInboundAttachments(value) {
|
|
|
4971
5014
|
...readString$8(entry.url) ? { url: readString$8(entry.url) } : {},
|
|
4972
5015
|
...readString$8(entry.assetUri) ? { assetUri: readString$8(entry.assetUri) } : {},
|
|
4973
5016
|
...readString$8(entry.mimeType) ? { mimeType: readString$8(entry.mimeType) } : {},
|
|
4974
|
-
...readOptionalNumber(entry.size) !== void 0 ? { size: readOptionalNumber(entry.size) } : {},
|
|
5017
|
+
...readOptionalNumber$1(entry.size) !== void 0 ? { size: readOptionalNumber$1(entry.size) } : {},
|
|
4975
5018
|
...readString$8(entry.source) ? { source: readString$8(entry.source) } : {},
|
|
4976
5019
|
...entry.status === "ready" || entry.status === "remote-only" ? { status: entry.status } : {},
|
|
4977
5020
|
...readString$8(entry.errorCode) ? { errorCode: readString$8(entry.errorCode) } : {}
|
|
@@ -5007,7 +5050,7 @@ function normalizeAuthPollResult(value) {
|
|
|
5007
5050
|
channel: readRequiredString$8(record.channel, "channel"),
|
|
5008
5051
|
status: record.status,
|
|
5009
5052
|
message: readString$8(record.message),
|
|
5010
|
-
nextPollMs: readOptionalNumber(record.nextPollMs),
|
|
5053
|
+
nextPollMs: readOptionalNumber$1(record.nextPollMs),
|
|
5011
5054
|
accountId: typeof record.accountId === "string" ? record.accountId : null,
|
|
5012
5055
|
notes: Array.isArray(record.notes) ? record.notes.filter((note) => typeof note === "string") : [],
|
|
5013
5056
|
channelConfig: normalizeChannelConfigResult(record)
|
|
@@ -5044,8 +5087,8 @@ var ExtensionIngressDiagnosticsService = class {
|
|
|
5044
5087
|
parentCorrelationId: readString$8(payload.parentCorrelationId),
|
|
5045
5088
|
reasonCode: readString$8(payload.reasonCode),
|
|
5046
5089
|
providerCode: readString$8(payload.providerCode),
|
|
5047
|
-
durationMs: readOptionalNumber(payload.durationMs),
|
|
5048
|
-
attempt: readOptionalNumber(payload.attempt),
|
|
5090
|
+
durationMs: readOptionalNumber$1(payload.durationMs),
|
|
5091
|
+
attempt: readOptionalNumber$1(payload.attempt),
|
|
5049
5092
|
facts: {
|
|
5050
5093
|
...readRecord$2(payload.facts),
|
|
5051
5094
|
extensionId: credential.extensionId,
|
|
@@ -5674,6 +5717,70 @@ function isRecord$15(value) {
|
|
|
5674
5717
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
5675
5718
|
}
|
|
5676
5719
|
//#endregion
|
|
5720
|
+
//#region src/utils/service-app-error.utils.ts
|
|
5721
|
+
const SERVICE_APP_ERROR_CODES = new Set([
|
|
5722
|
+
"AUTHORIZATION_REQUIRED",
|
|
5723
|
+
"SERVICE_APP_ACTION_NOT_DECLARED",
|
|
5724
|
+
"SERVICE_APP_ACTION_NOT_FOUND",
|
|
5725
|
+
"SERVICE_APP_INVALID_ACTION",
|
|
5726
|
+
"SERVICE_APP_INVALID_CALLER",
|
|
5727
|
+
"SERVICE_APP_INVALID_MANIFEST",
|
|
5728
|
+
"SERVICE_APP_MANAGED_SOURCE",
|
|
5729
|
+
"SERVICE_APP_NOT_FOUND",
|
|
5730
|
+
"SERVICE_APP_READ_FAILED",
|
|
5731
|
+
"SERVICE_APP_RUNTIME_FAILED"
|
|
5732
|
+
]);
|
|
5733
|
+
var ServiceAppError = class extends Error {
|
|
5734
|
+
constructor(code, message) {
|
|
5735
|
+
super(message);
|
|
5736
|
+
this.code = code;
|
|
5737
|
+
this.name = "ServiceAppError";
|
|
5738
|
+
}
|
|
5739
|
+
};
|
|
5740
|
+
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);
|
|
5741
|
+
//#endregion
|
|
5742
|
+
//#region src/utils/service-action.utils.ts
|
|
5743
|
+
const DEFAULT_SERVICE_ACTION_RISK = "dangerous";
|
|
5744
|
+
function buildServiceActionId(appId, actionName) {
|
|
5745
|
+
return `${appId}.${actionName}`;
|
|
5746
|
+
}
|
|
5747
|
+
function getServiceActionName(actionId, appId) {
|
|
5748
|
+
const prefix = `${appId}.`;
|
|
5749
|
+
if (!actionId.startsWith(prefix)) throw new Error("service action does not belong to service app.");
|
|
5750
|
+
const name = actionId.slice(prefix.length).trim();
|
|
5751
|
+
if (!name) throw new Error("service action name is required.");
|
|
5752
|
+
return name;
|
|
5753
|
+
}
|
|
5754
|
+
function parseServiceActionCallerKey(key) {
|
|
5755
|
+
const [surface, callerId, ...rest] = key.split(":");
|
|
5756
|
+
if (!callerId || rest.length > 0) return null;
|
|
5757
|
+
if (surface === "panel-app") return {
|
|
5758
|
+
surface,
|
|
5759
|
+
appId: callerId
|
|
5760
|
+
};
|
|
5761
|
+
if (surface === "agent") return {
|
|
5762
|
+
surface,
|
|
5763
|
+
agentId: callerId
|
|
5764
|
+
};
|
|
5765
|
+
return null;
|
|
5766
|
+
}
|
|
5767
|
+
function getServiceActionCallerId(caller) {
|
|
5768
|
+
return caller.surface === "panel-app" ? caller.appId : caller.agentId;
|
|
5769
|
+
}
|
|
5770
|
+
function assertServiceActionCaller(caller, hasAgent) {
|
|
5771
|
+
if (caller.surface === "panel-app" && caller.appId.trim()) return;
|
|
5772
|
+
if (caller.surface === "agent" && caller.agentId.trim() && (hasAgent?.(caller.agentId) ?? true)) return;
|
|
5773
|
+
throw new ServiceAppError("SERVICE_APP_INVALID_CALLER", "service action caller is invalid");
|
|
5774
|
+
}
|
|
5775
|
+
function assertServiceActionDeclared(caller, actionId, declaredActions) {
|
|
5776
|
+
if (caller.surface === "agent") return;
|
|
5777
|
+
if (!declaredActions?.includes(actionId)) throw new ServiceAppError("SERVICE_APP_ACTION_NOT_DECLARED", "panel app did not declare this service action");
|
|
5778
|
+
}
|
|
5779
|
+
function resolveServiceActionGrantState({ actionId, declaredActions, granted }) {
|
|
5780
|
+
if (declaredActions && !declaredActions.includes(actionId)) return "not-declared";
|
|
5781
|
+
return granted ? "granted" : "not-granted";
|
|
5782
|
+
}
|
|
5783
|
+
//#endregion
|
|
5677
5784
|
//#region src/features/capability-grants/utils/capability-grant-resource.utils.ts
|
|
5678
5785
|
function createPanelAppClientGrantRequest(appId) {
|
|
5679
5786
|
return {
|
|
@@ -5693,7 +5800,7 @@ function createPanelAppAgentGrantRequest(caller, capability) {
|
|
|
5693
5800
|
return {
|
|
5694
5801
|
subject: {
|
|
5695
5802
|
type: caller.surface,
|
|
5696
|
-
id: caller
|
|
5803
|
+
id: getServiceActionCallerId(caller)
|
|
5697
5804
|
},
|
|
5698
5805
|
resource: {
|
|
5699
5806
|
type: "agent.capability",
|
|
@@ -5707,7 +5814,7 @@ function createServiceActionGrantRequest(caller, action) {
|
|
|
5707
5814
|
return {
|
|
5708
5815
|
subject: {
|
|
5709
5816
|
type: caller.surface,
|
|
5710
|
-
id: caller
|
|
5817
|
+
id: getServiceActionCallerId(caller)
|
|
5711
5818
|
},
|
|
5712
5819
|
resource: {
|
|
5713
5820
|
type: "service.action",
|
|
@@ -5742,31 +5849,6 @@ function isPanelAppAgentCapability(value) {
|
|
|
5742
5849
|
return PANEL_APP_AGENT_CAPABILITIES.includes(value);
|
|
5743
5850
|
}
|
|
5744
5851
|
//#endregion
|
|
5745
|
-
//#region src/utils/service-action.utils.ts
|
|
5746
|
-
const DEFAULT_SERVICE_ACTION_RISK = "dangerous";
|
|
5747
|
-
function buildServiceActionId(appId, actionName) {
|
|
5748
|
-
return `${appId}.${actionName}`;
|
|
5749
|
-
}
|
|
5750
|
-
function getServiceActionName(actionId, appId) {
|
|
5751
|
-
const prefix = `${appId}.`;
|
|
5752
|
-
if (!actionId.startsWith(prefix)) throw new Error("service action does not belong to service app.");
|
|
5753
|
-
const name = actionId.slice(prefix.length).trim();
|
|
5754
|
-
if (!name) throw new Error("service action name is required.");
|
|
5755
|
-
return name;
|
|
5756
|
-
}
|
|
5757
|
-
function parseServiceActionCallerKey(key) {
|
|
5758
|
-
const [surface, appId, ...rest] = key.split(":");
|
|
5759
|
-
if (surface !== "panel-app" || !appId || rest.length > 0) return null;
|
|
5760
|
-
return {
|
|
5761
|
-
surface,
|
|
5762
|
-
appId
|
|
5763
|
-
};
|
|
5764
|
-
}
|
|
5765
|
-
function resolveServiceActionGrantState({ actionId, declaredActions, granted }) {
|
|
5766
|
-
if (declaredActions && !declaredActions.includes(actionId)) return "not-declared";
|
|
5767
|
-
return granted ? "granted" : "not-granted";
|
|
5768
|
-
}
|
|
5769
|
-
//#endregion
|
|
5770
5852
|
//#region src/features/capability-grants/services/capability-grant-legacy-migration.service.ts
|
|
5771
5853
|
const PANEL_AGENT_GRANTS_FILE_NAME = ".panel-app-capability-grants.json";
|
|
5772
5854
|
const PANEL_CLIENT_GRANTS_FILE_NAME = ".panel-app-client-grants.json";
|
|
@@ -5905,7 +5987,7 @@ function parseServiceActionGrants(value) {
|
|
|
5905
5987
|
return {
|
|
5906
5988
|
subject: {
|
|
5907
5989
|
type: caller.surface,
|
|
5908
|
-
id: caller.appId
|
|
5990
|
+
id: caller.surface === "panel-app" ? caller.appId : caller.agentId
|
|
5909
5991
|
},
|
|
5910
5992
|
resource: {
|
|
5911
5993
|
type: "service.action",
|
|
@@ -7502,7 +7584,7 @@ var ExtensionRuntimeService = class {
|
|
|
7502
7584
|
const credential = this.assertAuthorized(envelope, context);
|
|
7503
7585
|
const payload = readRecord$2(envelope.payload);
|
|
7504
7586
|
const generation = readRequiredString$8(payload.generation, "generation");
|
|
7505
|
-
const pid = readOptionalNumber(payload.pid);
|
|
7587
|
+
const pid = readOptionalNumber$1(payload.pid);
|
|
7506
7588
|
if (generation !== credential.generation || pid === void 0 || !Number.isInteger(pid) || pid <= 0) throw new Error("Invalid extension runtime ready payload");
|
|
7507
7589
|
this.lifecycle.markReady({
|
|
7508
7590
|
extensionId: credential.extensionId,
|
|
@@ -13419,6 +13501,7 @@ var McpServiceAppRuntimeService = class {
|
|
|
13419
13501
|
this.states.clear();
|
|
13420
13502
|
};
|
|
13421
13503
|
toMcpServerRecord = (app, manifest) => {
|
|
13504
|
+
if (!manifest.command || !manifest.args) throw new Error(`MCP Service App ${app.id} is missing its launch command.`);
|
|
13422
13505
|
const launch = resolveRuntimeCommandLaunch(manifest.command);
|
|
13423
13506
|
return {
|
|
13424
13507
|
name: app.id,
|
|
@@ -13480,6 +13563,466 @@ var McpServiceAppRuntimeService = class {
|
|
|
13480
13563
|
};
|
|
13481
13564
|
};
|
|
13482
13565
|
//#endregion
|
|
13566
|
+
//#region src/services/portable-service-runner-client.service.ts
|
|
13567
|
+
const PORTABLE_RUNNER_PROTOCOL_VERSION = "0.1.0";
|
|
13568
|
+
var PortableServiceRunnerError = class extends Error {
|
|
13569
|
+
constructor(code, message) {
|
|
13570
|
+
super(message);
|
|
13571
|
+
this.code = code;
|
|
13572
|
+
this.name = "PortableServiceRunnerError";
|
|
13573
|
+
}
|
|
13574
|
+
};
|
|
13575
|
+
var PortableServiceRunnerClientService = class {
|
|
13576
|
+
child;
|
|
13577
|
+
pending = /* @__PURE__ */ new Map();
|
|
13578
|
+
stderrTail = [];
|
|
13579
|
+
constructor(params = {}) {
|
|
13580
|
+
this.params = params;
|
|
13581
|
+
}
|
|
13582
|
+
listActions = async (app) => this.request({
|
|
13583
|
+
operation: "list-actions",
|
|
13584
|
+
app
|
|
13585
|
+
}, 7e3);
|
|
13586
|
+
invoke = async (app, actionName, input, timeoutMs = 7e3) => this.request({
|
|
13587
|
+
operation: "invoke",
|
|
13588
|
+
app,
|
|
13589
|
+
actionName,
|
|
13590
|
+
input
|
|
13591
|
+
}, timeoutMs);
|
|
13592
|
+
startResident = async (app, config) => this.request({
|
|
13593
|
+
operation: "start-resident",
|
|
13594
|
+
app,
|
|
13595
|
+
input: config
|
|
13596
|
+
}, 7e3);
|
|
13597
|
+
startProvider = async (app, config) => this.request({
|
|
13598
|
+
operation: "start-provider",
|
|
13599
|
+
app,
|
|
13600
|
+
input: config
|
|
13601
|
+
}, 7e3);
|
|
13602
|
+
deliverEvent = async (app, event) => this.request({
|
|
13603
|
+
operation: "deliver-event",
|
|
13604
|
+
app,
|
|
13605
|
+
input: event
|
|
13606
|
+
}, 7e3);
|
|
13607
|
+
stats = async () => this.request({ operation: "stats" }, 2e3);
|
|
13608
|
+
stop = async (app) => {
|
|
13609
|
+
await this.request({
|
|
13610
|
+
operation: "stop",
|
|
13611
|
+
app,
|
|
13612
|
+
input: {
|
|
13613
|
+
stoppedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
13614
|
+
reason: "host-stop"
|
|
13615
|
+
}
|
|
13616
|
+
}, 2e3);
|
|
13617
|
+
};
|
|
13618
|
+
dispose = async () => {
|
|
13619
|
+
const child = this.child;
|
|
13620
|
+
this.child = void 0;
|
|
13621
|
+
if (!child) return;
|
|
13622
|
+
child.stdin.end();
|
|
13623
|
+
await new Promise((resolve) => {
|
|
13624
|
+
const timeout = setTimeout(() => {
|
|
13625
|
+
child.kill("SIGKILL");
|
|
13626
|
+
resolve();
|
|
13627
|
+
}, 1e3);
|
|
13628
|
+
timeout.unref();
|
|
13629
|
+
child.once("exit", () => {
|
|
13630
|
+
clearTimeout(timeout);
|
|
13631
|
+
resolve();
|
|
13632
|
+
});
|
|
13633
|
+
});
|
|
13634
|
+
};
|
|
13635
|
+
request = async (request, timeoutMs) => {
|
|
13636
|
+
const child = this.ensureChild();
|
|
13637
|
+
const requestId = randomUUID();
|
|
13638
|
+
const payload = {
|
|
13639
|
+
...request,
|
|
13640
|
+
requestId,
|
|
13641
|
+
app: request.app ? this.toRunnerApp(request.app) : void 0
|
|
13642
|
+
};
|
|
13643
|
+
return await new Promise((resolve, reject) => {
|
|
13644
|
+
const timeout = setTimeout(() => {
|
|
13645
|
+
const error = new PortableServiceRunnerError("PORTABLE_RUNTIME_TIMEOUT", `Portable component exceeded its ${timeoutMs}ms execution budget.`);
|
|
13646
|
+
this.failAll(error);
|
|
13647
|
+
this.child?.kill("SIGKILL");
|
|
13648
|
+
this.child = void 0;
|
|
13649
|
+
}, timeoutMs);
|
|
13650
|
+
timeout.unref();
|
|
13651
|
+
this.pending.set(requestId, {
|
|
13652
|
+
resolve: (value) => resolve(value),
|
|
13653
|
+
reject,
|
|
13654
|
+
timeout
|
|
13655
|
+
});
|
|
13656
|
+
child.stdin.write(`${JSON.stringify(payload)}\n`, (error) => {
|
|
13657
|
+
if (!error) return;
|
|
13658
|
+
const pending = this.pending.get(requestId);
|
|
13659
|
+
if (!pending) return;
|
|
13660
|
+
clearTimeout(pending.timeout);
|
|
13661
|
+
this.pending.delete(requestId);
|
|
13662
|
+
reject(error);
|
|
13663
|
+
});
|
|
13664
|
+
});
|
|
13665
|
+
};
|
|
13666
|
+
ensureChild = () => {
|
|
13667
|
+
if (this.child && this.child.exitCode === null) return this.child;
|
|
13668
|
+
const child = spawn(this.resolveRunnerPath(), [], { stdio: [
|
|
13669
|
+
"pipe",
|
|
13670
|
+
"pipe",
|
|
13671
|
+
"pipe"
|
|
13672
|
+
] });
|
|
13673
|
+
this.child = child;
|
|
13674
|
+
this.stderrTail = [];
|
|
13675
|
+
createInterface({ input: child.stdout }).on("line", this.handleLine);
|
|
13676
|
+
child.stderr.on("data", (chunk) => {
|
|
13677
|
+
const message = chunk.toString("utf8").trim();
|
|
13678
|
+
if (!message) return;
|
|
13679
|
+
this.stderrTail = [...this.stderrTail, message].slice(-20);
|
|
13680
|
+
process.stderr.write(`${message}\n`);
|
|
13681
|
+
});
|
|
13682
|
+
child.stdin.on("error", (error) => {
|
|
13683
|
+
if (this.child !== child) return;
|
|
13684
|
+
this.handleChildFailure(new PortableServiceRunnerError("PORTABLE_RUNNER_IO_FAILED", `Portable runner input failed: ${error.message}. ${this.stderrTail.join(" ")}`.trim()));
|
|
13685
|
+
});
|
|
13686
|
+
child.once("error", (error) => {
|
|
13687
|
+
if (this.child === child) this.handleChildFailure(error);
|
|
13688
|
+
});
|
|
13689
|
+
child.once("exit", (code, signal) => {
|
|
13690
|
+
if (this.child !== child) return;
|
|
13691
|
+
this.child = void 0;
|
|
13692
|
+
const error = new PortableServiceRunnerError("PORTABLE_RUNNER_EXITED", `Portable runner exited (${signal ?? code ?? "unknown"}). ${this.stderrTail.join(" ")}`.trim());
|
|
13693
|
+
this.failAll(error);
|
|
13694
|
+
this.params.onUnexpectedExit?.(error);
|
|
13695
|
+
});
|
|
13696
|
+
return child;
|
|
13697
|
+
};
|
|
13698
|
+
handleLine = (line) => {
|
|
13699
|
+
let response;
|
|
13700
|
+
try {
|
|
13701
|
+
response = JSON.parse(line);
|
|
13702
|
+
} catch {
|
|
13703
|
+
this.handleChildFailure(/* @__PURE__ */ new Error(`Portable runner returned invalid JSON: ${line}`));
|
|
13704
|
+
return;
|
|
13705
|
+
}
|
|
13706
|
+
if (response.protocolVersion !== PORTABLE_RUNNER_PROTOCOL_VERSION) {
|
|
13707
|
+
const error = new PortableServiceRunnerError("PORTABLE_RUNNER_PROTOCOL_MISMATCH", `Portable runner protocol mismatch: expected ${PORTABLE_RUNNER_PROTOCOL_VERSION}, received ${response.protocolVersion ?? "missing"}.`);
|
|
13708
|
+
const child = this.child;
|
|
13709
|
+
this.handleChildFailure(error);
|
|
13710
|
+
child?.kill("SIGKILL");
|
|
13711
|
+
return;
|
|
13712
|
+
}
|
|
13713
|
+
const pending = this.pending.get(response.requestId);
|
|
13714
|
+
if (!pending) return;
|
|
13715
|
+
clearTimeout(pending.timeout);
|
|
13716
|
+
this.pending.delete(response.requestId);
|
|
13717
|
+
if (response.ok) {
|
|
13718
|
+
pending.resolve(response.result);
|
|
13719
|
+
return;
|
|
13720
|
+
}
|
|
13721
|
+
pending.reject(new PortableServiceRunnerError(response.error?.code ?? "PORTABLE_RUNTIME_FAILED", response.error?.message ?? "Portable runner request failed."));
|
|
13722
|
+
};
|
|
13723
|
+
handleChildFailure = (error) => {
|
|
13724
|
+
this.failAll(error);
|
|
13725
|
+
this.child = void 0;
|
|
13726
|
+
};
|
|
13727
|
+
failAll = (error) => {
|
|
13728
|
+
for (const pending of this.pending.values()) {
|
|
13729
|
+
clearTimeout(pending.timeout);
|
|
13730
|
+
pending.reject(error);
|
|
13731
|
+
}
|
|
13732
|
+
this.pending.clear();
|
|
13733
|
+
};
|
|
13734
|
+
toRunnerApp = (app) => ({
|
|
13735
|
+
id: app.id,
|
|
13736
|
+
componentPath: app.componentPath,
|
|
13737
|
+
dataDirectory: app.dataDirectory,
|
|
13738
|
+
allowedDomains: app.permissions.allowedDomains ?? [],
|
|
13739
|
+
allowedProviderIds: app.providerIds ?? [],
|
|
13740
|
+
storageEnabled: Boolean(app.permissions.storage)
|
|
13741
|
+
});
|
|
13742
|
+
resolveRunnerPath = () => {
|
|
13743
|
+
const runnerPath = (this.params.env ?? process.env).NEXTCLAW_WASMTIME_RUNNER_PATH?.trim() || this.params.runnerPath?.trim();
|
|
13744
|
+
if (!runnerPath) throw new PortableServiceRunnerError("PORTABLE_RUNNER_UNAVAILABLE", "Portable runner is not part of this NextClaw distribution. Build the product runtime resource; runner developers may set NEXTCLAW_WASMTIME_RUNNER_PATH explicitly.");
|
|
13745
|
+
try {
|
|
13746
|
+
accessSync(runnerPath, process.platform === "win32" ? constants.F_OK : constants.X_OK);
|
|
13747
|
+
} catch {
|
|
13748
|
+
throw new PortableServiceRunnerError("PORTABLE_RUNNER_UNAVAILABLE", `Portable runner is missing or not executable: ${runnerPath}`);
|
|
13749
|
+
}
|
|
13750
|
+
return runnerPath;
|
|
13751
|
+
};
|
|
13752
|
+
};
|
|
13753
|
+
//#endregion
|
|
13754
|
+
//#region src/services/portable-service-app-runtime.service.ts
|
|
13755
|
+
var PortableServiceAppRuntimeService = class {
|
|
13756
|
+
runner;
|
|
13757
|
+
states = /* @__PURE__ */ new Map();
|
|
13758
|
+
apps = /* @__PURE__ */ new Map();
|
|
13759
|
+
providers = /* @__PURE__ */ new Set();
|
|
13760
|
+
persistentRegistrations = /* @__PURE__ */ new Map();
|
|
13761
|
+
residentTimers = /* @__PURE__ */ new Map();
|
|
13762
|
+
residentDeliveries = /* @__PURE__ */ new Map();
|
|
13763
|
+
recoveryPromise;
|
|
13764
|
+
constructor(params = {}) {
|
|
13765
|
+
this.runner = new PortableServiceRunnerClientService({
|
|
13766
|
+
runnerPath: params.runnerPath,
|
|
13767
|
+
onUnexpectedExit: (error) => {
|
|
13768
|
+
this.recoverPersistentComponentsIfNeeded(error);
|
|
13769
|
+
}
|
|
13770
|
+
});
|
|
13771
|
+
}
|
|
13772
|
+
getStatus = (appId) => this.states.get(appId) ?? { status: "idle" };
|
|
13773
|
+
start = async ({ app, manifest }) => {
|
|
13774
|
+
if (!app.enabled || !manifest.lifecycle || manifest.lifecycle.mode === "action") return;
|
|
13775
|
+
this.persistentRegistrations.set(app.id, {
|
|
13776
|
+
app,
|
|
13777
|
+
manifest
|
|
13778
|
+
});
|
|
13779
|
+
const runnerApp = this.toRunnerApp(app, manifest.providerIds);
|
|
13780
|
+
this.apps.set(app.id, runnerApp);
|
|
13781
|
+
if (manifest.lifecycle.mode === "provider") {
|
|
13782
|
+
if (this.providers.has(app.id)) return;
|
|
13783
|
+
const lastStartedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
13784
|
+
this.states.set(app.id, {
|
|
13785
|
+
status: "starting",
|
|
13786
|
+
lastStartedAt
|
|
13787
|
+
});
|
|
13788
|
+
try {
|
|
13789
|
+
await this.runner.startProvider(runnerApp, {
|
|
13790
|
+
mode: "provider",
|
|
13791
|
+
startedAt: lastStartedAt
|
|
13792
|
+
});
|
|
13793
|
+
this.providers.add(app.id);
|
|
13794
|
+
this.states.set(app.id, {
|
|
13795
|
+
status: "running",
|
|
13796
|
+
lastStartedAt,
|
|
13797
|
+
lastReadyAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
13798
|
+
});
|
|
13799
|
+
} catch (error) {
|
|
13800
|
+
this.markFailed(app.id, lastStartedAt, error);
|
|
13801
|
+
throw error;
|
|
13802
|
+
}
|
|
13803
|
+
return;
|
|
13804
|
+
}
|
|
13805
|
+
if (this.residentTimers.has(app.id)) return;
|
|
13806
|
+
const lastStartedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
13807
|
+
this.states.set(app.id, {
|
|
13808
|
+
status: "starting",
|
|
13809
|
+
lastStartedAt
|
|
13810
|
+
});
|
|
13811
|
+
try {
|
|
13812
|
+
await this.runner.startResident(runnerApp, {
|
|
13813
|
+
eventIntervalMs: manifest.lifecycle.eventIntervalMs,
|
|
13814
|
+
startedAt: lastStartedAt
|
|
13815
|
+
});
|
|
13816
|
+
const timer = setInterval(() => {
|
|
13817
|
+
this.scheduleResidentEvent({
|
|
13818
|
+
appId: app.id,
|
|
13819
|
+
eventIntervalMs: manifest.lifecycle?.mode === "resident" ? manifest.lifecycle.eventIntervalMs : 0
|
|
13820
|
+
});
|
|
13821
|
+
}, manifest.lifecycle.eventIntervalMs);
|
|
13822
|
+
timer.unref();
|
|
13823
|
+
this.residentTimers.set(app.id, timer);
|
|
13824
|
+
this.states.set(app.id, {
|
|
13825
|
+
status: "running",
|
|
13826
|
+
lastStartedAt,
|
|
13827
|
+
lastReadyAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
13828
|
+
});
|
|
13829
|
+
} catch (error) {
|
|
13830
|
+
this.markFailed(app.id, lastStartedAt, error);
|
|
13831
|
+
throw error;
|
|
13832
|
+
}
|
|
13833
|
+
};
|
|
13834
|
+
listActions = async ({ app, manifest }) => {
|
|
13835
|
+
if (!app.enabled) return [];
|
|
13836
|
+
const runnerApp = this.toRunnerApp(app, manifest.providerIds);
|
|
13837
|
+
this.apps.set(app.id, runnerApp);
|
|
13838
|
+
const persistent = manifest.lifecycle?.mode === "resident" || manifest.lifecycle?.mode === "provider";
|
|
13839
|
+
const lastStartedAt = persistent ? this.states.get(app.id)?.lastStartedAt ?? (/* @__PURE__ */ new Date()).toISOString() : (/* @__PURE__ */ new Date()).toISOString();
|
|
13840
|
+
if (!persistent) this.states.set(app.id, {
|
|
13841
|
+
status: "starting",
|
|
13842
|
+
lastStartedAt
|
|
13843
|
+
});
|
|
13844
|
+
try {
|
|
13845
|
+
const actions = await this.runner.listActions(runnerApp);
|
|
13846
|
+
if (!persistent) this.states.set(app.id, {
|
|
13847
|
+
status: "running",
|
|
13848
|
+
lastStartedAt,
|
|
13849
|
+
lastReadyAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
13850
|
+
});
|
|
13851
|
+
return actions.map((action) => {
|
|
13852
|
+
const declared = manifest.actions[action.name];
|
|
13853
|
+
return {
|
|
13854
|
+
id: buildServiceActionId(app.id, action.name),
|
|
13855
|
+
appId: app.id,
|
|
13856
|
+
name: action.name,
|
|
13857
|
+
title: declared?.title ?? action.title,
|
|
13858
|
+
description: declared?.description ?? action.description,
|
|
13859
|
+
inputSchema: declared?.inputSchema,
|
|
13860
|
+
risk: declared?.risk ?? "dangerous"
|
|
13861
|
+
};
|
|
13862
|
+
});
|
|
13863
|
+
} catch (error) {
|
|
13864
|
+
this.markFailed(app.id, lastStartedAt, error);
|
|
13865
|
+
await this.recoverPersistentComponentsIfNeeded(error);
|
|
13866
|
+
throw error;
|
|
13867
|
+
}
|
|
13868
|
+
};
|
|
13869
|
+
invokeAction = async ({ app, manifest, actionName, input }) => {
|
|
13870
|
+
await this.start({
|
|
13871
|
+
app,
|
|
13872
|
+
manifest
|
|
13873
|
+
});
|
|
13874
|
+
const runnerApp = this.toRunnerApp(app, manifest.providerIds);
|
|
13875
|
+
this.apps.set(app.id, runnerApp);
|
|
13876
|
+
if (manifest.lifecycle?.mode === "resident") try {
|
|
13877
|
+
return await this.runner.invoke(runnerApp, actionName, input, manifest.actions[actionName]?.timeoutMs ?? 7e3);
|
|
13878
|
+
} catch (error) {
|
|
13879
|
+
this.markFailed(app.id, this.states.get(app.id)?.lastStartedAt ?? (/* @__PURE__ */ new Date()).toISOString(), error);
|
|
13880
|
+
await this.recoverPersistentComponentsIfNeeded(error);
|
|
13881
|
+
throw error;
|
|
13882
|
+
}
|
|
13883
|
+
const lastStartedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
13884
|
+
this.states.set(app.id, {
|
|
13885
|
+
status: "starting",
|
|
13886
|
+
lastStartedAt
|
|
13887
|
+
});
|
|
13888
|
+
try {
|
|
13889
|
+
const result = await this.runner.invoke(runnerApp, actionName, input, manifest.actions[actionName]?.timeoutMs ?? 7e3);
|
|
13890
|
+
this.states.set(app.id, {
|
|
13891
|
+
status: "running",
|
|
13892
|
+
lastStartedAt,
|
|
13893
|
+
lastReadyAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
13894
|
+
});
|
|
13895
|
+
return result;
|
|
13896
|
+
} catch (error) {
|
|
13897
|
+
this.markFailed(app.id, lastStartedAt, error);
|
|
13898
|
+
await this.recoverPersistentComponentsIfNeeded(error);
|
|
13899
|
+
throw error;
|
|
13900
|
+
}
|
|
13901
|
+
};
|
|
13902
|
+
stop = async (appId) => {
|
|
13903
|
+
this.clearResidentTimer(appId);
|
|
13904
|
+
this.providers.delete(appId);
|
|
13905
|
+
this.persistentRegistrations.delete(appId);
|
|
13906
|
+
const app = this.apps.get(appId);
|
|
13907
|
+
this.apps.delete(appId);
|
|
13908
|
+
if (app) await this.runner.stop(app);
|
|
13909
|
+
this.states.set(appId, { status: "idle" });
|
|
13910
|
+
};
|
|
13911
|
+
restart = async (appId) => await this.stop(appId);
|
|
13912
|
+
dispose = async () => {
|
|
13913
|
+
if (this.recoveryPromise) await this.recoveryPromise;
|
|
13914
|
+
for (const appId of this.residentTimers.keys()) this.clearResidentTimer(appId);
|
|
13915
|
+
await Promise.allSettled(this.residentDeliveries.values());
|
|
13916
|
+
await this.runner.dispose();
|
|
13917
|
+
this.states.clear();
|
|
13918
|
+
this.apps.clear();
|
|
13919
|
+
this.providers.clear();
|
|
13920
|
+
this.persistentRegistrations.clear();
|
|
13921
|
+
this.residentDeliveries.clear();
|
|
13922
|
+
};
|
|
13923
|
+
scheduleResidentEvent = (params) => {
|
|
13924
|
+
if (this.residentDeliveries.has(params.appId)) return;
|
|
13925
|
+
const delivery = this.deliverResidentEvent(params).finally(() => {
|
|
13926
|
+
this.residentDeliveries.delete(params.appId);
|
|
13927
|
+
});
|
|
13928
|
+
this.residentDeliveries.set(params.appId, delivery);
|
|
13929
|
+
};
|
|
13930
|
+
deliverResidentEvent = async (params) => {
|
|
13931
|
+
const app = this.apps.get(params.appId);
|
|
13932
|
+
if (!app) return;
|
|
13933
|
+
const triggeredAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
13934
|
+
try {
|
|
13935
|
+
await this.runner.deliverEvent(app, {
|
|
13936
|
+
eventId: `timer-${triggeredAt}`,
|
|
13937
|
+
kind: "timer",
|
|
13938
|
+
triggeredAt,
|
|
13939
|
+
eventIntervalMs: params.eventIntervalMs
|
|
13940
|
+
});
|
|
13941
|
+
} catch (error) {
|
|
13942
|
+
this.clearResidentTimer(params.appId);
|
|
13943
|
+
this.markFailed(params.appId, this.states.get(params.appId)?.lastStartedAt ?? triggeredAt, error);
|
|
13944
|
+
await this.recoverPersistentComponentsIfNeeded(error);
|
|
13945
|
+
}
|
|
13946
|
+
};
|
|
13947
|
+
clearResidentTimer = (appId) => {
|
|
13948
|
+
const timer = this.residentTimers.get(appId);
|
|
13949
|
+
if (timer) clearInterval(timer);
|
|
13950
|
+
this.residentTimers.delete(appId);
|
|
13951
|
+
};
|
|
13952
|
+
recoverPersistentComponentsIfNeeded = async (error) => {
|
|
13953
|
+
if (!(error instanceof PortableServiceRunnerError) || !["PORTABLE_RUNTIME_TIMEOUT", "PORTABLE_RUNNER_EXITED"].includes(error.code)) return;
|
|
13954
|
+
if (this.recoveryPromise) return await this.recoveryPromise;
|
|
13955
|
+
const registrations = Array.from(this.persistentRegistrations.values());
|
|
13956
|
+
if (registrations.length === 0) return;
|
|
13957
|
+
this.recoveryPromise = (async () => {
|
|
13958
|
+
for (const appId of this.residentTimers.keys()) this.clearResidentTimer(appId);
|
|
13959
|
+
this.providers.clear();
|
|
13960
|
+
const ordered = [...registrations.filter(({ manifest }) => manifest.lifecycle?.mode === "provider"), ...registrations.filter(({ manifest }) => manifest.lifecycle?.mode === "resident")];
|
|
13961
|
+
for (const registration of ordered) try {
|
|
13962
|
+
await this.start(registration);
|
|
13963
|
+
} catch {}
|
|
13964
|
+
})().finally(() => {
|
|
13965
|
+
this.recoveryPromise = void 0;
|
|
13966
|
+
});
|
|
13967
|
+
await this.recoveryPromise;
|
|
13968
|
+
};
|
|
13969
|
+
toRunnerApp = (app, providerIds) => {
|
|
13970
|
+
if (!app.componentPath || !app.dataDirectory) throw new Error(`Portable Service App ${app.id} is missing component or data storage.`);
|
|
13971
|
+
return {
|
|
13972
|
+
id: app.id,
|
|
13973
|
+
componentPath: app.componentPath,
|
|
13974
|
+
dataDirectory: app.dataDirectory,
|
|
13975
|
+
permissions: app.permissions ?? {},
|
|
13976
|
+
providerIds
|
|
13977
|
+
};
|
|
13978
|
+
};
|
|
13979
|
+
markFailed = (appId, lastStartedAt, error) => {
|
|
13980
|
+
this.states.set(appId, {
|
|
13981
|
+
status: "failed",
|
|
13982
|
+
lastError: error instanceof Error ? error.message : String(error),
|
|
13983
|
+
lastStartedAt,
|
|
13984
|
+
lastFailedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
13985
|
+
});
|
|
13986
|
+
};
|
|
13987
|
+
};
|
|
13988
|
+
//#endregion
|
|
13989
|
+
//#region src/services/service-app-runtime.service.ts
|
|
13990
|
+
var ServiceAppRuntimeService = class {
|
|
13991
|
+
mcp;
|
|
13992
|
+
portable;
|
|
13993
|
+
protocols = /* @__PURE__ */ new Map();
|
|
13994
|
+
constructor(params) {
|
|
13995
|
+
this.mcp = new McpServiceAppRuntimeService(params);
|
|
13996
|
+
this.portable = new PortableServiceAppRuntimeService({ runnerPath: params.portableServiceRunnerPath });
|
|
13997
|
+
}
|
|
13998
|
+
getStatus = (appId) => {
|
|
13999
|
+
return this.protocols.get(appId) === "wasi-component" ? this.portable.getStatus(appId) : this.mcp.getStatus(appId);
|
|
14000
|
+
};
|
|
14001
|
+
start = async (call) => {
|
|
14002
|
+
this.protocols.set(call.app.id, call.manifest.protocol);
|
|
14003
|
+
if (call.manifest.protocol === "wasi-component") await this.portable.start(call);
|
|
14004
|
+
};
|
|
14005
|
+
listActions = async (call) => {
|
|
14006
|
+
this.protocols.set(call.app.id, call.manifest.protocol);
|
|
14007
|
+
return call.manifest.protocol === "wasi-component" ? await this.portable.listActions(call) : await this.mcp.listActions(call);
|
|
14008
|
+
};
|
|
14009
|
+
invokeAction = async (call) => {
|
|
14010
|
+
this.protocols.set(call.app.id, call.manifest.protocol);
|
|
14011
|
+
return call.manifest.protocol === "wasi-component" ? await this.portable.invokeAction(call) : await this.mcp.invokeAction(call);
|
|
14012
|
+
};
|
|
14013
|
+
stop = async (appId) => {
|
|
14014
|
+
const protocol = this.protocols.get(appId);
|
|
14015
|
+
if (protocol === "wasi-component") await this.portable.stop(appId);
|
|
14016
|
+
else if (protocol === "mcp") await this.mcp.stop(appId);
|
|
14017
|
+
else await Promise.all([this.mcp.stop(appId), this.portable.stop(appId)]);
|
|
14018
|
+
};
|
|
14019
|
+
restart = async (appId) => await this.stop(appId);
|
|
14020
|
+
dispose = async () => {
|
|
14021
|
+
await Promise.all([this.mcp.dispose(), this.portable.dispose()]);
|
|
14022
|
+
this.protocols.clear();
|
|
14023
|
+
};
|
|
14024
|
+
};
|
|
14025
|
+
//#endregion
|
|
13483
14026
|
//#region src/utils/service-app-manifest.utils.ts
|
|
13484
14027
|
const SERVICE_APP_ID_PATTERN$1 = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
13485
14028
|
const SERVICE_ACTION_RISKS = new Set([
|
|
@@ -13506,19 +14049,56 @@ function parseServiceAppManifest(raw, options = {}) {
|
|
|
13506
14049
|
const id = readRequiredString$6(parsed, "id");
|
|
13507
14050
|
if (!SERVICE_APP_ID_PATTERN$1.test(id)) throw new Error("service app id must be kebab-case.");
|
|
13508
14051
|
const protocol = readOptionalString$8(parsed, "protocol") ?? "mcp";
|
|
13509
|
-
if (protocol !== "mcp") throw new Error("service app protocol must be mcp.");
|
|
13510
|
-
const launch = new AppServiceLaunchService(options.platformTargetService).resolve(parsed, options.target);
|
|
14052
|
+
if (protocol !== "mcp" && protocol !== "wasi-component") throw new Error("service app protocol must be mcp or wasi-component.");
|
|
14053
|
+
const launch = protocol === "mcp" ? new AppServiceLaunchService(options.platformTargetService).resolve(parsed, options.target) : void 0;
|
|
14054
|
+
const componentEntry = protocol === "wasi-component" ? readPortableComponentEntry(parsed) : void 0;
|
|
14055
|
+
const lifecycle = readServiceAppLifecycle(parsed.lifecycle);
|
|
14056
|
+
if (lifecycle.mode !== "action" && protocol !== "wasi-component") throw new Error(`${lifecycle.mode} service app lifecycle requires wasi-component protocol.`);
|
|
13511
14057
|
return {
|
|
13512
14058
|
id,
|
|
13513
14059
|
title: readRequiredString$6(parsed, "title"),
|
|
13514
14060
|
description: readOptionalString$8(parsed, "description"),
|
|
13515
14061
|
enabled: readOptionalBoolean$1(parsed, "enabled") ?? true,
|
|
13516
14062
|
protocol,
|
|
13517
|
-
command: launch
|
|
13518
|
-
args: launch
|
|
14063
|
+
command: launch?.command,
|
|
14064
|
+
args: launch?.args,
|
|
14065
|
+
componentEntry,
|
|
14066
|
+
providerIds: readProviderIds(parsed.providers),
|
|
14067
|
+
lifecycle,
|
|
13519
14068
|
actions: readManifestActions(parsed.actions)
|
|
13520
14069
|
};
|
|
13521
14070
|
}
|
|
14071
|
+
function readServiceAppLifecycle(value) {
|
|
14072
|
+
if (value === void 0) return { mode: "action" };
|
|
14073
|
+
if (!isRecord$7(value)) throw new Error("service app lifecycle must be an object.");
|
|
14074
|
+
const mode = readOptionalString$8(value, "mode") ?? "action";
|
|
14075
|
+
if (mode === "action") return { mode };
|
|
14076
|
+
if (mode === "provider") return { mode };
|
|
14077
|
+
if (mode !== "resident") throw new Error("service app lifecycle.mode must be action, resident or provider.");
|
|
14078
|
+
const eventIntervalMs = readOptionalNumber(value, "eventIntervalMs");
|
|
14079
|
+
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.");
|
|
14080
|
+
return {
|
|
14081
|
+
mode,
|
|
14082
|
+
eventIntervalMs
|
|
14083
|
+
};
|
|
14084
|
+
}
|
|
14085
|
+
function readProviderIds(value) {
|
|
14086
|
+
if (value === void 0) return [];
|
|
14087
|
+
if (!Array.isArray(value)) throw new Error("service app providers must be an array.");
|
|
14088
|
+
const providerIds = value.map((entry) => {
|
|
14089
|
+
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.");
|
|
14090
|
+
return entry.trim();
|
|
14091
|
+
});
|
|
14092
|
+
return Array.from(new Set(providerIds));
|
|
14093
|
+
}
|
|
14094
|
+
function readPortableComponentEntry(record) {
|
|
14095
|
+
const component = record.component;
|
|
14096
|
+
if (!isRecord$7(component)) throw new Error("service app component is required for wasi-component.");
|
|
14097
|
+
const entry = readRequiredString$6(component, "entry").replace(/\\/g, "/");
|
|
14098
|
+
if (path.isAbsolute(entry) || entry.includes("\0") || entry.split("/").includes("..")) throw new Error("service app component.entry must be a package-relative path.");
|
|
14099
|
+
if (!entry.endsWith(".wasm")) throw new Error("service app component.entry must reference a .wasm file.");
|
|
14100
|
+
return entry;
|
|
14101
|
+
}
|
|
13522
14102
|
function readManifestActions(value) {
|
|
13523
14103
|
if (value === void 0) throw new Error("service app actions are required.");
|
|
13524
14104
|
if (!isRecord$7(value)) throw new Error("service app actions must be an object.");
|
|
@@ -13531,11 +14111,14 @@ function readManifestActions(value) {
|
|
|
13531
14111
|
if (risk !== void 0 && !SERVICE_ACTION_RISKS.has(risk)) throw new Error(`service app action ${name} has invalid risk.`);
|
|
13532
14112
|
const inputSchema = action.inputSchema;
|
|
13533
14113
|
if (inputSchema !== void 0 && !isRecord$7(inputSchema)) throw new Error(`service app action ${name} inputSchema must be an object.`);
|
|
14114
|
+
const timeoutMs = readOptionalNumber(action, "timeoutMs");
|
|
14115
|
+
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.`);
|
|
13534
14116
|
actions[name] = {
|
|
13535
14117
|
risk,
|
|
13536
14118
|
title: readOptionalString$8(action, "title"),
|
|
13537
14119
|
description: readOptionalString$8(action, "description"),
|
|
13538
|
-
inputSchema
|
|
14120
|
+
inputSchema,
|
|
14121
|
+
timeoutMs
|
|
13539
14122
|
};
|
|
13540
14123
|
}
|
|
13541
14124
|
return actions;
|
|
@@ -13553,10 +14136,49 @@ function readOptionalBoolean$1(record, key) {
|
|
|
13553
14136
|
if (typeof record[key] !== "boolean") throw new Error(`service app ${key} must be boolean.`);
|
|
13554
14137
|
return record[key];
|
|
13555
14138
|
}
|
|
14139
|
+
function readOptionalNumber(record, key) {
|
|
14140
|
+
if (record[key] === void 0) return;
|
|
14141
|
+
if (typeof record[key] !== "number") throw new Error(`service app ${key} must be number.`);
|
|
14142
|
+
return record[key];
|
|
14143
|
+
}
|
|
13556
14144
|
function isRecord$7(value) {
|
|
13557
14145
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
13558
14146
|
}
|
|
13559
14147
|
//#endregion
|
|
14148
|
+
//#region src/services/service-app-lifecycle.service.ts
|
|
14149
|
+
var ServiceAppLifecycleService = class {
|
|
14150
|
+
constructor(params) {
|
|
14151
|
+
this.params = params;
|
|
14152
|
+
}
|
|
14153
|
+
startDiscovered = async (registrations) => {
|
|
14154
|
+
const active = registrations.filter(({ manifest, record }) => record.enabled && manifest.lifecycle && manifest.lifecycle.mode !== "action");
|
|
14155
|
+
await Promise.allSettled(this.order(active).map(async ({ manifest, record }) => await this.params.runtimeService.start?.({
|
|
14156
|
+
app: record,
|
|
14157
|
+
manifest
|
|
14158
|
+
})));
|
|
14159
|
+
};
|
|
14160
|
+
activatePackageComponents = async (components) => {
|
|
14161
|
+
const registrations = [];
|
|
14162
|
+
for (const component of components.filter((entry) => entry.kind === "service")) {
|
|
14163
|
+
const manifest = await readServiceAppManifest(component.sourcePath);
|
|
14164
|
+
if (!manifest.lifecycle || manifest.lifecycle.mode === "action") continue;
|
|
14165
|
+
registrations.push({
|
|
14166
|
+
manifest,
|
|
14167
|
+
record: this.params.recordService.fromManifest(component.sourcePath, manifest, component, component.storage)
|
|
14168
|
+
});
|
|
14169
|
+
}
|
|
14170
|
+
for (const { manifest, record } of this.order(registrations)) await this.params.runtimeService.start?.({
|
|
14171
|
+
app: record,
|
|
14172
|
+
manifest
|
|
14173
|
+
});
|
|
14174
|
+
};
|
|
14175
|
+
deactivatePackageComponents = async (components) => {
|
|
14176
|
+
const serviceIds = components.filter((component) => component.kind === "service").map((component) => component.id);
|
|
14177
|
+
await Promise.all(serviceIds.map(async (serviceId) => await this.params.runtimeService.stop(serviceId)));
|
|
14178
|
+
};
|
|
14179
|
+
order = (registrations) => [...registrations.filter(({ manifest }) => manifest.lifecycle?.mode === "provider"), ...registrations.filter(({ manifest }) => manifest.lifecycle?.mode === "resident")];
|
|
14180
|
+
};
|
|
14181
|
+
//#endregion
|
|
13560
14182
|
//#region src/services/service-app-record.service.ts
|
|
13561
14183
|
var ServiceAppRecordService = class {
|
|
13562
14184
|
instanceStorageService = new AppInstanceStorageService();
|
|
@@ -13638,7 +14260,12 @@ var ServiceAppRecordService = class {
|
|
|
13638
14260
|
dataDirectory: storage?.dataDirectory,
|
|
13639
14261
|
instanceId: packageSource?.instanceId ?? storage?.instanceId,
|
|
13640
14262
|
storage,
|
|
13641
|
-
isolation: packageSource?.isolation ?? "full-user"
|
|
14263
|
+
isolation: packageSource?.isolation ?? "full-user",
|
|
14264
|
+
runtimeProfile: packageSource?.runtimeProfile ?? "native-process",
|
|
14265
|
+
permissions: packageSource?.permissions ?? {},
|
|
14266
|
+
componentPath: manifest.componentEntry ? join(dirPath, manifest.componentEntry) : void 0,
|
|
14267
|
+
providerIds: manifest.providerIds,
|
|
14268
|
+
lifecycle: manifest.lifecycle
|
|
13642
14269
|
};
|
|
13643
14270
|
};
|
|
13644
14271
|
failedWorkspaceRecord = (id, dirPath, error) => ({
|
|
@@ -13669,7 +14296,9 @@ var ServiceAppRecordService = class {
|
|
|
13669
14296
|
dataDirectory: source.dataDirectory,
|
|
13670
14297
|
instanceId: source.instanceId,
|
|
13671
14298
|
storage: source.storage,
|
|
13672
|
-
isolation: source.isolation
|
|
14299
|
+
isolation: source.isolation,
|
|
14300
|
+
runtimeProfile: source.runtimeProfile,
|
|
14301
|
+
permissions: source.permissions
|
|
13673
14302
|
});
|
|
13674
14303
|
toTitle = (value) => basename(value).replace(/[-_]+/g, " ").trim() || value;
|
|
13675
14304
|
getWorkspaceInstanceDirectory = (serviceId) => join(this.params.getWorkspacePath(), ".nextclaw", "app-instances", serviceId, "default");
|
|
@@ -13838,13 +14467,16 @@ var ServiceActionGrantService = class {
|
|
|
13838
14467
|
const results = [];
|
|
13839
14468
|
for (const grant of grants) {
|
|
13840
14469
|
const actionId = readServiceActionTargetId(grant.resource.target);
|
|
13841
|
-
if (!actionId || grant.subject.type !== "panel-app") continue;
|
|
14470
|
+
if (!actionId || grant.subject.type !== "panel-app" && grant.subject.type !== "agent") continue;
|
|
13842
14471
|
try {
|
|
13843
14472
|
const action = await this.params.resolveAction(actionId);
|
|
13844
14473
|
results.push({
|
|
13845
|
-
caller: {
|
|
14474
|
+
caller: grant.subject.type === "panel-app" ? {
|
|
13846
14475
|
surface: "panel-app",
|
|
13847
14476
|
appId: grant.subject.id
|
|
14477
|
+
} : {
|
|
14478
|
+
surface: "agent",
|
|
14479
|
+
agentId: grant.subject.id
|
|
13848
14480
|
},
|
|
13849
14481
|
actionId,
|
|
13850
14482
|
risk: action.risk,
|
|
@@ -13860,7 +14492,7 @@ var ServiceActionGrantService = class {
|
|
|
13860
14492
|
await this.params.capabilityGrantManager.revoke({
|
|
13861
14493
|
subject: {
|
|
13862
14494
|
type: caller.surface,
|
|
13863
|
-
id: caller
|
|
14495
|
+
id: getServiceActionCallerId(caller)
|
|
13864
14496
|
},
|
|
13865
14497
|
resourceType: "service.action",
|
|
13866
14498
|
target: { actionId }
|
|
@@ -13961,28 +14593,6 @@ function mergeServiceAppRuntimeActions({ record, manifest, runtimeActions }) {
|
|
|
13961
14593
|
return [...declared, ...undeclared].sort((left, right) => left.id.localeCompare(right.id));
|
|
13962
14594
|
}
|
|
13963
14595
|
//#endregion
|
|
13964
|
-
//#region src/utils/service-app-error.utils.ts
|
|
13965
|
-
const SERVICE_APP_ERROR_CODES = new Set([
|
|
13966
|
-
"AUTHORIZATION_REQUIRED",
|
|
13967
|
-
"SERVICE_APP_ACTION_NOT_DECLARED",
|
|
13968
|
-
"SERVICE_APP_ACTION_NOT_FOUND",
|
|
13969
|
-
"SERVICE_APP_INVALID_ACTION",
|
|
13970
|
-
"SERVICE_APP_INVALID_CALLER",
|
|
13971
|
-
"SERVICE_APP_INVALID_MANIFEST",
|
|
13972
|
-
"SERVICE_APP_MANAGED_SOURCE",
|
|
13973
|
-
"SERVICE_APP_NOT_FOUND",
|
|
13974
|
-
"SERVICE_APP_READ_FAILED",
|
|
13975
|
-
"SERVICE_APP_RUNTIME_FAILED"
|
|
13976
|
-
]);
|
|
13977
|
-
var ServiceAppError = class extends Error {
|
|
13978
|
-
constructor(code, message) {
|
|
13979
|
-
super(message);
|
|
13980
|
-
this.code = code;
|
|
13981
|
-
this.name = "ServiceAppError";
|
|
13982
|
-
}
|
|
13983
|
-
};
|
|
13984
|
-
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);
|
|
13985
|
-
//#endregion
|
|
13986
14596
|
//#region src/managers/service-app.manager.ts
|
|
13987
14597
|
const SERVICE_APP_ID_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
13988
14598
|
var ServiceAppManager = class {
|
|
@@ -13991,14 +14601,22 @@ var ServiceAppManager = class {
|
|
|
13991
14601
|
recordService;
|
|
13992
14602
|
actionGrants;
|
|
13993
14603
|
packageRuntime;
|
|
14604
|
+
lifecycleService;
|
|
13994
14605
|
reconciliationDiagnostics = [];
|
|
13995
14606
|
constructor(params) {
|
|
13996
14607
|
this.params = params;
|
|
13997
|
-
this.runtimeService = params.runtimeService ?? new
|
|
14608
|
+
this.runtimeService = params.runtimeService ?? new ServiceAppRuntimeService({
|
|
14609
|
+
getConfig: () => params.configManager.config,
|
|
14610
|
+
portableServiceRunnerPath: params.portableServiceRunnerPath
|
|
14611
|
+
});
|
|
13998
14612
|
this.recordService = new ServiceAppRecordService({
|
|
13999
14613
|
getWorkspacePath: this.getWorkspacePath,
|
|
14000
14614
|
runtimeService: this.runtimeService
|
|
14001
14615
|
});
|
|
14616
|
+
this.lifecycleService = new ServiceAppLifecycleService({
|
|
14617
|
+
recordService: this.recordService,
|
|
14618
|
+
runtimeService: this.runtimeService
|
|
14619
|
+
});
|
|
14002
14620
|
this.actionGrants = new ServiceActionGrantService({
|
|
14003
14621
|
capabilityGrantManager: params.capabilityGrantManager,
|
|
14004
14622
|
resolveAction: this.requireServiceAction
|
|
@@ -14017,6 +14635,7 @@ var ServiceAppManager = class {
|
|
|
14017
14635
|
lockPathForAppId: (appId) => this.getServiceAppLockPath(this.getWorkspacePath(), appId),
|
|
14018
14636
|
serviceAppsPath: this.getServiceAppsPath(this.getWorkspacePath())
|
|
14019
14637
|
});
|
|
14638
|
+
await this.lifecycleService.startDiscovered(await this.listValidServiceApps());
|
|
14020
14639
|
};
|
|
14021
14640
|
listServiceApps = async () => {
|
|
14022
14641
|
const workspacePath = this.getWorkspacePath();
|
|
@@ -14052,8 +14671,8 @@ var ServiceAppManager = class {
|
|
|
14052
14671
|
});
|
|
14053
14672
|
};
|
|
14054
14673
|
invokeServiceAction = async (actionId, request) => {
|
|
14055
|
-
|
|
14056
|
-
|
|
14674
|
+
assertServiceActionCaller(request.caller, this.params.hasAgent);
|
|
14675
|
+
assertServiceActionDeclared(request.caller, actionId, request.declaredActions);
|
|
14057
14676
|
const { manifest, record } = await this.requireServiceAppForAction(actionId, true);
|
|
14058
14677
|
const actionName = getServiceActionName(actionId, record.id);
|
|
14059
14678
|
if (!Object.hasOwn(manifest.actions, actionName)) throw new ServiceAppError("SERVICE_APP_ACTION_NOT_FOUND", "service action not found");
|
|
@@ -14080,30 +14699,33 @@ var ServiceAppManager = class {
|
|
|
14080
14699
|
return grant;
|
|
14081
14700
|
};
|
|
14082
14701
|
grantServiceActions = async (actionIds, request) => {
|
|
14083
|
-
|
|
14702
|
+
assertServiceActionCaller(request.caller, this.params.hasAgent);
|
|
14084
14703
|
const normalizedActionIds = this.normalizeActionIds(actionIds);
|
|
14085
14704
|
if (normalizedActionIds.length === 0) throw new ServiceAppError("SERVICE_APP_INVALID_ACTION", "service action id is invalid");
|
|
14086
14705
|
const actions = [];
|
|
14087
14706
|
for (const actionId of normalizedActionIds) {
|
|
14088
|
-
|
|
14707
|
+
assertServiceActionDeclared(request.caller, actionId, request.declaredActions);
|
|
14089
14708
|
actions.push(await this.requireServiceAction(actionId));
|
|
14090
14709
|
}
|
|
14091
14710
|
return await this.actionGrants.grant(request.caller, actions);
|
|
14092
14711
|
};
|
|
14093
14712
|
listServiceActionGrants = async () => await this.actionGrants.list();
|
|
14094
14713
|
revokeServiceAction = async (caller, actionId) => {
|
|
14095
|
-
this.
|
|
14714
|
+
assertServiceActionCaller(caller, this.params.hasAgent);
|
|
14096
14715
|
await this.actionGrants.revoke(caller, actionId);
|
|
14097
14716
|
};
|
|
14098
14717
|
matchesCapabilityGrant = async (grant) => {
|
|
14099
|
-
if (grant.subject.type !== "panel-app" || grant.resource.type !== "service.action") return false;
|
|
14718
|
+
if (grant.subject.type !== "panel-app" && grant.subject.type !== "agent" || grant.resource.type !== "service.action") return false;
|
|
14100
14719
|
const actionId = readServiceActionTargetId(grant.resource.target);
|
|
14101
14720
|
if (!actionId) return false;
|
|
14102
14721
|
try {
|
|
14103
14722
|
const action = await this.requireServiceAction(actionId);
|
|
14104
|
-
return getCapabilityGrantKey(grant) === getCapabilityGrantKey(createServiceActionGrantRequest({
|
|
14723
|
+
return getCapabilityGrantKey(grant) === getCapabilityGrantKey(createServiceActionGrantRequest(grant.subject.type === "panel-app" ? {
|
|
14105
14724
|
surface: "panel-app",
|
|
14106
14725
|
appId: grant.subject.id
|
|
14726
|
+
} : {
|
|
14727
|
+
surface: "agent",
|
|
14728
|
+
agentId: grant.subject.id
|
|
14107
14729
|
}, action));
|
|
14108
14730
|
} catch {
|
|
14109
14731
|
return false;
|
|
@@ -14183,10 +14805,8 @@ var ServiceAppManager = class {
|
|
|
14183
14805
|
}
|
|
14184
14806
|
}
|
|
14185
14807
|
};
|
|
14186
|
-
|
|
14187
|
-
|
|
14188
|
-
await Promise.all(serviceIds.map(async (serviceId) => await this.runtimeService.stop(serviceId)));
|
|
14189
|
-
};
|
|
14808
|
+
activatePackageComponents = async (components) => await this.lifecycleService.activatePackageComponents(components);
|
|
14809
|
+
deactivatePackageComponents = async (components) => await this.lifecycleService.deactivatePackageComponents(components);
|
|
14190
14810
|
preparePackageComponentDeactivation = async (components) => await this.packageRuntime.prepareDeactivation(components);
|
|
14191
14811
|
removePackageComponentGrants = async (components) => await this.actionGrants.removePackageGrants(new Set(components.filter((component) => component.kind === "service").map((component) => component.id)));
|
|
14192
14812
|
withGrantState = async (action, params) => {
|
|
@@ -14267,12 +14887,6 @@ var ServiceAppManager = class {
|
|
|
14267
14887
|
}));
|
|
14268
14888
|
return [...workspaceEntries, ...packageEntries].filter((entry) => Boolean(entry));
|
|
14269
14889
|
};
|
|
14270
|
-
assertCaller = (caller) => {
|
|
14271
|
-
if (caller.surface !== "panel-app" || !caller.appId.trim()) throw new ServiceAppError("SERVICE_APP_INVALID_CALLER", "service action caller is invalid");
|
|
14272
|
-
};
|
|
14273
|
-
assertDeclaredAction = (actionId, declaredActions) => {
|
|
14274
|
-
if (!declaredActions.includes(actionId)) throw new ServiceAppError("SERVICE_APP_ACTION_NOT_DECLARED", "panel app did not declare this service action");
|
|
14275
|
-
};
|
|
14276
14890
|
normalizeActionIds = (actionIds) => Array.from(new Set(actionIds.map((actionId) => actionId.trim()).filter((actionId) => actionId.length > 0)));
|
|
14277
14891
|
getWorkspacePath = () => getWorkspacePathFromConfig(this.params.configManager.config);
|
|
14278
14892
|
getServiceAppsPath = (workspacePath) => join(workspacePath, DEFAULT_SERVICE_APPS_DIR);
|
|
@@ -21248,6 +21862,57 @@ function readOptionalString$1(value) {
|
|
|
21248
21862
|
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
21249
21863
|
}
|
|
21250
21864
|
//#endregion
|
|
21865
|
+
//#region src/utils/service-action-tool.utils.ts
|
|
21866
|
+
const MAX_TOOL_NAME_LENGTH = 64;
|
|
21867
|
+
const HASH_LENGTH = 8;
|
|
21868
|
+
const TOOL_PREFIX = "service__";
|
|
21869
|
+
function buildServiceActionToolName(actionId) {
|
|
21870
|
+
const readable = actionId.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "") || "action";
|
|
21871
|
+
const suffix = `__${createHash("sha256").update(actionId).digest("hex").slice(0, HASH_LENGTH)}`;
|
|
21872
|
+
const available = MAX_TOOL_NAME_LENGTH - 9 - suffix.length;
|
|
21873
|
+
return `${TOOL_PREFIX}${readable.slice(0, available)}${suffix}`;
|
|
21874
|
+
}
|
|
21875
|
+
//#endregion
|
|
21876
|
+
//#region src/contributions/tool-provider/providers/service-action-tool.provider.ts
|
|
21877
|
+
const EMPTY_OBJECT_SCHEMA = {
|
|
21878
|
+
type: "object",
|
|
21879
|
+
properties: {},
|
|
21880
|
+
additionalProperties: false
|
|
21881
|
+
};
|
|
21882
|
+
var ServiceActionToolProvider = class {
|
|
21883
|
+
constructor(runContextService, serviceAppManager) {
|
|
21884
|
+
this.runContextService = runContextService;
|
|
21885
|
+
this.serviceAppManager = serviceAppManager;
|
|
21886
|
+
}
|
|
21887
|
+
provide = async (request) => {
|
|
21888
|
+
const { toolRunContext } = await this.runContextService.resolve(request);
|
|
21889
|
+
const caller = {
|
|
21890
|
+
surface: "agent",
|
|
21891
|
+
agentId: toolRunContext.agentId
|
|
21892
|
+
};
|
|
21893
|
+
return (await this.serviceAppManager.listServiceActions({ caller })).filter((action) => action.grantState === "granted").map((action) => this.toTool(caller, action));
|
|
21894
|
+
};
|
|
21895
|
+
toTool = (caller, action) => ({
|
|
21896
|
+
name: buildServiceActionToolName(action.id),
|
|
21897
|
+
description: [
|
|
21898
|
+
action.title ?? action.name,
|
|
21899
|
+
action.description,
|
|
21900
|
+
`NextClaw Service Action: ${action.id}`
|
|
21901
|
+
].filter(Boolean).join("\n"),
|
|
21902
|
+
parameters: action.inputSchema ?? EMPTY_OBJECT_SCHEMA,
|
|
21903
|
+
supportsParallelToolCalls: action.risk === "read",
|
|
21904
|
+
execute: async (args) => await this.serviceAppManager.invokeServiceAction(action.id, {
|
|
21905
|
+
caller,
|
|
21906
|
+
input: readInput(args)
|
|
21907
|
+
})
|
|
21908
|
+
});
|
|
21909
|
+
};
|
|
21910
|
+
function readInput(value) {
|
|
21911
|
+
if (value === void 0) return {};
|
|
21912
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("Service Action tool input must be an object.");
|
|
21913
|
+
return value;
|
|
21914
|
+
}
|
|
21915
|
+
//#endregion
|
|
21251
21916
|
//#region src/contributions/tool-provider/services/tool-provider-run-context.service.ts
|
|
21252
21917
|
var ToolProviderRunContextService = class {
|
|
21253
21918
|
constructor(sessionManager, agentManager, configManager) {
|
|
@@ -21304,6 +21969,7 @@ var ToolProviderContribution = class extends Contribution$1 {
|
|
|
21304
21969
|
new ProjectToolProvider(this.kernel.projectManager),
|
|
21305
21970
|
new SessionToolProvider(runContextService, this.kernel.sessionManager, this.kernel.sessionRequests, this.kernel.sessionSearch),
|
|
21306
21971
|
new AssetToolProvider(this.kernel.assetStore),
|
|
21972
|
+
new ServiceActionToolProvider(runContextService, this.kernel.serviceAppManager),
|
|
21307
21973
|
new McpToolProvider(runContextService, this.kernel.mcpManager)
|
|
21308
21974
|
];
|
|
21309
21975
|
};
|
|
@@ -21332,11 +21998,13 @@ function createKernelOperationalManagers(params) {
|
|
|
21332
21998
|
};
|
|
21333
21999
|
}
|
|
21334
22000
|
function createKernelServiceAppManagers(params) {
|
|
21335
|
-
const { appHomeDirectory, appPackageManager, capabilityGrantManager, configManager } = params;
|
|
22001
|
+
const { appHomeDirectory, appPackageManager, capabilityGrantManager, configManager, hasAgent, portableServiceRunnerPath } = params;
|
|
21336
22002
|
const serviceAppManager = new ServiceAppManager({
|
|
21337
22003
|
configManager,
|
|
21338
22004
|
listPackageComponentSources: appPackageManager.listActiveComponentSources,
|
|
21339
|
-
capabilityGrantManager
|
|
22005
|
+
capabilityGrantManager,
|
|
22006
|
+
hasAgent,
|
|
22007
|
+
portableServiceRunnerPath
|
|
21340
22008
|
});
|
|
21341
22009
|
return {
|
|
21342
22010
|
serviceAppManager,
|
|
@@ -21405,6 +22073,9 @@ function installKernelAppPackageRuntimeHooks(params) {
|
|
|
21405
22073
|
await panelAppManager.assertCanActivatePackageComponents(sources);
|
|
21406
22074
|
await serviceAppManager.assertCanActivatePackageComponents(sources);
|
|
21407
22075
|
},
|
|
22076
|
+
afterActivate: async (sources) => {
|
|
22077
|
+
await serviceAppManager.activatePackageComponents(sources);
|
|
22078
|
+
},
|
|
21408
22079
|
beforeDeactivate: async (sources) => {
|
|
21409
22080
|
panelAppManager.deactivatePackageComponents(sources);
|
|
21410
22081
|
await serviceAppManager.deactivatePackageComponents(sources);
|
|
@@ -21548,7 +22219,9 @@ var NextclawKernel = class {
|
|
|
21548
22219
|
appHomeDirectory: resolveKernelAppHomeDirectory(options),
|
|
21549
22220
|
appPackageManager: this.appPackageManager,
|
|
21550
22221
|
configManager: this.configManager,
|
|
21551
|
-
capabilityGrantManager: this.capabilityGrants
|
|
22222
|
+
capabilityGrantManager: this.capabilityGrants,
|
|
22223
|
+
hasAgent: (agentId) => this.agents.getAgent(agentId) !== null,
|
|
22224
|
+
portableServiceRunnerPath: options.portableServiceRunnerPath
|
|
21552
22225
|
}));
|
|
21553
22226
|
installKernelAppPackageRuntimeHooks({
|
|
21554
22227
|
appPackageManager: this.appPackageManager,
|
|
@@ -22776,6 +23449,6 @@ function resolveLegacyEventType(message) {
|
|
|
22776
23449
|
return `message.${role || "other"}`;
|
|
22777
23450
|
}
|
|
22778
23451
|
//#endregion
|
|
22779
|
-
export { AUTOMATIC_UPDATE_CHECK_INTERVAL_MS, AccessManager, AgentManager, AgentRunClient, AppDataError, AppDataManager, AppPackageError, AppPackageManager, AutomationManager, BuiltinNarpRuntimeProviderService, CONTEXT_COMPACTION_CONTINUATION_TEXT, CONTEXT_COMPACTION_PROJECTION_KIND, CONTEXT_COMPACTION_PROJECTION_METADATA_KEY, CONTEXT_COMPACTION_SYSTEM_PREAMBLE, CONTEXT_COMPACTION_TIMELINE_KIND, CapabilityGrantLegacyMigrationService, CapabilityGrantManager, CapabilityGrantStore, ChannelManager, CommandRegistry, ConfigManager, ContextCompactionJournalRecoveryService, ContextCompactionPreflightService, Contribution, DEFAULT_AGENT_RUNTIME_ENTRY_ID, DEFAULT_SERVICE_ACTION_RISK, DESKTOP_HOST_ACCESS, DESKTOP_HOST_PROTOCOL_VERSION, DesktopHostCapabilityManager, DesktopNodeReplService, DesktopSessionStateService, EventBus, ExtensionManager, FeatureControlsService, GatewayInboundProcessor, InboxDeliveryError, InboxDeliveryManager, Ingress, LlmProviderManager, LlmUsageManager, LlmUsageStore, MAX_INBOX_DELIVERY_CONTENT_LENGTH, McpManager, McpServiceAppRuntimeService, NARP_HTTP_RUNTIME_KIND, NARP_STDIO_RUNTIME_KIND, NEXTCLAW_TIMELINE_KIND_METADATA_KEY, NcpAgentSessionJournalStore, NextclawHarness, NextclawHarnessError, NextclawKernel, ObservationManager, PANEL_APP_AGENT_CAPABILITIES, PROJECT_TEMPLATE_IDS, PROVIDER_MODEL_CATALOG_REFRESH_INTERVAL_MS, PanelAppAssetTokenService, PanelAppError, PanelAppManager, PreferenceError, PreferenceManager, ProjectError, ProjectManager, ProviderManagerNcpLLMApi, ProviderModelCatalogManager, SERVICE_APP_MANIFEST_FILE_NAME, ServiceAppError, ServiceAppManager, SessionContextCompactionError, SessionContextCompactionManager, SessionManager, SessionMessageCursorError, SessionRequestManager, SessionSettingsError, SkillManager, SystemObjectReferenceError, SystemObjectReferenceManager, UnavailableDesktopHost, UpdateManifestReader, assertObservationJsonValue, assertObservationPredicate, buildAgentRunSendPayload, buildContextCompactionModelProjection, buildContextCompactionTimelineNcpMessage, buildLlmUsageSummary, buildLocalizedTextMap, buildNextclawNcpRunContext, buildObservationEventModelMessage, buildServiceActionId, capabilityGrantCovers, createAgentRuntimeSessionRequestDispatcher, createAssetTools, createCapabilityDeclarationFingerprint, createContextCompactionMessageId, createContextWindowSignature, createCronJobSystemObjectProvider, createDesktopHostError, createInboxDeliverySystemObjectProvider, createLlmUsageRecord, createPanelAppAgentGrantRequest, createPanelAppClientGrantRequest, createServiceActionGrantRequest, createTypedKey, describeAgentRuntimeSessionTypes, dispatchAgentRuntimeSessionRequest, dispatchChannelReplyRoute, dispatchPromptOverNcp, dispatchPromptOverNcpResult, evaluateObservationEventAdmission, eventKeys, getAutomaticUpdateCheckDelay, getCapabilityGrantKey, getServiceActionName, getServiceAppManifestPath, getUiContentParamsBootstrapScript, getUnsignedUpdateManifest, hasLlmUsageTelemetry, ingressKeys, injectUiContentParamsBootstrap, isAppDataError, isAppPackageError, isContextCompactionProjectionMessage, isContextCompactionTimelineMessage, isContextWindowSnapshot, isInboxDeliveryError, isPanelAppAgentCapability, isPanelAppError, isPreferenceError, isProjectError, isReplyCapableChannel, isServiceAppError, isSessionContextCompactionError, isSessionMessageCursorError, isSessionSettingsError, isSystemObjectReferenceError, listExtensionChannelIds, listServiceAppManifestActions, matchesCapabilityGrantFilter, matchesObservationPredicate, mergeServiceAppRuntimeActions, normalizeAgentRuntimeSessionTypeIcon, normalizeCapabilityGrantRequest, normalizeLlmUsageModel, normalizeOptionalString, parseObservationDuration, parseServiceAppManifest, parseSkillFrontmatter, readContextCompactionCheckpoint, readContextWindowEventSessionId, readJsonPointer, readLatestContextCompactionCheckpoint, readLearningLoopRuntimeConfig, readMetadataModel, readMetadataThinking, readServiceActionTargetId, readServiceAppManifest, resolveAgentRuntimeEntries, resolveAutomaticUpdateCheckIntervalMs, resolveChannelReplyRoute, resolveEffectiveModel, resolveLegacyEventType, resolveSessionChannelContext, runGatewayInboundLoop, runNextclawTask, sanitizeLlmUsage, serializeContextTail, serializeUnsignedUpdateManifest, shouldRefreshContextWindowDuringStream, shouldRefreshContextWindowImmediately, startPromptOverNcpExecution, stripSkillFrontmatter, syncSessionThinkingPreference, toBoundedJson, toNcpMessages, waitForAgentRuntimeSessionReply };
|
|
23452
|
+
export { AUTOMATIC_UPDATE_CHECK_INTERVAL_MS, AccessManager, AgentManager, AgentRunClient, AppDataError, AppDataManager, AppPackageError, AppPackageManager, AutomationManager, BuiltinNarpRuntimeProviderService, CONTEXT_COMPACTION_CONTINUATION_TEXT, CONTEXT_COMPACTION_PROJECTION_KIND, CONTEXT_COMPACTION_PROJECTION_METADATA_KEY, CONTEXT_COMPACTION_SYSTEM_PREAMBLE, CONTEXT_COMPACTION_TIMELINE_KIND, CapabilityGrantLegacyMigrationService, CapabilityGrantManager, CapabilityGrantStore, ChannelManager, CommandRegistry, ConfigManager, ContextCompactionJournalRecoveryService, ContextCompactionPreflightService, Contribution, DEFAULT_AGENT_RUNTIME_ENTRY_ID, DEFAULT_SERVICE_ACTION_RISK, DESKTOP_HOST_ACCESS, DESKTOP_HOST_PROTOCOL_VERSION, DesktopHostCapabilityManager, DesktopNodeReplService, DesktopSessionStateService, EventBus, ExtensionManager, FeatureControlsService, GatewayInboundProcessor, InboxDeliveryError, InboxDeliveryManager, Ingress, LlmProviderManager, LlmUsageManager, LlmUsageStore, MAX_INBOX_DELIVERY_CONTENT_LENGTH, McpManager, McpServiceAppRuntimeService, NARP_HTTP_RUNTIME_KIND, NARP_STDIO_RUNTIME_KIND, NEXTCLAW_TIMELINE_KIND_METADATA_KEY, NcpAgentSessionJournalStore, NextclawHarness, NextclawHarnessError, NextclawKernel, ObservationManager, PANEL_APP_AGENT_CAPABILITIES, PROJECT_TEMPLATE_IDS, PROVIDER_MODEL_CATALOG_REFRESH_INTERVAL_MS, PanelAppAssetTokenService, PanelAppError, PanelAppManager, PreferenceError, PreferenceManager, ProjectError, ProjectManager, ProviderManagerNcpLLMApi, ProviderModelCatalogManager, SERVICE_APP_MANIFEST_FILE_NAME, ServiceAppError, ServiceAppManager, ServiceAppRuntimeService, SessionContextCompactionError, SessionContextCompactionManager, SessionManager, SessionMessageCursorError, SessionRequestManager, SessionSettingsError, SkillManager, SystemObjectReferenceError, SystemObjectReferenceManager, UnavailableDesktopHost, UpdateManifestReader, assertObservationJsonValue, assertObservationPredicate, buildAgentRunSendPayload, buildContextCompactionModelProjection, buildContextCompactionTimelineNcpMessage, buildLlmUsageSummary, buildLocalizedTextMap, buildNextclawNcpRunContext, buildObservationEventModelMessage, buildServiceActionId, capabilityGrantCovers, createAgentRuntimeSessionRequestDispatcher, createAssetTools, createCapabilityDeclarationFingerprint, createContextCompactionMessageId, createContextWindowSignature, createCronJobSystemObjectProvider, createDesktopHostError, createInboxDeliverySystemObjectProvider, createLlmUsageRecord, createPanelAppAgentGrantRequest, createPanelAppClientGrantRequest, createServiceActionGrantRequest, createTypedKey, describeAgentRuntimeSessionTypes, dispatchAgentRuntimeSessionRequest, dispatchChannelReplyRoute, dispatchPromptOverNcp, dispatchPromptOverNcpResult, evaluateObservationEventAdmission, eventKeys, getAutomaticUpdateCheckDelay, getCapabilityGrantKey, getServiceActionName, getServiceAppManifestPath, getUiContentParamsBootstrapScript, getUnsignedUpdateManifest, hasLlmUsageTelemetry, ingressKeys, injectUiContentParamsBootstrap, isAppDataError, isAppPackageError, isContextCompactionProjectionMessage, isContextCompactionTimelineMessage, isContextWindowSnapshot, isInboxDeliveryError, isPanelAppAgentCapability, isPanelAppError, isPreferenceError, isProjectError, isReplyCapableChannel, isServiceAppError, isSessionContextCompactionError, isSessionMessageCursorError, isSessionSettingsError, isSystemObjectReferenceError, listExtensionChannelIds, listServiceAppManifestActions, matchesCapabilityGrantFilter, matchesObservationPredicate, mergeServiceAppRuntimeActions, normalizeAgentRuntimeSessionTypeIcon, normalizeCapabilityGrantRequest, normalizeLlmUsageModel, normalizeOptionalString, parseObservationDuration, parseServiceAppManifest, parseSkillFrontmatter, readContextCompactionCheckpoint, readContextWindowEventSessionId, readJsonPointer, readLatestContextCompactionCheckpoint, readLearningLoopRuntimeConfig, readMetadataModel, readMetadataThinking, readServiceActionTargetId, readServiceAppManifest, resolveAgentRuntimeEntries, resolveAutomaticUpdateCheckIntervalMs, resolveChannelReplyRoute, resolveEffectiveModel, resolveLegacyEventType, resolveSessionChannelContext, runGatewayInboundLoop, runNextclawTask, sanitizeLlmUsage, serializeContextTail, serializeUnsignedUpdateManifest, shouldRefreshContextWindowDuringStream, shouldRefreshContextWindowImmediately, startPromptOverNcpExecution, stripSkillFrontmatter, syncSessionThinkingPreference, toBoundedJson, toNcpMessages, waitForAgentRuntimeSessionReply };
|
|
22780
23453
|
|
|
22781
23454
|
//# sourceMappingURL=index.js.map
|