@nextclaw/kernel 0.11.0 → 0.12.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.d.ts +103 -43
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +768 -99
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
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,462 @@ 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.once("error", (error) => {
|
|
13683
|
+
if (this.child === child) this.handleChildFailure(error);
|
|
13684
|
+
});
|
|
13685
|
+
child.once("exit", (code, signal) => {
|
|
13686
|
+
if (this.child !== child) return;
|
|
13687
|
+
this.child = void 0;
|
|
13688
|
+
const error = new PortableServiceRunnerError("PORTABLE_RUNNER_EXITED", `Portable runner exited (${signal ?? code ?? "unknown"}). ${this.stderrTail.join(" ")}`.trim());
|
|
13689
|
+
this.failAll(error);
|
|
13690
|
+
this.params.onUnexpectedExit?.(error);
|
|
13691
|
+
});
|
|
13692
|
+
return child;
|
|
13693
|
+
};
|
|
13694
|
+
handleLine = (line) => {
|
|
13695
|
+
let response;
|
|
13696
|
+
try {
|
|
13697
|
+
response = JSON.parse(line);
|
|
13698
|
+
} catch {
|
|
13699
|
+
this.handleChildFailure(/* @__PURE__ */ new Error(`Portable runner returned invalid JSON: ${line}`));
|
|
13700
|
+
return;
|
|
13701
|
+
}
|
|
13702
|
+
if (response.protocolVersion !== PORTABLE_RUNNER_PROTOCOL_VERSION) {
|
|
13703
|
+
const error = new PortableServiceRunnerError("PORTABLE_RUNNER_PROTOCOL_MISMATCH", `Portable runner protocol mismatch: expected ${PORTABLE_RUNNER_PROTOCOL_VERSION}, received ${response.protocolVersion ?? "missing"}.`);
|
|
13704
|
+
const child = this.child;
|
|
13705
|
+
this.handleChildFailure(error);
|
|
13706
|
+
child?.kill("SIGKILL");
|
|
13707
|
+
return;
|
|
13708
|
+
}
|
|
13709
|
+
const pending = this.pending.get(response.requestId);
|
|
13710
|
+
if (!pending) return;
|
|
13711
|
+
clearTimeout(pending.timeout);
|
|
13712
|
+
this.pending.delete(response.requestId);
|
|
13713
|
+
if (response.ok) {
|
|
13714
|
+
pending.resolve(response.result);
|
|
13715
|
+
return;
|
|
13716
|
+
}
|
|
13717
|
+
pending.reject(new PortableServiceRunnerError(response.error?.code ?? "PORTABLE_RUNTIME_FAILED", response.error?.message ?? "Portable runner request failed."));
|
|
13718
|
+
};
|
|
13719
|
+
handleChildFailure = (error) => {
|
|
13720
|
+
this.failAll(error);
|
|
13721
|
+
this.child = void 0;
|
|
13722
|
+
};
|
|
13723
|
+
failAll = (error) => {
|
|
13724
|
+
for (const pending of this.pending.values()) {
|
|
13725
|
+
clearTimeout(pending.timeout);
|
|
13726
|
+
pending.reject(error);
|
|
13727
|
+
}
|
|
13728
|
+
this.pending.clear();
|
|
13729
|
+
};
|
|
13730
|
+
toRunnerApp = (app) => ({
|
|
13731
|
+
id: app.id,
|
|
13732
|
+
componentPath: app.componentPath,
|
|
13733
|
+
dataDirectory: app.dataDirectory,
|
|
13734
|
+
allowedDomains: app.permissions.allowedDomains ?? [],
|
|
13735
|
+
allowedProviderIds: app.providerIds ?? [],
|
|
13736
|
+
storageEnabled: Boolean(app.permissions.storage)
|
|
13737
|
+
});
|
|
13738
|
+
resolveRunnerPath = () => {
|
|
13739
|
+
const runnerPath = (this.params.env ?? process.env).NEXTCLAW_WASMTIME_RUNNER_PATH?.trim() || this.params.runnerPath?.trim();
|
|
13740
|
+
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.");
|
|
13741
|
+
try {
|
|
13742
|
+
accessSync(runnerPath, process.platform === "win32" ? constants.F_OK : constants.X_OK);
|
|
13743
|
+
} catch {
|
|
13744
|
+
throw new PortableServiceRunnerError("PORTABLE_RUNNER_UNAVAILABLE", `Portable runner is missing or not executable: ${runnerPath}`);
|
|
13745
|
+
}
|
|
13746
|
+
return runnerPath;
|
|
13747
|
+
};
|
|
13748
|
+
};
|
|
13749
|
+
//#endregion
|
|
13750
|
+
//#region src/services/portable-service-app-runtime.service.ts
|
|
13751
|
+
var PortableServiceAppRuntimeService = class {
|
|
13752
|
+
runner;
|
|
13753
|
+
states = /* @__PURE__ */ new Map();
|
|
13754
|
+
apps = /* @__PURE__ */ new Map();
|
|
13755
|
+
providers = /* @__PURE__ */ new Set();
|
|
13756
|
+
persistentRegistrations = /* @__PURE__ */ new Map();
|
|
13757
|
+
residentTimers = /* @__PURE__ */ new Map();
|
|
13758
|
+
residentDeliveries = /* @__PURE__ */ new Map();
|
|
13759
|
+
recoveryPromise;
|
|
13760
|
+
constructor(params = {}) {
|
|
13761
|
+
this.runner = new PortableServiceRunnerClientService({
|
|
13762
|
+
runnerPath: params.runnerPath,
|
|
13763
|
+
onUnexpectedExit: (error) => {
|
|
13764
|
+
this.recoverPersistentComponentsIfNeeded(error);
|
|
13765
|
+
}
|
|
13766
|
+
});
|
|
13767
|
+
}
|
|
13768
|
+
getStatus = (appId) => this.states.get(appId) ?? { status: "idle" };
|
|
13769
|
+
start = async ({ app, manifest }) => {
|
|
13770
|
+
if (!app.enabled || !manifest.lifecycle || manifest.lifecycle.mode === "action") return;
|
|
13771
|
+
this.persistentRegistrations.set(app.id, {
|
|
13772
|
+
app,
|
|
13773
|
+
manifest
|
|
13774
|
+
});
|
|
13775
|
+
const runnerApp = this.toRunnerApp(app, manifest.providerIds);
|
|
13776
|
+
this.apps.set(app.id, runnerApp);
|
|
13777
|
+
if (manifest.lifecycle.mode === "provider") {
|
|
13778
|
+
if (this.providers.has(app.id)) return;
|
|
13779
|
+
const lastStartedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
13780
|
+
this.states.set(app.id, {
|
|
13781
|
+
status: "starting",
|
|
13782
|
+
lastStartedAt
|
|
13783
|
+
});
|
|
13784
|
+
try {
|
|
13785
|
+
await this.runner.startProvider(runnerApp, {
|
|
13786
|
+
mode: "provider",
|
|
13787
|
+
startedAt: lastStartedAt
|
|
13788
|
+
});
|
|
13789
|
+
this.providers.add(app.id);
|
|
13790
|
+
this.states.set(app.id, {
|
|
13791
|
+
status: "running",
|
|
13792
|
+
lastStartedAt,
|
|
13793
|
+
lastReadyAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
13794
|
+
});
|
|
13795
|
+
} catch (error) {
|
|
13796
|
+
this.markFailed(app.id, lastStartedAt, error);
|
|
13797
|
+
throw error;
|
|
13798
|
+
}
|
|
13799
|
+
return;
|
|
13800
|
+
}
|
|
13801
|
+
if (this.residentTimers.has(app.id)) return;
|
|
13802
|
+
const lastStartedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
13803
|
+
this.states.set(app.id, {
|
|
13804
|
+
status: "starting",
|
|
13805
|
+
lastStartedAt
|
|
13806
|
+
});
|
|
13807
|
+
try {
|
|
13808
|
+
await this.runner.startResident(runnerApp, {
|
|
13809
|
+
eventIntervalMs: manifest.lifecycle.eventIntervalMs,
|
|
13810
|
+
startedAt: lastStartedAt
|
|
13811
|
+
});
|
|
13812
|
+
const timer = setInterval(() => {
|
|
13813
|
+
this.scheduleResidentEvent({
|
|
13814
|
+
appId: app.id,
|
|
13815
|
+
eventIntervalMs: manifest.lifecycle?.mode === "resident" ? manifest.lifecycle.eventIntervalMs : 0
|
|
13816
|
+
});
|
|
13817
|
+
}, manifest.lifecycle.eventIntervalMs);
|
|
13818
|
+
timer.unref();
|
|
13819
|
+
this.residentTimers.set(app.id, timer);
|
|
13820
|
+
this.states.set(app.id, {
|
|
13821
|
+
status: "running",
|
|
13822
|
+
lastStartedAt,
|
|
13823
|
+
lastReadyAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
13824
|
+
});
|
|
13825
|
+
} catch (error) {
|
|
13826
|
+
this.markFailed(app.id, lastStartedAt, error);
|
|
13827
|
+
throw error;
|
|
13828
|
+
}
|
|
13829
|
+
};
|
|
13830
|
+
listActions = async ({ app, manifest }) => {
|
|
13831
|
+
if (!app.enabled) return [];
|
|
13832
|
+
const runnerApp = this.toRunnerApp(app, manifest.providerIds);
|
|
13833
|
+
this.apps.set(app.id, runnerApp);
|
|
13834
|
+
const persistent = manifest.lifecycle?.mode === "resident" || manifest.lifecycle?.mode === "provider";
|
|
13835
|
+
const lastStartedAt = persistent ? this.states.get(app.id)?.lastStartedAt ?? (/* @__PURE__ */ new Date()).toISOString() : (/* @__PURE__ */ new Date()).toISOString();
|
|
13836
|
+
if (!persistent) this.states.set(app.id, {
|
|
13837
|
+
status: "starting",
|
|
13838
|
+
lastStartedAt
|
|
13839
|
+
});
|
|
13840
|
+
try {
|
|
13841
|
+
const actions = await this.runner.listActions(runnerApp);
|
|
13842
|
+
if (!persistent) this.states.set(app.id, {
|
|
13843
|
+
status: "running",
|
|
13844
|
+
lastStartedAt,
|
|
13845
|
+
lastReadyAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
13846
|
+
});
|
|
13847
|
+
return actions.map((action) => {
|
|
13848
|
+
const declared = manifest.actions[action.name];
|
|
13849
|
+
return {
|
|
13850
|
+
id: buildServiceActionId(app.id, action.name),
|
|
13851
|
+
appId: app.id,
|
|
13852
|
+
name: action.name,
|
|
13853
|
+
title: declared?.title ?? action.title,
|
|
13854
|
+
description: declared?.description ?? action.description,
|
|
13855
|
+
inputSchema: declared?.inputSchema,
|
|
13856
|
+
risk: declared?.risk ?? "dangerous"
|
|
13857
|
+
};
|
|
13858
|
+
});
|
|
13859
|
+
} catch (error) {
|
|
13860
|
+
this.markFailed(app.id, lastStartedAt, error);
|
|
13861
|
+
await this.recoverPersistentComponentsIfNeeded(error);
|
|
13862
|
+
throw error;
|
|
13863
|
+
}
|
|
13864
|
+
};
|
|
13865
|
+
invokeAction = async ({ app, manifest, actionName, input }) => {
|
|
13866
|
+
await this.start({
|
|
13867
|
+
app,
|
|
13868
|
+
manifest
|
|
13869
|
+
});
|
|
13870
|
+
const runnerApp = this.toRunnerApp(app, manifest.providerIds);
|
|
13871
|
+
this.apps.set(app.id, runnerApp);
|
|
13872
|
+
if (manifest.lifecycle?.mode === "resident") try {
|
|
13873
|
+
return await this.runner.invoke(runnerApp, actionName, input, manifest.actions[actionName]?.timeoutMs ?? 7e3);
|
|
13874
|
+
} catch (error) {
|
|
13875
|
+
this.markFailed(app.id, this.states.get(app.id)?.lastStartedAt ?? (/* @__PURE__ */ new Date()).toISOString(), error);
|
|
13876
|
+
await this.recoverPersistentComponentsIfNeeded(error);
|
|
13877
|
+
throw error;
|
|
13878
|
+
}
|
|
13879
|
+
const lastStartedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
13880
|
+
this.states.set(app.id, {
|
|
13881
|
+
status: "starting",
|
|
13882
|
+
lastStartedAt
|
|
13883
|
+
});
|
|
13884
|
+
try {
|
|
13885
|
+
const result = await this.runner.invoke(runnerApp, actionName, input, manifest.actions[actionName]?.timeoutMs ?? 7e3);
|
|
13886
|
+
this.states.set(app.id, {
|
|
13887
|
+
status: "running",
|
|
13888
|
+
lastStartedAt,
|
|
13889
|
+
lastReadyAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
13890
|
+
});
|
|
13891
|
+
return result;
|
|
13892
|
+
} catch (error) {
|
|
13893
|
+
this.markFailed(app.id, lastStartedAt, error);
|
|
13894
|
+
await this.recoverPersistentComponentsIfNeeded(error);
|
|
13895
|
+
throw error;
|
|
13896
|
+
}
|
|
13897
|
+
};
|
|
13898
|
+
stop = async (appId) => {
|
|
13899
|
+
this.clearResidentTimer(appId);
|
|
13900
|
+
this.providers.delete(appId);
|
|
13901
|
+
this.persistentRegistrations.delete(appId);
|
|
13902
|
+
const app = this.apps.get(appId);
|
|
13903
|
+
this.apps.delete(appId);
|
|
13904
|
+
if (app) await this.runner.stop(app);
|
|
13905
|
+
this.states.set(appId, { status: "idle" });
|
|
13906
|
+
};
|
|
13907
|
+
restart = async (appId) => await this.stop(appId);
|
|
13908
|
+
dispose = async () => {
|
|
13909
|
+
if (this.recoveryPromise) await this.recoveryPromise;
|
|
13910
|
+
for (const appId of this.residentTimers.keys()) this.clearResidentTimer(appId);
|
|
13911
|
+
await Promise.allSettled(this.residentDeliveries.values());
|
|
13912
|
+
await this.runner.dispose();
|
|
13913
|
+
this.states.clear();
|
|
13914
|
+
this.apps.clear();
|
|
13915
|
+
this.providers.clear();
|
|
13916
|
+
this.persistentRegistrations.clear();
|
|
13917
|
+
this.residentDeliveries.clear();
|
|
13918
|
+
};
|
|
13919
|
+
scheduleResidentEvent = (params) => {
|
|
13920
|
+
if (this.residentDeliveries.has(params.appId)) return;
|
|
13921
|
+
const delivery = this.deliverResidentEvent(params).finally(() => {
|
|
13922
|
+
this.residentDeliveries.delete(params.appId);
|
|
13923
|
+
});
|
|
13924
|
+
this.residentDeliveries.set(params.appId, delivery);
|
|
13925
|
+
};
|
|
13926
|
+
deliverResidentEvent = async (params) => {
|
|
13927
|
+
const app = this.apps.get(params.appId);
|
|
13928
|
+
if (!app) return;
|
|
13929
|
+
const triggeredAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
13930
|
+
try {
|
|
13931
|
+
await this.runner.deliverEvent(app, {
|
|
13932
|
+
eventId: `timer-${triggeredAt}`,
|
|
13933
|
+
kind: "timer",
|
|
13934
|
+
triggeredAt,
|
|
13935
|
+
eventIntervalMs: params.eventIntervalMs
|
|
13936
|
+
});
|
|
13937
|
+
} catch (error) {
|
|
13938
|
+
this.clearResidentTimer(params.appId);
|
|
13939
|
+
this.markFailed(params.appId, this.states.get(params.appId)?.lastStartedAt ?? triggeredAt, error);
|
|
13940
|
+
await this.recoverPersistentComponentsIfNeeded(error);
|
|
13941
|
+
}
|
|
13942
|
+
};
|
|
13943
|
+
clearResidentTimer = (appId) => {
|
|
13944
|
+
const timer = this.residentTimers.get(appId);
|
|
13945
|
+
if (timer) clearInterval(timer);
|
|
13946
|
+
this.residentTimers.delete(appId);
|
|
13947
|
+
};
|
|
13948
|
+
recoverPersistentComponentsIfNeeded = async (error) => {
|
|
13949
|
+
if (!(error instanceof PortableServiceRunnerError) || !["PORTABLE_RUNTIME_TIMEOUT", "PORTABLE_RUNNER_EXITED"].includes(error.code)) return;
|
|
13950
|
+
if (this.recoveryPromise) return await this.recoveryPromise;
|
|
13951
|
+
const registrations = Array.from(this.persistentRegistrations.values());
|
|
13952
|
+
if (registrations.length === 0) return;
|
|
13953
|
+
this.recoveryPromise = (async () => {
|
|
13954
|
+
for (const appId of this.residentTimers.keys()) this.clearResidentTimer(appId);
|
|
13955
|
+
this.providers.clear();
|
|
13956
|
+
const ordered = [...registrations.filter(({ manifest }) => manifest.lifecycle?.mode === "provider"), ...registrations.filter(({ manifest }) => manifest.lifecycle?.mode === "resident")];
|
|
13957
|
+
for (const registration of ordered) try {
|
|
13958
|
+
await this.start(registration);
|
|
13959
|
+
} catch {}
|
|
13960
|
+
})().finally(() => {
|
|
13961
|
+
this.recoveryPromise = void 0;
|
|
13962
|
+
});
|
|
13963
|
+
await this.recoveryPromise;
|
|
13964
|
+
};
|
|
13965
|
+
toRunnerApp = (app, providerIds) => {
|
|
13966
|
+
if (!app.componentPath || !app.dataDirectory) throw new Error(`Portable Service App ${app.id} is missing component or data storage.`);
|
|
13967
|
+
return {
|
|
13968
|
+
id: app.id,
|
|
13969
|
+
componentPath: app.componentPath,
|
|
13970
|
+
dataDirectory: app.dataDirectory,
|
|
13971
|
+
permissions: app.permissions ?? {},
|
|
13972
|
+
providerIds
|
|
13973
|
+
};
|
|
13974
|
+
};
|
|
13975
|
+
markFailed = (appId, lastStartedAt, error) => {
|
|
13976
|
+
this.states.set(appId, {
|
|
13977
|
+
status: "failed",
|
|
13978
|
+
lastError: error instanceof Error ? error.message : String(error),
|
|
13979
|
+
lastStartedAt,
|
|
13980
|
+
lastFailedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
13981
|
+
});
|
|
13982
|
+
};
|
|
13983
|
+
};
|
|
13984
|
+
//#endregion
|
|
13985
|
+
//#region src/services/service-app-runtime.service.ts
|
|
13986
|
+
var ServiceAppRuntimeService = class {
|
|
13987
|
+
mcp;
|
|
13988
|
+
portable;
|
|
13989
|
+
protocols = /* @__PURE__ */ new Map();
|
|
13990
|
+
constructor(params) {
|
|
13991
|
+
this.mcp = new McpServiceAppRuntimeService(params);
|
|
13992
|
+
this.portable = new PortableServiceAppRuntimeService({ runnerPath: params.portableServiceRunnerPath });
|
|
13993
|
+
}
|
|
13994
|
+
getStatus = (appId) => {
|
|
13995
|
+
return this.protocols.get(appId) === "wasi-component" ? this.portable.getStatus(appId) : this.mcp.getStatus(appId);
|
|
13996
|
+
};
|
|
13997
|
+
start = async (call) => {
|
|
13998
|
+
this.protocols.set(call.app.id, call.manifest.protocol);
|
|
13999
|
+
if (call.manifest.protocol === "wasi-component") await this.portable.start(call);
|
|
14000
|
+
};
|
|
14001
|
+
listActions = async (call) => {
|
|
14002
|
+
this.protocols.set(call.app.id, call.manifest.protocol);
|
|
14003
|
+
return call.manifest.protocol === "wasi-component" ? await this.portable.listActions(call) : await this.mcp.listActions(call);
|
|
14004
|
+
};
|
|
14005
|
+
invokeAction = async (call) => {
|
|
14006
|
+
this.protocols.set(call.app.id, call.manifest.protocol);
|
|
14007
|
+
return call.manifest.protocol === "wasi-component" ? await this.portable.invokeAction(call) : await this.mcp.invokeAction(call);
|
|
14008
|
+
};
|
|
14009
|
+
stop = async (appId) => {
|
|
14010
|
+
const protocol = this.protocols.get(appId);
|
|
14011
|
+
if (protocol === "wasi-component") await this.portable.stop(appId);
|
|
14012
|
+
else if (protocol === "mcp") await this.mcp.stop(appId);
|
|
14013
|
+
else await Promise.all([this.mcp.stop(appId), this.portable.stop(appId)]);
|
|
14014
|
+
};
|
|
14015
|
+
restart = async (appId) => await this.stop(appId);
|
|
14016
|
+
dispose = async () => {
|
|
14017
|
+
await Promise.all([this.mcp.dispose(), this.portable.dispose()]);
|
|
14018
|
+
this.protocols.clear();
|
|
14019
|
+
};
|
|
14020
|
+
};
|
|
14021
|
+
//#endregion
|
|
13483
14022
|
//#region src/utils/service-app-manifest.utils.ts
|
|
13484
14023
|
const SERVICE_APP_ID_PATTERN$1 = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
13485
14024
|
const SERVICE_ACTION_RISKS = new Set([
|
|
@@ -13506,19 +14045,56 @@ function parseServiceAppManifest(raw, options = {}) {
|
|
|
13506
14045
|
const id = readRequiredString$6(parsed, "id");
|
|
13507
14046
|
if (!SERVICE_APP_ID_PATTERN$1.test(id)) throw new Error("service app id must be kebab-case.");
|
|
13508
14047
|
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);
|
|
14048
|
+
if (protocol !== "mcp" && protocol !== "wasi-component") throw new Error("service app protocol must be mcp or wasi-component.");
|
|
14049
|
+
const launch = protocol === "mcp" ? new AppServiceLaunchService(options.platformTargetService).resolve(parsed, options.target) : void 0;
|
|
14050
|
+
const componentEntry = protocol === "wasi-component" ? readPortableComponentEntry(parsed) : void 0;
|
|
14051
|
+
const lifecycle = readServiceAppLifecycle(parsed.lifecycle);
|
|
14052
|
+
if (lifecycle.mode !== "action" && protocol !== "wasi-component") throw new Error(`${lifecycle.mode} service app lifecycle requires wasi-component protocol.`);
|
|
13511
14053
|
return {
|
|
13512
14054
|
id,
|
|
13513
14055
|
title: readRequiredString$6(parsed, "title"),
|
|
13514
14056
|
description: readOptionalString$8(parsed, "description"),
|
|
13515
14057
|
enabled: readOptionalBoolean$1(parsed, "enabled") ?? true,
|
|
13516
14058
|
protocol,
|
|
13517
|
-
command: launch
|
|
13518
|
-
args: launch
|
|
14059
|
+
command: launch?.command,
|
|
14060
|
+
args: launch?.args,
|
|
14061
|
+
componentEntry,
|
|
14062
|
+
providerIds: readProviderIds(parsed.providers),
|
|
14063
|
+
lifecycle,
|
|
13519
14064
|
actions: readManifestActions(parsed.actions)
|
|
13520
14065
|
};
|
|
13521
14066
|
}
|
|
14067
|
+
function readServiceAppLifecycle(value) {
|
|
14068
|
+
if (value === void 0) return { mode: "action" };
|
|
14069
|
+
if (!isRecord$7(value)) throw new Error("service app lifecycle must be an object.");
|
|
14070
|
+
const mode = readOptionalString$8(value, "mode") ?? "action";
|
|
14071
|
+
if (mode === "action") return { mode };
|
|
14072
|
+
if (mode === "provider") return { mode };
|
|
14073
|
+
if (mode !== "resident") throw new Error("service app lifecycle.mode must be action, resident or provider.");
|
|
14074
|
+
const eventIntervalMs = readOptionalNumber(value, "eventIntervalMs");
|
|
14075
|
+
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.");
|
|
14076
|
+
return {
|
|
14077
|
+
mode,
|
|
14078
|
+
eventIntervalMs
|
|
14079
|
+
};
|
|
14080
|
+
}
|
|
14081
|
+
function readProviderIds(value) {
|
|
14082
|
+
if (value === void 0) return [];
|
|
14083
|
+
if (!Array.isArray(value)) throw new Error("service app providers must be an array.");
|
|
14084
|
+
const providerIds = value.map((entry) => {
|
|
14085
|
+
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.");
|
|
14086
|
+
return entry.trim();
|
|
14087
|
+
});
|
|
14088
|
+
return Array.from(new Set(providerIds));
|
|
14089
|
+
}
|
|
14090
|
+
function readPortableComponentEntry(record) {
|
|
14091
|
+
const component = record.component;
|
|
14092
|
+
if (!isRecord$7(component)) throw new Error("service app component is required for wasi-component.");
|
|
14093
|
+
const entry = readRequiredString$6(component, "entry").replace(/\\/g, "/");
|
|
14094
|
+
if (path.isAbsolute(entry) || entry.includes("\0") || entry.split("/").includes("..")) throw new Error("service app component.entry must be a package-relative path.");
|
|
14095
|
+
if (!entry.endsWith(".wasm")) throw new Error("service app component.entry must reference a .wasm file.");
|
|
14096
|
+
return entry;
|
|
14097
|
+
}
|
|
13522
14098
|
function readManifestActions(value) {
|
|
13523
14099
|
if (value === void 0) throw new Error("service app actions are required.");
|
|
13524
14100
|
if (!isRecord$7(value)) throw new Error("service app actions must be an object.");
|
|
@@ -13531,11 +14107,14 @@ function readManifestActions(value) {
|
|
|
13531
14107
|
if (risk !== void 0 && !SERVICE_ACTION_RISKS.has(risk)) throw new Error(`service app action ${name} has invalid risk.`);
|
|
13532
14108
|
const inputSchema = action.inputSchema;
|
|
13533
14109
|
if (inputSchema !== void 0 && !isRecord$7(inputSchema)) throw new Error(`service app action ${name} inputSchema must be an object.`);
|
|
14110
|
+
const timeoutMs = readOptionalNumber(action, "timeoutMs");
|
|
14111
|
+
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
14112
|
actions[name] = {
|
|
13535
14113
|
risk,
|
|
13536
14114
|
title: readOptionalString$8(action, "title"),
|
|
13537
14115
|
description: readOptionalString$8(action, "description"),
|
|
13538
|
-
inputSchema
|
|
14116
|
+
inputSchema,
|
|
14117
|
+
timeoutMs
|
|
13539
14118
|
};
|
|
13540
14119
|
}
|
|
13541
14120
|
return actions;
|
|
@@ -13553,10 +14132,49 @@ function readOptionalBoolean$1(record, key) {
|
|
|
13553
14132
|
if (typeof record[key] !== "boolean") throw new Error(`service app ${key} must be boolean.`);
|
|
13554
14133
|
return record[key];
|
|
13555
14134
|
}
|
|
14135
|
+
function readOptionalNumber(record, key) {
|
|
14136
|
+
if (record[key] === void 0) return;
|
|
14137
|
+
if (typeof record[key] !== "number") throw new Error(`service app ${key} must be number.`);
|
|
14138
|
+
return record[key];
|
|
14139
|
+
}
|
|
13556
14140
|
function isRecord$7(value) {
|
|
13557
14141
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
13558
14142
|
}
|
|
13559
14143
|
//#endregion
|
|
14144
|
+
//#region src/services/service-app-lifecycle.service.ts
|
|
14145
|
+
var ServiceAppLifecycleService = class {
|
|
14146
|
+
constructor(params) {
|
|
14147
|
+
this.params = params;
|
|
14148
|
+
}
|
|
14149
|
+
startDiscovered = async (registrations) => {
|
|
14150
|
+
const active = registrations.filter(({ manifest, record }) => record.enabled && manifest.lifecycle && manifest.lifecycle.mode !== "action");
|
|
14151
|
+
await Promise.allSettled(this.order(active).map(async ({ manifest, record }) => await this.params.runtimeService.start?.({
|
|
14152
|
+
app: record,
|
|
14153
|
+
manifest
|
|
14154
|
+
})));
|
|
14155
|
+
};
|
|
14156
|
+
activatePackageComponents = async (components) => {
|
|
14157
|
+
const registrations = [];
|
|
14158
|
+
for (const component of components.filter((entry) => entry.kind === "service")) {
|
|
14159
|
+
const manifest = await readServiceAppManifest(component.sourcePath);
|
|
14160
|
+
if (!manifest.lifecycle || manifest.lifecycle.mode === "action") continue;
|
|
14161
|
+
registrations.push({
|
|
14162
|
+
manifest,
|
|
14163
|
+
record: this.params.recordService.fromManifest(component.sourcePath, manifest, component, component.storage)
|
|
14164
|
+
});
|
|
14165
|
+
}
|
|
14166
|
+
for (const { manifest, record } of this.order(registrations)) await this.params.runtimeService.start?.({
|
|
14167
|
+
app: record,
|
|
14168
|
+
manifest
|
|
14169
|
+
});
|
|
14170
|
+
};
|
|
14171
|
+
deactivatePackageComponents = async (components) => {
|
|
14172
|
+
const serviceIds = components.filter((component) => component.kind === "service").map((component) => component.id);
|
|
14173
|
+
await Promise.all(serviceIds.map(async (serviceId) => await this.params.runtimeService.stop(serviceId)));
|
|
14174
|
+
};
|
|
14175
|
+
order = (registrations) => [...registrations.filter(({ manifest }) => manifest.lifecycle?.mode === "provider"), ...registrations.filter(({ manifest }) => manifest.lifecycle?.mode === "resident")];
|
|
14176
|
+
};
|
|
14177
|
+
//#endregion
|
|
13560
14178
|
//#region src/services/service-app-record.service.ts
|
|
13561
14179
|
var ServiceAppRecordService = class {
|
|
13562
14180
|
instanceStorageService = new AppInstanceStorageService();
|
|
@@ -13638,7 +14256,12 @@ var ServiceAppRecordService = class {
|
|
|
13638
14256
|
dataDirectory: storage?.dataDirectory,
|
|
13639
14257
|
instanceId: packageSource?.instanceId ?? storage?.instanceId,
|
|
13640
14258
|
storage,
|
|
13641
|
-
isolation: packageSource?.isolation ?? "full-user"
|
|
14259
|
+
isolation: packageSource?.isolation ?? "full-user",
|
|
14260
|
+
runtimeProfile: packageSource?.runtimeProfile ?? "native-process",
|
|
14261
|
+
permissions: packageSource?.permissions ?? {},
|
|
14262
|
+
componentPath: manifest.componentEntry ? join(dirPath, manifest.componentEntry) : void 0,
|
|
14263
|
+
providerIds: manifest.providerIds,
|
|
14264
|
+
lifecycle: manifest.lifecycle
|
|
13642
14265
|
};
|
|
13643
14266
|
};
|
|
13644
14267
|
failedWorkspaceRecord = (id, dirPath, error) => ({
|
|
@@ -13669,7 +14292,9 @@ var ServiceAppRecordService = class {
|
|
|
13669
14292
|
dataDirectory: source.dataDirectory,
|
|
13670
14293
|
instanceId: source.instanceId,
|
|
13671
14294
|
storage: source.storage,
|
|
13672
|
-
isolation: source.isolation
|
|
14295
|
+
isolation: source.isolation,
|
|
14296
|
+
runtimeProfile: source.runtimeProfile,
|
|
14297
|
+
permissions: source.permissions
|
|
13673
14298
|
});
|
|
13674
14299
|
toTitle = (value) => basename(value).replace(/[-_]+/g, " ").trim() || value;
|
|
13675
14300
|
getWorkspaceInstanceDirectory = (serviceId) => join(this.params.getWorkspacePath(), ".nextclaw", "app-instances", serviceId, "default");
|
|
@@ -13838,13 +14463,16 @@ var ServiceActionGrantService = class {
|
|
|
13838
14463
|
const results = [];
|
|
13839
14464
|
for (const grant of grants) {
|
|
13840
14465
|
const actionId = readServiceActionTargetId(grant.resource.target);
|
|
13841
|
-
if (!actionId || grant.subject.type !== "panel-app") continue;
|
|
14466
|
+
if (!actionId || grant.subject.type !== "panel-app" && grant.subject.type !== "agent") continue;
|
|
13842
14467
|
try {
|
|
13843
14468
|
const action = await this.params.resolveAction(actionId);
|
|
13844
14469
|
results.push({
|
|
13845
|
-
caller: {
|
|
14470
|
+
caller: grant.subject.type === "panel-app" ? {
|
|
13846
14471
|
surface: "panel-app",
|
|
13847
14472
|
appId: grant.subject.id
|
|
14473
|
+
} : {
|
|
14474
|
+
surface: "agent",
|
|
14475
|
+
agentId: grant.subject.id
|
|
13848
14476
|
},
|
|
13849
14477
|
actionId,
|
|
13850
14478
|
risk: action.risk,
|
|
@@ -13860,7 +14488,7 @@ var ServiceActionGrantService = class {
|
|
|
13860
14488
|
await this.params.capabilityGrantManager.revoke({
|
|
13861
14489
|
subject: {
|
|
13862
14490
|
type: caller.surface,
|
|
13863
|
-
id: caller
|
|
14491
|
+
id: getServiceActionCallerId(caller)
|
|
13864
14492
|
},
|
|
13865
14493
|
resourceType: "service.action",
|
|
13866
14494
|
target: { actionId }
|
|
@@ -13961,28 +14589,6 @@ function mergeServiceAppRuntimeActions({ record, manifest, runtimeActions }) {
|
|
|
13961
14589
|
return [...declared, ...undeclared].sort((left, right) => left.id.localeCompare(right.id));
|
|
13962
14590
|
}
|
|
13963
14591
|
//#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
14592
|
//#region src/managers/service-app.manager.ts
|
|
13987
14593
|
const SERVICE_APP_ID_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
13988
14594
|
var ServiceAppManager = class {
|
|
@@ -13991,14 +14597,22 @@ var ServiceAppManager = class {
|
|
|
13991
14597
|
recordService;
|
|
13992
14598
|
actionGrants;
|
|
13993
14599
|
packageRuntime;
|
|
14600
|
+
lifecycleService;
|
|
13994
14601
|
reconciliationDiagnostics = [];
|
|
13995
14602
|
constructor(params) {
|
|
13996
14603
|
this.params = params;
|
|
13997
|
-
this.runtimeService = params.runtimeService ?? new
|
|
14604
|
+
this.runtimeService = params.runtimeService ?? new ServiceAppRuntimeService({
|
|
14605
|
+
getConfig: () => params.configManager.config,
|
|
14606
|
+
portableServiceRunnerPath: params.portableServiceRunnerPath
|
|
14607
|
+
});
|
|
13998
14608
|
this.recordService = new ServiceAppRecordService({
|
|
13999
14609
|
getWorkspacePath: this.getWorkspacePath,
|
|
14000
14610
|
runtimeService: this.runtimeService
|
|
14001
14611
|
});
|
|
14612
|
+
this.lifecycleService = new ServiceAppLifecycleService({
|
|
14613
|
+
recordService: this.recordService,
|
|
14614
|
+
runtimeService: this.runtimeService
|
|
14615
|
+
});
|
|
14002
14616
|
this.actionGrants = new ServiceActionGrantService({
|
|
14003
14617
|
capabilityGrantManager: params.capabilityGrantManager,
|
|
14004
14618
|
resolveAction: this.requireServiceAction
|
|
@@ -14017,6 +14631,7 @@ var ServiceAppManager = class {
|
|
|
14017
14631
|
lockPathForAppId: (appId) => this.getServiceAppLockPath(this.getWorkspacePath(), appId),
|
|
14018
14632
|
serviceAppsPath: this.getServiceAppsPath(this.getWorkspacePath())
|
|
14019
14633
|
});
|
|
14634
|
+
await this.lifecycleService.startDiscovered(await this.listValidServiceApps());
|
|
14020
14635
|
};
|
|
14021
14636
|
listServiceApps = async () => {
|
|
14022
14637
|
const workspacePath = this.getWorkspacePath();
|
|
@@ -14052,8 +14667,8 @@ var ServiceAppManager = class {
|
|
|
14052
14667
|
});
|
|
14053
14668
|
};
|
|
14054
14669
|
invokeServiceAction = async (actionId, request) => {
|
|
14055
|
-
|
|
14056
|
-
|
|
14670
|
+
assertServiceActionCaller(request.caller, this.params.hasAgent);
|
|
14671
|
+
assertServiceActionDeclared(request.caller, actionId, request.declaredActions);
|
|
14057
14672
|
const { manifest, record } = await this.requireServiceAppForAction(actionId, true);
|
|
14058
14673
|
const actionName = getServiceActionName(actionId, record.id);
|
|
14059
14674
|
if (!Object.hasOwn(manifest.actions, actionName)) throw new ServiceAppError("SERVICE_APP_ACTION_NOT_FOUND", "service action not found");
|
|
@@ -14080,30 +14695,33 @@ var ServiceAppManager = class {
|
|
|
14080
14695
|
return grant;
|
|
14081
14696
|
};
|
|
14082
14697
|
grantServiceActions = async (actionIds, request) => {
|
|
14083
|
-
|
|
14698
|
+
assertServiceActionCaller(request.caller, this.params.hasAgent);
|
|
14084
14699
|
const normalizedActionIds = this.normalizeActionIds(actionIds);
|
|
14085
14700
|
if (normalizedActionIds.length === 0) throw new ServiceAppError("SERVICE_APP_INVALID_ACTION", "service action id is invalid");
|
|
14086
14701
|
const actions = [];
|
|
14087
14702
|
for (const actionId of normalizedActionIds) {
|
|
14088
|
-
|
|
14703
|
+
assertServiceActionDeclared(request.caller, actionId, request.declaredActions);
|
|
14089
14704
|
actions.push(await this.requireServiceAction(actionId));
|
|
14090
14705
|
}
|
|
14091
14706
|
return await this.actionGrants.grant(request.caller, actions);
|
|
14092
14707
|
};
|
|
14093
14708
|
listServiceActionGrants = async () => await this.actionGrants.list();
|
|
14094
14709
|
revokeServiceAction = async (caller, actionId) => {
|
|
14095
|
-
this.
|
|
14710
|
+
assertServiceActionCaller(caller, this.params.hasAgent);
|
|
14096
14711
|
await this.actionGrants.revoke(caller, actionId);
|
|
14097
14712
|
};
|
|
14098
14713
|
matchesCapabilityGrant = async (grant) => {
|
|
14099
|
-
if (grant.subject.type !== "panel-app" || grant.resource.type !== "service.action") return false;
|
|
14714
|
+
if (grant.subject.type !== "panel-app" && grant.subject.type !== "agent" || grant.resource.type !== "service.action") return false;
|
|
14100
14715
|
const actionId = readServiceActionTargetId(grant.resource.target);
|
|
14101
14716
|
if (!actionId) return false;
|
|
14102
14717
|
try {
|
|
14103
14718
|
const action = await this.requireServiceAction(actionId);
|
|
14104
|
-
return getCapabilityGrantKey(grant) === getCapabilityGrantKey(createServiceActionGrantRequest({
|
|
14719
|
+
return getCapabilityGrantKey(grant) === getCapabilityGrantKey(createServiceActionGrantRequest(grant.subject.type === "panel-app" ? {
|
|
14105
14720
|
surface: "panel-app",
|
|
14106
14721
|
appId: grant.subject.id
|
|
14722
|
+
} : {
|
|
14723
|
+
surface: "agent",
|
|
14724
|
+
agentId: grant.subject.id
|
|
14107
14725
|
}, action));
|
|
14108
14726
|
} catch {
|
|
14109
14727
|
return false;
|
|
@@ -14183,10 +14801,8 @@ var ServiceAppManager = class {
|
|
|
14183
14801
|
}
|
|
14184
14802
|
}
|
|
14185
14803
|
};
|
|
14186
|
-
|
|
14187
|
-
|
|
14188
|
-
await Promise.all(serviceIds.map(async (serviceId) => await this.runtimeService.stop(serviceId)));
|
|
14189
|
-
};
|
|
14804
|
+
activatePackageComponents = async (components) => await this.lifecycleService.activatePackageComponents(components);
|
|
14805
|
+
deactivatePackageComponents = async (components) => await this.lifecycleService.deactivatePackageComponents(components);
|
|
14190
14806
|
preparePackageComponentDeactivation = async (components) => await this.packageRuntime.prepareDeactivation(components);
|
|
14191
14807
|
removePackageComponentGrants = async (components) => await this.actionGrants.removePackageGrants(new Set(components.filter((component) => component.kind === "service").map((component) => component.id)));
|
|
14192
14808
|
withGrantState = async (action, params) => {
|
|
@@ -14267,12 +14883,6 @@ var ServiceAppManager = class {
|
|
|
14267
14883
|
}));
|
|
14268
14884
|
return [...workspaceEntries, ...packageEntries].filter((entry) => Boolean(entry));
|
|
14269
14885
|
};
|
|
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
14886
|
normalizeActionIds = (actionIds) => Array.from(new Set(actionIds.map((actionId) => actionId.trim()).filter((actionId) => actionId.length > 0)));
|
|
14277
14887
|
getWorkspacePath = () => getWorkspacePathFromConfig(this.params.configManager.config);
|
|
14278
14888
|
getServiceAppsPath = (workspacePath) => join(workspacePath, DEFAULT_SERVICE_APPS_DIR);
|
|
@@ -21248,6 +21858,57 @@ function readOptionalString$1(value) {
|
|
|
21248
21858
|
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
21249
21859
|
}
|
|
21250
21860
|
//#endregion
|
|
21861
|
+
//#region src/utils/service-action-tool.utils.ts
|
|
21862
|
+
const MAX_TOOL_NAME_LENGTH = 64;
|
|
21863
|
+
const HASH_LENGTH = 8;
|
|
21864
|
+
const TOOL_PREFIX = "service__";
|
|
21865
|
+
function buildServiceActionToolName(actionId) {
|
|
21866
|
+
const readable = actionId.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "") || "action";
|
|
21867
|
+
const suffix = `__${createHash("sha256").update(actionId).digest("hex").slice(0, HASH_LENGTH)}`;
|
|
21868
|
+
const available = MAX_TOOL_NAME_LENGTH - 9 - suffix.length;
|
|
21869
|
+
return `${TOOL_PREFIX}${readable.slice(0, available)}${suffix}`;
|
|
21870
|
+
}
|
|
21871
|
+
//#endregion
|
|
21872
|
+
//#region src/contributions/tool-provider/providers/service-action-tool.provider.ts
|
|
21873
|
+
const EMPTY_OBJECT_SCHEMA = {
|
|
21874
|
+
type: "object",
|
|
21875
|
+
properties: {},
|
|
21876
|
+
additionalProperties: false
|
|
21877
|
+
};
|
|
21878
|
+
var ServiceActionToolProvider = class {
|
|
21879
|
+
constructor(runContextService, serviceAppManager) {
|
|
21880
|
+
this.runContextService = runContextService;
|
|
21881
|
+
this.serviceAppManager = serviceAppManager;
|
|
21882
|
+
}
|
|
21883
|
+
provide = async (request) => {
|
|
21884
|
+
const { toolRunContext } = await this.runContextService.resolve(request);
|
|
21885
|
+
const caller = {
|
|
21886
|
+
surface: "agent",
|
|
21887
|
+
agentId: toolRunContext.agentId
|
|
21888
|
+
};
|
|
21889
|
+
return (await this.serviceAppManager.listServiceActions({ caller })).filter((action) => action.grantState === "granted").map((action) => this.toTool(caller, action));
|
|
21890
|
+
};
|
|
21891
|
+
toTool = (caller, action) => ({
|
|
21892
|
+
name: buildServiceActionToolName(action.id),
|
|
21893
|
+
description: [
|
|
21894
|
+
action.title ?? action.name,
|
|
21895
|
+
action.description,
|
|
21896
|
+
`NextClaw Service Action: ${action.id}`
|
|
21897
|
+
].filter(Boolean).join("\n"),
|
|
21898
|
+
parameters: action.inputSchema ?? EMPTY_OBJECT_SCHEMA,
|
|
21899
|
+
supportsParallelToolCalls: action.risk === "read",
|
|
21900
|
+
execute: async (args) => await this.serviceAppManager.invokeServiceAction(action.id, {
|
|
21901
|
+
caller,
|
|
21902
|
+
input: readInput(args)
|
|
21903
|
+
})
|
|
21904
|
+
});
|
|
21905
|
+
};
|
|
21906
|
+
function readInput(value) {
|
|
21907
|
+
if (value === void 0) return {};
|
|
21908
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("Service Action tool input must be an object.");
|
|
21909
|
+
return value;
|
|
21910
|
+
}
|
|
21911
|
+
//#endregion
|
|
21251
21912
|
//#region src/contributions/tool-provider/services/tool-provider-run-context.service.ts
|
|
21252
21913
|
var ToolProviderRunContextService = class {
|
|
21253
21914
|
constructor(sessionManager, agentManager, configManager) {
|
|
@@ -21304,6 +21965,7 @@ var ToolProviderContribution = class extends Contribution$1 {
|
|
|
21304
21965
|
new ProjectToolProvider(this.kernel.projectManager),
|
|
21305
21966
|
new SessionToolProvider(runContextService, this.kernel.sessionManager, this.kernel.sessionRequests, this.kernel.sessionSearch),
|
|
21306
21967
|
new AssetToolProvider(this.kernel.assetStore),
|
|
21968
|
+
new ServiceActionToolProvider(runContextService, this.kernel.serviceAppManager),
|
|
21307
21969
|
new McpToolProvider(runContextService, this.kernel.mcpManager)
|
|
21308
21970
|
];
|
|
21309
21971
|
};
|
|
@@ -21332,11 +21994,13 @@ function createKernelOperationalManagers(params) {
|
|
|
21332
21994
|
};
|
|
21333
21995
|
}
|
|
21334
21996
|
function createKernelServiceAppManagers(params) {
|
|
21335
|
-
const { appHomeDirectory, appPackageManager, capabilityGrantManager, configManager } = params;
|
|
21997
|
+
const { appHomeDirectory, appPackageManager, capabilityGrantManager, configManager, hasAgent, portableServiceRunnerPath } = params;
|
|
21336
21998
|
const serviceAppManager = new ServiceAppManager({
|
|
21337
21999
|
configManager,
|
|
21338
22000
|
listPackageComponentSources: appPackageManager.listActiveComponentSources,
|
|
21339
|
-
capabilityGrantManager
|
|
22001
|
+
capabilityGrantManager,
|
|
22002
|
+
hasAgent,
|
|
22003
|
+
portableServiceRunnerPath
|
|
21340
22004
|
});
|
|
21341
22005
|
return {
|
|
21342
22006
|
serviceAppManager,
|
|
@@ -21405,6 +22069,9 @@ function installKernelAppPackageRuntimeHooks(params) {
|
|
|
21405
22069
|
await panelAppManager.assertCanActivatePackageComponents(sources);
|
|
21406
22070
|
await serviceAppManager.assertCanActivatePackageComponents(sources);
|
|
21407
22071
|
},
|
|
22072
|
+
afterActivate: async (sources) => {
|
|
22073
|
+
await serviceAppManager.activatePackageComponents(sources);
|
|
22074
|
+
},
|
|
21408
22075
|
beforeDeactivate: async (sources) => {
|
|
21409
22076
|
panelAppManager.deactivatePackageComponents(sources);
|
|
21410
22077
|
await serviceAppManager.deactivatePackageComponents(sources);
|
|
@@ -21548,7 +22215,9 @@ var NextclawKernel = class {
|
|
|
21548
22215
|
appHomeDirectory: resolveKernelAppHomeDirectory(options),
|
|
21549
22216
|
appPackageManager: this.appPackageManager,
|
|
21550
22217
|
configManager: this.configManager,
|
|
21551
|
-
capabilityGrantManager: this.capabilityGrants
|
|
22218
|
+
capabilityGrantManager: this.capabilityGrants,
|
|
22219
|
+
hasAgent: (agentId) => this.agents.getAgent(agentId) !== null,
|
|
22220
|
+
portableServiceRunnerPath: options.portableServiceRunnerPath
|
|
21552
22221
|
}));
|
|
21553
22222
|
installKernelAppPackageRuntimeHooks({
|
|
21554
22223
|
appPackageManager: this.appPackageManager,
|
|
@@ -22776,6 +23445,6 @@ function resolveLegacyEventType(message) {
|
|
|
22776
23445
|
return `message.${role || "other"}`;
|
|
22777
23446
|
}
|
|
22778
23447
|
//#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 };
|
|
23448
|
+
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
23449
|
|
|
22781
23450
|
//# sourceMappingURL=index.js.map
|