@nextclaw/kernel 0.8.0 → 0.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +45 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +230 -50
- package/dist/index.js.map +1 -1
- package/package.json +15 -15
package/dist/index.js
CHANGED
|
@@ -16,7 +16,7 @@ import { fileURLToPath } from "node:url";
|
|
|
16
16
|
import { BUILTIN_PROVIDER_PLUGINS } from "@nextclaw/runtime";
|
|
17
17
|
import { McpRegistryService, McpServerLifecycleManager } from "@nextclaw/mcp";
|
|
18
18
|
import { McpNcpToolRegistryAdapter } from "@nextclaw/ncp-mcp";
|
|
19
|
-
import { DefaultNcpAgentConversationStateManager, insertMessageByTimeline } from "@nextclaw/ncp-toolkit";
|
|
19
|
+
import { DefaultNcpAgentConversationStateManager, insertMessageByTimeline, mergeToolExecutionTiming } from "@nextclaw/ncp-toolkit";
|
|
20
20
|
import { parse } from "yaml";
|
|
21
21
|
import { createInterface } from "node:readline";
|
|
22
22
|
import { HttpRuntimeConfigResolver, HttpRuntimeNcpAgentRuntime } from "@nextclaw/nextclaw-ncp-runtime-http-client";
|
|
@@ -2876,17 +2876,16 @@ var AppPackageManager = class {
|
|
|
2876
2876
|
installRuntimeHooks = (hooks) => {
|
|
2877
2877
|
this.runtimeHooks = hooks;
|
|
2878
2878
|
};
|
|
2879
|
-
|
|
2880
|
-
|
|
2879
|
+
start = async () => await this.ensureBuiltInPackages();
|
|
2880
|
+
listPackages = async (options = {}) => {
|
|
2881
2881
|
const records = await this.registryService.listApps();
|
|
2882
|
-
return { entries: await Promise.all(records.map(async (record) => await this.toPackageView(await this.installationService.info(record.appId)))) };
|
|
2882
|
+
return { entries: await Promise.all(records.map(async (record) => await this.toPackageView(await this.installationService.info(record.appId, { measureStorageUsage: options.includeStorageUsage !== false })))) };
|
|
2883
2883
|
};
|
|
2884
2884
|
listInstalledDataOwners = async () => (await this.registryService.listApps()).map((record) => ({
|
|
2885
2885
|
id: record.appId,
|
|
2886
2886
|
name: record.name
|
|
2887
2887
|
}));
|
|
2888
2888
|
getPackage = async (appId) => {
|
|
2889
|
-
await this.ensureBuiltInPackages();
|
|
2890
2889
|
try {
|
|
2891
2890
|
return await this.toPackageView(await this.installationService.info(appId));
|
|
2892
2891
|
} catch (error) {
|
|
@@ -2895,7 +2894,6 @@ var AppPackageManager = class {
|
|
|
2895
2894
|
}
|
|
2896
2895
|
};
|
|
2897
2896
|
listActiveComponentSources = async () => {
|
|
2898
|
-
await this.ensureBuiltInPackages();
|
|
2899
2897
|
const records = await this.registryService.listApps();
|
|
2900
2898
|
return (await Promise.all(records.filter((record) => record.enabled).map(async (record) => {
|
|
2901
2899
|
await this.installationService.assertVersionIntegrity(record.appId, record.activeVersion);
|
|
@@ -3212,10 +3210,22 @@ var AppDataManager = class {
|
|
|
3212
3210
|
appHomeService;
|
|
3213
3211
|
fileLockService = new FileLockService();
|
|
3214
3212
|
inventoryService = new AppInstanceInventoryService();
|
|
3213
|
+
reconciliationDiagnostics = [];
|
|
3215
3214
|
constructor(params) {
|
|
3216
3215
|
this.params = params;
|
|
3217
3216
|
this.appHomeService = new AppHomeService(params.appHomeDirectory);
|
|
3218
3217
|
}
|
|
3218
|
+
start = async () => {
|
|
3219
|
+
const workspacePath = path.resolve(this.params.getWorkspacePath());
|
|
3220
|
+
const [packageDiagnostics, workspaceDiagnostics] = await Promise.all([this.inventoryService.reconcileDeletions(this.appHomeService.getInstancesDirectory()), this.inventoryService.reconcileDeletions(this.getWorkspaceInstancesRoot(workspacePath))]);
|
|
3221
|
+
this.reconciliationDiagnostics = [...packageDiagnostics.map((entry) => ({
|
|
3222
|
+
...entry,
|
|
3223
|
+
source: "package"
|
|
3224
|
+
})), ...workspaceDiagnostics.map((entry) => ({
|
|
3225
|
+
...entry,
|
|
3226
|
+
source: "workspace-service"
|
|
3227
|
+
}))];
|
|
3228
|
+
};
|
|
3219
3229
|
list = async () => {
|
|
3220
3230
|
const workspacePath = path.resolve(this.params.getWorkspacePath());
|
|
3221
3231
|
const workspaceInstancesRoot = this.getWorkspaceInstancesRoot(workspacePath);
|
|
@@ -3228,26 +3238,31 @@ var AppDataManager = class {
|
|
|
3228
3238
|
const packageOwners = new Map(packageList.map((entry) => [entry.id, entry]));
|
|
3229
3239
|
const workspaceOwnerMap = new Map(workspaceOwners.map((entry) => [entry.id, entry]));
|
|
3230
3240
|
const workspaceScope = this.workspaceScope(workspacePath);
|
|
3241
|
+
const entries = [...packageInventory.entries.map((entry) => this.toEntry({
|
|
3242
|
+
entry,
|
|
3243
|
+
source: "package",
|
|
3244
|
+
displayName: packageOwners.get(entry.appId)?.name ?? entry.appId,
|
|
3245
|
+
active: packageOwners.has(entry.appId)
|
|
3246
|
+
})), ...workspaceInventory.entries.map((entry) => this.toEntry({
|
|
3247
|
+
entry,
|
|
3248
|
+
source: "workspace-service",
|
|
3249
|
+
displayName: workspaceOwnerMap.get(entry.appId)?.title ?? entry.appId,
|
|
3250
|
+
active: workspaceOwnerMap.has(entry.appId),
|
|
3251
|
+
scope: workspaceScope
|
|
3252
|
+
}))].sort((left, right) => left.displayName.localeCompare(right.displayName) || left.id.localeCompare(right.id));
|
|
3253
|
+
const currentDiagnostics = [...packageInventory.diagnostics.map((diagnostic) => ({
|
|
3254
|
+
...diagnostic,
|
|
3255
|
+
source: "package"
|
|
3256
|
+
})), ...workspaceInventory.diagnostics.map((diagnostic) => ({
|
|
3257
|
+
...diagnostic,
|
|
3258
|
+
source: "workspace-service"
|
|
3259
|
+
}))];
|
|
3260
|
+
const currentDiagnosticKeys = new Set(currentDiagnostics.map((entry) => `${entry.source}:${entry.instanceDirectory}`));
|
|
3261
|
+
const diagnostics = /* @__PURE__ */ new Map();
|
|
3262
|
+
for (const entry of [...currentDiagnostics, ...this.reconciliationDiagnostics.filter((diagnostic) => currentDiagnosticKeys.has(`${diagnostic.source}:${diagnostic.instanceDirectory}`))]) diagnostics.set(`${entry.source}:${entry.instanceDirectory}`, entry);
|
|
3231
3263
|
return {
|
|
3232
|
-
entries
|
|
3233
|
-
|
|
3234
|
-
source: "package",
|
|
3235
|
-
displayName: packageOwners.get(entry.appId)?.name ?? entry.appId,
|
|
3236
|
-
active: packageOwners.has(entry.appId)
|
|
3237
|
-
})), ...workspaceInventory.entries.map((entry) => this.toEntry({
|
|
3238
|
-
entry,
|
|
3239
|
-
source: "workspace-service",
|
|
3240
|
-
displayName: workspaceOwnerMap.get(entry.appId)?.title ?? entry.appId,
|
|
3241
|
-
active: workspaceOwnerMap.has(entry.appId),
|
|
3242
|
-
scope: workspaceScope
|
|
3243
|
-
}))].sort((left, right) => left.displayName.localeCompare(right.displayName) || left.id.localeCompare(right.id)),
|
|
3244
|
-
diagnostics: [...packageInventory.diagnostics.map((entry) => ({
|
|
3245
|
-
...entry,
|
|
3246
|
-
source: "package"
|
|
3247
|
-
})), ...workspaceInventory.diagnostics.map((entry) => ({
|
|
3248
|
-
...entry,
|
|
3249
|
-
source: "workspace-service"
|
|
3250
|
-
}))]
|
|
3264
|
+
entries,
|
|
3265
|
+
diagnostics: Array.from(diagnostics.values())
|
|
3251
3266
|
};
|
|
3252
3267
|
};
|
|
3253
3268
|
deleteRetained = async (dataId, confirmAppId) => {
|
|
@@ -6698,7 +6713,7 @@ async function replayNcpAgentSessionEvents(events, seedMessages = [], activeMess
|
|
|
6698
6713
|
const replayMessageId = readReplayMessageId(replayEvent);
|
|
6699
6714
|
if (replayMessageId) knownMessageIds.add(replayMessageId);
|
|
6700
6715
|
await stateManager.dispatch(replayEvent);
|
|
6701
|
-
if (replayEvent.type === NcpEventType.MessageToolCallResult) toolResultsByCallId.set(replayEvent.payload.toolCallId, replayEvent.payload);
|
|
6716
|
+
if (replayEvent.type === NcpEventType.MessageToolCallResult && replayEvent.payload.final !== false) toolResultsByCallId.set(replayEvent.payload.toolCallId, replayEvent.payload);
|
|
6702
6717
|
}
|
|
6703
6718
|
const snapshot = stateManager.getSnapshot();
|
|
6704
6719
|
return (snapshot.streamingMessage ? insertMessageByTimeline(snapshot.messages, snapshot.streamingMessage) : snapshot.messages).map(compactionRecovery.terminalize);
|
|
@@ -6727,7 +6742,7 @@ function readReplayEventOccurredAt(event) {
|
|
|
6727
6742
|
function mergeReplayCompletedToolResults(message, toolResultsByCallId) {
|
|
6728
6743
|
let changed = false;
|
|
6729
6744
|
const parts = message.parts.map((part) => {
|
|
6730
|
-
if (part.type !== "tool-invocation" ||
|
|
6745
|
+
if (part.type !== "tool-invocation" || !part.toolCallId) return part;
|
|
6731
6746
|
const result = toolResultsByCallId.get(part.toolCallId);
|
|
6732
6747
|
if (!result) return part;
|
|
6733
6748
|
changed = true;
|
|
@@ -6735,7 +6750,8 @@ function mergeReplayCompletedToolResults(message, toolResultsByCallId) {
|
|
|
6735
6750
|
...part,
|
|
6736
6751
|
state: "result",
|
|
6737
6752
|
result: result.content,
|
|
6738
|
-
resultContentItems: result.contentItems
|
|
6753
|
+
...result.contentItems ? { resultContentItems: result.contentItems } : {},
|
|
6754
|
+
...result.execution ? { execution: mergeToolExecutionTiming(result.execution, part.execution) ?? result.execution } : {}
|
|
6739
6755
|
};
|
|
6740
6756
|
});
|
|
6741
6757
|
return changed ? {
|
|
@@ -6793,7 +6809,8 @@ function readStreamingMessageId(event) {
|
|
|
6793
6809
|
case NcpEventType.MessageReasoningDelta:
|
|
6794
6810
|
case NcpEventType.MessageReasoningEnd:
|
|
6795
6811
|
case NcpEventType.MessageToolCallStart:
|
|
6796
|
-
case NcpEventType.MessageToolCallArgsDelta:
|
|
6812
|
+
case NcpEventType.MessageToolCallArgsDelta:
|
|
6813
|
+
case NcpEventType.MessageToolExecutionStarted: return event.payload.messageId?.trim() || null;
|
|
6797
6814
|
default: return null;
|
|
6798
6815
|
}
|
|
6799
6816
|
}
|
|
@@ -6976,7 +6993,7 @@ function applySessionOverrides(params) {
|
|
|
6976
6993
|
//#endregion
|
|
6977
6994
|
//#region src/utils/session-context-inheritance.utils.ts
|
|
6978
6995
|
const CONTEXT_INHERITANCE_METADATA_KEY = "context_inheritance";
|
|
6979
|
-
const INHERITED_FROM_SESSION_METADATA_KEY = "inherited_from_session_id";
|
|
6996
|
+
const INHERITED_FROM_SESSION_METADATA_KEY$1 = "inherited_from_session_id";
|
|
6980
6997
|
const INHERITED_FROM_MESSAGE_METADATA_KEY = "inherited_from_message_id";
|
|
6981
6998
|
function hasToolCall(message, toolCallId) {
|
|
6982
6999
|
return message.parts.some((part) => part.type === "tool-invocation" && part.toolCallId === toolCallId);
|
|
@@ -6995,7 +7012,7 @@ function createInheritedContextMessage(params) {
|
|
|
6995
7012
|
const { childSessionId, index, sourceMessage, sourceSessionId } = params;
|
|
6996
7013
|
const message = structuredClone(sourceMessage);
|
|
6997
7014
|
const metadata = structuredClone(message.metadata ?? {});
|
|
6998
|
-
metadata[INHERITED_FROM_SESSION_METADATA_KEY] = sourceSessionId;
|
|
7015
|
+
metadata[INHERITED_FROM_SESSION_METADATA_KEY$1] = sourceSessionId;
|
|
6999
7016
|
metadata[INHERITED_FROM_MESSAGE_METADATA_KEY] = sourceMessage.id;
|
|
7000
7017
|
return {
|
|
7001
7018
|
...message,
|
|
@@ -7050,6 +7067,71 @@ function createSessionContextInheritance(params) {
|
|
|
7050
7067
|
};
|
|
7051
7068
|
}
|
|
7052
7069
|
//#endregion
|
|
7070
|
+
//#region src/managers/session-token-usage.manager.ts
|
|
7071
|
+
const INHERITED_FROM_SESSION_METADATA_KEY = "inherited_from_session_id";
|
|
7072
|
+
function sumNullableUsageMetric(executions, metric) {
|
|
7073
|
+
const values = executions.flatMap((execution) => {
|
|
7074
|
+
const value = execution.usage[metric];
|
|
7075
|
+
return value === null ? [] : [value];
|
|
7076
|
+
});
|
|
7077
|
+
return values.length > 0 ? values.reduce((total, value) => total + value, 0) : null;
|
|
7078
|
+
}
|
|
7079
|
+
function buildTokenUsageTotals(executions) {
|
|
7080
|
+
const inputTokens = sumNullableUsageMetric(executions, "inputTokens");
|
|
7081
|
+
const cachedInputTokens = sumNullableUsageMetric(executions, "cachedInputTokens");
|
|
7082
|
+
const hasCompleteCacheUsage = executions.length > 0 && executions.every((execution) => execution.usage.inputTokens !== null && execution.usage.cachedInputTokens !== null);
|
|
7083
|
+
return {
|
|
7084
|
+
inputTokens,
|
|
7085
|
+
outputTokens: sumNullableUsageMetric(executions, "outputTokens"),
|
|
7086
|
+
cachedInputTokens,
|
|
7087
|
+
totalTokens: sumNullableUsageMetric(executions, "totalTokens"),
|
|
7088
|
+
cacheHitRate: hasCompleteCacheUsage && inputTokens !== null && inputTokens > 0 && cachedInputTokens !== null && cachedInputTokens <= inputTokens ? cachedInputTokens / inputTokens : null
|
|
7089
|
+
};
|
|
7090
|
+
}
|
|
7091
|
+
function resolveTokenUsageStatus(executions) {
|
|
7092
|
+
if (executions.length === 0 || executions.every((execution) => execution.usage.status === "unavailable")) return "unavailable";
|
|
7093
|
+
return executions.every((execution) => execution.usage.status === "reported") ? "reported" : "partial";
|
|
7094
|
+
}
|
|
7095
|
+
function buildModelTokenUsage(model, executions) {
|
|
7096
|
+
return {
|
|
7097
|
+
model,
|
|
7098
|
+
...buildTokenUsageTotals(executions),
|
|
7099
|
+
runCount: executions.length,
|
|
7100
|
+
modelCallCount: sumNullableUsageMetric(executions, "modelCallCount"),
|
|
7101
|
+
reportedModelCallCount: sumNullableUsageMetric(executions, "reportedModelCallCount"),
|
|
7102
|
+
status: resolveTokenUsageStatus(executions)
|
|
7103
|
+
};
|
|
7104
|
+
}
|
|
7105
|
+
function buildSessionTokenUsageSummary(params) {
|
|
7106
|
+
const executionsByRunId = /* @__PURE__ */ new Map();
|
|
7107
|
+
for (const message of params.messages) {
|
|
7108
|
+
if (message.role !== "assistant" || typeof message.metadata?.[INHERITED_FROM_SESSION_METADATA_KEY] === "string") continue;
|
|
7109
|
+
const execution = readNcpAiExecutionMetadata(message.metadata);
|
|
7110
|
+
if (execution) executionsByRunId.set(execution.runId, execution);
|
|
7111
|
+
}
|
|
7112
|
+
const executions = [...executionsByRunId.values()];
|
|
7113
|
+
const executionsByModel = /* @__PURE__ */ new Map();
|
|
7114
|
+
for (const execution of executions) {
|
|
7115
|
+
const modelExecutions = executionsByModel.get(execution.model) ?? [];
|
|
7116
|
+
modelExecutions.push(execution);
|
|
7117
|
+
executionsByModel.set(execution.model, modelExecutions);
|
|
7118
|
+
}
|
|
7119
|
+
const models = [...executionsByModel.entries()].map(([model, modelExecutions]) => buildModelTokenUsage(model, modelExecutions)).sort((left, right) => {
|
|
7120
|
+
const leftTotal = left.totalTokens ?? -1;
|
|
7121
|
+
const rightTotal = right.totalTokens ?? -1;
|
|
7122
|
+
return rightTotal === leftTotal ? left.model.localeCompare(right.model) : rightTotal - leftTotal;
|
|
7123
|
+
});
|
|
7124
|
+
return {
|
|
7125
|
+
sessionId: params.sessionId,
|
|
7126
|
+
totals: buildTokenUsageTotals(executions),
|
|
7127
|
+
models,
|
|
7128
|
+
runCount: executions.length,
|
|
7129
|
+
modelCallCount: sumNullableUsageMetric(executions, "modelCallCount"),
|
|
7130
|
+
reportedModelCallCount: sumNullableUsageMetric(executions, "reportedModelCallCount"),
|
|
7131
|
+
status: resolveTokenUsageStatus(executions)
|
|
7132
|
+
};
|
|
7133
|
+
}
|
|
7134
|
+
//#endregion
|
|
7053
7135
|
//#region src/services/session-event-ingestion.service.ts
|
|
7054
7136
|
const SESSION_METADATA_PATCH_RUN_METADATA_KIND = "session_metadata_patch";
|
|
7055
7137
|
function isDurableSessionEvent(event) {
|
|
@@ -7338,6 +7420,13 @@ var SessionManager = class {
|
|
|
7338
7420
|
}))?.messages ?? [];
|
|
7339
7421
|
return await this.options.journalStore.listSessionMessages(normalizedSessionId);
|
|
7340
7422
|
};
|
|
7423
|
+
getSessionTokenUsage = async (sessionId) => {
|
|
7424
|
+
const record = await this.getSessionRecord(sessionId);
|
|
7425
|
+
return record ? buildSessionTokenUsageSummary({
|
|
7426
|
+
sessionId: record.sessionId,
|
|
7427
|
+
messages: record.messages
|
|
7428
|
+
}) : null;
|
|
7429
|
+
};
|
|
7341
7430
|
listSessionMessagePage = async (sessionId, options) => {
|
|
7342
7431
|
const { cursor, limit } = options;
|
|
7343
7432
|
const normalizedSessionId = normalizeSessionId(sessionId);
|
|
@@ -10115,7 +10204,8 @@ var ServiceAppRecordService = class {
|
|
|
10115
10204
|
const dirPath = join(serviceAppsPath, dirName);
|
|
10116
10205
|
try {
|
|
10117
10206
|
const manifest = await readServiceAppManifest(dirPath);
|
|
10118
|
-
|
|
10207
|
+
if (manifest.id !== dirName) throw new Error(`service app manifest id must match directory name: ${dirName}`);
|
|
10208
|
+
return this.fromManifest(dirPath, manifest, void 0, await this.inspectWorkspaceStorage(manifest.id));
|
|
10119
10209
|
} catch (error) {
|
|
10120
10210
|
if (this.isMissingFileError(error)) return null;
|
|
10121
10211
|
return this.failedWorkspaceRecord(dirName, dirPath, error);
|
|
@@ -10144,13 +10234,22 @@ var ServiceAppRecordService = class {
|
|
|
10144
10234
|
}))).filter((entry) => Boolean(entry));
|
|
10145
10235
|
};
|
|
10146
10236
|
materializeWorkspaceStorage = async (serviceId) => {
|
|
10147
|
-
const instanceDirectory =
|
|
10237
|
+
const instanceDirectory = this.getWorkspaceInstanceDirectory(serviceId);
|
|
10148
10238
|
return (await this.instanceStorageService.materialize({
|
|
10149
10239
|
appId: serviceId,
|
|
10150
10240
|
instanceId: "default",
|
|
10151
10241
|
instanceDirectory
|
|
10152
10242
|
})).storage;
|
|
10153
10243
|
};
|
|
10244
|
+
inspectWorkspaceStorage = async (serviceId) => {
|
|
10245
|
+
const instanceDirectory = this.getWorkspaceInstanceDirectory(serviceId);
|
|
10246
|
+
if (!await this.pathExists(instanceDirectory)) return;
|
|
10247
|
+
return (await this.instanceStorageService.inspect({
|
|
10248
|
+
appId: serviceId,
|
|
10249
|
+
instanceId: "default",
|
|
10250
|
+
instanceDirectory
|
|
10251
|
+
})).storage;
|
|
10252
|
+
};
|
|
10154
10253
|
fromManifest = (dirPath, manifest, packageSource, storage) => {
|
|
10155
10254
|
const runtimeStatus = this.params.runtimeService.getStatus(manifest.id);
|
|
10156
10255
|
return {
|
|
@@ -10210,10 +10309,26 @@ var ServiceAppRecordService = class {
|
|
|
10210
10309
|
isolation: source.isolation
|
|
10211
10310
|
});
|
|
10212
10311
|
toTitle = (value) => basename(value).replace(/[-_]+/g, " ").trim() || value;
|
|
10312
|
+
getWorkspaceInstanceDirectory = (serviceId) => join(this.params.getWorkspacePath(), ".nextclaw", "app-instances", serviceId, "default");
|
|
10313
|
+
pathExists = async (targetPath) => {
|
|
10314
|
+
try {
|
|
10315
|
+
await access(targetPath);
|
|
10316
|
+
return true;
|
|
10317
|
+
} catch (error) {
|
|
10318
|
+
if (this.isMissingFileError(error)) return false;
|
|
10319
|
+
throw error;
|
|
10320
|
+
}
|
|
10321
|
+
};
|
|
10213
10322
|
isMissingFileError = (error) => typeof error === "object" && error !== null && error.code === "ENOENT";
|
|
10214
10323
|
};
|
|
10215
10324
|
//#endregion
|
|
10216
10325
|
//#region src/services/service-app-removal.service.ts
|
|
10326
|
+
const TRANSACTION_ID = "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}";
|
|
10327
|
+
const CURRENT_DELETION_PATTERN = new RegExp(`^\\.deleting-(?<appId>[a-z0-9]+(?:-[a-z0-9]+)*)-(?<transactionId>${TRANSACTION_ID})$`);
|
|
10328
|
+
const LEGACY_DELETION_PATTERN = new RegExp(`^(?<appId>[a-z0-9]+(?:-[a-z0-9]+)*)\\.deleting-(?<transactionId>${TRANSACTION_ID})$`);
|
|
10329
|
+
function isServiceAppDeletionTombstone(directoryName) {
|
|
10330
|
+
return Boolean(parseServiceAppDeletionTombstone(directoryName));
|
|
10331
|
+
}
|
|
10217
10332
|
var ServiceAppRemovalCleanupError = class extends Error {
|
|
10218
10333
|
constructor(cause) {
|
|
10219
10334
|
super(`临时目录清理失败:${cause instanceof Error ? cause.message : String(cause)}`);
|
|
@@ -10223,6 +10338,7 @@ var ServiceAppRemovalCleanupError = class extends Error {
|
|
|
10223
10338
|
};
|
|
10224
10339
|
var ServiceAppRemovalService = class {
|
|
10225
10340
|
fileLockService = new FileLockService();
|
|
10341
|
+
listCanonicalDirectoryNames = async (serviceAppsPath) => (await this.listDirectories(serviceAppsPath)).filter((directoryName) => !directoryName.startsWith(".") && !isServiceAppDeletionTombstone(directoryName));
|
|
10226
10342
|
remove = async (params) => await this.fileLockService.withLock(params.lockPath, async () => {
|
|
10227
10343
|
const record = await params.loadRecord();
|
|
10228
10344
|
await this.removeLocked({
|
|
@@ -10231,14 +10347,39 @@ var ServiceAppRemovalService = class {
|
|
|
10231
10347
|
});
|
|
10232
10348
|
return record;
|
|
10233
10349
|
});
|
|
10350
|
+
reconcile = async (params) => {
|
|
10351
|
+
const { grantStore, lockPathForAppId, serviceAppsPath } = params;
|
|
10352
|
+
const diagnostics = [];
|
|
10353
|
+
for (const directoryName of await this.listDirectories(serviceAppsPath)) {
|
|
10354
|
+
const appId = parseServiceAppDeletionTombstone(directoryName);
|
|
10355
|
+
if (!appId) continue;
|
|
10356
|
+
const stagedPath = join(serviceAppsPath, directoryName);
|
|
10357
|
+
const canonicalPath = join(serviceAppsPath, appId);
|
|
10358
|
+
try {
|
|
10359
|
+
await this.fileLockService.withLock(lockPathForAppId(appId), async () => {
|
|
10360
|
+
const manifest = await readServiceAppManifest(stagedPath);
|
|
10361
|
+
if (manifest.id !== appId) throw new Error(`Service App 删除墓碑 identity 不匹配:${manifest.id}`);
|
|
10362
|
+
if (!await this.pathExists(canonicalPath)) await grantStore.revokeActionsByPrefix(`${appId}.`);
|
|
10363
|
+
await rm(stagedPath, { recursive: true });
|
|
10364
|
+
});
|
|
10365
|
+
} catch (error) {
|
|
10366
|
+
diagnostics.push({
|
|
10367
|
+
appId,
|
|
10368
|
+
stagedPath,
|
|
10369
|
+
message: error instanceof Error ? error.message : String(error)
|
|
10370
|
+
});
|
|
10371
|
+
}
|
|
10372
|
+
}
|
|
10373
|
+
return diagnostics.sort((left, right) => left.stagedPath.localeCompare(right.stagedPath));
|
|
10374
|
+
};
|
|
10234
10375
|
removeLocked = async (params) => {
|
|
10235
10376
|
const { grantStore, purgeData, record, stopRuntime } = params;
|
|
10236
10377
|
const grants = (await grantStore.list()).filter((grant) => grant.actionId.startsWith(`${record.id}.`));
|
|
10237
10378
|
const stagedPaths = [];
|
|
10238
10379
|
try {
|
|
10239
10380
|
await stopRuntime(record);
|
|
10240
|
-
await this.
|
|
10241
|
-
if (purgeData && record.storage) await this.
|
|
10381
|
+
await this.stageSourcePath(record.dirPath, stagedPaths);
|
|
10382
|
+
if (purgeData && record.storage) await this.stageInstancePath(record.storage.instanceDirectory, stagedPaths);
|
|
10242
10383
|
await grantStore.revokeActionsByPrefix(`${record.id}.`);
|
|
10243
10384
|
} catch (error) {
|
|
10244
10385
|
const recoveryErrors = [...await this.restoreStagedPaths(stagedPaths), ...await this.restoreGrants(grantStore, grants)];
|
|
@@ -10251,7 +10392,15 @@ var ServiceAppRemovalService = class {
|
|
|
10251
10392
|
throw new ServiceAppRemovalCleanupError(error);
|
|
10252
10393
|
}
|
|
10253
10394
|
};
|
|
10254
|
-
|
|
10395
|
+
stageSourcePath = async (originalPath, stagedPaths) => {
|
|
10396
|
+
const stagedPath = join(dirname(originalPath), `.deleting-${basename(originalPath)}-${randomUUID()}`);
|
|
10397
|
+
await rename(originalPath, stagedPath);
|
|
10398
|
+
stagedPaths.push({
|
|
10399
|
+
originalPath,
|
|
10400
|
+
stagedPath
|
|
10401
|
+
});
|
|
10402
|
+
};
|
|
10403
|
+
stageInstancePath = async (originalPath, stagedPaths) => {
|
|
10255
10404
|
const stagedPath = `${originalPath}.deleting-${randomUUID()}`;
|
|
10256
10405
|
await rename(originalPath, stagedPath);
|
|
10257
10406
|
stagedPaths.push({
|
|
@@ -10259,6 +10408,24 @@ var ServiceAppRemovalService = class {
|
|
|
10259
10408
|
stagedPath
|
|
10260
10409
|
});
|
|
10261
10410
|
};
|
|
10411
|
+
listDirectories = async (directory) => {
|
|
10412
|
+
try {
|
|
10413
|
+
return (await readdir(directory, { withFileTypes: true })).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
10414
|
+
} catch (error) {
|
|
10415
|
+
if (this.isMissingFileError(error)) return [];
|
|
10416
|
+
throw error;
|
|
10417
|
+
}
|
|
10418
|
+
};
|
|
10419
|
+
pathExists = async (targetPath) => {
|
|
10420
|
+
try {
|
|
10421
|
+
await access(targetPath);
|
|
10422
|
+
return true;
|
|
10423
|
+
} catch (error) {
|
|
10424
|
+
if (this.isMissingFileError(error)) return false;
|
|
10425
|
+
throw error;
|
|
10426
|
+
}
|
|
10427
|
+
};
|
|
10428
|
+
isMissingFileError = (error) => typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
10262
10429
|
restoreStagedPaths = async (stagedPaths) => {
|
|
10263
10430
|
const errors = [];
|
|
10264
10431
|
for (const entry of [...stagedPaths].reverse()) try {
|
|
@@ -10278,6 +10445,9 @@ var ServiceAppRemovalService = class {
|
|
|
10278
10445
|
return errors;
|
|
10279
10446
|
};
|
|
10280
10447
|
};
|
|
10448
|
+
function parseServiceAppDeletionTombstone(directoryName) {
|
|
10449
|
+
return CURRENT_DELETION_PATTERN.exec(directoryName)?.groups?.appId ?? LEGACY_DELETION_PATTERN.exec(directoryName)?.groups?.appId;
|
|
10450
|
+
}
|
|
10281
10451
|
//#endregion
|
|
10282
10452
|
//#region src/stores/service-action-grant.store.ts
|
|
10283
10453
|
const EMPTY_GRANTS = {
|
|
@@ -10425,13 +10595,12 @@ var ServiceAppError = class extends Error {
|
|
|
10425
10595
|
this.name = "ServiceAppError";
|
|
10426
10596
|
}
|
|
10427
10597
|
};
|
|
10428
|
-
|
|
10429
|
-
return error instanceof ServiceAppError;
|
|
10430
|
-
}
|
|
10598
|
+
const isServiceAppError = (error) => error instanceof ServiceAppError;
|
|
10431
10599
|
var ServiceAppManager = class {
|
|
10432
10600
|
removalService = new ServiceAppRemovalService();
|
|
10433
10601
|
runtimeService;
|
|
10434
10602
|
recordService;
|
|
10603
|
+
reconciliationDiagnostics = [];
|
|
10435
10604
|
constructor(params) {
|
|
10436
10605
|
this.params = params;
|
|
10437
10606
|
this.runtimeService = params.runtimeService ?? new McpServiceAppRuntimeService({ getConfig: () => params.configManager.config });
|
|
@@ -10440,6 +10609,13 @@ var ServiceAppManager = class {
|
|
|
10440
10609
|
runtimeService: this.runtimeService
|
|
10441
10610
|
});
|
|
10442
10611
|
}
|
|
10612
|
+
start = async () => {
|
|
10613
|
+
this.reconciliationDiagnostics = await this.removalService.reconcile({
|
|
10614
|
+
grantStore: this.createGrantStore(),
|
|
10615
|
+
lockPathForAppId: (appId) => this.getServiceAppLockPath(this.getWorkspacePath(), appId),
|
|
10616
|
+
serviceAppsPath: this.getServiceAppsPath(this.getWorkspacePath())
|
|
10617
|
+
});
|
|
10618
|
+
};
|
|
10443
10619
|
listServiceApps = async () => {
|
|
10444
10620
|
const workspacePath = this.getWorkspacePath();
|
|
10445
10621
|
const serviceAppsPath = this.getServiceAppsPath(workspacePath);
|
|
@@ -10450,6 +10626,7 @@ var ServiceAppManager = class {
|
|
|
10450
10626
|
return {
|
|
10451
10627
|
workspacePath,
|
|
10452
10628
|
serviceAppsPath,
|
|
10629
|
+
diagnostics: this.reconciliationDiagnostics,
|
|
10453
10630
|
entries: [...workspaceEntries, ...packageEntries].filter((entry) => Boolean(entry)).sort((left, right) => left.title.localeCompare(right.title))
|
|
10454
10631
|
};
|
|
10455
10632
|
};
|
|
@@ -10462,7 +10639,7 @@ var ServiceAppManager = class {
|
|
|
10462
10639
|
return await Promise.all(actions.map(async (action) => await this.withGrantState(action, params)));
|
|
10463
10640
|
};
|
|
10464
10641
|
discoverServiceAppActions = async (appId) => {
|
|
10465
|
-
const { manifest, record } = await this.requireServiceApp(appId);
|
|
10642
|
+
const { manifest, record } = await this.requireServiceApp(appId, true);
|
|
10466
10643
|
return mergeServiceAppRuntimeActions({
|
|
10467
10644
|
record,
|
|
10468
10645
|
manifest,
|
|
@@ -10475,7 +10652,7 @@ var ServiceAppManager = class {
|
|
|
10475
10652
|
invokeServiceAction = async (actionId, request) => {
|
|
10476
10653
|
this.assertCaller(request.caller);
|
|
10477
10654
|
this.assertDeclaredAction(actionId, request.declaredActions);
|
|
10478
|
-
const { manifest, record } = await this.requireServiceAppForAction(actionId);
|
|
10655
|
+
const { manifest, record } = await this.requireServiceAppForAction(actionId, true);
|
|
10479
10656
|
const actionName = getServiceActionName(actionId, record.id);
|
|
10480
10657
|
if (!Object.hasOwn(manifest.actions, actionName)) throw new ServiceAppError("SERVICE_APP_ACTION_NOT_FOUND", "service action not found");
|
|
10481
10658
|
const declaredRisk = manifest.actions[actionName]?.risk ?? "dangerous";
|
|
@@ -10535,12 +10712,11 @@ var ServiceAppManager = class {
|
|
|
10535
10712
|
try {
|
|
10536
10713
|
record = await this.removalService.remove({
|
|
10537
10714
|
grantStore: this.createGrantStore(),
|
|
10538
|
-
lockPath:
|
|
10715
|
+
lockPath: this.getServiceAppLockPath(workspacePath, appId),
|
|
10539
10716
|
purgeData,
|
|
10540
10717
|
loadRecord: async () => {
|
|
10541
10718
|
const { record } = await this.requireServiceApp(appId);
|
|
10542
10719
|
if (record.sourceKind === "package") throw new ServiceAppError("SERVICE_APP_MANAGED_SOURCE", `package service must be managed through Apps: ${record.packageId}`);
|
|
10543
|
-
if (purgeData && !record.storage) throw new ServiceAppError("SERVICE_APP_READ_FAILED", `Service App ${appId} 缺少受管数据目录,已停止删除。`);
|
|
10544
10720
|
return record;
|
|
10545
10721
|
},
|
|
10546
10722
|
stopRuntime: async (loaded) => await this.runtimeService.restart(loaded.id)
|
|
@@ -10624,12 +10800,12 @@ var ServiceAppManager = class {
|
|
|
10624
10800
|
if (!action) throw new ServiceAppError("SERVICE_APP_ACTION_NOT_FOUND", "service action not found");
|
|
10625
10801
|
return action;
|
|
10626
10802
|
};
|
|
10627
|
-
requireServiceAppForAction = async (actionId) => {
|
|
10803
|
+
requireServiceAppForAction = async (actionId, materializeStorage = false) => {
|
|
10628
10804
|
const appId = actionId.split(".")[0]?.trim();
|
|
10629
10805
|
if (!appId) throw new ServiceAppError("SERVICE_APP_INVALID_ACTION", "service action id is invalid");
|
|
10630
|
-
return await this.requireServiceApp(appId);
|
|
10806
|
+
return await this.requireServiceApp(appId, materializeStorage);
|
|
10631
10807
|
};
|
|
10632
|
-
requireServiceApp = async (appId) => {
|
|
10808
|
+
requireServiceApp = async (appId, materializeStorage = false) => {
|
|
10633
10809
|
let dirPath = join(this.getServiceAppsPath(this.getWorkspacePath()), appId);
|
|
10634
10810
|
let packageSource;
|
|
10635
10811
|
try {
|
|
@@ -10644,9 +10820,10 @@ var ServiceAppManager = class {
|
|
|
10644
10820
|
if (!(await stat(dirPath)).isDirectory()) throw new ServiceAppError("SERVICE_APP_NOT_FOUND", "service app not found");
|
|
10645
10821
|
const manifest = await readServiceAppManifest(dirPath);
|
|
10646
10822
|
if (manifest.id !== appId) throw new ServiceAppError("SERVICE_APP_INVALID_MANIFEST", "service app manifest id must match directory name");
|
|
10823
|
+
const storage = packageSource?.storage ?? (materializeStorage ? await this.recordService.materializeWorkspaceStorage(manifest.id) : await this.recordService.inspectWorkspaceStorage(manifest.id));
|
|
10647
10824
|
return {
|
|
10648
10825
|
manifest,
|
|
10649
|
-
record: this.recordService.fromManifest(dirPath, manifest, packageSource,
|
|
10826
|
+
record: this.recordService.fromManifest(dirPath, manifest, packageSource, storage)
|
|
10650
10827
|
};
|
|
10651
10828
|
} catch (error) {
|
|
10652
10829
|
if (isServiceAppError(error)) throw error;
|
|
@@ -10664,7 +10841,7 @@ var ServiceAppManager = class {
|
|
|
10664
10841
|
const manifest = await readServiceAppManifest(dirPath);
|
|
10665
10842
|
return {
|
|
10666
10843
|
manifest,
|
|
10667
|
-
record: this.recordService.fromManifest(dirPath, manifest, void 0, await this.recordService.
|
|
10844
|
+
record: this.recordService.fromManifest(dirPath, manifest, void 0, await this.recordService.inspectWorkspaceStorage(manifest.id))
|
|
10668
10845
|
};
|
|
10669
10846
|
} catch {
|
|
10670
10847
|
return null;
|
|
@@ -10692,13 +10869,13 @@ var ServiceAppManager = class {
|
|
|
10692
10869
|
normalizeActionIds = (actionIds) => Array.from(new Set(actionIds.map((actionId) => actionId.trim()).filter((actionId) => actionId.length > 0)));
|
|
10693
10870
|
getWorkspacePath = () => getWorkspacePathFromConfig(this.params.configManager.config);
|
|
10694
10871
|
getServiceAppsPath = (workspacePath) => join(workspacePath, DEFAULT_SERVICE_APPS_DIR);
|
|
10872
|
+
getServiceAppLockPath = (workspacePath, appId) => join(workspacePath, ".nextclaw", "locks", "service-apps", `${appId}.lock`);
|
|
10695
10873
|
createGrantStore = () => new ServiceActionGrantStore(join(this.getServiceAppsPath(this.getWorkspacePath()), SERVICE_ACTION_GRANTS_FILE_NAME));
|
|
10696
10874
|
listPackageComponentSources = async () => await this.params.listPackageComponentSources?.() ?? [];
|
|
10697
10875
|
listServiceAppDirNames = async (serviceAppsPath) => {
|
|
10698
10876
|
try {
|
|
10699
|
-
return
|
|
10877
|
+
return await this.removalService.listCanonicalDirectoryNames(serviceAppsPath);
|
|
10700
10878
|
} catch (error) {
|
|
10701
|
-
if (this.isMissingFileError(error)) return [];
|
|
10702
10879
|
throw new ServiceAppError("SERVICE_APP_READ_FAILED", error instanceof Error ? error.message : String(error));
|
|
10703
10880
|
}
|
|
10704
10881
|
};
|
|
@@ -15970,6 +16147,9 @@ var NextclawKernel = class {
|
|
|
15970
16147
|
};
|
|
15971
16148
|
getGatewayController = () => this.gatewayController;
|
|
15972
16149
|
start = async () => {
|
|
16150
|
+
await this.appPackageManager.start();
|
|
16151
|
+
await this.appDataManager.start();
|
|
16152
|
+
await this.serviceAppManager.start();
|
|
15973
16153
|
this.sessionSearch.start();
|
|
15974
16154
|
this.mcpManager.start();
|
|
15975
16155
|
this.providerModelCatalog.start();
|