@nextclaw/kernel 0.14.0 → 0.15.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +378 -15
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2917 -515
- package/dist/index.js.map +1 -1
- package/package.json +12 -12
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { AUTOMATIC_UPDATE_CHECK_INTERVAL_MS, getAutomaticUpdateCheckDelay, resolveAutomaticUpdateCheckIntervalMs } from "./utils/automatic-update-check.utils.js";
|
|
2
2
|
import { n as getUnsignedUpdateManifest, r as serializeUnsignedUpdateManifest, t as UpdateManifestReader } from "./update-manifest.types-C0qPrjGQ.js";
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
|
-
import { APP_NAME, AgentRouteResolver, BUILTIN_MAIN_AGENT_ID, CLEAR_THINKING_TOKENS, CONTEXT_COMPACTION_METADATA_KEY, ConfigSchema, ContextCompactionService, ContextWindowBudgetService, CronService, CronTool, DEFAULT_PANELS_DIR, DEFAULT_SERVICE_APPS_DIR, DEFAULT_SESSION_SEARCH_LIMIT, DEFAULT_WORKSPACE_REPOSITORY_IDENTITY_RESOLVER, DIAGNOSTIC_CORRELATION_METADATA_KEY, DiagnosticRuntime, EditFileTool, ExecTool, ExtensionChannelAdapter, GatewayTool, InputBudgetPruner, LLMProvider, ListDirTool, LiteLLMProvider, LocalExecutionClaimService, MAX_SESSION_SEARCH_LIMIT, MemoryGetTool, MemorySearchTool, MemoryStore, MessageBus, MessageTool, ProviderModelDiscoveryService, ProviderRegistry, ReadFileTool,
|
|
4
|
+
import { APP_NAME, AgentRouteResolver, BUILTIN_MAIN_AGENT_ID, CLEAR_THINKING_TOKENS, CONTEXT_COMPACTION_METADATA_KEY, ConfigSchema, ContextCompactionService, ContextWindowBudgetService, CronService, CronTool, DEFAULT_PANELS_DIR, DEFAULT_SERVICE_APPS_DIR, DEFAULT_SESSION_SEARCH_LIMIT, DEFAULT_WORKSPACE_REPOSITORY_IDENTITY_RESOLVER, DIAGNOSTIC_CORRELATION_METADATA_KEY, DiagnosticRuntime, EditFileTool, ExecTool, ExtensionChannelAdapter, GatewayTool, InputBudgetPruner, LLMProvider, ListDirTool, LiteLLMProvider, LocalExecutionClaimService, MAX_SESSION_SEARCH_LIMIT, MemoryGetTool, MemorySearchTool, MemoryStore, MessageBus, MessageTool, ProviderModelDiscoveryService, ProviderRegistry, ReadFileTool, SILENT_REPLY_TOKEN, SessionProjectContextResolver, SessionSearchService, SkillsLoader, THINKING_LEVELS, ViewImageTool, WebFetchTool, WebSearchTool, WriteFileTool, buildCompressingCompactionCheckpoint, buildConfigSchema, buildContextWindowSnapshot, buildReloadPlan, buildSessionRequestToolResult, buildToolCatalogEntries, createAgentProfile, createAssistantStreamDeltaControlMessage, createAssistantStreamResetControlMessage, createCompletedSessionRequest, createFailedSessionRequest, createRunningSessionRequest, createRuntimeChildEnv, createTypingStopControlMessage, diffConfigPaths, ensureDir, estimateInputTokens, evaluateSilentReply, expandHome, findEffectiveAgentProfile, getConfigPath, getDataDir, getDataPath, getSessionsPath, getWorkspacePath, getWorkspacePathFromConfig, isNextclawControlMessage, loadConfig, mergeExtensionConfigView, modelSupportsVision, normalizeAgentProfileId, normalizeInlineSecretRefs, normalizeModelThinkingCapability, normalizeProviderModelConfig, normalizeToolParams, parseAgentScopedSessionKey, parseThinkingLevel, readCompressedContextCompactionCheckpoint, readOptionalString, readParentSessionId, readSessionProjectRoot, redactConfigObject, removeAgentProfile, resolveConfigSecrets, resolveDefaultAgentProfileId, resolveEffectiveAgentProfiles, resolveNextclawSelfManageGuidePaths, resolveProviderRuntime, resolveRuntimeCommandLaunch, resolveSecretRef, resolveSessionProjectContext, resolveSessionWorkspacePath, resolveThinkingLevel, safeFilename, sanitizeOutboundAssistantContent, saveConfig, summarizeSessionRequestTask, toExtensionConfigView, updateAgentProfile } from "@nextclaw/core";
|
|
5
5
|
import { NCP_AI_EXECUTION_METADATA_KEY, NCP_INTERNAL_VISIBILITY_METADATA_KEY, NCP_RUN_TRIGGER_METADATA_KEY, NcpEventType, OBSERVATION_EVENT_EXTENSION_TYPE, createUnavailableNcpAiExecutionMetadata, isHiddenNcpMessage, normalizeAssistantText, parseNcpRunTriggerInput, readNcpAiExecutionMetadata, sanitizeAssistantReplyTags } from "@nextclaw/ncp";
|
|
6
6
|
import { LocalAssetStore, buildAssetContentPath, buildOpenAiFunctionTool, isTextLikeAsset, ncpMessageToOpenAiMessages, validateToolArgs } from "@nextclaw/ncp-agent-runtime";
|
|
7
7
|
import { CHAT_CONTINUATION_TARGET_MESSAGE_METADATA_KEY, CHAT_CONVERSATION_EXCERPT_TOKEN_KIND, CHAT_INLINE_TOKENS_METADATA_KEY, CHAT_INLINE_TOKENS_SCHEMA_VERSION, CHAT_PROJECT_TOKEN_KIND, CHAT_SESSION_MATERIALIZATION_METADATA_KEY, CHAT_SYSTEM_OBJECT_TOKEN_KIND, CHAT_UI_RESOURCE_TOKEN_KIND, CHAT_WORKSPACE_DIRECTORY_TOKEN_KIND, CHAT_WORKSPACE_EXCERPT_TOKEN_KIND, CHAT_WORKSPACE_FILE_TOKEN_KIND, Contribution as Contribution$1, EventBus, EventBus as EventBus$1, Ingress, Ingress as Ingress$1, PANEL_APP_INLINE_HOST_CONTRACT, PANEL_APP_SCROLL_RESTORATION_CONTRACT, SYSTEM_OBJECT_REFERENCE_DEFAULT_LIMIT, SYSTEM_OBJECT_REFERENCE_MAX_LIMIT, SYSTEM_OBJECT_TYPE_CRON_JOB, SYSTEM_OBJECT_TYPE_INBOX_DELIVERY, UI_CONTENT_PARAMS_HOST_CONTRACT, classifyDiagnosticError, createSystemObjectReferenceUri, createTypedKey, eventKeys, eventKeys as eventKeys$1, ingressKeys, ingressKeys as ingressKeys$1, isRuntimeDefaultModelValue, isSilentReplyNcpMessage, normalizeRuntimeModelSelectionMode, parseSystemObjectReferenceUri, readChatUiResourceReference, readInlineContentHeight, readSystemObjectResolvedReference, readUiContentParams } from "@nextclaw/shared";
|
|
@@ -10,8 +10,8 @@ import { catchError, filter, from, lastValueFrom, tap } from "rxjs";
|
|
|
10
10
|
import { DefaultNcpAgentConversationStateManager, insertMessageByTimeline, mergeToolExecutionTiming } from "@nextclaw/ncp-toolkit";
|
|
11
11
|
import { accessSync, appendFileSync, chmodSync, constants, createReadStream, existsSync, mkdirSync, readFileSync, readdirSync, readlinkSync, realpathSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
12
12
|
import path, { basename, delimiter, dirname, extname, isAbsolute, join, normalize, relative, resolve, sep } from "node:path";
|
|
13
|
-
import { access, appendFile, chmod, mkdir, mkdtemp, open, readFile, readdir, realpath, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
|
|
14
|
-
import { AppHomeService, AppInstallationService, AppInstanceInventoryService, AppInstanceStorageService, AppManifestService, AppPlatformTargetService, AppRegistryService, AppServiceLaunchService, FileLockService, isAppComponentManifestBundle } from "@nextclaw/app-runtime";
|
|
13
|
+
import { access, appendFile, chmod, lstat, mkdir, mkdtemp, open, readFile, readdir, realpath, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
|
|
14
|
+
import { AppGrantService, AppHomeService, AppInstallationService, AppInstanceInventoryService, AppInstanceStorageService, AppManifestService, AppPlatformTargetService, AppRegistryService, AppServiceLaunchService, FileLockService, isAppComponentManifestBundle } from "@nextclaw/app-runtime";
|
|
15
15
|
import { execFileSync, spawn } from "node:child_process";
|
|
16
16
|
import { fileURLToPath } from "node:url";
|
|
17
17
|
import { BUILTIN_PROVIDER_PLUGINS } from "@nextclaw/runtime";
|
|
@@ -1393,26 +1393,26 @@ const SESSION_ACTIVITY_PREVIEW_STATUS_KINDS = new Set([
|
|
|
1393
1393
|
"run-failed",
|
|
1394
1394
|
"run-interrupted"
|
|
1395
1395
|
]);
|
|
1396
|
-
function isRecord$
|
|
1396
|
+
function isRecord$22(value) {
|
|
1397
1397
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
1398
1398
|
}
|
|
1399
|
-
function readOptionalString$
|
|
1399
|
+
function readOptionalString$16(value) {
|
|
1400
1400
|
if (typeof value !== "string") return;
|
|
1401
1401
|
const trimmed = value.trim();
|
|
1402
1402
|
return trimmed.length > 0 ? trimmed : void 0;
|
|
1403
1403
|
}
|
|
1404
1404
|
function readSessionActivityPreviewMetadata(value) {
|
|
1405
|
-
if (!isRecord$
|
|
1405
|
+
if (!isRecord$22(value)) return null;
|
|
1406
1406
|
const state = value.state;
|
|
1407
|
-
const timestamp = readOptionalString$
|
|
1407
|
+
const timestamp = readOptionalString$16(value.timestamp);
|
|
1408
1408
|
if (!SESSION_ACTIVITY_PREVIEW_STATES.has(state) || !timestamp) return null;
|
|
1409
|
-
const statusKind = readOptionalString$
|
|
1409
|
+
const statusKind = readOptionalString$16(value.statusKind);
|
|
1410
1410
|
return {
|
|
1411
1411
|
state,
|
|
1412
1412
|
timestamp,
|
|
1413
1413
|
statusKind: SESSION_ACTIVITY_PREVIEW_STATUS_KINDS.has(statusKind) ? statusKind : void 0,
|
|
1414
|
-
statusText: readOptionalString$
|
|
1415
|
-
replyText: readOptionalString$
|
|
1414
|
+
statusText: readOptionalString$16(value.statusText),
|
|
1415
|
+
replyText: readOptionalString$16(value.replyText)
|
|
1416
1416
|
};
|
|
1417
1417
|
}
|
|
1418
1418
|
function compareIsoTimestamp(left, right) {
|
|
@@ -1496,7 +1496,7 @@ var SessionActivityPreviewEventService = class {
|
|
|
1496
1496
|
//#endregion
|
|
1497
1497
|
//#region src/managers/agent-run-session-command.manager.ts
|
|
1498
1498
|
const CONTINUATION_PROMPT = "Continue from where you stopped. Preserve completed work and avoid repeating it.";
|
|
1499
|
-
function isRecord$
|
|
1499
|
+
function isRecord$21(value) {
|
|
1500
1500
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
1501
1501
|
}
|
|
1502
1502
|
function hasSendableMessageContent(message) {
|
|
@@ -1504,7 +1504,7 @@ function hasSendableMessageContent(message) {
|
|
|
1504
1504
|
}
|
|
1505
1505
|
function readSessionActivityState(metadata) {
|
|
1506
1506
|
const preview = metadata?.[SESSION_ACTIVITY_PREVIEW_METADATA_KEY];
|
|
1507
|
-
return isRecord$
|
|
1507
|
+
return isRecord$21(preview) && typeof preview.state === "string" ? preview.state : null;
|
|
1508
1508
|
}
|
|
1509
1509
|
var AgentRunSessionCommandManager = class {
|
|
1510
1510
|
pendingCommands = /* @__PURE__ */ new Map();
|
|
@@ -1879,13 +1879,13 @@ function buildRunMetadata(params) {
|
|
|
1879
1879
|
}
|
|
1880
1880
|
//#endregion
|
|
1881
1881
|
//#region src/utils/agent-run-request.utils.ts
|
|
1882
|
-
function readOptionalString$
|
|
1882
|
+
function readOptionalString$15(value) {
|
|
1883
1883
|
if (typeof value !== "string") return;
|
|
1884
1884
|
return value.trim() || void 0;
|
|
1885
1885
|
}
|
|
1886
1886
|
function toAgentRunRequest(envelope) {
|
|
1887
1887
|
const metadata = envelope.metadata ?? {};
|
|
1888
|
-
const peerId = readOptionalString$
|
|
1888
|
+
const peerId = readOptionalString$15(envelope.peerId);
|
|
1889
1889
|
const requestMetadata = {
|
|
1890
1890
|
agentRuntimeId: metadata.agentRuntimeId,
|
|
1891
1891
|
agentId: metadata.agentId,
|
|
@@ -1897,10 +1897,10 @@ function toAgentRunRequest(envelope) {
|
|
|
1897
1897
|
maxTokens: metadata.maxTokens,
|
|
1898
1898
|
thinkingEffort: metadata.thinkingEffort,
|
|
1899
1899
|
delivery: envelope.delivery,
|
|
1900
|
-
idempotencyKey: readOptionalString$
|
|
1900
|
+
idempotencyKey: readOptionalString$15(envelope.idempotencyKey)
|
|
1901
1901
|
};
|
|
1902
1902
|
if (Array.isArray(envelope.content)) {
|
|
1903
|
-
const sessionId = readOptionalString$
|
|
1903
|
+
const sessionId = readOptionalString$15(envelope.sessionId);
|
|
1904
1904
|
if (sessionId && peerId) throw new Error("agent-run.send cannot accept both sessionId and peerId.");
|
|
1905
1905
|
return {
|
|
1906
1906
|
...requestMetadata,
|
|
@@ -1918,8 +1918,8 @@ function toAgentRunRequest(envelope) {
|
|
|
1918
1918
|
}
|
|
1919
1919
|
const sourceMessage = envelope.message;
|
|
1920
1920
|
if (!sourceMessage) throw new Error("Invalid agent run send request.");
|
|
1921
|
-
const envelopeSessionId = readOptionalString$
|
|
1922
|
-
const messageSessionId = readOptionalString$
|
|
1921
|
+
const envelopeSessionId = readOptionalString$15(envelope.sessionId);
|
|
1922
|
+
const messageSessionId = readOptionalString$15(sourceMessage.sessionId);
|
|
1923
1923
|
const sessionId = envelopeSessionId ?? messageSessionId;
|
|
1924
1924
|
if (sessionId && peerId) throw new Error("agent-run.send cannot accept both sessionId and peerId.");
|
|
1925
1925
|
return {
|
|
@@ -1980,7 +1980,7 @@ function readSessionMaterialization(metadata) {
|
|
|
1980
1980
|
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
1981
1981
|
const materialization = value;
|
|
1982
1982
|
if (materialization.kind !== "child") throw new Error("session_materialization.kind must be \"child\".");
|
|
1983
|
-
const parentSessionId = readOptionalString$
|
|
1983
|
+
const parentSessionId = readOptionalString$15(materialization.parentSessionId);
|
|
1984
1984
|
if (!parentSessionId) throw new Error("session_materialization.parentSessionId is required.");
|
|
1985
1985
|
if (materialization.inheritContext !== true) throw new Error("session_materialization.inheritContext must be true.");
|
|
1986
1986
|
return {
|
|
@@ -2185,15 +2185,15 @@ var AgentRuntimeRunObserverService = class {
|
|
|
2185
2185
|
};
|
|
2186
2186
|
//#endregion
|
|
2187
2187
|
//#region src/utils/agent-run-trigger.utils.ts
|
|
2188
|
-
function isRecord$
|
|
2188
|
+
function isRecord$20(value) {
|
|
2189
2189
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
2190
2190
|
}
|
|
2191
|
-
function readOptionalString$
|
|
2191
|
+
function readOptionalString$14(value) {
|
|
2192
2192
|
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
2193
2193
|
}
|
|
2194
2194
|
function readSourceRunSpec(message) {
|
|
2195
2195
|
const value = message.metadata?.[AGENT_RUN_MESSAGE_RUN_SPEC_METADATA_KEY];
|
|
2196
|
-
return isRecord$
|
|
2196
|
+
return isRecord$20(value) ? value : {};
|
|
2197
2197
|
}
|
|
2198
2198
|
const SOURCE_CONTEXT_KEYS = [
|
|
2199
2199
|
"channel",
|
|
@@ -2222,9 +2222,9 @@ function readSourceContext(request) {
|
|
|
2222
2222
|
function createAgentToolRunTriggerInput(params) {
|
|
2223
2223
|
const { request, source } = params;
|
|
2224
2224
|
const runSpec = readSourceRunSpec(request.message);
|
|
2225
|
-
const sourceSessionId = readOptionalString$
|
|
2226
|
-
const sourceRunId = readOptionalString$
|
|
2227
|
-
const sourceModel = readOptionalString$
|
|
2225
|
+
const sourceSessionId = readOptionalString$14(request.sessionId);
|
|
2226
|
+
const sourceRunId = readOptionalString$14(runSpec.runId);
|
|
2227
|
+
const sourceModel = readOptionalString$14(runSpec.model);
|
|
2228
2228
|
const sourceContext = readSourceContext(request);
|
|
2229
2229
|
return {
|
|
2230
2230
|
actor: "agent",
|
|
@@ -2240,9 +2240,9 @@ function createAgentToolRunTriggerInput(params) {
|
|
|
2240
2240
|
function createIngressRunTriggerInput(params) {
|
|
2241
2241
|
const { request } = params;
|
|
2242
2242
|
const source = request.channel ? `channel:${request.channel}` : params.source.trim() || "internal";
|
|
2243
|
-
const sourceSessionId = readOptionalString$
|
|
2243
|
+
const sourceSessionId = readOptionalString$14(request.sessionId);
|
|
2244
2244
|
const sourceContext = readSourceContext(request);
|
|
2245
|
-
const cronJobId = readOptionalString$
|
|
2245
|
+
const cronJobId = readOptionalString$14(request.metadata?.cron_job_id);
|
|
2246
2246
|
const isCron = request.metadata?.session_origin === "cron" || Boolean(cronJobId);
|
|
2247
2247
|
return {
|
|
2248
2248
|
actor: isCron ? "automation" : request.message.role === "user" ? "human" : request.message.role === "service" ? "automation" : "system",
|
|
@@ -2296,7 +2296,7 @@ function createRunTriggerMetadataEvent(params) {
|
|
|
2296
2296
|
};
|
|
2297
2297
|
}
|
|
2298
2298
|
function attachSourceToolCall(trigger, sourceToolCallId) {
|
|
2299
|
-
const normalizedToolCallId = readOptionalString$
|
|
2299
|
+
const normalizedToolCallId = readOptionalString$14(sourceToolCallId);
|
|
2300
2300
|
return {
|
|
2301
2301
|
...structuredClone(trigger),
|
|
2302
2302
|
...normalizedToolCallId ? { sourceToolCallId: normalizedToolCallId } : {}
|
|
@@ -2698,7 +2698,7 @@ const BUILTIN_RUNTIME_PRESENTATION = {
|
|
|
2698
2698
|
}
|
|
2699
2699
|
}
|
|
2700
2700
|
};
|
|
2701
|
-
function readString$
|
|
2701
|
+
function readString$12(value) {
|
|
2702
2702
|
if (typeof value !== "string") return;
|
|
2703
2703
|
return value.trim() || void 0;
|
|
2704
2704
|
}
|
|
@@ -2707,7 +2707,7 @@ function readRecord$3(value) {
|
|
|
2707
2707
|
return value;
|
|
2708
2708
|
}
|
|
2709
2709
|
function resolveBuiltinRuntimePresentation(value) {
|
|
2710
|
-
const normalized = readString$
|
|
2710
|
+
const normalized = readString$12(value)?.toLowerCase();
|
|
2711
2711
|
if (!normalized) return null;
|
|
2712
2712
|
if (!(normalized in BUILTIN_RUNTIME_PRESENTATION)) return null;
|
|
2713
2713
|
return BUILTIN_RUNTIME_PRESENTATION[normalized] ?? null;
|
|
@@ -2728,13 +2728,13 @@ function resolveAgentRuntimeEntries(params) {
|
|
|
2728
2728
|
entries.set(DEFAULT_AGENT_RUNTIME_ENTRY_ID, buildNativeRuntimeEntry(params.config));
|
|
2729
2729
|
for (const [rawId, rawEntry] of Object.entries(params.config.agents.runtimes.entries ?? {})) {
|
|
2730
2730
|
const id = rawId.trim().toLowerCase();
|
|
2731
|
-
const type = readString$
|
|
2731
|
+
const type = readString$12(rawEntry.type)?.toLowerCase();
|
|
2732
2732
|
if (!id || !type) continue;
|
|
2733
2733
|
const explicitIcon = normalizeAgentRuntimeSessionTypeIcon(rawEntry.icon);
|
|
2734
2734
|
const builtinPresentation = resolveBuiltinRuntimePresentation(id) ?? resolveBuiltinRuntimePresentation(type);
|
|
2735
2735
|
entries.set(id, {
|
|
2736
2736
|
id,
|
|
2737
|
-
label: readString$
|
|
2737
|
+
label: readString$12(rawEntry.label) ?? builtinPresentation?.label ?? id,
|
|
2738
2738
|
...explicitIcon ?? builtinPresentation?.icon ? { icon: explicitIcon ?? builtinPresentation?.icon } : {},
|
|
2739
2739
|
type,
|
|
2740
2740
|
enabled: rawEntry.enabled !== false,
|
|
@@ -3664,7 +3664,11 @@ var AppPackageReadinessManager = class {
|
|
|
3664
3664
|
isolation: manifestBundle.manifest.main.kind === "wasi-http-component" ? "host-mediated" : "sandboxed",
|
|
3665
3665
|
permissions: manifestBundle.manifest.permissions ?? {}
|
|
3666
3666
|
};
|
|
3667
|
-
const [dependencies, secrets] = await Promise.all([
|
|
3667
|
+
const [dependencies, secrets, documentAccess] = await Promise.all([
|
|
3668
|
+
this.params.dependencyCoordinator.inspectTarget(this.params.dependencyCoordinator.targetForInfo(info, selectedVersion), providers),
|
|
3669
|
+
this.inspectSecretsForInfo(info, true, selectedVersion),
|
|
3670
|
+
this.params.grantService.summarize(info.appId).then((state) => state.documentAccess)
|
|
3671
|
+
]);
|
|
3668
3672
|
return {
|
|
3669
3673
|
id: info.appId,
|
|
3670
3674
|
name: info.name,
|
|
@@ -3701,7 +3705,8 @@ var AppPackageReadinessManager = class {
|
|
|
3701
3705
|
isolation: security.isolation,
|
|
3702
3706
|
readiness: this.combineReadiness(dependencies.readiness, secrets.readiness),
|
|
3703
3707
|
secrets,
|
|
3704
|
-
dependencies
|
|
3708
|
+
dependencies,
|
|
3709
|
+
documentAccess
|
|
3705
3710
|
};
|
|
3706
3711
|
};
|
|
3707
3712
|
inspectSecrets = async (info, verify, selectedVersion = info.activeVersion) => {
|
|
@@ -3807,10 +3812,10 @@ function parseServiceAppManifest(raw, options = {}) {
|
|
|
3807
3812
|
} catch (error) {
|
|
3808
3813
|
throw new Error(`service-app.json is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
3809
3814
|
}
|
|
3810
|
-
if (!isRecord$
|
|
3815
|
+
if (!isRecord$19(parsed)) throw new Error("service-app.json must contain an object.");
|
|
3811
3816
|
const id = readRequiredString$8(parsed, "id");
|
|
3812
3817
|
if (!SERVICE_APP_ID_PATTERN$1.test(id)) throw new Error("service app id must be kebab-case.");
|
|
3813
|
-
const protocol = readOptionalString$
|
|
3818
|
+
const protocol = readOptionalString$13(parsed, "protocol") ?? "mcp";
|
|
3814
3819
|
if (protocol !== "mcp" && protocol !== "wasi-component") throw new Error("service app protocol must be mcp or wasi-component.");
|
|
3815
3820
|
const launch = protocol === "mcp" ? new AppServiceLaunchService(options.platformTargetService).resolve(parsed, options.target) : void 0;
|
|
3816
3821
|
const componentEntry = protocol === "wasi-component" ? readPortableComponentEntry(parsed) : void 0;
|
|
@@ -3821,7 +3826,7 @@ function parseServiceAppManifest(raw, options = {}) {
|
|
|
3821
3826
|
return {
|
|
3822
3827
|
id,
|
|
3823
3828
|
title: readRequiredString$8(parsed, "title"),
|
|
3824
|
-
description: readOptionalString$
|
|
3829
|
+
description: readOptionalString$13(parsed, "description"),
|
|
3825
3830
|
enabled: readOptionalBoolean$2(parsed, "enabled") ?? true,
|
|
3826
3831
|
protocol,
|
|
3827
3832
|
command: launch?.command,
|
|
@@ -3836,14 +3841,14 @@ function parseServiceAppManifest(raw, options = {}) {
|
|
|
3836
3841
|
}
|
|
3837
3842
|
function readServiceAppProvides(value) {
|
|
3838
3843
|
if (value === void 0) return void 0;
|
|
3839
|
-
if (!isRecord$
|
|
3844
|
+
if (!isRecord$19(value)) throw new Error("service app provides must be an object.");
|
|
3840
3845
|
if (value.capabilities === void 0) return void 0;
|
|
3841
3846
|
if (!Array.isArray(value.capabilities)) throw new Error("service app provides.capabilities must be an array.");
|
|
3842
3847
|
const seen = /* @__PURE__ */ new Set();
|
|
3843
3848
|
const capabilities = value.capabilities.map((entry, index) => {
|
|
3844
|
-
if (!isRecord$
|
|
3849
|
+
if (!isRecord$19(entry)) throw new Error(`service app provides.capabilities[${index}] must be an object.`);
|
|
3845
3850
|
const id = readRequirementIdentifier(entry, "id", `provides.capabilities[${index}].id`);
|
|
3846
|
-
const version = readOptionalString$
|
|
3851
|
+
const version = readOptionalString$13(entry, "version");
|
|
3847
3852
|
if (!version) throw new Error(`service app provides.capabilities[${index}].version is required.`);
|
|
3848
3853
|
const key = `${id}@${version}`;
|
|
3849
3854
|
if (seen.has(key)) throw new Error(`service app provides.capabilities contains duplicate ${key}.`);
|
|
@@ -3869,7 +3874,7 @@ function readCapabilityResourceTypes(value, index) {
|
|
|
3869
3874
|
}
|
|
3870
3875
|
function readServiceAppRequirements(value, protocol) {
|
|
3871
3876
|
if (value === void 0) return void 0;
|
|
3872
|
-
if (!isRecord$
|
|
3877
|
+
if (!isRecord$19(value)) throw new Error("service app requires must be an object.");
|
|
3873
3878
|
const capabilities = readCapabilityRequirements(value.capabilities, protocol);
|
|
3874
3879
|
const resources = readResourceRequirements(value.resources);
|
|
3875
3880
|
const modelSlots = readModelCapabilitySlots(value.modelSlots);
|
|
@@ -3893,7 +3898,7 @@ function readCapabilitySlots(value, name, hasModelLimits) {
|
|
|
3893
3898
|
const seen = /* @__PURE__ */ new Set();
|
|
3894
3899
|
return value.map((entry, index) => {
|
|
3895
3900
|
const field = `requires.${name}[${index}]`;
|
|
3896
|
-
if (!isRecord$
|
|
3901
|
+
if (!isRecord$19(entry)) throw new Error(`service app ${field} must be an object.`);
|
|
3897
3902
|
const allowed = hasModelLimits ? [
|
|
3898
3903
|
"id",
|
|
3899
3904
|
"title",
|
|
@@ -3935,9 +3940,9 @@ function readCapabilityRequirements(value, protocol) {
|
|
|
3935
3940
|
if (!Array.isArray(value)) throw new Error("service app requires.capabilities must be an array.");
|
|
3936
3941
|
const seen = /* @__PURE__ */ new Set();
|
|
3937
3942
|
return value.map((entry, index) => {
|
|
3938
|
-
if (!isRecord$
|
|
3943
|
+
if (!isRecord$19(entry)) throw new Error(`service app requires.capabilities[${index}] must be an object.`);
|
|
3939
3944
|
const id = readRequirementIdentifier(entry, "id", `requires.capabilities[${index}].id`);
|
|
3940
|
-
const version = readOptionalString$
|
|
3945
|
+
const version = readOptionalString$13(entry, "version");
|
|
3941
3946
|
const key = `${id}@${version ?? "*"}`;
|
|
3942
3947
|
if (seen.has(key)) throw new Error(`service app requires.capabilities contains duplicate ${key}.`);
|
|
3943
3948
|
seen.add(key);
|
|
@@ -3950,19 +3955,19 @@ function readCapabilityRequirements(value, protocol) {
|
|
|
3950
3955
|
version,
|
|
3951
3956
|
provider,
|
|
3952
3957
|
wit,
|
|
3953
|
-
title: readOptionalString$
|
|
3954
|
-
description: readOptionalString$
|
|
3958
|
+
title: readOptionalString$13(entry, "title"),
|
|
3959
|
+
description: readOptionalString$13(entry, "description"),
|
|
3955
3960
|
remediation: readExternalRemediation(entry.remediation, `requires.capabilities[${index}]`)
|
|
3956
3961
|
};
|
|
3957
3962
|
});
|
|
3958
3963
|
}
|
|
3959
3964
|
function readWitContract(value, fieldName, role) {
|
|
3960
3965
|
if (value === void 0) return void 0;
|
|
3961
|
-
if (!isRecord$
|
|
3962
|
-
const packageName = readOptionalString$
|
|
3966
|
+
if (!isRecord$19(value)) throw new Error(`service app ${fieldName} must be an object.`);
|
|
3967
|
+
const packageName = readOptionalString$13(value, "package");
|
|
3963
3968
|
if (!packageName || !WIT_PACKAGE_PATTERN.test(packageName)) throw new Error(`service app ${fieldName}.package must be a WIT package identifier.`);
|
|
3964
3969
|
const interfaceName = readRequirementIdentifier(value, "interface", `${fieldName}.interface`);
|
|
3965
|
-
const version = readOptionalString$
|
|
3970
|
+
const version = readOptionalString$13(value, "version");
|
|
3966
3971
|
if (!version) throw new Error(`service app ${fieldName}.version is required.`);
|
|
3967
3972
|
if (role === "provider" && !SEMVER_VERSION_PATTERN.test(version)) throw new Error(`service app ${fieldName}.version must be an exact semver version for a Provider.`);
|
|
3968
3973
|
if (role === "consumer" && !isSemverRange(version)) throw new Error(`service app ${fieldName}.version must be a semver range for a Consumer.`);
|
|
@@ -3985,7 +3990,7 @@ function readResourceRequirements(value) {
|
|
|
3985
3990
|
if (!Array.isArray(value)) throw new Error("service app requires.resources must be an array.");
|
|
3986
3991
|
const seen = /* @__PURE__ */ new Set();
|
|
3987
3992
|
return value.map((entry, index) => {
|
|
3988
|
-
if (!isRecord$
|
|
3993
|
+
if (!isRecord$19(entry)) throw new Error(`service app requires.resources[${index}] must be an object.`);
|
|
3989
3994
|
const binding = readRequirementIdentifier(entry, "binding", `requires.resources[${index}].binding`);
|
|
3990
3995
|
if (seen.has(binding)) throw new Error(`service app requires.resources contains duplicate binding ${binding}.`);
|
|
3991
3996
|
seen.add(binding);
|
|
@@ -3993,21 +3998,21 @@ function readResourceRequirements(value) {
|
|
|
3993
3998
|
binding,
|
|
3994
3999
|
type: readRequirementIdentifier(entry, "type", `requires.resources[${index}].type`),
|
|
3995
4000
|
required: readOptionalBoolean$2(entry, "required") ?? true,
|
|
3996
|
-
title: readOptionalString$
|
|
3997
|
-
description: readOptionalString$
|
|
4001
|
+
title: readOptionalString$13(entry, "title"),
|
|
4002
|
+
description: readOptionalString$13(entry, "description"),
|
|
3998
4003
|
remediation: readExternalRemediation(entry.remediation, `requires.resources[${index}]`)
|
|
3999
4004
|
};
|
|
4000
4005
|
});
|
|
4001
4006
|
}
|
|
4002
4007
|
function readRequirementIdentifier(record, key, fieldName) {
|
|
4003
|
-
const value = readOptionalString$
|
|
4008
|
+
const value = readOptionalString$13(record, key);
|
|
4004
4009
|
if (!value || !/^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)*$/.test(value)) throw new Error(`service app ${fieldName} must be a lowercase identifier.`);
|
|
4005
4010
|
return value;
|
|
4006
4011
|
}
|
|
4007
4012
|
function readExternalRemediation(value, fieldPrefix) {
|
|
4008
4013
|
if (value === void 0) return void 0;
|
|
4009
|
-
if (!isRecord$
|
|
4010
|
-
const summary = readOptionalString$
|
|
4014
|
+
if (!isRecord$19(value) || value.kind !== "agent-setup") throw new Error(`service app ${fieldPrefix}.remediation.kind must be agent-setup.`);
|
|
4015
|
+
const summary = readOptionalString$13(value, "summary");
|
|
4011
4016
|
if (!summary) throw new Error(`service app ${fieldPrefix}.remediation.summary is required.`);
|
|
4012
4017
|
return {
|
|
4013
4018
|
kind: "agent-setup",
|
|
@@ -4017,8 +4022,8 @@ function readExternalRemediation(value, fieldPrefix) {
|
|
|
4017
4022
|
}
|
|
4018
4023
|
function readServiceAppLifecycle(value) {
|
|
4019
4024
|
if (value === void 0) return { mode: "action" };
|
|
4020
|
-
if (!isRecord$
|
|
4021
|
-
const mode = readOptionalString$
|
|
4025
|
+
if (!isRecord$19(value)) throw new Error("service app lifecycle must be an object.");
|
|
4026
|
+
const mode = readOptionalString$13(value, "mode") ?? "action";
|
|
4022
4027
|
if (mode === "action") return { mode };
|
|
4023
4028
|
if (mode === "provider") return { mode };
|
|
4024
4029
|
if (mode !== "resident") throw new Error("service app lifecycle.mode must be action, resident or provider.");
|
|
@@ -4039,13 +4044,13 @@ function readProviderIds(value) {
|
|
|
4039
4044
|
return Array.from(new Set(providerIds));
|
|
4040
4045
|
}
|
|
4041
4046
|
function readServiceAppId(record, key, fieldName) {
|
|
4042
|
-
const value = readOptionalString$
|
|
4047
|
+
const value = readOptionalString$13(record, key);
|
|
4043
4048
|
if (!value || !SERVICE_APP_ID_PATTERN$1.test(value)) throw new Error(`service app ${fieldName} must be a kebab-case service app id.`);
|
|
4044
4049
|
return value;
|
|
4045
4050
|
}
|
|
4046
4051
|
function readPortableComponentEntry(record) {
|
|
4047
4052
|
const component = record.component;
|
|
4048
|
-
if (!isRecord$
|
|
4053
|
+
if (!isRecord$19(component)) throw new Error("service app component is required for wasi-component.");
|
|
4049
4054
|
const entry = readRequiredString$8(component, "entry").replace(/\\/g, "/");
|
|
4050
4055
|
if (path.isAbsolute(entry) || entry.includes("\0") || entry.split("/").includes("..")) throw new Error("service app component.entry must be a package-relative path.");
|
|
4051
4056
|
if (!entry.endsWith(".wasm")) throw new Error("service app component.entry must reference a .wasm file.");
|
|
@@ -4053,22 +4058,22 @@ function readPortableComponentEntry(record) {
|
|
|
4053
4058
|
}
|
|
4054
4059
|
function readManifestActions(value) {
|
|
4055
4060
|
if (value === void 0) throw new Error("service app actions are required.");
|
|
4056
|
-
if (!isRecord$
|
|
4061
|
+
if (!isRecord$19(value)) throw new Error("service app actions must be an object.");
|
|
4057
4062
|
if (Object.keys(value).length === 0) throw new Error("service app actions cannot be empty.");
|
|
4058
4063
|
const actions = {};
|
|
4059
4064
|
for (const [name, action] of Object.entries(value)) {
|
|
4060
4065
|
if (!name.trim()) throw new Error("service app action name cannot be empty.");
|
|
4061
|
-
if (!isRecord$
|
|
4062
|
-
const risk = readOptionalString$
|
|
4066
|
+
if (!isRecord$19(action)) throw new Error(`service app action ${name} must be an object.`);
|
|
4067
|
+
const risk = readOptionalString$13(action, "risk");
|
|
4063
4068
|
if (risk !== void 0 && !SERVICE_ACTION_RISKS.has(risk)) throw new Error(`service app action ${name} has invalid risk.`);
|
|
4064
4069
|
const inputSchema = action.inputSchema;
|
|
4065
|
-
if (inputSchema !== void 0 && !isRecord$
|
|
4070
|
+
if (inputSchema !== void 0 && !isRecord$19(inputSchema)) throw new Error(`service app action ${name} inputSchema must be an object.`);
|
|
4066
4071
|
const timeoutMs = readOptionalNumber$1(action, "timeoutMs");
|
|
4067
4072
|
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.`);
|
|
4068
4073
|
actions[name] = {
|
|
4069
4074
|
risk,
|
|
4070
|
-
title: readOptionalString$
|
|
4071
|
-
description: readOptionalString$
|
|
4075
|
+
title: readOptionalString$13(action, "title"),
|
|
4076
|
+
description: readOptionalString$13(action, "description"),
|
|
4072
4077
|
inputSchema,
|
|
4073
4078
|
timeoutMs
|
|
4074
4079
|
};
|
|
@@ -4076,7 +4081,7 @@ function readManifestActions(value) {
|
|
|
4076
4081
|
return actions;
|
|
4077
4082
|
}
|
|
4078
4083
|
function readRequiredString$8(record, key) {
|
|
4079
|
-
const value = readOptionalString$
|
|
4084
|
+
const value = readOptionalString$13(record, key);
|
|
4080
4085
|
if (!value) throw new Error(`service app ${key} is required.`);
|
|
4081
4086
|
return value;
|
|
4082
4087
|
}
|
|
@@ -4084,7 +4089,7 @@ function readRequiredBoolean(record, key) {
|
|
|
4084
4089
|
if (typeof record[key] !== "boolean") throw new Error(`service app ${key} is required and must be a boolean.`);
|
|
4085
4090
|
return record[key];
|
|
4086
4091
|
}
|
|
4087
|
-
function readOptionalString$
|
|
4092
|
+
function readOptionalString$13(record, key) {
|
|
4088
4093
|
return typeof record[key] === "string" && record[key].trim() ? record[key].trim() : void 0;
|
|
4089
4094
|
}
|
|
4090
4095
|
function readOptionalBoolean$2(record, key) {
|
|
@@ -4097,7 +4102,7 @@ function readOptionalNumber$1(record, key) {
|
|
|
4097
4102
|
if (typeof record[key] !== "number") throw new Error(`service app ${key} must be number.`);
|
|
4098
4103
|
return record[key];
|
|
4099
4104
|
}
|
|
4100
|
-
function isRecord$
|
|
4105
|
+
function isRecord$19(value) {
|
|
4101
4106
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
4102
4107
|
}
|
|
4103
4108
|
//#endregion
|
|
@@ -4537,6 +4542,73 @@ var AppPackageDependencyCoordinator = class {
|
|
|
4537
4542
|
};
|
|
4538
4543
|
};
|
|
4539
4544
|
//#endregion
|
|
4545
|
+
//#region src/services/app-package-document-access.service.ts
|
|
4546
|
+
var AppPackageDocumentAccessService = class {
|
|
4547
|
+
constructor(params) {
|
|
4548
|
+
this.params = params;
|
|
4549
|
+
}
|
|
4550
|
+
inspect = async (appId) => await this.params.installationService.withAppOperation(appId, async () => {
|
|
4551
|
+
try {
|
|
4552
|
+
return await this.params.grantService.summarize(appId);
|
|
4553
|
+
} catch (error) {
|
|
4554
|
+
throw this.toError(error);
|
|
4555
|
+
}
|
|
4556
|
+
});
|
|
4557
|
+
assert = async (appId, scopeId, requestedMode) => {
|
|
4558
|
+
const scope = (await this.inspect(appId)).documentAccess.find((entry) => entry.id === scopeId);
|
|
4559
|
+
if (!scope) throw new AppPackageError("DOCUMENT_SCOPE_NOT_DECLARED", `App ${appId} does not declare document scope ${scopeId}.`);
|
|
4560
|
+
if (!scope.granted) throw new AppPackageError("DOCUMENT_SCOPE_NOT_GRANTED", `App ${appId} requires document scope ${scopeId}. Grant it and retry the action.`);
|
|
4561
|
+
if (scope.status === "unavailable") throw new AppPackageError("DOCUMENT_SCOPE_UNAVAILABLE", `App ${appId} document scope ${scopeId} is unavailable. Replace or revoke it.`);
|
|
4562
|
+
if (requestedMode === "read-write" && scope.effectiveMode !== "read-write") throw new AppPackageError("DOCUMENT_SCOPE_MODE_INSUFFICIENT", `App ${appId} document scope ${scopeId} needs read-write access.`);
|
|
4563
|
+
};
|
|
4564
|
+
grant = async (appId, input) => await this.mutate(appId, input.scopeId, async () => await this.params.grantService.grantDocumentScope({
|
|
4565
|
+
appId,
|
|
4566
|
+
...input
|
|
4567
|
+
}));
|
|
4568
|
+
revoke = async (appId, scopeId) => await this.mutate(appId, scopeId, async () => {
|
|
4569
|
+
if (!(await this.params.grantService.summarize(appId)).documentAccess.some((scope) => scope.id === scopeId)) throw new AppPackageError("DOCUMENT_SCOPE_NOT_DECLARED", `App ${appId} does not declare document scope ${scopeId}.`);
|
|
4570
|
+
return await this.params.grantService.revokeDocumentScope({
|
|
4571
|
+
appId,
|
|
4572
|
+
scopeId
|
|
4573
|
+
});
|
|
4574
|
+
});
|
|
4575
|
+
mutate = async (appId, scopeId, mutation) => await this.params.installationService.withAppOperation(appId, async () => {
|
|
4576
|
+
const app = await this.params.getPackage(appId);
|
|
4577
|
+
const previousGrant = (await this.params.registryService.getApp(appId))?.grants[scopeId];
|
|
4578
|
+
const sources = this.toComponentSources(app);
|
|
4579
|
+
const runtimeHooks = this.params.getRuntimeHooks();
|
|
4580
|
+
const rollbackRuntime = app.enabled ? await runtimeHooks.prepareCapabilityChange(sources) : async () => void 0;
|
|
4581
|
+
try {
|
|
4582
|
+
const result = await mutation();
|
|
4583
|
+
if (app.enabled) await runtimeHooks.afterCapabilityChange(sources);
|
|
4584
|
+
return result;
|
|
4585
|
+
} catch (error) {
|
|
4586
|
+
const recoveryErrors = [];
|
|
4587
|
+
try {
|
|
4588
|
+
await this.params.registryService.restoreDocumentGrant(appId, scopeId, previousGrant);
|
|
4589
|
+
} catch (recoveryError) {
|
|
4590
|
+
recoveryErrors.push(recoveryError);
|
|
4591
|
+
}
|
|
4592
|
+
try {
|
|
4593
|
+
await rollbackRuntime();
|
|
4594
|
+
} catch (recoveryError) {
|
|
4595
|
+
recoveryErrors.push(recoveryError);
|
|
4596
|
+
}
|
|
4597
|
+
if (recoveryErrors.length > 0) throw new AggregateError([error, ...recoveryErrors], `App ${appId} document scope ${scopeId} mutation failed and recovery was incomplete.`);
|
|
4598
|
+
throw this.toError(error);
|
|
4599
|
+
}
|
|
4600
|
+
});
|
|
4601
|
+
toComponentSources = (app) => app.components.map((component) => ({ ...component }));
|
|
4602
|
+
toError = (error) => {
|
|
4603
|
+
if (error instanceof AppPackageError) return error;
|
|
4604
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
4605
|
+
if (message.includes("未声明 documentAccess scope")) return new AppPackageError("DOCUMENT_SCOPE_NOT_DECLARED", message);
|
|
4606
|
+
if (message.includes("只声明了 read")) return new AppPackageError("DOCUMENT_SCOPE_MODE_INSUFFICIENT", message);
|
|
4607
|
+
if (message.includes("不是可用目录")) return new AppPackageError("DOCUMENT_SCOPE_UNAVAILABLE", message);
|
|
4608
|
+
return new AppPackageError("DOCUMENT_SCOPE_MUTATION_FAILED", message);
|
|
4609
|
+
};
|
|
4610
|
+
};
|
|
4611
|
+
//#endregion
|
|
4540
4612
|
//#region src/services/app-package-host-target.service.ts
|
|
4541
4613
|
var AppPackageHostTargetService = class {
|
|
4542
4614
|
platformTargetService = new AppPlatformTargetService();
|
|
@@ -4607,6 +4679,8 @@ const EMPTY_APP_PACKAGE_RUNTIME_HOOKS = {
|
|
|
4607
4679
|
assertCanActivate: async () => void 0,
|
|
4608
4680
|
afterActivate: async () => void 0,
|
|
4609
4681
|
beforeDeactivate: async () => void 0,
|
|
4682
|
+
prepareCapabilityChange: async () => async () => void 0,
|
|
4683
|
+
afterCapabilityChange: async () => void 0,
|
|
4610
4684
|
beforeUninstall: async () => void 0
|
|
4611
4685
|
};
|
|
4612
4686
|
var AppPackageRuntimeActivationService = class {
|
|
@@ -4637,6 +4711,8 @@ var AppPackageManager = class {
|
|
|
4637
4711
|
dependencyCoordinator;
|
|
4638
4712
|
runtimeActivationService = new AppPackageRuntimeActivationService();
|
|
4639
4713
|
registryService;
|
|
4714
|
+
grantService;
|
|
4715
|
+
documentAccessService;
|
|
4640
4716
|
readinessManager;
|
|
4641
4717
|
runtimeHooks = EMPTY_APP_PACKAGE_RUNTIME_HOOKS;
|
|
4642
4718
|
builtInBootstrapPromise;
|
|
@@ -4646,6 +4722,14 @@ var AppPackageManager = class {
|
|
|
4646
4722
|
this.appHomeService = new AppHomeService(params.appHomeDirectory);
|
|
4647
4723
|
this.installationService = new AppInstallationService(this.appHomeService);
|
|
4648
4724
|
this.registryService = new AppRegistryService(this.appHomeService);
|
|
4725
|
+
this.grantService = new AppGrantService(this.registryService, this.manifestService);
|
|
4726
|
+
this.documentAccessService = new AppPackageDocumentAccessService({
|
|
4727
|
+
grantService: this.grantService,
|
|
4728
|
+
installationService: this.installationService,
|
|
4729
|
+
registryService: this.registryService,
|
|
4730
|
+
getPackage: this.getPackage,
|
|
4731
|
+
getRuntimeHooks: () => this.runtimeHooks
|
|
4732
|
+
});
|
|
4649
4733
|
this.dependencyCoordinator = new AppPackageDependencyCoordinator({
|
|
4650
4734
|
installationService: this.installationService,
|
|
4651
4735
|
registryService: this.registryService,
|
|
@@ -4658,6 +4742,7 @@ var AppPackageManager = class {
|
|
|
4658
4742
|
presentationService: this.presentationService,
|
|
4659
4743
|
dependencyCoordinator: this.dependencyCoordinator,
|
|
4660
4744
|
registryService: this.registryService,
|
|
4745
|
+
grantService: this.grantService,
|
|
4661
4746
|
productVersion: params.productVersion,
|
|
4662
4747
|
getSecretConfig: params.getSecretConfig,
|
|
4663
4748
|
secretConfigPath: params.secretConfigPath,
|
|
@@ -4718,6 +4803,12 @@ var AppPackageManager = class {
|
|
|
4718
4803
|
await this.registryService.unbindSecret(appId, slotId);
|
|
4719
4804
|
return await this.inspectSecrets(appId);
|
|
4720
4805
|
});
|
|
4806
|
+
inspectDocumentAccess = async (appId) => await this.documentAccessService.inspect(appId);
|
|
4807
|
+
assertDocumentAccess = async (appId, scopeId, requestedMode) => {
|
|
4808
|
+
await this.documentAccessService.assert(appId, scopeId, requestedMode);
|
|
4809
|
+
};
|
|
4810
|
+
grantDocumentAccess = async (appId, input) => await this.documentAccessService.grant(appId, input);
|
|
4811
|
+
revokeDocumentAccess = async (appId, scopeId) => await this.documentAccessService.revoke(appId, scopeId);
|
|
4721
4812
|
listActiveComponentSources = async () => (await this.listActiveComponentSourcesWithDiagnostics()).sources;
|
|
4722
4813
|
listActiveComponentSourcesWithDiagnostics = async () => await this.dependencyCoordinator.listActiveComponentSourcesWithDiagnostics();
|
|
4723
4814
|
listOperations = async () => await this.operationManager.list();
|
|
@@ -5856,14 +5947,14 @@ var ExtensionLifecycleService = class {
|
|
|
5856
5947
|
};
|
|
5857
5948
|
//#endregion
|
|
5858
5949
|
//#region src/features/extension-runtime/utils/extension-runtime-payload.utils.ts
|
|
5859
|
-
function readString$
|
|
5950
|
+
function readString$11(value) {
|
|
5860
5951
|
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
5861
5952
|
}
|
|
5862
5953
|
function readRecord$2(value) {
|
|
5863
5954
|
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
5864
5955
|
}
|
|
5865
5956
|
function readRequiredString$7(value, name) {
|
|
5866
|
-
const trimmed = readString$
|
|
5957
|
+
const trimmed = readString$11(value);
|
|
5867
5958
|
if (!trimmed) throw new Error(`${name} is required`);
|
|
5868
5959
|
return trimmed;
|
|
5869
5960
|
}
|
|
@@ -5878,16 +5969,16 @@ function readTextContent(value) {
|
|
|
5878
5969
|
function readInboundAttachments(value) {
|
|
5879
5970
|
if (!Array.isArray(value)) return [];
|
|
5880
5971
|
return value.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))).map((entry) => ({
|
|
5881
|
-
...readString$
|
|
5882
|
-
...readString$
|
|
5883
|
-
...readString$
|
|
5884
|
-
...readString$
|
|
5885
|
-
...readString$
|
|
5886
|
-
...readString$
|
|
5972
|
+
...readString$11(entry.id) ? { id: readString$11(entry.id) } : {},
|
|
5973
|
+
...readString$11(entry.name) ? { name: readString$11(entry.name) } : {},
|
|
5974
|
+
...readString$11(entry.path) ? { path: readString$11(entry.path) } : {},
|
|
5975
|
+
...readString$11(entry.url) ? { url: readString$11(entry.url) } : {},
|
|
5976
|
+
...readString$11(entry.assetUri) ? { assetUri: readString$11(entry.assetUri) } : {},
|
|
5977
|
+
...readString$11(entry.mimeType) ? { mimeType: readString$11(entry.mimeType) } : {},
|
|
5887
5978
|
...readOptionalNumber(entry.size) !== void 0 ? { size: readOptionalNumber(entry.size) } : {},
|
|
5888
|
-
...readString$
|
|
5979
|
+
...readString$11(entry.source) ? { source: readString$11(entry.source) } : {},
|
|
5889
5980
|
...entry.status === "ready" || entry.status === "remote-only" ? { status: entry.status } : {},
|
|
5890
|
-
...readString$
|
|
5981
|
+
...readString$11(entry.errorCode) ? { errorCode: readString$11(entry.errorCode) } : {}
|
|
5891
5982
|
}));
|
|
5892
5983
|
}
|
|
5893
5984
|
function toInboundMessage(value) {
|
|
@@ -5919,7 +6010,7 @@ function normalizeAuthPollResult(value) {
|
|
|
5919
6010
|
return {
|
|
5920
6011
|
channel: readRequiredString$7(record.channel, "channel"),
|
|
5921
6012
|
status: record.status,
|
|
5922
|
-
message: readString$
|
|
6013
|
+
message: readString$11(record.message),
|
|
5923
6014
|
nextPollMs: readOptionalNumber(record.nextPollMs),
|
|
5924
6015
|
accountId: typeof record.accountId === "string" ? record.accountId : null,
|
|
5925
6016
|
notes: Array.isArray(record.notes) ? record.notes.filter((note) => typeof note === "string") : [],
|
|
@@ -5953,10 +6044,10 @@ var ExtensionIngressDiagnosticsService = class {
|
|
|
5953
6044
|
event: readRequiredString$7(payload.event, "event"),
|
|
5954
6045
|
component: readRequiredString$7(payload.component, "component"),
|
|
5955
6046
|
outcome: readRequiredString$7(payload.outcome, "outcome"),
|
|
5956
|
-
correlationId: readString$
|
|
5957
|
-
parentCorrelationId: readString$
|
|
5958
|
-
reasonCode: readString$
|
|
5959
|
-
providerCode: readString$
|
|
6047
|
+
correlationId: readString$11(payload.correlationId),
|
|
6048
|
+
parentCorrelationId: readString$11(payload.parentCorrelationId),
|
|
6049
|
+
reasonCode: readString$11(payload.reasonCode),
|
|
6050
|
+
providerCode: readString$11(payload.providerCode),
|
|
5960
6051
|
durationMs: readOptionalNumber(payload.durationMs),
|
|
5961
6052
|
attempt: readOptionalNumber(payload.attempt),
|
|
5962
6053
|
facts: {
|
|
@@ -6100,12 +6191,12 @@ var ExtensionAuthLeaseService = class {
|
|
|
6100
6191
|
if (kind === "channel.auth.poll") {
|
|
6101
6192
|
const sessionId = readRequiredString$7(payload.sessionId, "sessionId");
|
|
6102
6193
|
const result = value ? readRecord$2(value) : null;
|
|
6103
|
-
const status = result ? readString$
|
|
6194
|
+
const status = result ? readString$11(result.status) : null;
|
|
6104
6195
|
if (status === "authorized") await this.retainHandoff(extensionId, channelId, generation);
|
|
6105
6196
|
if (!result || status === "authorized" || status === "expired" || status === "error") this.releaseSession(extensionId, sessionId);
|
|
6106
6197
|
return;
|
|
6107
6198
|
}
|
|
6108
|
-
if (kind === "channel.auth.connect" && readString$
|
|
6199
|
+
if (kind === "channel.auth.connect" && readString$11(readRecord$2(value).status) === "authorized") {
|
|
6109
6200
|
await this.retainHandoff(extensionId, channelId, generation);
|
|
6110
6201
|
return;
|
|
6111
6202
|
}
|
|
@@ -6224,7 +6315,7 @@ const BUILTIN_EXTENSION_PACKAGES = [
|
|
|
6224
6315
|
"@nextclaw/channel-extension-weixin"
|
|
6225
6316
|
];
|
|
6226
6317
|
const runtimeRequire = createRequire(import.meta.url);
|
|
6227
|
-
function readString$
|
|
6318
|
+
function readString$10(value) {
|
|
6228
6319
|
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
6229
6320
|
}
|
|
6230
6321
|
function readStringArray$1(value) {
|
|
@@ -6273,16 +6364,16 @@ function toManifest(value, rootDir) {
|
|
|
6273
6364
|
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("extension manifest must be an object");
|
|
6274
6365
|
const record = value;
|
|
6275
6366
|
const server = readRecord$1(record.server);
|
|
6276
|
-
const id = readString$
|
|
6277
|
-
const command = readString$
|
|
6367
|
+
const id = readString$10(record.id);
|
|
6368
|
+
const command = readString$10(server.command);
|
|
6278
6369
|
if (!id) throw new Error("extension manifest id is required");
|
|
6279
6370
|
if (server.type !== "stdio") throw new Error("extension server.type must be stdio");
|
|
6280
6371
|
if (!command) throw new Error("extension server.command is required");
|
|
6281
6372
|
return {
|
|
6282
6373
|
id,
|
|
6283
6374
|
rootDir,
|
|
6284
|
-
...readString$
|
|
6285
|
-
...readString$
|
|
6375
|
+
...readString$10(record.name) ? { name: readString$10(record.name) } : {},
|
|
6376
|
+
...readString$10(record.version) ? { version: readString$10(record.version) } : {},
|
|
6286
6377
|
server: {
|
|
6287
6378
|
type: "stdio",
|
|
6288
6379
|
command,
|
|
@@ -6478,13 +6569,13 @@ var ExtensionObservationRuntimeService = class {
|
|
|
6478
6569
|
eventId,
|
|
6479
6570
|
eventType,
|
|
6480
6571
|
occurredAt,
|
|
6481
|
-
observedAt: readString$
|
|
6482
|
-
...readString$
|
|
6483
|
-
...readString$
|
|
6572
|
+
observedAt: readString$11(event.observedAt) ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
6573
|
+
...readString$11(event.cursor) ? { cursor: readString$11(event.cursor) } : {},
|
|
6574
|
+
...readString$11(event.dedupeKey) ? { dedupeKey: readString$11(event.dedupeKey) } : {},
|
|
6484
6575
|
payload: event.payload,
|
|
6485
6576
|
...Array.isArray(event.sourceRefs) && event.sourceRefs.every((value) => typeof value === "string") ? { sourceRefs: event.sourceRefs } : {},
|
|
6486
|
-
...readString$
|
|
6487
|
-
...readString$
|
|
6577
|
+
...readString$11(event.causationId) ? { causationId: readString$11(event.causationId) } : {},
|
|
6578
|
+
...readString$11(event.correlationId) ? { correlationId: readString$11(event.correlationId) } : {}
|
|
6488
6579
|
}
|
|
6489
6580
|
}) ?? { accepted: false };
|
|
6490
6581
|
};
|
|
@@ -6569,13 +6660,13 @@ function stableStringify(value) {
|
|
|
6569
6660
|
}
|
|
6570
6661
|
function sortValue(value) {
|
|
6571
6662
|
if (Array.isArray(value)) return value.map(sortValue);
|
|
6572
|
-
if (!isRecord$
|
|
6663
|
+
if (!isRecord$18(value)) return value;
|
|
6573
6664
|
return Object.fromEntries(Object.keys(value).sort().map((key) => [key, sortValue(value[key])]));
|
|
6574
6665
|
}
|
|
6575
6666
|
function normalizeJsonValue(value) {
|
|
6576
6667
|
if (value === null || typeof value === "string" || typeof value === "boolean" || typeof value === "number" && Number.isFinite(value)) return value;
|
|
6577
6668
|
if (Array.isArray(value)) return value.map(normalizeJsonValue);
|
|
6578
|
-
if (isRecord$
|
|
6669
|
+
if (isRecord$18(value)) return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, normalizeJsonValue(item)]));
|
|
6579
6670
|
throw new Error("Capability grant target must be JSON-compatible.");
|
|
6580
6671
|
}
|
|
6581
6672
|
function requireString$1(value, field) {
|
|
@@ -6583,13 +6674,16 @@ function requireString$1(value, field) {
|
|
|
6583
6674
|
if (!normalized) throw new Error(`Capability grant ${field} is required.`);
|
|
6584
6675
|
return normalized;
|
|
6585
6676
|
}
|
|
6586
|
-
function isRecord$
|
|
6677
|
+
function isRecord$18(value) {
|
|
6587
6678
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
6588
6679
|
}
|
|
6589
6680
|
//#endregion
|
|
6590
6681
|
//#region src/utils/service-app-error.utils.ts
|
|
6591
6682
|
const SERVICE_APP_ERROR_CODES = new Set([
|
|
6592
6683
|
"AUTHORIZATION_REQUIRED",
|
|
6684
|
+
"DOCUMENT_SCOPE_NOT_GRANTED",
|
|
6685
|
+
"DOCUMENT_SCOPE_MODE_INSUFFICIENT",
|
|
6686
|
+
"DOCUMENT_SCOPE_UNAVAILABLE",
|
|
6593
6687
|
"SERVICE_APP_ACTION_NOT_DECLARED",
|
|
6594
6688
|
"SERVICE_APP_ACTION_NOT_FOUND",
|
|
6595
6689
|
"SERVICE_APP_INVALID_ACTION",
|
|
@@ -7063,10 +7157,10 @@ function toGrantRecord(grants) {
|
|
|
7063
7157
|
}));
|
|
7064
7158
|
}
|
|
7065
7159
|
function normalizeStoreData(value) {
|
|
7066
|
-
if (!isRecord$
|
|
7160
|
+
if (!isRecord$17(value) || value.version !== 1 || !isRecord$17(value.grants)) throw new Error("Capability grant store is invalid.");
|
|
7067
7161
|
const grants = [];
|
|
7068
7162
|
for (const grant of Object.values(value.grants)) {
|
|
7069
|
-
if (!isRecord$
|
|
7163
|
+
if (!isRecord$17(grant) || !isRecord$17(grant.subject) || !isRecord$17(grant.resource) || !Array.isArray(grant.access) || !grant.access.every((item) => typeof item === "string") || typeof grant.declarationFingerprint !== "string" || typeof grant.grantedAt !== "string") throw new Error("Capability grant store contains an invalid grant.");
|
|
7070
7164
|
const request = normalizeCapabilityGrantRequest({
|
|
7071
7165
|
subject: {
|
|
7072
7166
|
type: String(grant.subject.type ?? ""),
|
|
@@ -7090,11 +7184,11 @@ function normalizeStoreData(value) {
|
|
|
7090
7184
|
grants: toGrantRecord(grants)
|
|
7091
7185
|
};
|
|
7092
7186
|
}
|
|
7093
|
-
function isRecord$
|
|
7187
|
+
function isRecord$17(value) {
|
|
7094
7188
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
7095
7189
|
}
|
|
7096
7190
|
function isMissingFileError$1(error) {
|
|
7097
|
-
return isRecord$
|
|
7191
|
+
return isRecord$17(error) && error.code === "ENOENT";
|
|
7098
7192
|
}
|
|
7099
7193
|
//#endregion
|
|
7100
7194
|
//#region src/features/capability-grants/managers/capability-grant.manager.ts
|
|
@@ -7598,10 +7692,10 @@ var DesktopNodeReplService = class {
|
|
|
7598
7692
|
}
|
|
7599
7693
|
async handleWorkerMessage(key, worker, value) {
|
|
7600
7694
|
const message = asRecord$1(value);
|
|
7601
|
-
const executionId = readString$
|
|
7695
|
+
const executionId = readString$9(message.executionId);
|
|
7602
7696
|
if (!executionId || !worker.pending.has(executionId)) return;
|
|
7603
7697
|
if (message.type === "desktop.request") {
|
|
7604
|
-
const requestId = readString$
|
|
7698
|
+
const requestId = readString$9(message.requestId);
|
|
7605
7699
|
if (!requestId) return;
|
|
7606
7700
|
try {
|
|
7607
7701
|
const result = await this.invokeDesktop(worker.pending.get(executionId).caller, message.operation, message.args);
|
|
@@ -7632,7 +7726,7 @@ var DesktopNodeReplService = class {
|
|
|
7632
7726
|
}
|
|
7633
7727
|
if (message.type === "error") {
|
|
7634
7728
|
const error = asRecord$1(message.error);
|
|
7635
|
-
this.reject(worker, executionId, replError(readString$
|
|
7729
|
+
this.reject(worker, executionId, replError(readString$9(error.code) ?? "repl_execution_failed", readString$9(error.message) ?? "node_repl execution failed."));
|
|
7636
7730
|
return;
|
|
7637
7731
|
}
|
|
7638
7732
|
this.stopWorker(key, worker);
|
|
@@ -7650,23 +7744,23 @@ var DesktopNodeReplService = class {
|
|
|
7650
7744
|
});
|
|
7651
7745
|
case "setValue": return await this.sessionState.setValue(caller, {
|
|
7652
7746
|
target,
|
|
7653
|
-
stateId: requiredString$
|
|
7747
|
+
stateId: requiredString$2(args.stateId, "stateId"),
|
|
7654
7748
|
elementIndex: readElementIndex(args.element),
|
|
7655
7749
|
value: requiredValue$1(args.value ?? args.text, "value")
|
|
7656
7750
|
});
|
|
7657
7751
|
case "click": return await this.sessionState.click(caller, {
|
|
7658
7752
|
target,
|
|
7659
|
-
stateId: requiredString$
|
|
7753
|
+
stateId: requiredString$2(args.stateId, "stateId"),
|
|
7660
7754
|
...args.coordinate === void 0 ? { elementIndex: readElementIndex(args.element) } : { coordinate: readCoordinate(args.coordinate) }
|
|
7661
7755
|
});
|
|
7662
7756
|
case "typeText": return await this.sessionState.typeText(caller, {
|
|
7663
7757
|
target,
|
|
7664
|
-
stateId: requiredString$
|
|
7758
|
+
stateId: requiredString$2(args.stateId, "stateId"),
|
|
7665
7759
|
text: requiredText(args.text, "text")
|
|
7666
7760
|
});
|
|
7667
7761
|
case "pressKey": return await this.sessionState.pressKey(caller, {
|
|
7668
7762
|
target,
|
|
7669
|
-
stateId: requiredString$
|
|
7763
|
+
stateId: requiredString$2(args.stateId, "stateId"),
|
|
7670
7764
|
key: readKeyboardKey(args.key),
|
|
7671
7765
|
...args.modifiers === void 0 ? {} : { modifiers: readKeyboardModifiers(args.modifiers) }
|
|
7672
7766
|
});
|
|
@@ -7713,7 +7807,7 @@ function pickCaller(input) {
|
|
|
7713
7807
|
};
|
|
7714
7808
|
}
|
|
7715
7809
|
function readTarget(value) {
|
|
7716
|
-
return { applicationId: requiredString$
|
|
7810
|
+
return { applicationId: requiredString$2(asRecord$1(value).applicationId, "target.applicationId") };
|
|
7717
7811
|
}
|
|
7718
7812
|
function readElementIndex(value) {
|
|
7719
7813
|
const index = asRecord$1(value).index;
|
|
@@ -7737,7 +7831,7 @@ function isFiniteNumber$1(value) {
|
|
|
7737
7831
|
function asRecord$1(value) {
|
|
7738
7832
|
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
7739
7833
|
}
|
|
7740
|
-
function requiredString$
|
|
7834
|
+
function requiredString$2(value, field) {
|
|
7741
7835
|
if (typeof value !== "string" || !value.trim()) throw replError("invalid_tool_arguments", `${field} must be a non-empty string.`);
|
|
7742
7836
|
return value.trim();
|
|
7743
7837
|
}
|
|
@@ -7769,7 +7863,7 @@ const SUPPORTED_KEY_MODIFIERS = new Set([
|
|
|
7769
7863
|
"shift"
|
|
7770
7864
|
]);
|
|
7771
7865
|
function readKeyboardKey(value) {
|
|
7772
|
-
const key = requiredString$
|
|
7866
|
+
const key = requiredString$2(value, "key");
|
|
7773
7867
|
if (!SUPPORTED_KEYS.has(key)) throw replError("invalid_tool_arguments", "key must be a supported named key or a lowercase letter/digit.");
|
|
7774
7868
|
return key;
|
|
7775
7869
|
}
|
|
@@ -7778,7 +7872,7 @@ function readKeyboardModifiers(value) {
|
|
|
7778
7872
|
if (new Set(value).size !== value.length) throw replError("invalid_tool_arguments", "modifiers cannot contain duplicates.");
|
|
7779
7873
|
return value;
|
|
7780
7874
|
}
|
|
7781
|
-
function readString$
|
|
7875
|
+
function readString$9(value) {
|
|
7782
7876
|
return typeof value === "string" && value.trim() ? value : void 0;
|
|
7783
7877
|
}
|
|
7784
7878
|
function trimOutput(value) {
|
|
@@ -8249,9 +8343,9 @@ var ExtensionDesktopRuntimeService = class {
|
|
|
8249
8343
|
method: readRequiredString$7(payload.method, "method"),
|
|
8250
8344
|
payload: readRecord$2(payload.payload),
|
|
8251
8345
|
caller: {
|
|
8252
|
-
...readString$
|
|
8253
|
-
...readString$
|
|
8254
|
-
...readString$
|
|
8346
|
+
...readString$11(caller.sessionId) ? { sessionId: readString$11(caller.sessionId) } : {},
|
|
8347
|
+
...readString$11(caller.agentRunId) ? { agentRunId: readString$11(caller.agentRunId) } : {},
|
|
8348
|
+
...readString$11(caller.subscriptionId) ? { subscriptionId: readString$11(caller.subscriptionId) } : {}
|
|
8255
8349
|
}
|
|
8256
8350
|
});
|
|
8257
8351
|
};
|
|
@@ -8392,7 +8486,7 @@ var ExtensionRuntimeService = class {
|
|
|
8392
8486
|
const desired = /* @__PURE__ */ new Map();
|
|
8393
8487
|
const channelConfig = readRecord$2(config.channels);
|
|
8394
8488
|
for (const manifest of this.manifests) for (const channel of manifest.contributes?.channels ?? []) {
|
|
8395
|
-
const channelId = readString$
|
|
8489
|
+
const channelId = readString$11(channel.id);
|
|
8396
8490
|
if (!channelId || readRecord$2(channelConfig[channelId]).enabled !== true) continue;
|
|
8397
8491
|
desired.set(this.persistentLeaseKey(manifest.id, channelId), {
|
|
8398
8492
|
manifest,
|
|
@@ -8469,15 +8563,15 @@ var ExtensionRuntimeService = class {
|
|
|
8469
8563
|
channel,
|
|
8470
8564
|
chatId,
|
|
8471
8565
|
senderId,
|
|
8472
|
-
content: readString$
|
|
8566
|
+
content: readString$11(payload.rawText) ?? readString$11(payload.commandName) ?? "",
|
|
8473
8567
|
timestamp: /* @__PURE__ */ new Date(),
|
|
8474
8568
|
attachments: [],
|
|
8475
8569
|
metadata
|
|
8476
8570
|
},
|
|
8477
|
-
forcedAgentId: readString$
|
|
8478
|
-
sessionKeyOverride: readString$
|
|
8571
|
+
forcedAgentId: readString$11(metadata.target_agent_id),
|
|
8572
|
+
sessionKeyOverride: readString$11(metadata.session_key_override)
|
|
8479
8573
|
});
|
|
8480
|
-
const rawText = readString$
|
|
8574
|
+
const rawText = readString$11(payload.rawText);
|
|
8481
8575
|
if (rawText) return await registry.executeText(rawText, {
|
|
8482
8576
|
channel,
|
|
8483
8577
|
chatId,
|
|
@@ -8510,7 +8604,7 @@ var ExtensionRuntimeService = class {
|
|
|
8510
8604
|
this.pendingRequests.delete(requestId);
|
|
8511
8605
|
pending.cleanup();
|
|
8512
8606
|
if (payload.ok === false) {
|
|
8513
|
-
const message = readString$
|
|
8607
|
+
const message = readString$11(readRecord$2(payload.error).message) ?? "Extension request failed";
|
|
8514
8608
|
pending.reject(new Error(message));
|
|
8515
8609
|
return { accepted: true };
|
|
8516
8610
|
}
|
|
@@ -8621,9 +8715,9 @@ var ExtensionRuntimeService = class {
|
|
|
8621
8715
|
};
|
|
8622
8716
|
assertAuthorized = (envelope, context) => {
|
|
8623
8717
|
const credential = this.lifecycle.authenticateCredential({
|
|
8624
|
-
extensionId: readString$
|
|
8625
|
-
generation: readString$
|
|
8626
|
-
token: readString$
|
|
8718
|
+
extensionId: readString$11(envelope.extensionId) ?? null,
|
|
8719
|
+
generation: readString$11(envelope.generation) ?? null,
|
|
8720
|
+
token: readString$11(context.token) ?? null
|
|
8627
8721
|
});
|
|
8628
8722
|
if (!credential) throw new Error("Unauthorized ingress token");
|
|
8629
8723
|
return credential;
|
|
@@ -8765,11 +8859,11 @@ var ExtensionManager = class {
|
|
|
8765
8859
|
//#endregion
|
|
8766
8860
|
//#region src/utils/model-message-vision.utils.ts
|
|
8767
8861
|
const IMAGE_OMITTED_TEXT = "[Image omitted: the selected model is not configured for vision input.]";
|
|
8768
|
-
function isRecord$
|
|
8862
|
+
function isRecord$16(value) {
|
|
8769
8863
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
8770
8864
|
}
|
|
8771
8865
|
function isImageContentPart(value) {
|
|
8772
|
-
if (!isRecord$
|
|
8866
|
+
if (!isRecord$16(value)) return false;
|
|
8773
8867
|
const type = value.type;
|
|
8774
8868
|
return type === "image_url" || type === "input_image";
|
|
8775
8869
|
}
|
|
@@ -8785,7 +8879,7 @@ function normalizeContentWithoutVision(content) {
|
|
|
8785
8879
|
};
|
|
8786
8880
|
});
|
|
8787
8881
|
if (!sawImage) return content;
|
|
8788
|
-
const textParts = parts.filter((part) => isRecord$
|
|
8882
|
+
const textParts = parts.filter((part) => isRecord$16(part) && part.type === "text" && typeof part.text === "string").map((part) => part.text.trim()).filter(Boolean);
|
|
8789
8883
|
if (textParts.length === parts.length) return textParts.join("\n\n");
|
|
8790
8884
|
return parts;
|
|
8791
8885
|
}
|
|
@@ -10344,14 +10438,14 @@ function parsePanelAppFolderManifest(raw) {
|
|
|
10344
10438
|
} catch (error) {
|
|
10345
10439
|
throw new Error(`panel-app.json is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
10346
10440
|
}
|
|
10347
|
-
if (!isRecord$
|
|
10348
|
-
const id = readOptionalString$
|
|
10441
|
+
if (!isRecord$15(parsed)) throw new Error("panel-app.json must contain an object.");
|
|
10442
|
+
const id = readOptionalString$12(parsed, "id");
|
|
10349
10443
|
if (id && !PANEL_APP_ID_PATTERN.test(id)) throw new Error("panel app id must be kebab-case.");
|
|
10350
10444
|
return {
|
|
10351
10445
|
id,
|
|
10352
10446
|
title: readRequiredString$6(parsed, "title"),
|
|
10353
|
-
description: readOptionalString$
|
|
10354
|
-
icon: readOptionalString$
|
|
10447
|
+
description: readOptionalString$12(parsed, "description"),
|
|
10448
|
+
icon: readOptionalString$12(parsed, "icon"),
|
|
10355
10449
|
entry: readRequiredString$6(parsed, "entry"),
|
|
10356
10450
|
capabilities: readStringArray(parsed.capabilities, "capabilities"),
|
|
10357
10451
|
client: readOptionalBoolean$1(parsed, "client"),
|
|
@@ -10413,11 +10507,11 @@ function parseTokenList(content) {
|
|
|
10413
10507
|
return [...new Set(content.split(/[,\s]+/).map((entry) => entry.trim()).filter(Boolean))];
|
|
10414
10508
|
}
|
|
10415
10509
|
function readRequiredString$6(record, key) {
|
|
10416
|
-
const value = readOptionalString$
|
|
10510
|
+
const value = readOptionalString$12(record, key);
|
|
10417
10511
|
if (!value) throw new Error(`panel app ${key} is required.`);
|
|
10418
10512
|
return value;
|
|
10419
10513
|
}
|
|
10420
|
-
function readOptionalString$
|
|
10514
|
+
function readOptionalString$12(record, key) {
|
|
10421
10515
|
return typeof record[key] === "string" && record[key].trim() ? record[key].trim() : void 0;
|
|
10422
10516
|
}
|
|
10423
10517
|
function readOptionalBoolean$1(record, key) {
|
|
@@ -10431,7 +10525,7 @@ function readStringArray(value, key) {
|
|
|
10431
10525
|
if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string")) throw new Error(`panel app ${key} must be a string array.`);
|
|
10432
10526
|
return [...new Set(value.map((entry) => entry.trim()).filter(Boolean))];
|
|
10433
10527
|
}
|
|
10434
|
-
function isRecord$
|
|
10528
|
+
function isRecord$15(value) {
|
|
10435
10529
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
10436
10530
|
}
|
|
10437
10531
|
function normalizeTextValue(value) {
|
|
@@ -10931,7 +11025,7 @@ const PANEL_APP_AGENT_MAX_CONTEXT_CHARS = 8e4;
|
|
|
10931
11025
|
function normalizePanelAppGenerateObjectInput(input) {
|
|
10932
11026
|
const peerId = input.peerId.trim();
|
|
10933
11027
|
const prompt = input.prompt.trim();
|
|
10934
|
-
if (!peerId || !prompt || !isRecord$
|
|
11028
|
+
if (!peerId || !prompt || !isRecord$14(input.schema)) throw new PanelAppError("PANEL_APP_AGENT_REQUEST_INVALID", "invalid generateObject request");
|
|
10935
11029
|
if (prompt.length > PANEL_APP_AGENT_MAX_PROMPT_CHARS) throw new PanelAppError("PANEL_APP_AGENT_REQUEST_INVALID", "generateObject prompt is too large");
|
|
10936
11030
|
if (stringifyJsonValue(input.context ?? null).length > PANEL_APP_AGENT_MAX_CONTEXT_CHARS) throw new PanelAppError("PANEL_APP_AGENT_REQUEST_INVALID", "generateObject context is too large");
|
|
10937
11031
|
return {
|
|
@@ -11072,7 +11166,7 @@ function readStructuredResultEvent(event, resultToolCallId) {
|
|
|
11072
11166
|
};
|
|
11073
11167
|
}
|
|
11074
11168
|
function assertToolResultContent(content) {
|
|
11075
|
-
if (!isRecord$
|
|
11169
|
+
if (!isRecord$14(content) || content.ok !== false || !isRecord$14(content.error)) return;
|
|
11076
11170
|
if (content.error.code === "invalid_tool_arguments") throw new PanelAppError("AGENT_OBJECT_RESULT_SCHEMA_INVALID", "agent object result did not match the schema");
|
|
11077
11171
|
throw new PanelAppError("AGENT_OBJECT_REQUEST_FAILED", typeof content.error.message === "string" ? content.error.message : "agent object request failed");
|
|
11078
11172
|
}
|
|
@@ -11088,7 +11182,7 @@ function stringifyJsonValue(value) {
|
|
|
11088
11182
|
throw new PanelAppError("PANEL_APP_AGENT_REQUEST_INVALID", "value is not JSON serializable");
|
|
11089
11183
|
}
|
|
11090
11184
|
}
|
|
11091
|
-
function isRecord$
|
|
11185
|
+
function isRecord$14(value) {
|
|
11092
11186
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
11093
11187
|
}
|
|
11094
11188
|
//#endregion
|
|
@@ -12509,12 +12603,12 @@ function parseSkillFrontmatter(raw) {
|
|
|
12509
12603
|
const summaryI18n = readLocalizedTextMap(parsed, "summaryi18n", "summary_i18n");
|
|
12510
12604
|
const descriptionI18n = readLocalizedTextMap(parsed, "descriptioni18n", "description_i18n");
|
|
12511
12605
|
return {
|
|
12512
|
-
name: readString$
|
|
12513
|
-
summary: readString$
|
|
12514
|
-
summaryI18n: mergeLocalizedTextMap(summaryI18n, { zh: readString$
|
|
12515
|
-
description: readString$
|
|
12516
|
-
descriptionI18n: mergeLocalizedTextMap(descriptionI18n, { zh: readString$
|
|
12517
|
-
author: readString$
|
|
12606
|
+
name: readString$8(parsed, "name"),
|
|
12607
|
+
summary: readString$8(parsed, "summary"),
|
|
12608
|
+
summaryI18n: mergeLocalizedTextMap(summaryI18n, { zh: readString$8(parsed, "summaryzh", "summary_zh") }),
|
|
12609
|
+
description: readString$8(parsed, "description"),
|
|
12610
|
+
descriptionI18n: mergeLocalizedTextMap(descriptionI18n, { zh: readString$8(parsed, "descriptionzh", "description_zh") }),
|
|
12611
|
+
author: readString$8(parsed, "author"),
|
|
12518
12612
|
tags: readTags(parsed)
|
|
12519
12613
|
};
|
|
12520
12614
|
}
|
|
@@ -12535,19 +12629,19 @@ function parseFrontmatterBlock(raw) {
|
|
|
12535
12629
|
function parseYamlFrontmatter(raw) {
|
|
12536
12630
|
try {
|
|
12537
12631
|
const parsed = parse(raw);
|
|
12538
|
-
return isRecord$
|
|
12632
|
+
return isRecord$13(parsed) ? parsed : {};
|
|
12539
12633
|
} catch (error) {
|
|
12540
12634
|
const message = error instanceof Error ? error.message : String(error);
|
|
12541
12635
|
throw new Error(`Invalid SKILL.md frontmatter: ${message}`);
|
|
12542
12636
|
}
|
|
12543
12637
|
}
|
|
12544
|
-
function readString$
|
|
12638
|
+
function readString$8(record, ...names) {
|
|
12545
12639
|
const value = readValue(record, names);
|
|
12546
12640
|
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
12547
12641
|
}
|
|
12548
12642
|
function readLocalizedTextMap(record, ...names) {
|
|
12549
12643
|
const value = readValue(record, names);
|
|
12550
|
-
if (!isRecord$
|
|
12644
|
+
if (!isRecord$13(value)) return;
|
|
12551
12645
|
const localized = Object.fromEntries(Object.entries(value).filter((entry) => typeof entry[1] === "string" && entry[1].trim().length > 0).map(([locale, text]) => [normalizeLocaleTag(locale), text.trim()]));
|
|
12552
12646
|
return Object.keys(localized).length > 0 ? localized : void 0;
|
|
12553
12647
|
}
|
|
@@ -12571,7 +12665,7 @@ function normalizeFrontmatterKey(raw) {
|
|
|
12571
12665
|
function normalizeLocaleTag(raw) {
|
|
12572
12666
|
return raw.trim().toLowerCase();
|
|
12573
12667
|
}
|
|
12574
|
-
function isRecord$
|
|
12668
|
+
function isRecord$13(value) {
|
|
12575
12669
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
12576
12670
|
}
|
|
12577
12671
|
//#endregion
|
|
@@ -12791,11 +12885,11 @@ function createAgentPeerSessionIdentity(params) {
|
|
|
12791
12885
|
}
|
|
12792
12886
|
function resolveAgentPeerScope(params) {
|
|
12793
12887
|
const metadata = params.metadata ?? {};
|
|
12794
|
-
const explicitScope = readOptionalString$
|
|
12888
|
+
const explicitScope = readOptionalString$11(metadata["agent_peer_scope"]) ?? readOptionalString$11(metadata.agentPeerScope);
|
|
12795
12889
|
if (explicitScope) return explicitScope;
|
|
12796
|
-
return `agent:${readOptionalString$
|
|
12890
|
+
return `agent:${readOptionalString$11(params.agentId) ?? BUILTIN_MAIN_AGENT_ID}:${readOptionalString$11(params.channel) ?? readOptionalString$11(metadata.channel) ?? "agent-run"}:${readOptionalString$11(metadata.accountId) ?? readOptionalString$11(metadata.account_id) ?? "default"}`;
|
|
12797
12891
|
}
|
|
12798
|
-
function readOptionalString$
|
|
12892
|
+
function readOptionalString$11(value) {
|
|
12799
12893
|
if (typeof value !== "string") return;
|
|
12800
12894
|
return value.trim() || void 0;
|
|
12801
12895
|
}
|
|
@@ -12855,20 +12949,20 @@ function readEventMessageId(event) {
|
|
|
12855
12949
|
if (event.type === "session.snapshot.message") return null;
|
|
12856
12950
|
const streamingMessageId = readStreamingMessageId(event);
|
|
12857
12951
|
if (streamingMessageId) return streamingMessageId;
|
|
12858
|
-
const messageId = ("payload" in event && isRecord$
|
|
12952
|
+
const messageId = ("payload" in event && isRecord$12(event.payload) ? event.payload : null)?.messageId;
|
|
12859
12953
|
if (typeof messageId === "string" && messageId.trim()) return messageId.trim();
|
|
12860
12954
|
return null;
|
|
12861
12955
|
}
|
|
12862
12956
|
function readEventRunId(event) {
|
|
12863
12957
|
if (isJournalOnlyEvent(event)) return null;
|
|
12864
12958
|
if (event.type === "session.snapshot.message") return null;
|
|
12865
|
-
const runId = ("payload" in event && isRecord$
|
|
12959
|
+
const runId = ("payload" in event && isRecord$12(event.payload) ? event.payload : null)?.runId;
|
|
12866
12960
|
if (typeof runId === "string" && runId.trim()) return runId.trim();
|
|
12867
12961
|
return null;
|
|
12868
12962
|
}
|
|
12869
12963
|
function readEventToolCallId(event) {
|
|
12870
12964
|
if (isJournalOnlyEvent(event)) return null;
|
|
12871
|
-
const toolCallId = ("payload" in event && isRecord$
|
|
12965
|
+
const toolCallId = ("payload" in event && isRecord$12(event.payload) ? event.payload : null)?.toolCallId;
|
|
12872
12966
|
return typeof toolCallId === "string" && toolCallId.trim() ? toolCallId.trim() : null;
|
|
12873
12967
|
}
|
|
12874
12968
|
function createReplayEvent(event, toolResultsByCallId) {
|
|
@@ -12913,7 +13007,7 @@ function mergeReplayCompletedToolResults(message, toolResultsByCallId) {
|
|
|
12913
13007
|
} : message;
|
|
12914
13008
|
}
|
|
12915
13009
|
function readLegacyContextCompactionMessageId(message) {
|
|
12916
|
-
const checkpoint = isRecord$
|
|
13010
|
+
const checkpoint = isRecord$12(message?.metadata?.checkpoint) ? message.metadata.checkpoint : null;
|
|
12917
13011
|
const checkpointId = typeof checkpoint?.id === "string" ? checkpoint.id : "";
|
|
12918
13012
|
const coveredCount = checkpoint?.coveredSessionMessageCount;
|
|
12919
13013
|
const legacyId = `${message?.sessionId}:service:context-compaction:${checkpointId}`;
|
|
@@ -12945,11 +13039,11 @@ function readReplayMessageId(event) {
|
|
|
12945
13039
|
return readMessageFromSummaryEvent$1(event)?.id ?? null;
|
|
12946
13040
|
}
|
|
12947
13041
|
function readEventSessionId$1(event) {
|
|
12948
|
-
const sessionId = ("payload" in event && isRecord$
|
|
13042
|
+
const sessionId = ("payload" in event && isRecord$12(event.payload) ? event.payload : null)?.sessionId;
|
|
12949
13043
|
return typeof sessionId === "string" ? sessionId : "";
|
|
12950
13044
|
}
|
|
12951
13045
|
function readReplayPayloadTimestamp(event) {
|
|
12952
|
-
const payload = "payload" in event && isRecord$
|
|
13046
|
+
const payload = "payload" in event && isRecord$12(event.payload) ? event.payload : null;
|
|
12953
13047
|
const timestamp = typeof payload?.timestamp === "string" ? payload.timestamp : "";
|
|
12954
13048
|
return Number.isFinite(Date.parse(timestamp)) ? new Date(timestamp).toISOString() : null;
|
|
12955
13049
|
}
|
|
@@ -12970,7 +13064,7 @@ function readStreamingMessageId(event) {
|
|
|
12970
13064
|
function isJournalOnlyEvent(event) {
|
|
12971
13065
|
return event.type === "session.request.accepted" || event.type === "session.request.completed" || event.type === "session.request.failed";
|
|
12972
13066
|
}
|
|
12973
|
-
function isRecord$
|
|
13067
|
+
function isRecord$12(value) {
|
|
12974
13068
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
12975
13069
|
}
|
|
12976
13070
|
function readMessageFromSummaryEvent$1(event) {
|
|
@@ -13077,7 +13171,7 @@ function safeNcpSessionFilename(value) {
|
|
|
13077
13171
|
function normalizeNcpAgentId(agentId) {
|
|
13078
13172
|
return agentId?.trim().toLowerCase() || void 0;
|
|
13079
13173
|
}
|
|
13080
|
-
function isRecord$
|
|
13174
|
+
function isRecord$11(value) {
|
|
13081
13175
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
13082
13176
|
}
|
|
13083
13177
|
function toIsoString(value, fallback) {
|
|
@@ -13644,16 +13738,15 @@ function applySessionRuntimePatch(metadata, patch) {
|
|
|
13644
13738
|
function applySessionSettingsMetadataPatch(currentMetadata, patch) {
|
|
13645
13739
|
return applySessionRuntimePatch(applySessionPreferencePatch(structuredClone(currentMetadata), patch), patch);
|
|
13646
13740
|
}
|
|
13647
|
-
async function applySessionProjectMetadataPatch(metadata, patch,
|
|
13741
|
+
async function applySessionProjectMetadataPatch(metadata, patch, normalizeProjectContext) {
|
|
13648
13742
|
if (!Object.prototype.hasOwnProperty.call(patch, "projectRoot")) return metadata;
|
|
13649
|
-
const
|
|
13650
|
-
const { projectRoot: _legacyProjectRoot, ...nextMetadata } = metadata;
|
|
13651
|
-
|
|
13743
|
+
const project = await normalizeProjectContext(patch.projectRoot);
|
|
13744
|
+
const { projectRoot: _legacyProjectRoot, projectId: _legacyProjectId, project_root: _projectRoot, project_id: _projectId, ...nextMetadata } = metadata;
|
|
13745
|
+
return project ? {
|
|
13652
13746
|
...nextMetadata,
|
|
13653
|
-
|
|
13654
|
-
|
|
13655
|
-
|
|
13656
|
-
return metadataWithoutProjectRoot;
|
|
13747
|
+
project_id: project.projectId,
|
|
13748
|
+
project_root: project.rootPath
|
|
13749
|
+
} : nextMetadata;
|
|
13657
13750
|
}
|
|
13658
13751
|
function publishSessionMetadataChanged(eventBus, sessionKey, metadata, mode) {
|
|
13659
13752
|
eventBus.emit(eventKeys$1.sessionMetadataChanged, {
|
|
@@ -13685,7 +13778,7 @@ function applyLimit(items, limit) {
|
|
|
13685
13778
|
if (!Number.isFinite(limit) || typeof limit !== "number" || limit <= 0) return items;
|
|
13686
13779
|
return items.slice(0, Math.trunc(limit));
|
|
13687
13780
|
}
|
|
13688
|
-
function readOptionalString$
|
|
13781
|
+
function readOptionalString$10(value) {
|
|
13689
13782
|
if (typeof value !== "string") return null;
|
|
13690
13783
|
const trimmed = value.trim();
|
|
13691
13784
|
return trimmed.length > 0 ? trimmed : null;
|
|
@@ -13715,6 +13808,9 @@ function readThinkingEffort(metadata) {
|
|
|
13715
13808
|
function readProjectRoot(metadata) {
|
|
13716
13809
|
return readOptionalMetadataString(metadata?.project_root) ?? readOptionalMetadataString(metadata?.projectRoot);
|
|
13717
13810
|
}
|
|
13811
|
+
function readProjectId(metadata) {
|
|
13812
|
+
return readOptionalMetadataString(metadata?.project_id) ?? readOptionalMetadataString(metadata?.projectId);
|
|
13813
|
+
}
|
|
13718
13814
|
function readAgentRuntimeId(metadata) {
|
|
13719
13815
|
return readOptionalMetadataString(metadata?.agentRuntimeId) ?? readOptionalMetadataString(metadata?.runtime) ?? readOptionalMetadataString(metadata?.session_type);
|
|
13720
13816
|
}
|
|
@@ -13731,7 +13827,7 @@ function cloneInheritedMetadata(sourceMetadata) {
|
|
|
13731
13827
|
"preferred_model",
|
|
13732
13828
|
"preferred_thinking",
|
|
13733
13829
|
"project_root",
|
|
13734
|
-
"
|
|
13830
|
+
"project_id",
|
|
13735
13831
|
"codex_runtime_backend",
|
|
13736
13832
|
"reasoningNormalizationMode",
|
|
13737
13833
|
"reasoning_normalization_mode"
|
|
@@ -13746,7 +13842,7 @@ function mergeMetadataOverrides(metadata, overrides) {
|
|
|
13746
13842
|
}
|
|
13747
13843
|
function resolveSessionType(params) {
|
|
13748
13844
|
const { metadata, runtime, sessionType } = params;
|
|
13749
|
-
return readOptionalString$
|
|
13845
|
+
return readOptionalString$10(runtime) ?? readOptionalString$10(metadata.runtime) ?? readOptionalString$10(sessionType) ?? readOptionalString$10(metadata.session_type) ?? "native";
|
|
13750
13846
|
}
|
|
13751
13847
|
function applySessionOverrides(params) {
|
|
13752
13848
|
const { lifecycle, metadata, model, parentSessionId, projectRoot, requestId, sessionType, thinkingLevel, title } = params;
|
|
@@ -13756,15 +13852,15 @@ function applySessionOverrides(params) {
|
|
|
13756
13852
|
metadata[CHILD_SESSION_LIFECYCLE_METADATA_KEY] = lifecycle;
|
|
13757
13853
|
if (parentSessionId) metadata[CHILD_SESSION_PARENT_METADATA_KEY] = parentSessionId;
|
|
13758
13854
|
if (requestId) metadata[CHILD_SESSION_REQUEST_METADATA_KEY] = requestId;
|
|
13759
|
-
if (readOptionalString$
|
|
13855
|
+
if (readOptionalString$10(model)) {
|
|
13760
13856
|
metadata.model = model?.trim();
|
|
13761
13857
|
metadata.preferred_model = model?.trim();
|
|
13762
13858
|
}
|
|
13763
|
-
if (readOptionalString$
|
|
13859
|
+
if (readOptionalString$10(thinkingLevel)) {
|
|
13764
13860
|
metadata.thinking = thinkingLevel?.trim();
|
|
13765
13861
|
metadata.preferred_thinking = thinkingLevel?.trim();
|
|
13766
13862
|
}
|
|
13767
|
-
if (readOptionalString$
|
|
13863
|
+
if (readOptionalString$10(projectRoot)) metadata.project_root = projectRoot?.trim();
|
|
13768
13864
|
}
|
|
13769
13865
|
//#endregion
|
|
13770
13866
|
//#region src/app/kernel-storage-paths.ts
|
|
@@ -13785,6 +13881,9 @@ function resolveKernelPreferenceStorePath(options) {
|
|
|
13785
13881
|
function resolveKernelProjectStorePath(options) {
|
|
13786
13882
|
return resolveKernelDataPath(options, "projects", "projects.json");
|
|
13787
13883
|
}
|
|
13884
|
+
function resolveKernelProjectWorkStorePath(options) {
|
|
13885
|
+
return resolveKernelDataPath(options, "projects", "work-items.db");
|
|
13886
|
+
}
|
|
13788
13887
|
function resolveKernelInboxDeliveryStorePath(options) {
|
|
13789
13888
|
return resolveKernelDataPath(options, "inbox", "deliveries.json");
|
|
13790
13889
|
}
|
|
@@ -15106,10 +15205,14 @@ var PortableServiceCapabilityResolverService = class {
|
|
|
15106
15205
|
if (!record || app.packageVersion && record.activeVersion !== app.packageVersion) return [];
|
|
15107
15206
|
const resolved = [];
|
|
15108
15207
|
for (const scope of scopes) {
|
|
15109
|
-
const
|
|
15110
|
-
if (
|
|
15111
|
-
|
|
15112
|
-
|
|
15208
|
+
const grant = record.grants[scope.id];
|
|
15209
|
+
if (!grant || grant.mode === "read-write" && scope.mode !== "read-write") continue;
|
|
15210
|
+
resolved.push({
|
|
15211
|
+
scope: {
|
|
15212
|
+
...scope,
|
|
15213
|
+
mode: grant.mode
|
|
15214
|
+
},
|
|
15215
|
+
hostPath: await this.requireCanonicalDirectory(grant.path, `${app.id}:${scope.id}`)
|
|
15113
15216
|
});
|
|
15114
15217
|
}
|
|
15115
15218
|
return resolved;
|
|
@@ -16051,7 +16154,7 @@ var ServiceAppAiCapabilityService = class {
|
|
|
16051
16154
|
const payload = requireHostCallObject(call.request.input);
|
|
16052
16155
|
if (call.request.capability === "model-complete") {
|
|
16053
16156
|
const messages = payload.messages;
|
|
16054
|
-
if (!Array.isArray(messages) || !messages.every(isRecord$
|
|
16157
|
+
if (!Array.isArray(messages) || !messages.every(isRecord$10)) throw new ServiceAppError("SERVICE_APP_RUNTIME_FAILED", "AI model messages must be an array of objects.");
|
|
16055
16158
|
return await this.modelComplete({
|
|
16056
16159
|
app: call.app,
|
|
16057
16160
|
manifest: call.manifest,
|
|
@@ -16063,7 +16166,7 @@ var ServiceAppAiCapabilityService = class {
|
|
|
16063
16166
|
}
|
|
16064
16167
|
if (call.request.capability === "agent-start") {
|
|
16065
16168
|
const input = payload.input;
|
|
16066
|
-
if (!isRecord$
|
|
16169
|
+
if (!isRecord$10(input)) throw new ServiceAppError("SERVICE_APP_RUNTIME_FAILED", "AI Agent input must be an object.");
|
|
16067
16170
|
const execution = await this.agentStart({
|
|
16068
16171
|
app: call.app,
|
|
16069
16172
|
manifest: call.manifest,
|
|
@@ -16188,11 +16291,11 @@ var ServiceAppAiCapabilityService = class {
|
|
|
16188
16291
|
};
|
|
16189
16292
|
};
|
|
16190
16293
|
};
|
|
16191
|
-
function isRecord$
|
|
16294
|
+
function isRecord$10(value) {
|
|
16192
16295
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
16193
16296
|
}
|
|
16194
16297
|
function requireHostCallObject(value) {
|
|
16195
|
-
if (!isRecord$
|
|
16298
|
+
if (!isRecord$10(value)) throw new ServiceAppError("SERVICE_APP_RUNTIME_FAILED", "Portable host call input must be an object.");
|
|
16196
16299
|
return value;
|
|
16197
16300
|
}
|
|
16198
16301
|
function requireHostCallString(value, key) {
|
|
@@ -17341,6 +17444,7 @@ function projectCapabilityProviders(entries) {
|
|
|
17341
17444
|
//#endregion
|
|
17342
17445
|
//#region src/managers/service-app.manager.ts
|
|
17343
17446
|
const SERVICE_APP_ID_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
17447
|
+
const DOCUMENT_GUEST_PATH_PATTERN = /^\/documents\/([^/]+)(?:\/|$)/;
|
|
17344
17448
|
var ServiceAppManager = class {
|
|
17345
17449
|
removalService = new ServiceAppRemovalService();
|
|
17346
17450
|
runtimeService;
|
|
@@ -17489,6 +17593,7 @@ var ServiceAppManager = class {
|
|
|
17489
17593
|
const action = listServiceAppManifestActions(record, manifest).find((entry) => entry.id === actionId);
|
|
17490
17594
|
if (!action) throw new ServiceAppError("SERVICE_APP_ACTION_NOT_FOUND", "service action not found");
|
|
17491
17595
|
if (!await this.actionGrants.isGranted(request.caller, action)) throw new ServiceAppError("AUTHORIZATION_REQUIRED", `This panel app needs permission to call ${actionId}.`);
|
|
17596
|
+
await this.assertDocumentInputAccess(record, action.risk, request.input ?? {});
|
|
17492
17597
|
return await this.jobManager.invoke({
|
|
17493
17598
|
actionId,
|
|
17494
17599
|
actionName,
|
|
@@ -17505,6 +17610,8 @@ var ServiceAppManager = class {
|
|
|
17505
17610
|
*/
|
|
17506
17611
|
invokeInstalledServiceAction = async (appId, actionName, input = {}) => {
|
|
17507
17612
|
const { manifest, record } = await this.requireInstalledServiceApp(appId, actionName);
|
|
17613
|
+
const action = listServiceAppManifestActions(record, manifest).find((entry) => entry.name === actionName);
|
|
17614
|
+
await this.assertDocumentInputAccess(record, action?.risk ?? "dangerous", input);
|
|
17508
17615
|
return await this.jobManager.invoke({
|
|
17509
17616
|
actionId: buildServiceActionId(record.id, actionName),
|
|
17510
17617
|
actionName,
|
|
@@ -17515,6 +17622,24 @@ var ServiceAppManager = class {
|
|
|
17515
17622
|
entrySurface: "installed-app-cli"
|
|
17516
17623
|
});
|
|
17517
17624
|
};
|
|
17625
|
+
assertDocumentInputAccess = async (record, risk, input) => {
|
|
17626
|
+
const match = (typeof input.path === "string" ? input.path : void 0)?.match(DOCUMENT_GUEST_PATH_PATTERN);
|
|
17627
|
+
if (!match || !record.packageId || !this.params.assertDocumentAccess) return;
|
|
17628
|
+
const scopeId = decodeURIComponent(match[1]);
|
|
17629
|
+
const requestedMode = risk === "read" ? "read" : "read-write";
|
|
17630
|
+
try {
|
|
17631
|
+
await this.params.assertDocumentAccess(record.packageId, scopeId, requestedMode);
|
|
17632
|
+
} catch (error) {
|
|
17633
|
+
const code = typeof error === "object" && error !== null && "code" in error ? String(error.code) : "DOCUMENT_SCOPE_NOT_GRANTED";
|
|
17634
|
+
if (code === "DOCUMENT_SCOPE_NOT_GRANTED" || code === "DOCUMENT_SCOPE_MODE_INSUFFICIENT" || code === "DOCUMENT_SCOPE_UNAVAILABLE") throw new ServiceAppError(code, error instanceof Error ? error.message : code, {
|
|
17635
|
+
appId: record.packageId,
|
|
17636
|
+
scopeId,
|
|
17637
|
+
requestedMode,
|
|
17638
|
+
recoveryActions: code === "DOCUMENT_SCOPE_UNAVAILABLE" ? ["replace", "revoke"] : code === "DOCUMENT_SCOPE_MODE_INSUFFICIENT" ? ["upgrade"] : ["grant"]
|
|
17639
|
+
});
|
|
17640
|
+
throw error;
|
|
17641
|
+
}
|
|
17642
|
+
};
|
|
17518
17643
|
listVerificationRecords = async (filters = {}) => await this.jobManager.listVerificationRecords(filters);
|
|
17519
17644
|
exportVerificationRecords = async (filters = {}) => await this.jobManager.exportVerificationRecords(filters);
|
|
17520
17645
|
listServiceAppJobs = async (appId, params = {}) => await this.jobManager.list(appId, params.caller);
|
|
@@ -18609,7 +18734,7 @@ function hasToolCall(message, toolCallId) {
|
|
|
18609
18734
|
return message.parts.some((part) => part.type === "tool-invocation" && part.toolCallId === toolCallId);
|
|
18610
18735
|
}
|
|
18611
18736
|
function findContextInheritanceAnchor(params) {
|
|
18612
|
-
const anchorToolCallId = readOptionalString$
|
|
18737
|
+
const anchorToolCallId = readOptionalString$10(params.anchorToolCallId);
|
|
18613
18738
|
if (!anchorToolCallId) return null;
|
|
18614
18739
|
const index = params.messages.findIndex((message) => hasToolCall(message, anchorToolCallId));
|
|
18615
18740
|
const message = index >= 0 ? params.messages[index] : void 0;
|
|
@@ -18649,7 +18774,7 @@ function createInheritedContextSnapshot(params) {
|
|
|
18649
18774
|
enabled: true,
|
|
18650
18775
|
sourceSessionId: sourceRecord.sessionId,
|
|
18651
18776
|
anchorKind: anchor ? "tool_call" : "latest_persisted",
|
|
18652
|
-
anchorToolCallId: readOptionalString$
|
|
18777
|
+
anchorToolCallId: readOptionalString$10(anchorToolCallId),
|
|
18653
18778
|
anchorMessageId: anchor?.message.id,
|
|
18654
18779
|
inheritedMessageCount: messages.length
|
|
18655
18780
|
}
|
|
@@ -18871,7 +18996,7 @@ var SessionSettingsService = class {
|
|
|
18871
18996
|
existing = await this.options.getSessionRecord(sessionId);
|
|
18872
18997
|
}
|
|
18873
18998
|
if (!existing) return null;
|
|
18874
|
-
const metadata = await applySessionProjectMetadataPatch(applySessionSettingsMetadataPatch(existing.metadata ?? {}, patch), patch, this.options.
|
|
18999
|
+
const metadata = await applySessionProjectMetadataPatch(applySessionSettingsMetadataPatch(existing.metadata ?? {}, patch), patch, this.options.normalizeProjectContext);
|
|
18875
19000
|
return await this.options.setSessionMetadata(sessionId, metadata) ? await this.options.getSession(sessionId) : null;
|
|
18876
19001
|
};
|
|
18877
19002
|
};
|
|
@@ -18937,7 +19062,7 @@ var SessionWorkingDirResolver = class {
|
|
|
18937
19062
|
return this.resolveContext(params).effectiveWorkspace;
|
|
18938
19063
|
};
|
|
18939
19064
|
resolveContext = (params) => {
|
|
18940
|
-
const profile = this.agentManager.resolveAgentProfile(readOptionalString$
|
|
19065
|
+
const profile = this.agentManager.resolveAgentProfile(readOptionalString$10(params.agentId));
|
|
18941
19066
|
return resolveSessionProjectContext({
|
|
18942
19067
|
sessionMetadata: params.metadata,
|
|
18943
19068
|
workspace: profile.workspace
|
|
@@ -18974,7 +19099,7 @@ var SessionManager = class {
|
|
|
18974
19099
|
createSession: this.createSession,
|
|
18975
19100
|
getSession: this.getSession,
|
|
18976
19101
|
getSessionRecord: this.getSessionRecord,
|
|
18977
|
-
|
|
19102
|
+
normalizeProjectContext: options.projectManager.normalizeSessionProjectContext,
|
|
18978
19103
|
setSessionMetadata: this.setSessionMetadata
|
|
18979
19104
|
});
|
|
18980
19105
|
}
|
|
@@ -18985,10 +19110,10 @@ var SessionManager = class {
|
|
|
18985
19110
|
const { agentId: requestedAgentId, contextInheritance, metadataOverrides, model, parentSessionId: rawParentSessionId, projectRoot, requestId: rawRequestId, runtime, sessionId: requestedSessionId, sessionType: requestedSessionType, sourceSessionId, sourceSessionMetadata, task, thinkingLevel, title: requestedTitle } = params;
|
|
18986
19111
|
const sourceRecord = sourceSessionId ? await this.getSessionRecord(sourceSessionId) : null;
|
|
18987
19112
|
const metadata = cloneInheritedMetadata(sourceSessionMetadata);
|
|
18988
|
-
const title = readOptionalString$
|
|
18989
|
-
const parentSessionId = readOptionalString$
|
|
19113
|
+
const title = readOptionalString$10(requestedTitle) ?? summarizeTask(task);
|
|
19114
|
+
const parentSessionId = readOptionalString$10(rawParentSessionId);
|
|
18990
19115
|
await assertCanCreateSessionFromLineage(parentSessionId, sourceRecord, metadataOverrides, this.getSessionRecord);
|
|
18991
|
-
const requestId = readOptionalString$
|
|
19116
|
+
const requestId = readOptionalString$10(rawRequestId);
|
|
18992
19117
|
const sessionType = resolveSessionType({
|
|
18993
19118
|
runtime,
|
|
18994
19119
|
sessionType: requestedSessionType,
|
|
@@ -19006,16 +19131,11 @@ var SessionManager = class {
|
|
|
19006
19131
|
title
|
|
19007
19132
|
});
|
|
19008
19133
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
19009
|
-
|
|
19134
|
+
let nextMetadata = mergeMetadataOverrides(metadata, metadataOverrides);
|
|
19010
19135
|
const requestedProjectRoot = projectRoot !== void 0 ? projectRoot : readProjectRoot(nextMetadata);
|
|
19011
|
-
if (requestedProjectRoot !== void 0) {
|
|
19012
|
-
|
|
19013
|
-
|
|
19014
|
-
if (normalizedProjectRoot) nextMetadata.project_root = normalizedProjectRoot;
|
|
19015
|
-
else delete nextMetadata.project_root;
|
|
19016
|
-
}
|
|
19017
|
-
const agentId = readOptionalString$8(requestedAgentId) ?? readOptionalString$8(sourceRecord?.agentId) ?? BUILTIN_MAIN_AGENT_ID;
|
|
19018
|
-
const sessionId = readOptionalString$8(requestedSessionId) ?? buildSessionId();
|
|
19136
|
+
if (requestedProjectRoot !== void 0) nextMetadata = await applySessionProjectMetadataPatch(nextMetadata, { projectRoot: requestedProjectRoot }, this.options.projectManager.normalizeSessionProjectContext);
|
|
19137
|
+
const agentId = readOptionalString$10(requestedAgentId) ?? readOptionalString$10(sourceRecord?.agentId) ?? BUILTIN_MAIN_AGENT_ID;
|
|
19138
|
+
const sessionId = readOptionalString$10(requestedSessionId) ?? buildSessionId();
|
|
19019
19139
|
const inheritedContext = createSessionContextInheritance({
|
|
19020
19140
|
childSessionId: sessionId,
|
|
19021
19141
|
contextInheritance,
|
|
@@ -19101,7 +19221,7 @@ var SessionManager = class {
|
|
|
19101
19221
|
listSessions = async (options) => {
|
|
19102
19222
|
const { limit, peerId: rawPeerId } = options ?? {};
|
|
19103
19223
|
const { journalStore } = this.options;
|
|
19104
|
-
const peerId = readOptionalString$
|
|
19224
|
+
const peerId = readOptionalString$10(rawPeerId);
|
|
19105
19225
|
const summaryReadOptions = !peerId && limit !== void 0 ? { limit } : void 0;
|
|
19106
19226
|
return applyLimit((await journalStore.listSessionSummaries(summaryReadOptions)).filter((summary) => !peerId || summary.peerId === peerId).map(this.workingDirResolver.withWorkingDir), limit);
|
|
19107
19227
|
};
|
|
@@ -19160,9 +19280,9 @@ var SessionManager = class {
|
|
|
19160
19280
|
};
|
|
19161
19281
|
createAgentRunSession = async (params) => {
|
|
19162
19282
|
const { agentId, agentRuntimeId: requestedAgentRuntimeId, channel, contextInheritance, metadata, model, parentSessionId: rawParentSessionId, peerId: rawPeerId, projectRoot, sessionId, sourceSessionId: rawSourceSessionId, sourceSessionMetadata: requestedSourceSessionMetadata, task, thinkingEffort } = params;
|
|
19163
|
-
const peerId = readOptionalString$
|
|
19164
|
-
const parentSessionId = readOptionalString$
|
|
19165
|
-
const sourceSessionId = readOptionalString$
|
|
19283
|
+
const peerId = readOptionalString$10(rawPeerId);
|
|
19284
|
+
const parentSessionId = readOptionalString$10(rawParentSessionId);
|
|
19285
|
+
const sourceSessionId = readOptionalString$10(rawSourceSessionId);
|
|
19166
19286
|
const sourceRecord = sourceSessionId ? await this.getSessionRecord(sourceSessionId) : null;
|
|
19167
19287
|
const sourceSessionMetadata = requestedSourceSessionMetadata ?? sourceRecord?.metadata ?? {};
|
|
19168
19288
|
const agentRuntimeId = requestedAgentRuntimeId ?? readAgentRuntimeId(sourceSessionMetadata) ?? "native";
|
|
@@ -19172,7 +19292,7 @@ var SessionManager = class {
|
|
|
19172
19292
|
metadata,
|
|
19173
19293
|
peerId
|
|
19174
19294
|
}) : void 0;
|
|
19175
|
-
const requestedSessionId = readOptionalString$
|
|
19295
|
+
const requestedSessionId = readOptionalString$10(sessionId);
|
|
19176
19296
|
const created = await this.createSession({
|
|
19177
19297
|
contextInheritance,
|
|
19178
19298
|
parentSessionId: parentSessionId ?? void 0,
|
|
@@ -19198,6 +19318,7 @@ var SessionManager = class {
|
|
|
19198
19318
|
metadata: structuredClone(created.metadata ?? {}),
|
|
19199
19319
|
model: model ?? readOptionalMetadataString(created.metadata?.model) ?? readOptionalMetadataString(created.metadata?.preferred_model),
|
|
19200
19320
|
projectRoot: readProjectRoot(created.metadata),
|
|
19321
|
+
projectId: readProjectId(created.metadata),
|
|
19201
19322
|
workingDir: this.workingDirResolver.resolve({
|
|
19202
19323
|
agentId: created.agentId,
|
|
19203
19324
|
metadata: created.metadata
|
|
@@ -19219,6 +19340,7 @@ var SessionManager = class {
|
|
|
19219
19340
|
metadata: structuredClone(metadata),
|
|
19220
19341
|
model: readOptionalMetadataString(metadata.model) ?? readOptionalMetadataString(metadata.preferred_model),
|
|
19221
19342
|
projectRoot: readProjectRoot(metadata),
|
|
19343
|
+
projectId: readProjectId(metadata),
|
|
19222
19344
|
workingDir: this.workingDirResolver.resolve({
|
|
19223
19345
|
agentId: summary.agentId,
|
|
19224
19346
|
metadata
|
|
@@ -20206,8 +20328,11 @@ var ObservationManager = class {
|
|
|
20206
20328
|
};
|
|
20207
20329
|
};
|
|
20208
20330
|
//#endregion
|
|
20209
|
-
//#region src/stores/project.store.ts
|
|
20210
|
-
const PROJECT_STORE_VERSION =
|
|
20331
|
+
//#region src/features/projects/stores/project.store.ts
|
|
20332
|
+
const PROJECT_STORE_VERSION = 2;
|
|
20333
|
+
function createProjectId() {
|
|
20334
|
+
return randomBytes(9).toString("base64url");
|
|
20335
|
+
}
|
|
20211
20336
|
var ProjectStoreError = class extends Error {
|
|
20212
20337
|
constructor(message) {
|
|
20213
20338
|
super(message);
|
|
@@ -20227,6 +20352,28 @@ var ProjectStore = class {
|
|
|
20227
20352
|
throw error;
|
|
20228
20353
|
}
|
|
20229
20354
|
};
|
|
20355
|
+
migrateLegacyRecords = async () => {
|
|
20356
|
+
let source;
|
|
20357
|
+
try {
|
|
20358
|
+
source = await readFile(this.storePath, "utf8");
|
|
20359
|
+
} catch (error) {
|
|
20360
|
+
if (this.isMissingFileError(error)) return false;
|
|
20361
|
+
throw error;
|
|
20362
|
+
}
|
|
20363
|
+
let storeFile;
|
|
20364
|
+
try {
|
|
20365
|
+
storeFile = this.parseStoredFile(source);
|
|
20366
|
+
} catch (error) {
|
|
20367
|
+
if (error instanceof SyntaxError) throw new ProjectStoreError("project registry contains invalid JSON");
|
|
20368
|
+
throw error;
|
|
20369
|
+
}
|
|
20370
|
+
if (storeFile.version === PROJECT_STORE_VERSION) return false;
|
|
20371
|
+
await this.save(storeFile.projects.map((project) => ({
|
|
20372
|
+
id: createProjectId(),
|
|
20373
|
+
...project
|
|
20374
|
+
})));
|
|
20375
|
+
return true;
|
|
20376
|
+
};
|
|
20230
20377
|
save = async (projects) => {
|
|
20231
20378
|
const tempPath = `${this.storePath}.${randomUUID()}.tmp`;
|
|
20232
20379
|
const storeFile = {
|
|
@@ -20243,25 +20390,46 @@ var ProjectStore = class {
|
|
|
20243
20390
|
}
|
|
20244
20391
|
};
|
|
20245
20392
|
parseStoreFile = (source) => {
|
|
20246
|
-
const value =
|
|
20247
|
-
if (
|
|
20393
|
+
const value = this.parseStoredFile(source);
|
|
20394
|
+
if (value.version !== PROJECT_STORE_VERSION || !value.projects.every(this.isProjectRecord)) throw new ProjectStoreError("project registry has an unsupported structure");
|
|
20248
20395
|
return {
|
|
20249
20396
|
version: PROJECT_STORE_VERSION,
|
|
20250
20397
|
projects: value.projects.map((project) => structuredClone(project))
|
|
20251
20398
|
};
|
|
20252
20399
|
};
|
|
20400
|
+
parseStoredFile = (source) => {
|
|
20401
|
+
const value = JSON.parse(source);
|
|
20402
|
+
if (!this.isRecord(value) || !Array.isArray(value.projects)) throw new ProjectStoreError("project registry has an unsupported structure");
|
|
20403
|
+
if (value.version === PROJECT_STORE_VERSION && value.projects.every(this.isProjectRecord)) return {
|
|
20404
|
+
version: PROJECT_STORE_VERSION,
|
|
20405
|
+
projects: value.projects.map((project) => structuredClone(project))
|
|
20406
|
+
};
|
|
20407
|
+
if (value.version === 1 && value.projects.every(this.isLegacyProjectRecord)) return {
|
|
20408
|
+
version: 1,
|
|
20409
|
+
projects: value.projects.map((project) => structuredClone(project))
|
|
20410
|
+
};
|
|
20411
|
+
throw new ProjectStoreError("project registry has an unsupported structure");
|
|
20412
|
+
};
|
|
20253
20413
|
isProjectRecord = (value) => {
|
|
20254
20414
|
if (!this.isRecord(value)) return false;
|
|
20255
|
-
return typeof value.name === "string" && typeof value.rootPath === "string" && (value.template === void 0 || value.template === "empty" || value.template === "knowledge-base") && typeof value.createdAt === "string" && typeof value.updatedAt === "string";
|
|
20415
|
+
return typeof value.id === "string" && value.id.length > 0 && typeof value.name === "string" && typeof value.rootPath === "string" && (value.template === void 0 || value.template === "empty" || value.template === "knowledge-base") && typeof value.createdAt === "string" && typeof value.updatedAt === "string";
|
|
20416
|
+
};
|
|
20417
|
+
isLegacyProjectRecord = (value) => {
|
|
20418
|
+
if (!this.isRecord(value)) return false;
|
|
20419
|
+
const { id: _id, ...legacy } = value;
|
|
20420
|
+
return this.isProjectRecord({
|
|
20421
|
+
id: "legacy",
|
|
20422
|
+
...legacy
|
|
20423
|
+
});
|
|
20256
20424
|
};
|
|
20257
20425
|
isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
20258
20426
|
isMissingFileError = (error) => typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
20259
20427
|
};
|
|
20260
20428
|
//#endregion
|
|
20261
|
-
//#region src/types/project.types.ts
|
|
20429
|
+
//#region src/features/projects/types/project.types.ts
|
|
20262
20430
|
const PROJECT_TEMPLATE_IDS = ["empty", "knowledge-base"];
|
|
20263
20431
|
//#endregion
|
|
20264
|
-
//#region src/managers/project.manager.ts
|
|
20432
|
+
//#region src/features/projects/managers/project.manager.ts
|
|
20265
20433
|
const PROJECT_TEMPLATES = [{
|
|
20266
20434
|
id: "empty",
|
|
20267
20435
|
name: "Empty project",
|
|
@@ -20288,6 +20456,17 @@ var ProjectManager = class {
|
|
|
20288
20456
|
this.store = new ProjectStore(options.storePath);
|
|
20289
20457
|
}
|
|
20290
20458
|
listProjects = async () => (await this.store.list()).sort((left, right) => right.updatedAt.localeCompare(left.updatedAt) || left.name.localeCompare(right.name));
|
|
20459
|
+
migrateLegacyProjects = async () => await this.store.migrateLegacyRecords();
|
|
20460
|
+
getProjectById = async (projectId) => {
|
|
20461
|
+
const project = (await this.store.list()).find((entry) => entry.id === projectId);
|
|
20462
|
+
return project ? structuredClone(project) : null;
|
|
20463
|
+
};
|
|
20464
|
+
getRegisteredProject = async (rootPath) => {
|
|
20465
|
+
const canonicalPath = await this.resolveExistingProjectRoot(rootPath);
|
|
20466
|
+
if (!canonicalPath) return null;
|
|
20467
|
+
const project = (await this.store.list()).find((entry) => entry.rootPath === canonicalPath);
|
|
20468
|
+
return project ? structuredClone(project) : null;
|
|
20469
|
+
};
|
|
20291
20470
|
listTemplates = () => structuredClone(PROJECT_TEMPLATES);
|
|
20292
20471
|
createProject = async (input) => {
|
|
20293
20472
|
const name = this.normalizeName(input.name);
|
|
@@ -20319,14 +20498,20 @@ var ProjectManager = class {
|
|
|
20319
20498
|
});
|
|
20320
20499
|
};
|
|
20321
20500
|
normalizeSessionProjectRoot = async (value) => {
|
|
20501
|
+
return (await this.normalizeSessionProjectContext(value))?.rootPath ?? null;
|
|
20502
|
+
};
|
|
20503
|
+
normalizeSessionProjectContext = async (value) => {
|
|
20322
20504
|
if (value == null || typeof value === "string" && !value.trim()) return null;
|
|
20323
20505
|
const rootPath = await this.resolveExistingProjectRoot(value);
|
|
20324
20506
|
if (!rootPath) return null;
|
|
20325
|
-
await this.upsertProject({
|
|
20507
|
+
const project = await this.upsertProject({
|
|
20326
20508
|
name: basename(rootPath),
|
|
20327
20509
|
rootPath
|
|
20328
20510
|
});
|
|
20329
|
-
return
|
|
20511
|
+
return {
|
|
20512
|
+
projectId: project.id,
|
|
20513
|
+
rootPath: project.rootPath
|
|
20514
|
+
};
|
|
20330
20515
|
};
|
|
20331
20516
|
resolveExistingProjectRoot = async (value) => {
|
|
20332
20517
|
if (typeof value !== "string") throw new ProjectError("PROJECT_PATH_INVALID_TYPE", "project path must be a string or null");
|
|
@@ -20357,6 +20542,7 @@ var ProjectManager = class {
|
|
|
20357
20542
|
if (existing) return existing;
|
|
20358
20543
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
20359
20544
|
const project = {
|
|
20545
|
+
id: createProjectId(),
|
|
20360
20546
|
name: input.name,
|
|
20361
20547
|
rootPath: input.rootPath,
|
|
20362
20548
|
...input.template ? { template: input.template } : {},
|
|
@@ -20364,6 +20550,7 @@ var ProjectManager = class {
|
|
|
20364
20550
|
updatedAt: now
|
|
20365
20551
|
};
|
|
20366
20552
|
await this.store.save([...projects, project]);
|
|
20553
|
+
await this.options.onProjectRegistered?.(structuredClone(project));
|
|
20367
20554
|
return structuredClone(project);
|
|
20368
20555
|
};
|
|
20369
20556
|
assertNotDefaultWorkspace = async (rootPath) => {
|
|
@@ -20421,138 +20608,2254 @@ var ProjectManager = class {
|
|
|
20421
20608
|
isMissingFileError = (error) => typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
20422
20609
|
};
|
|
20423
20610
|
//#endregion
|
|
20424
|
-
//#region src/stores/
|
|
20425
|
-
|
|
20426
|
-
|
|
20427
|
-
|
|
20428
|
-
if (projectionMeta && projectionMeta.projectedJournalOffset <= journalStat.size) {
|
|
20429
|
-
const metadata = await params.metadataStore.read(params.sessionId, {
|
|
20430
|
-
createdAt: params.updatedAt,
|
|
20431
|
-
updatedAt: params.updatedAt,
|
|
20432
|
-
metadata: {}
|
|
20433
|
-
});
|
|
20434
|
-
const page = await params.projectionStore.listPage({
|
|
20435
|
-
sessionId: params.sessionId,
|
|
20436
|
-
limit: 1
|
|
20437
|
-
});
|
|
20438
|
-
return {
|
|
20439
|
-
...createNcpAgentSessionSummary({
|
|
20440
|
-
sessionId: params.sessionId,
|
|
20441
|
-
...metadata.agentId ? { agentId: metadata.agentId } : {},
|
|
20442
|
-
createdAt: metadata.createdAt,
|
|
20443
|
-
updatedAt: metadata.updatedAt,
|
|
20444
|
-
metadata: metadata.metadata,
|
|
20445
|
-
messages: page?.messages ?? []
|
|
20446
|
-
}),
|
|
20447
|
-
messageCount: page?.total ?? projectionMeta.total
|
|
20448
|
-
};
|
|
20449
|
-
}
|
|
20450
|
-
return await loadNcpAgentSessionSummaryWithoutReplay({
|
|
20451
|
-
sessionId: params.sessionId,
|
|
20452
|
-
journalPath: params.journalPath,
|
|
20453
|
-
updatedAt: params.updatedAt,
|
|
20454
|
-
metadataStore: params.metadataStore
|
|
20455
|
-
});
|
|
20456
|
-
}
|
|
20457
|
-
async function loadNcpAgentSessionSummaryWithoutReplay(params) {
|
|
20458
|
-
const stream = createReadStream(params.journalPath, { encoding: "utf-8" });
|
|
20459
|
-
const lines = createInterface({
|
|
20460
|
-
input: stream,
|
|
20461
|
-
crlfDelay: Infinity
|
|
20462
|
-
});
|
|
20463
|
-
const state = {
|
|
20464
|
-
createdAt: params.updatedAt,
|
|
20465
|
-
metadata: {},
|
|
20466
|
-
messageCount: 0,
|
|
20467
|
-
messageIds: /* @__PURE__ */ new Set(),
|
|
20468
|
-
requiresReplay: false
|
|
20469
|
-
};
|
|
20611
|
+
//#region src/stores/sqlite-database.store.ts
|
|
20612
|
+
const require = createRequire(import.meta.url);
|
|
20613
|
+
const portableDatabases = /* @__PURE__ */ new Map();
|
|
20614
|
+
async function openSqliteDatabase(databasePath) {
|
|
20470
20615
|
try {
|
|
20471
|
-
|
|
20472
|
-
} catch {
|
|
20473
|
-
|
|
20474
|
-
|
|
20475
|
-
lines.close();
|
|
20476
|
-
stream.destroy();
|
|
20477
|
-
}
|
|
20478
|
-
if (state.requiresReplay) return null;
|
|
20479
|
-
const snapshot = await params.metadataStore.read(params.sessionId, {
|
|
20480
|
-
...state.agentId ? { agentId: state.agentId } : {},
|
|
20481
|
-
createdAt: state.createdAt,
|
|
20482
|
-
updatedAt: params.updatedAt,
|
|
20483
|
-
metadata: state.metadata
|
|
20484
|
-
});
|
|
20485
|
-
return {
|
|
20486
|
-
...createNcpAgentSessionSummary({
|
|
20487
|
-
sessionId: params.sessionId,
|
|
20488
|
-
...snapshot.agentId ? { agentId: snapshot.agentId } : {},
|
|
20489
|
-
createdAt: snapshot.createdAt,
|
|
20490
|
-
updatedAt: snapshot.updatedAt,
|
|
20491
|
-
metadata: snapshot.metadata,
|
|
20492
|
-
messages: []
|
|
20493
|
-
}),
|
|
20494
|
-
messageCount: state.messageCount,
|
|
20495
|
-
...state.lastMessageAt ? { lastMessageAt: state.lastMessageAt } : {}
|
|
20496
|
-
};
|
|
20497
|
-
}
|
|
20498
|
-
function applyJournalSummaryLine(state, line) {
|
|
20499
|
-
const parsed = parseJournalLine(line);
|
|
20500
|
-
if (!parsed) return;
|
|
20501
|
-
if (parsed._type === "metadata") {
|
|
20502
|
-
state.createdAt = toIsoString(parsed.created_at, state.createdAt);
|
|
20503
|
-
state.agentId = typeof parsed.agent_id === "string" ? parsed.agent_id : state.agentId;
|
|
20504
|
-
if (isRecord$9(parsed.metadata)) state.metadata = structuredClone(parsed.metadata);
|
|
20505
|
-
return;
|
|
20616
|
+
return new (require("node:sqlite")).DatabaseSync(databasePath);
|
|
20617
|
+
} catch (error) {
|
|
20618
|
+
if (!isMissingNodeSqlite(error)) throw error;
|
|
20619
|
+
return await openPortableDatabase(databasePath);
|
|
20506
20620
|
}
|
|
20507
|
-
if (parsed._type !== "event" || !isRecord$9(parsed.event)) return;
|
|
20508
|
-
const event = parsed.event;
|
|
20509
|
-
const payload = isRecord$9(event.payload) ? event.payload : null;
|
|
20510
|
-
const message = payload && isRecord$9(payload.message) ? payload.message : null;
|
|
20511
|
-
const messageId = typeof message?.id === "string" ? message.id : typeof payload?.messageId === "string" ? payload.messageId : null;
|
|
20512
|
-
if (!isMessageStartEvent(event.type) || !messageId || state.messageIds.has(messageId)) return;
|
|
20513
|
-
state.messageIds.add(messageId);
|
|
20514
|
-
state.messageCount += 1;
|
|
20515
|
-
if (isRecord$9(message?.metadata) && message.metadata.nextclaw_timeline_kind === "context_compaction") state.requiresReplay = true;
|
|
20516
|
-
if (!isFinalMessageEvent(event.type)) return;
|
|
20517
|
-
const timestamp = toIsoString(message?.timestamp, "");
|
|
20518
|
-
if (timestamp && (!state.lastMessageAt || timestamp > state.lastMessageAt)) state.lastMessageAt = timestamp;
|
|
20519
20621
|
}
|
|
20520
|
-
function
|
|
20521
|
-
|
|
20622
|
+
function runSqliteTransaction(database, operation, mode = "DEFERRED") {
|
|
20623
|
+
database.exec(`BEGIN ${mode}`);
|
|
20522
20624
|
try {
|
|
20523
|
-
const
|
|
20524
|
-
|
|
20525
|
-
|
|
20526
|
-
|
|
20625
|
+
const result = operation();
|
|
20626
|
+
database.exec("COMMIT");
|
|
20627
|
+
return result;
|
|
20628
|
+
} catch (error) {
|
|
20629
|
+
database.exec("ROLLBACK");
|
|
20630
|
+
throw error;
|
|
20527
20631
|
}
|
|
20528
20632
|
}
|
|
20529
|
-
function
|
|
20530
|
-
|
|
20633
|
+
function isMissingNodeSqlite(error) {
|
|
20634
|
+
const code = typeof error === "object" && error !== null ? error.code : void 0;
|
|
20635
|
+
return code === "ERR_UNKNOWN_BUILTIN_MODULE" || code === "MODULE_NOT_FOUND";
|
|
20531
20636
|
}
|
|
20532
|
-
function
|
|
20533
|
-
|
|
20637
|
+
async function openPortableDatabase(databasePath) {
|
|
20638
|
+
const existing = portableDatabases.get(databasePath);
|
|
20639
|
+
if (existing) return (await existing).acquire();
|
|
20640
|
+
const opening = createPortableDatabase(databasePath).catch((error) => {
|
|
20641
|
+
portableDatabases.delete(databasePath);
|
|
20642
|
+
throw error;
|
|
20643
|
+
});
|
|
20644
|
+
portableDatabases.set(databasePath, opening);
|
|
20645
|
+
return (await opening).acquire();
|
|
20534
20646
|
}
|
|
20535
|
-
|
|
20536
|
-
|
|
20537
|
-
|
|
20538
|
-
|
|
20539
|
-
sessionId,
|
|
20540
|
-
...event.payload.messageId ? { messageId: event.payload.messageId } : {},
|
|
20541
|
-
...event.payload.runId ? { runId: event.payload.runId } : {},
|
|
20542
|
-
...event.payload.startedAt ? { startedAt: event.payload.startedAt } : {}
|
|
20543
|
-
};
|
|
20544
|
-
if (activeRun && (event.type === NcpEventType.RunFinished || event.type === NcpEventType.RunError || event.type === NcpEventType.MessageAbort) && (!event.payload.runId || !activeRun.runId || event.payload.runId === activeRun.runId)) return null;
|
|
20545
|
-
return activeRun;
|
|
20647
|
+
async function createPortableDatabase(databasePath) {
|
|
20648
|
+
const initialize = (await import("sql.js")).default;
|
|
20649
|
+
const sqlite = await initialize({ locateFile: (file) => require.resolve(`sql.js/dist/${file}`) });
|
|
20650
|
+
return new PortableSqliteDatabase(databasePath, existsSync(databasePath) ? new sqlite.Database(readFileSync(databasePath)) : new sqlite.Database());
|
|
20546
20651
|
}
|
|
20547
|
-
|
|
20548
|
-
|
|
20549
|
-
|
|
20550
|
-
|
|
20551
|
-
|
|
20552
|
-
this.
|
|
20652
|
+
var PortableSqliteDatabase = class {
|
|
20653
|
+
inTransaction = false;
|
|
20654
|
+
dirty = false;
|
|
20655
|
+
leaseCount = 0;
|
|
20656
|
+
constructor(databasePath, database) {
|
|
20657
|
+
this.databasePath = databasePath;
|
|
20658
|
+
this.database = database;
|
|
20553
20659
|
}
|
|
20554
|
-
|
|
20555
|
-
|
|
20660
|
+
acquire = () => {
|
|
20661
|
+
this.leaseCount += 1;
|
|
20662
|
+
return new PortableSqliteLease(this);
|
|
20663
|
+
};
|
|
20664
|
+
exec = (sql) => {
|
|
20665
|
+
const command = sql.trim().toUpperCase();
|
|
20666
|
+
this.database.run(sql);
|
|
20667
|
+
if (command.startsWith("BEGIN")) {
|
|
20668
|
+
this.inTransaction = true;
|
|
20669
|
+
return;
|
|
20670
|
+
}
|
|
20671
|
+
if (command.startsWith("ROLLBACK")) {
|
|
20672
|
+
this.inTransaction = false;
|
|
20673
|
+
this.dirty = false;
|
|
20674
|
+
return;
|
|
20675
|
+
}
|
|
20676
|
+
if (command.startsWith("COMMIT")) {
|
|
20677
|
+
this.inTransaction = false;
|
|
20678
|
+
this.persistIfDirty();
|
|
20679
|
+
return;
|
|
20680
|
+
}
|
|
20681
|
+
if (!command.startsWith("PRAGMA")) this.markDirty();
|
|
20682
|
+
};
|
|
20683
|
+
prepare = (sql) => new PortableSqliteStatement(this, sql);
|
|
20684
|
+
release = () => {
|
|
20685
|
+
this.leaseCount -= 1;
|
|
20686
|
+
if (this.leaseCount > 0) return;
|
|
20687
|
+
this.persistIfDirty();
|
|
20688
|
+
this.database.close();
|
|
20689
|
+
portableDatabases.delete(this.databasePath);
|
|
20690
|
+
};
|
|
20691
|
+
run = (sql, params) => {
|
|
20692
|
+
this.database.run(sql, normalizeParams(params));
|
|
20693
|
+
this.markDirty();
|
|
20694
|
+
};
|
|
20695
|
+
readRows = (sql, params) => {
|
|
20696
|
+
const statement = this.database.prepare(sql);
|
|
20697
|
+
try {
|
|
20698
|
+
bindStatement(statement, params);
|
|
20699
|
+
const rows = [];
|
|
20700
|
+
while (statement.step()) rows.push(statement.getAsObject());
|
|
20701
|
+
return rows;
|
|
20702
|
+
} finally {
|
|
20703
|
+
statement.free();
|
|
20704
|
+
}
|
|
20705
|
+
};
|
|
20706
|
+
markDirty = () => {
|
|
20707
|
+
this.dirty = true;
|
|
20708
|
+
if (!this.inTransaction) this.persistIfDirty();
|
|
20709
|
+
};
|
|
20710
|
+
persistIfDirty = () => {
|
|
20711
|
+
if (!this.dirty) return;
|
|
20712
|
+
const temporaryPath = `${this.databasePath}.tmp`;
|
|
20713
|
+
writeFileSync(temporaryPath, this.database.export());
|
|
20714
|
+
renameSync(temporaryPath, this.databasePath);
|
|
20715
|
+
this.dirty = false;
|
|
20716
|
+
};
|
|
20717
|
+
};
|
|
20718
|
+
var PortableSqliteLease = class {
|
|
20719
|
+
closed = false;
|
|
20720
|
+
constructor(owner) {
|
|
20721
|
+
this.owner = owner;
|
|
20722
|
+
}
|
|
20723
|
+
close = () => {
|
|
20724
|
+
if (this.closed) return;
|
|
20725
|
+
this.closed = true;
|
|
20726
|
+
this.owner.release();
|
|
20727
|
+
};
|
|
20728
|
+
exec = (sql) => this.owner.exec(sql);
|
|
20729
|
+
prepare = (sql) => this.owner.prepare(sql);
|
|
20730
|
+
};
|
|
20731
|
+
var PortableSqliteStatement = class {
|
|
20732
|
+
constructor(database, sql) {
|
|
20733
|
+
this.database = database;
|
|
20734
|
+
this.sql = sql;
|
|
20735
|
+
}
|
|
20736
|
+
all = (...params) => this.database.readRows(this.sql, params);
|
|
20737
|
+
get = (...params) => this.all(...params)[0];
|
|
20738
|
+
run = (...params) => {
|
|
20739
|
+
this.database.run(this.sql, params);
|
|
20740
|
+
return {};
|
|
20741
|
+
};
|
|
20742
|
+
};
|
|
20743
|
+
function bindStatement(statement, params) {
|
|
20744
|
+
const normalized = normalizeParams(params);
|
|
20745
|
+
if (Array.isArray(normalized) && normalized.length === 0) return;
|
|
20746
|
+
statement.bind(normalized);
|
|
20747
|
+
}
|
|
20748
|
+
function normalizeParams(params) {
|
|
20749
|
+
if (params.length !== 1 || !isRecord$9(params[0])) return params;
|
|
20750
|
+
return Object.fromEntries(Object.entries(params[0]).map(([key, value]) => [`@${key}`, value]));
|
|
20751
|
+
}
|
|
20752
|
+
function isRecord$9(value) {
|
|
20753
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
20754
|
+
}
|
|
20755
|
+
//#endregion
|
|
20756
|
+
//#region src/features/projects/stores/project-work-activity.store.ts
|
|
20757
|
+
var ProjectWorkActivityStore = class {
|
|
20758
|
+
constructor(db) {
|
|
20759
|
+
this.db = db;
|
|
20760
|
+
}
|
|
20761
|
+
listActivities = async (projectId, workItemId, options) => {
|
|
20762
|
+
const { cursor, limit } = options;
|
|
20763
|
+
const cursorSequence = cursor ? Number(cursor) : null;
|
|
20764
|
+
if (cursorSequence !== null && !Number.isInteger(cursorSequence)) throw new Error("PROJECT_WORK_ACTIVITY_CURSOR_INVALID");
|
|
20765
|
+
const rows = this.db().prepare(`SELECT rowid AS sequence, * FROM project_work_activities
|
|
20766
|
+
WHERE project_id = ? AND work_item_id = ? ${cursorSequence !== null ? "AND rowid < ?" : ""}
|
|
20767
|
+
ORDER BY rowid DESC LIMIT ?`).all(projectId, workItemId, ...cursorSequence !== null ? [cursorSequence] : [], limit + 1);
|
|
20768
|
+
const hasMore = rows.length > limit;
|
|
20769
|
+
const pageRows = rows.slice(0, limit);
|
|
20770
|
+
return {
|
|
20771
|
+
activities: pageRows.map(toActivity),
|
|
20772
|
+
nextCursor: hasMore ? String(pageRows.at(-1)?.sequence) : null
|
|
20773
|
+
};
|
|
20774
|
+
};
|
|
20775
|
+
listArtifacts = async (projectId, workItemId) => this.db().prepare(`SELECT * FROM project_work_artifact_links
|
|
20776
|
+
WHERE project_id = ? AND work_item_id = ? ORDER BY created_at DESC`).all(projectId, workItemId).map(toArtifactLink);
|
|
20777
|
+
linkArtifact = async (params) => {
|
|
20778
|
+
const { actor, label, path, projectId, workItemId } = params;
|
|
20779
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
20780
|
+
const id = randomUUID();
|
|
20781
|
+
runSqliteTransaction(this.db(), () => {
|
|
20782
|
+
this.db().prepare(`INSERT INTO project_work_artifact_links
|
|
20783
|
+
(id, project_id, work_item_id, path, label, created_at) VALUES (?, ?, ?, ?, ?, ?)`).run(id, projectId, workItemId, path, label ?? null, now);
|
|
20784
|
+
this.insertActivity({
|
|
20785
|
+
projectId,
|
|
20786
|
+
workItemId,
|
|
20787
|
+
type: "artifact-linked",
|
|
20788
|
+
actor,
|
|
20789
|
+
details: {
|
|
20790
|
+
artifactLinkId: id,
|
|
20791
|
+
path
|
|
20792
|
+
},
|
|
20793
|
+
createdAt: now
|
|
20794
|
+
});
|
|
20795
|
+
}, "IMMEDIATE");
|
|
20796
|
+
return (await this.listArtifacts(projectId, workItemId)).find((link) => link.id === id);
|
|
20797
|
+
};
|
|
20798
|
+
unlinkArtifact = async (params) => {
|
|
20799
|
+
const { actor, artifactLinkId, projectId, workItemId } = params;
|
|
20800
|
+
const link = this.db().prepare(`SELECT * FROM project_work_artifact_links
|
|
20801
|
+
WHERE project_id = ? AND work_item_id = ? AND id = ? LIMIT 1`).get(projectId, workItemId, artifactLinkId);
|
|
20802
|
+
if (!link) return false;
|
|
20803
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
20804
|
+
runSqliteTransaction(this.db(), () => {
|
|
20805
|
+
this.db().prepare("DELETE FROM project_work_artifact_links WHERE project_id = ? AND work_item_id = ? AND id = ?").run(projectId, workItemId, artifactLinkId);
|
|
20806
|
+
this.insertActivity({
|
|
20807
|
+
projectId,
|
|
20808
|
+
workItemId,
|
|
20809
|
+
type: "artifact-unlinked",
|
|
20810
|
+
actor,
|
|
20811
|
+
details: {
|
|
20812
|
+
artifactLinkId: link.id,
|
|
20813
|
+
path: link.path
|
|
20814
|
+
},
|
|
20815
|
+
createdAt: now
|
|
20816
|
+
});
|
|
20817
|
+
}, "IMMEDIATE");
|
|
20818
|
+
return true;
|
|
20819
|
+
};
|
|
20820
|
+
insertActivity = (activity) => {
|
|
20821
|
+
this.db().prepare(`INSERT INTO project_work_activities
|
|
20822
|
+
(id, project_id, work_item_id, type, actor_json, details_json, created_at)
|
|
20823
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`).run(randomUUID(), activity.projectId, activity.workItemId, activity.type, JSON.stringify(activity.actor), JSON.stringify(activity.details), activity.createdAt);
|
|
20824
|
+
};
|
|
20825
|
+
};
|
|
20826
|
+
function toActivity(row) {
|
|
20827
|
+
return {
|
|
20828
|
+
id: row.id,
|
|
20829
|
+
projectId: row.project_id,
|
|
20830
|
+
workItemId: row.work_item_id,
|
|
20831
|
+
type: row.type,
|
|
20832
|
+
actor: JSON.parse(row.actor_json),
|
|
20833
|
+
details: JSON.parse(row.details_json),
|
|
20834
|
+
createdAt: row.created_at
|
|
20835
|
+
};
|
|
20836
|
+
}
|
|
20837
|
+
function toArtifactLink(row) {
|
|
20838
|
+
return {
|
|
20839
|
+
id: row.id,
|
|
20840
|
+
projectId: row.project_id,
|
|
20841
|
+
workItemId: row.work_item_id,
|
|
20842
|
+
path: row.path,
|
|
20843
|
+
label: row.label,
|
|
20844
|
+
createdAt: row.created_at
|
|
20845
|
+
};
|
|
20846
|
+
}
|
|
20847
|
+
//#endregion
|
|
20848
|
+
//#region src/features/projects/stores/project-work-state.store.ts
|
|
20849
|
+
const DEFAULT_PROJECT_WORK_STATES = [
|
|
20850
|
+
{
|
|
20851
|
+
name: "Backlog",
|
|
20852
|
+
category: "backlog",
|
|
20853
|
+
position: 0,
|
|
20854
|
+
isDefault: false
|
|
20855
|
+
},
|
|
20856
|
+
{
|
|
20857
|
+
name: "Planned",
|
|
20858
|
+
category: "unstarted",
|
|
20859
|
+
position: 1,
|
|
20860
|
+
isDefault: true
|
|
20861
|
+
},
|
|
20862
|
+
{
|
|
20863
|
+
name: "In Progress",
|
|
20864
|
+
category: "started",
|
|
20865
|
+
position: 2,
|
|
20866
|
+
isDefault: false
|
|
20867
|
+
},
|
|
20868
|
+
{
|
|
20869
|
+
name: "In Review",
|
|
20870
|
+
category: "started",
|
|
20871
|
+
position: 3,
|
|
20872
|
+
isDefault: false
|
|
20873
|
+
},
|
|
20874
|
+
{
|
|
20875
|
+
name: "Awaiting Acceptance",
|
|
20876
|
+
category: "started",
|
|
20877
|
+
position: 4,
|
|
20878
|
+
isDefault: false
|
|
20879
|
+
},
|
|
20880
|
+
{
|
|
20881
|
+
name: "Completed",
|
|
20882
|
+
category: "completed",
|
|
20883
|
+
position: 5,
|
|
20884
|
+
isDefault: false
|
|
20885
|
+
},
|
|
20886
|
+
{
|
|
20887
|
+
name: "Canceled",
|
|
20888
|
+
category: "canceled",
|
|
20889
|
+
position: 6,
|
|
20890
|
+
isDefault: false
|
|
20891
|
+
}
|
|
20892
|
+
];
|
|
20893
|
+
var ProjectWorkStateStore = class {
|
|
20894
|
+
constructor(db, insertActivity) {
|
|
20895
|
+
this.db = db;
|
|
20896
|
+
this.insertActivity = insertActivity;
|
|
20897
|
+
}
|
|
20898
|
+
ensureProject = async (projectId) => {
|
|
20899
|
+
if (this.db().prepare("SELECT COUNT(*) AS total FROM project_work_states WHERE project_id = ?").get(projectId).total > 0) return;
|
|
20900
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
20901
|
+
runSqliteTransaction(this.db(), () => {
|
|
20902
|
+
if (this.db().prepare("SELECT COUNT(*) AS total FROM project_work_states WHERE project_id = ?").get(projectId).total > 0) return;
|
|
20903
|
+
const insert = this.db().prepare(`INSERT INTO project_work_states
|
|
20904
|
+
(id, project_id, name, category, position, is_default, created_at, updated_at)
|
|
20905
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
20906
|
+
for (const state of DEFAULT_PROJECT_WORK_STATES) insert.run(randomUUID(), projectId, state.name, state.category, state.position, state.isDefault ? 1 : 0, now, now);
|
|
20907
|
+
}, "IMMEDIATE");
|
|
20908
|
+
};
|
|
20909
|
+
listStates = async (projectId) => this.db().prepare("SELECT * FROM project_work_states WHERE project_id = ? ORDER BY position, created_at").all(projectId).map(toState);
|
|
20910
|
+
getState = async (projectId, stateId) => {
|
|
20911
|
+
const row = this.db().prepare("SELECT * FROM project_work_states WHERE project_id = ? AND id = ? LIMIT 1").get(projectId, stateId);
|
|
20912
|
+
return row ? toState(row) : null;
|
|
20913
|
+
};
|
|
20914
|
+
createState = async (projectId, input) => {
|
|
20915
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
20916
|
+
const id = randomUUID();
|
|
20917
|
+
const position = input.position ?? this.nextStatePosition(projectId);
|
|
20918
|
+
runSqliteTransaction(this.db(), () => {
|
|
20919
|
+
if (input.isDefault) this.clearDefaultState(projectId);
|
|
20920
|
+
this.db().prepare(`INSERT INTO project_work_states
|
|
20921
|
+
(id, project_id, name, category, position, is_default, created_at, updated_at)
|
|
20922
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`).run(id, projectId, input.name, input.category, position, input.isDefault ? 1 : 0, now, now);
|
|
20923
|
+
}, "IMMEDIATE");
|
|
20924
|
+
return await this.getState(projectId, id);
|
|
20925
|
+
};
|
|
20926
|
+
updateState = async (projectId, stateId, input) => {
|
|
20927
|
+
const current = await this.getState(projectId, stateId);
|
|
20928
|
+
if (!current) return null;
|
|
20929
|
+
const next = {
|
|
20930
|
+
...current,
|
|
20931
|
+
...input,
|
|
20932
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
20933
|
+
};
|
|
20934
|
+
runSqliteTransaction(this.db(), () => {
|
|
20935
|
+
if (next.isDefault) this.clearDefaultState(projectId);
|
|
20936
|
+
this.db().prepare(`UPDATE project_work_states
|
|
20937
|
+
SET name = ?, category = ?, position = ?, is_default = ?, updated_at = ?
|
|
20938
|
+
WHERE project_id = ? AND id = ?`).run(next.name, next.category, next.position, next.isDefault ? 1 : 0, next.updatedAt, projectId, stateId);
|
|
20939
|
+
}, "IMMEDIATE");
|
|
20940
|
+
return await this.getState(projectId, stateId);
|
|
20941
|
+
};
|
|
20942
|
+
deleteState = async (projectId, stateId, migrateToStateId, actor) => {
|
|
20943
|
+
const current = await this.getState(projectId, stateId);
|
|
20944
|
+
if (!current) return false;
|
|
20945
|
+
runSqliteTransaction(this.db(), () => {
|
|
20946
|
+
if (this.db().prepare("SELECT COUNT(*) AS total FROM project_work_items WHERE project_id = ? AND state_id = ?").get(projectId, stateId).total > 0 && !migrateToStateId) throw new Error("PROJECT_WORK_STATE_IN_USE");
|
|
20947
|
+
if (migrateToStateId) this.migrateItems(projectId, stateId, migrateToStateId, actor);
|
|
20948
|
+
this.db().prepare("DELETE FROM project_work_states WHERE project_id = ? AND id = ?").run(projectId, stateId);
|
|
20949
|
+
if (current.isDefault) this.assignReplacementDefault(projectId, migrateToStateId);
|
|
20950
|
+
}, "IMMEDIATE");
|
|
20951
|
+
return true;
|
|
20952
|
+
};
|
|
20953
|
+
migrateItems = (projectId, stateId, migrateToStateId, actor) => {
|
|
20954
|
+
const affected = this.db().prepare("SELECT id FROM project_work_items WHERE project_id = ? AND state_id = ?").all(projectId, stateId);
|
|
20955
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
20956
|
+
this.db().prepare(`UPDATE project_work_items
|
|
20957
|
+
SET state_id = ?, version = version + 1, updated_at = ?
|
|
20958
|
+
WHERE project_id = ? AND state_id = ?`).run(migrateToStateId, now, projectId, stateId);
|
|
20959
|
+
for (const item of affected) this.insertActivity({
|
|
20960
|
+
projectId,
|
|
20961
|
+
workItemId: item.id,
|
|
20962
|
+
type: "state-changed",
|
|
20963
|
+
actor,
|
|
20964
|
+
details: {
|
|
20965
|
+
fromStateId: stateId,
|
|
20966
|
+
toStateId: migrateToStateId,
|
|
20967
|
+
reason: "state-deleted"
|
|
20968
|
+
},
|
|
20969
|
+
createdAt: now
|
|
20970
|
+
});
|
|
20971
|
+
};
|
|
20972
|
+
assignReplacementDefault = (projectId, migrateToStateId) => {
|
|
20973
|
+
const replacement = migrateToStateId ?? this.db().prepare("SELECT id FROM project_work_states WHERE project_id = ? ORDER BY position LIMIT 1").get(projectId)?.id;
|
|
20974
|
+
if (replacement) this.db().prepare("UPDATE project_work_states SET is_default = 1 WHERE project_id = ? AND id = ?").run(projectId, replacement);
|
|
20975
|
+
};
|
|
20976
|
+
nextStatePosition = (projectId) => {
|
|
20977
|
+
return this.db().prepare("SELECT COALESCE(MAX(position), -1) + 1 AS position FROM project_work_states WHERE project_id = ?").get(projectId).position;
|
|
20978
|
+
};
|
|
20979
|
+
clearDefaultState = (projectId) => {
|
|
20980
|
+
this.db().prepare("UPDATE project_work_states SET is_default = 0 WHERE project_id = ?").run(projectId);
|
|
20981
|
+
};
|
|
20982
|
+
};
|
|
20983
|
+
function toState(row) {
|
|
20984
|
+
return {
|
|
20985
|
+
id: row.id,
|
|
20986
|
+
projectId: row.project_id,
|
|
20987
|
+
name: row.name,
|
|
20988
|
+
category: row.category,
|
|
20989
|
+
position: row.position,
|
|
20990
|
+
isDefault: Boolean(row.is_default),
|
|
20991
|
+
createdAt: row.created_at,
|
|
20992
|
+
updatedAt: row.updated_at
|
|
20993
|
+
};
|
|
20994
|
+
}
|
|
20995
|
+
//#endregion
|
|
20996
|
+
//#region src/features/projects/stores/project-work.store.ts
|
|
20997
|
+
var ProjectWorkStore = class {
|
|
20998
|
+
database = null;
|
|
20999
|
+
readyPromise = null;
|
|
21000
|
+
activities = new ProjectWorkActivityStore(() => this.db());
|
|
21001
|
+
states = new ProjectWorkStateStore(() => this.db(), this.activities.insertActivity);
|
|
21002
|
+
constructor(databasePath) {
|
|
21003
|
+
this.databasePath = databasePath;
|
|
21004
|
+
}
|
|
21005
|
+
initialize = async () => await this.ensureReady();
|
|
21006
|
+
close = () => {
|
|
21007
|
+
this.database?.close();
|
|
21008
|
+
this.database = null;
|
|
21009
|
+
this.readyPromise = null;
|
|
21010
|
+
};
|
|
21011
|
+
ensureProject = async (projectId) => {
|
|
21012
|
+
await this.ensureReady();
|
|
21013
|
+
await this.states.ensureProject(projectId);
|
|
21014
|
+
};
|
|
21015
|
+
listStates = async (projectId) => {
|
|
21016
|
+
await this.ensureReady();
|
|
21017
|
+
return await this.states.listStates(projectId);
|
|
21018
|
+
};
|
|
21019
|
+
getState = async (projectId, stateId) => {
|
|
21020
|
+
await this.ensureReady();
|
|
21021
|
+
return await this.states.getState(projectId, stateId);
|
|
21022
|
+
};
|
|
21023
|
+
createState = async (projectId, input) => {
|
|
21024
|
+
await this.ensureReady();
|
|
21025
|
+
return await this.states.createState(projectId, input);
|
|
21026
|
+
};
|
|
21027
|
+
updateState = async (projectId, stateId, input) => {
|
|
21028
|
+
await this.ensureReady();
|
|
21029
|
+
return await this.states.updateState(projectId, stateId, input);
|
|
21030
|
+
};
|
|
21031
|
+
deleteState = async (projectId, stateId, migrateToStateId, actor) => {
|
|
21032
|
+
await this.ensureReady();
|
|
21033
|
+
return await this.states.deleteState(projectId, stateId, migrateToStateId, actor);
|
|
21034
|
+
};
|
|
21035
|
+
listItems = async (projectId, includeDeleted = false) => {
|
|
21036
|
+
await this.ensureReady();
|
|
21037
|
+
return this.db().prepare(`SELECT * FROM project_work_items
|
|
21038
|
+
WHERE project_id = ? ${includeDeleted ? "" : "AND deleted_at IS NULL"}
|
|
21039
|
+
ORDER BY updated_at DESC, created_at DESC`).all(projectId).map(toItem);
|
|
21040
|
+
};
|
|
21041
|
+
getItem = async (projectId, workItemId) => {
|
|
21042
|
+
await this.ensureReady();
|
|
21043
|
+
const row = this.db().prepare("SELECT * FROM project_work_items WHERE project_id = ? AND id = ? LIMIT 1").get(projectId, workItemId);
|
|
21044
|
+
return row ? toItem(row) : null;
|
|
21045
|
+
};
|
|
21046
|
+
createItem = async (params) => {
|
|
21047
|
+
await this.ensureReady();
|
|
21048
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
21049
|
+
const id = randomUUID();
|
|
21050
|
+
runSqliteTransaction(this.db(), () => {
|
|
21051
|
+
this.db().prepare(`INSERT INTO project_work_items
|
|
21052
|
+
(id, project_id, title, description, state_id, attention, version, created_at, updated_at, deleted_at)
|
|
21053
|
+
VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?, NULL)`).run(id, params.projectId, params.input.title, params.input.description ?? "", params.stateId, params.input.attention ?? "none", now, now);
|
|
21054
|
+
this.activities.insertActivity({
|
|
21055
|
+
projectId: params.projectId,
|
|
21056
|
+
workItemId: id,
|
|
21057
|
+
type: "created",
|
|
21058
|
+
actor: params.actor,
|
|
21059
|
+
details: { stateId: params.stateId },
|
|
21060
|
+
createdAt: now
|
|
21061
|
+
});
|
|
21062
|
+
}, "IMMEDIATE");
|
|
21063
|
+
return await this.getItem(params.projectId, id);
|
|
21064
|
+
};
|
|
21065
|
+
updateItem = async (params) => {
|
|
21066
|
+
await this.ensureReady();
|
|
21067
|
+
const { actor, input, projectId, workItemId } = params;
|
|
21068
|
+
const current = await this.getItem(projectId, workItemId);
|
|
21069
|
+
if (!current) return null;
|
|
21070
|
+
if (input.expectedVersion !== void 0 && input.expectedVersion !== current.version) throw new Error("PROJECT_WORK_VERSION_CONFLICT");
|
|
21071
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
21072
|
+
const next = {
|
|
21073
|
+
...current,
|
|
21074
|
+
...input
|
|
21075
|
+
};
|
|
21076
|
+
runSqliteTransaction(this.db(), () => {
|
|
21077
|
+
this.db().prepare(`UPDATE project_work_items SET title = ?, description = ?, state_id = ?, attention = ?,
|
|
21078
|
+
version = version + 1, updated_at = ? WHERE project_id = ? AND id = ?`).run(next.title, next.description, next.stateId, next.attention, now, projectId, workItemId);
|
|
21079
|
+
const stateChanged = next.stateId !== current.stateId;
|
|
21080
|
+
this.activities.insertActivity({
|
|
21081
|
+
projectId,
|
|
21082
|
+
workItemId,
|
|
21083
|
+
type: stateChanged ? "state-changed" : "updated",
|
|
21084
|
+
actor,
|
|
21085
|
+
details: stateChanged ? {
|
|
21086
|
+
fromStateId: current.stateId,
|
|
21087
|
+
toStateId: next.stateId
|
|
21088
|
+
} : changedItemFields(current, next),
|
|
21089
|
+
createdAt: now
|
|
21090
|
+
});
|
|
21091
|
+
}, "IMMEDIATE");
|
|
21092
|
+
return await this.getItem(projectId, workItemId);
|
|
21093
|
+
};
|
|
21094
|
+
setDeleted = async (params) => {
|
|
21095
|
+
await this.ensureReady();
|
|
21096
|
+
const { actor, deleted, projectId, workItemId } = params;
|
|
21097
|
+
if (!await this.getItem(projectId, workItemId)) return null;
|
|
21098
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
21099
|
+
runSqliteTransaction(this.db(), () => {
|
|
21100
|
+
this.db().prepare(`UPDATE project_work_items SET deleted_at = ?, version = version + 1, updated_at = ?
|
|
21101
|
+
WHERE project_id = ? AND id = ?`).run(deleted ? now : null, now, projectId, workItemId);
|
|
21102
|
+
this.activities.insertActivity({
|
|
21103
|
+
projectId,
|
|
21104
|
+
workItemId,
|
|
21105
|
+
type: deleted ? "deleted" : "restored",
|
|
21106
|
+
actor,
|
|
21107
|
+
details: {},
|
|
21108
|
+
createdAt: now
|
|
21109
|
+
});
|
|
21110
|
+
}, "IMMEDIATE");
|
|
21111
|
+
return await this.getItem(projectId, workItemId);
|
|
21112
|
+
};
|
|
21113
|
+
listActivities = async (projectId, workItemId, options) => {
|
|
21114
|
+
await this.ensureReady();
|
|
21115
|
+
return await this.activities.listActivities(projectId, workItemId, options);
|
|
21116
|
+
};
|
|
21117
|
+
listArtifacts = async (projectId, workItemId) => {
|
|
21118
|
+
await this.ensureReady();
|
|
21119
|
+
return await this.activities.listArtifacts(projectId, workItemId);
|
|
21120
|
+
};
|
|
21121
|
+
linkArtifact = async (params) => {
|
|
21122
|
+
await this.ensureReady();
|
|
21123
|
+
return await this.activities.linkArtifact(params);
|
|
21124
|
+
};
|
|
21125
|
+
unlinkArtifact = async (params) => {
|
|
21126
|
+
await this.ensureReady();
|
|
21127
|
+
return await this.activities.unlinkArtifact(params);
|
|
21128
|
+
};
|
|
21129
|
+
ensureReady = async () => {
|
|
21130
|
+
this.readyPromise ??= this.open();
|
|
21131
|
+
await this.readyPromise;
|
|
21132
|
+
};
|
|
21133
|
+
open = async () => {
|
|
21134
|
+
await mkdir(dirname(this.databasePath), { recursive: true });
|
|
21135
|
+
this.database = await openSqliteDatabase(this.databasePath);
|
|
21136
|
+
this.database.exec(`
|
|
21137
|
+
PRAGMA busy_timeout = 10000;
|
|
21138
|
+
PRAGMA journal_mode = WAL;
|
|
21139
|
+
PRAGMA synchronous = NORMAL;
|
|
21140
|
+
PRAGMA foreign_keys = ON;
|
|
21141
|
+
CREATE TABLE IF NOT EXISTS project_work_states (
|
|
21142
|
+
id TEXT PRIMARY KEY,
|
|
21143
|
+
project_id TEXT NOT NULL,
|
|
21144
|
+
name TEXT NOT NULL,
|
|
21145
|
+
category TEXT NOT NULL,
|
|
21146
|
+
position INTEGER NOT NULL,
|
|
21147
|
+
is_default INTEGER NOT NULL DEFAULT 0,
|
|
21148
|
+
created_at TEXT NOT NULL,
|
|
21149
|
+
updated_at TEXT NOT NULL,
|
|
21150
|
+
UNIQUE(project_id, name)
|
|
21151
|
+
);
|
|
21152
|
+
CREATE TABLE IF NOT EXISTS project_work_items (
|
|
21153
|
+
id TEXT PRIMARY KEY,
|
|
21154
|
+
project_id TEXT NOT NULL,
|
|
21155
|
+
title TEXT NOT NULL,
|
|
21156
|
+
description TEXT NOT NULL DEFAULT '',
|
|
21157
|
+
state_id TEXT NOT NULL,
|
|
21158
|
+
attention TEXT NOT NULL DEFAULT 'none',
|
|
21159
|
+
version INTEGER NOT NULL DEFAULT 1,
|
|
21160
|
+
created_at TEXT NOT NULL,
|
|
21161
|
+
updated_at TEXT NOT NULL,
|
|
21162
|
+
deleted_at TEXT,
|
|
21163
|
+
FOREIGN KEY(state_id) REFERENCES project_work_states(id)
|
|
21164
|
+
);
|
|
21165
|
+
CREATE TABLE IF NOT EXISTS project_work_activities (
|
|
21166
|
+
id TEXT PRIMARY KEY,
|
|
21167
|
+
project_id TEXT NOT NULL,
|
|
21168
|
+
work_item_id TEXT NOT NULL,
|
|
21169
|
+
type TEXT NOT NULL,
|
|
21170
|
+
actor_json TEXT NOT NULL,
|
|
21171
|
+
details_json TEXT NOT NULL,
|
|
21172
|
+
created_at TEXT NOT NULL,
|
|
21173
|
+
FOREIGN KEY(work_item_id) REFERENCES project_work_items(id)
|
|
21174
|
+
);
|
|
21175
|
+
CREATE TABLE IF NOT EXISTS project_work_artifact_links (
|
|
21176
|
+
id TEXT PRIMARY KEY,
|
|
21177
|
+
project_id TEXT NOT NULL,
|
|
21178
|
+
work_item_id TEXT NOT NULL,
|
|
21179
|
+
path TEXT NOT NULL,
|
|
21180
|
+
label TEXT,
|
|
21181
|
+
created_at TEXT NOT NULL,
|
|
21182
|
+
UNIQUE(project_id, work_item_id, path),
|
|
21183
|
+
FOREIGN KEY(work_item_id) REFERENCES project_work_items(id)
|
|
21184
|
+
);
|
|
21185
|
+
CREATE INDEX IF NOT EXISTS project_work_items_list_idx
|
|
21186
|
+
ON project_work_items(project_id, deleted_at, updated_at);
|
|
21187
|
+
CREATE INDEX IF NOT EXISTS project_work_activity_timeline_idx
|
|
21188
|
+
ON project_work_activities(project_id, work_item_id, created_at);
|
|
21189
|
+
`);
|
|
21190
|
+
};
|
|
21191
|
+
db = () => {
|
|
21192
|
+
if (!this.database) throw new Error("Project work database is not initialized.");
|
|
21193
|
+
return this.database;
|
|
21194
|
+
};
|
|
21195
|
+
};
|
|
21196
|
+
function toItem(row) {
|
|
21197
|
+
return {
|
|
21198
|
+
id: row.id,
|
|
21199
|
+
projectId: row.project_id,
|
|
21200
|
+
title: row.title,
|
|
21201
|
+
description: row.description,
|
|
21202
|
+
stateId: row.state_id,
|
|
21203
|
+
attention: row.attention,
|
|
21204
|
+
version: row.version,
|
|
21205
|
+
createdAt: row.created_at,
|
|
21206
|
+
updatedAt: row.updated_at,
|
|
21207
|
+
deletedAt: row.deleted_at
|
|
21208
|
+
};
|
|
21209
|
+
}
|
|
21210
|
+
function changedItemFields(current, next) {
|
|
21211
|
+
const fields = {};
|
|
21212
|
+
if (current.title !== next.title) fields.title = {
|
|
21213
|
+
from: current.title,
|
|
21214
|
+
to: next.title
|
|
21215
|
+
};
|
|
21216
|
+
if (current.description !== next.description) fields.descriptionChanged = true;
|
|
21217
|
+
if (current.attention !== next.attention) fields.attention = {
|
|
21218
|
+
from: current.attention,
|
|
21219
|
+
to: next.attention
|
|
21220
|
+
};
|
|
21221
|
+
return fields;
|
|
21222
|
+
}
|
|
21223
|
+
//#endregion
|
|
21224
|
+
//#region src/features/projects/types/project-work-error.types.ts
|
|
21225
|
+
var ProjectWorkError = class extends Error {
|
|
21226
|
+
constructor(code, message) {
|
|
21227
|
+
super(message);
|
|
21228
|
+
this.code = code;
|
|
21229
|
+
this.name = "ProjectWorkError";
|
|
21230
|
+
}
|
|
21231
|
+
};
|
|
21232
|
+
function isProjectWorkError(error) {
|
|
21233
|
+
return error instanceof ProjectWorkError;
|
|
21234
|
+
}
|
|
21235
|
+
//#endregion
|
|
21236
|
+
//#region src/features/projects/types/project-work.types.ts
|
|
21237
|
+
const PROJECT_WORK_STATE_CATEGORIES = [
|
|
21238
|
+
"backlog",
|
|
21239
|
+
"unstarted",
|
|
21240
|
+
"started",
|
|
21241
|
+
"completed",
|
|
21242
|
+
"canceled"
|
|
21243
|
+
];
|
|
21244
|
+
const PROJECT_WORK_ATTENTION_VALUES = [
|
|
21245
|
+
"none",
|
|
21246
|
+
"blocked",
|
|
21247
|
+
"awaiting-user"
|
|
21248
|
+
];
|
|
21249
|
+
//#endregion
|
|
21250
|
+
//#region src/features/projects/managers/project-work.manager.ts
|
|
21251
|
+
var ProjectWorkManager = class {
|
|
21252
|
+
store;
|
|
21253
|
+
constructor(options) {
|
|
21254
|
+
this.options = options;
|
|
21255
|
+
this.store = new ProjectWorkStore(options.databasePath);
|
|
21256
|
+
}
|
|
21257
|
+
initialize = async () => {
|
|
21258
|
+
await this.store.initialize();
|
|
21259
|
+
for (const project of await this.options.projectManager.listProjects()) await this.store.ensureProject(project.id);
|
|
21260
|
+
};
|
|
21261
|
+
dispose = () => this.store.close();
|
|
21262
|
+
ensureProject = async (projectId) => {
|
|
21263
|
+
await this.requireProject(projectId);
|
|
21264
|
+
await this.store.ensureProject(projectId);
|
|
21265
|
+
};
|
|
21266
|
+
list = async (projectId, includeDeleted = false) => {
|
|
21267
|
+
await this.ensureProject(projectId);
|
|
21268
|
+
const [states, items] = await Promise.all([this.store.listStates(projectId), this.store.listItems(projectId, includeDeleted)]);
|
|
21269
|
+
const stateById = new Map(states.map((state) => [state.id, state]));
|
|
21270
|
+
const detailed = await Promise.all(items.map(async (item) => ({
|
|
21271
|
+
...item,
|
|
21272
|
+
state: this.requireMappedState(stateById, item.stateId),
|
|
21273
|
+
artifacts: await this.store.listArtifacts(projectId, item.id)
|
|
21274
|
+
})));
|
|
21275
|
+
return {
|
|
21276
|
+
items: detailed,
|
|
21277
|
+
states,
|
|
21278
|
+
total: detailed.length
|
|
21279
|
+
};
|
|
21280
|
+
};
|
|
21281
|
+
summary = async (projectId) => {
|
|
21282
|
+
const { items } = await this.list(projectId);
|
|
21283
|
+
return {
|
|
21284
|
+
total: items.length,
|
|
21285
|
+
active: items.filter((item) => item.state.category !== "completed" && item.state.category !== "canceled").length,
|
|
21286
|
+
completed: items.filter((item) => item.state.category === "completed").length,
|
|
21287
|
+
attention: items.filter((item) => item.attention !== "none").length,
|
|
21288
|
+
updatedAt: items[0]?.updatedAt ?? null
|
|
21289
|
+
};
|
|
21290
|
+
};
|
|
21291
|
+
get = async (projectId, workItemId) => {
|
|
21292
|
+
await this.ensureProject(projectId);
|
|
21293
|
+
const item = await this.store.getItem(projectId, workItemId);
|
|
21294
|
+
if (!item) throw new ProjectWorkError("PROJECT_WORK_ITEM_NOT_FOUND", "work item was not found");
|
|
21295
|
+
const state = await this.store.getState(projectId, item.stateId);
|
|
21296
|
+
if (!state) throw new ProjectWorkError("PROJECT_WORK_STATE_NOT_FOUND", "work item state was not found");
|
|
21297
|
+
return {
|
|
21298
|
+
...item,
|
|
21299
|
+
state,
|
|
21300
|
+
artifacts: await this.store.listArtifacts(projectId, workItemId)
|
|
21301
|
+
};
|
|
21302
|
+
};
|
|
21303
|
+
create = async (projectId, input, actor) => {
|
|
21304
|
+
await this.ensureProject(projectId);
|
|
21305
|
+
const title = this.requireName(input.title, "work item title");
|
|
21306
|
+
const attention = input.attention ?? "none";
|
|
21307
|
+
this.assertAttention(attention);
|
|
21308
|
+
const states = await this.store.listStates(projectId);
|
|
21309
|
+
const state = input.stateId ? states.find((entry) => entry.id === input.stateId) : states.find((entry) => entry.isDefault);
|
|
21310
|
+
if (!state) throw new ProjectWorkError("PROJECT_WORK_STATE_NOT_FOUND", "work item state was not found");
|
|
21311
|
+
const item = await this.store.createItem({
|
|
21312
|
+
projectId,
|
|
21313
|
+
actor: this.normalizeActor(actor),
|
|
21314
|
+
stateId: state.id,
|
|
21315
|
+
input: {
|
|
21316
|
+
title,
|
|
21317
|
+
description: input.description?.trim() ?? "",
|
|
21318
|
+
attention
|
|
21319
|
+
}
|
|
21320
|
+
});
|
|
21321
|
+
await this.emit(projectId, "created", item.id);
|
|
21322
|
+
return await this.get(projectId, item.id);
|
|
21323
|
+
};
|
|
21324
|
+
update = async (projectId, workItemId, input, actor) => {
|
|
21325
|
+
await this.ensureProject(projectId);
|
|
21326
|
+
const current = await this.get(projectId, workItemId);
|
|
21327
|
+
if (input.expectedVersion !== void 0 && input.expectedVersion !== current.version) throw new ProjectWorkError("PROJECT_WORK_VERSION_CONFLICT", "work item changed since it was loaded");
|
|
21328
|
+
const normalizedInput = {
|
|
21329
|
+
...input,
|
|
21330
|
+
...input.title !== void 0 ? { title: this.requireName(input.title, "work item title") } : {},
|
|
21331
|
+
...input.description !== void 0 ? { description: input.description.trim() } : {}
|
|
21332
|
+
};
|
|
21333
|
+
if (normalizedInput.attention !== void 0) this.assertAttention(normalizedInput.attention);
|
|
21334
|
+
if (normalizedInput.stateId !== void 0 && !await this.store.getState(projectId, normalizedInput.stateId)) throw new ProjectWorkError("PROJECT_WORK_STATE_NOT_FOUND", "work item state was not found");
|
|
21335
|
+
if ((normalizedInput.title === void 0 || normalizedInput.title === current.title) && (normalizedInput.description === void 0 || normalizedInput.description === current.description) && (normalizedInput.stateId === void 0 || normalizedInput.stateId === current.stateId) && (normalizedInput.attention === void 0 || normalizedInput.attention === current.attention)) return current;
|
|
21336
|
+
let item;
|
|
21337
|
+
try {
|
|
21338
|
+
item = await this.store.updateItem({
|
|
21339
|
+
projectId,
|
|
21340
|
+
workItemId,
|
|
21341
|
+
input: normalizedInput,
|
|
21342
|
+
actor: this.normalizeActor(actor)
|
|
21343
|
+
});
|
|
21344
|
+
} catch (error) {
|
|
21345
|
+
if (error instanceof Error && error.message === "PROJECT_WORK_VERSION_CONFLICT") throw new ProjectWorkError("PROJECT_WORK_VERSION_CONFLICT", "work item changed since it was loaded");
|
|
21346
|
+
throw error;
|
|
21347
|
+
}
|
|
21348
|
+
if (!item) throw new ProjectWorkError("PROJECT_WORK_ITEM_NOT_FOUND", "work item was not found");
|
|
21349
|
+
await this.emit(projectId, "updated", workItemId);
|
|
21350
|
+
return await this.get(projectId, workItemId);
|
|
21351
|
+
};
|
|
21352
|
+
delete = async (projectId, workItemId, actor) => {
|
|
21353
|
+
return await this.setDeleted(projectId, workItemId, true, actor);
|
|
21354
|
+
};
|
|
21355
|
+
restore = async (projectId, workItemId, actor) => {
|
|
21356
|
+
return await this.setDeleted(projectId, workItemId, false, actor);
|
|
21357
|
+
};
|
|
21358
|
+
listActivities = async (projectId, workItemId, options = {}) => {
|
|
21359
|
+
await this.get(projectId, workItemId);
|
|
21360
|
+
if (options.cursor && !/^\d+$/.test(options.cursor)) throw new ProjectWorkError("PROJECT_WORK_VALIDATION_FAILED", "work item activity cursor is invalid");
|
|
21361
|
+
return await this.store.listActivities(projectId, workItemId, {
|
|
21362
|
+
...options.cursor ? { cursor: options.cursor } : {},
|
|
21363
|
+
limit: Math.min(100, Math.max(1, options.limit ?? 50))
|
|
21364
|
+
});
|
|
21365
|
+
};
|
|
21366
|
+
listStates = async (projectId) => {
|
|
21367
|
+
await this.ensureProject(projectId);
|
|
21368
|
+
return await this.store.listStates(projectId);
|
|
21369
|
+
};
|
|
21370
|
+
createState = async (projectId, input) => {
|
|
21371
|
+
await this.ensureProject(projectId);
|
|
21372
|
+
const normalized = {
|
|
21373
|
+
...input,
|
|
21374
|
+
name: this.requireName(input.name, "state name")
|
|
21375
|
+
};
|
|
21376
|
+
this.assertCategory(normalized.category);
|
|
21377
|
+
if ((await this.store.listStates(projectId)).some((state) => state.name.toLocaleLowerCase() === normalized.name.toLocaleLowerCase())) throw new ProjectWorkError("PROJECT_WORK_STATE_INVALID", "state name is already in use");
|
|
21378
|
+
const state = await this.store.createState(projectId, normalized);
|
|
21379
|
+
await this.emit(projectId, "state-config");
|
|
21380
|
+
return state;
|
|
21381
|
+
};
|
|
21382
|
+
updateState = async (projectId, stateId, input) => {
|
|
21383
|
+
await this.ensureProject(projectId);
|
|
21384
|
+
const normalizedInput = {
|
|
21385
|
+
...input,
|
|
21386
|
+
...input.name !== void 0 ? { name: this.requireName(input.name, "state name") } : {}
|
|
21387
|
+
};
|
|
21388
|
+
if (normalizedInput.category !== void 0) this.assertCategory(normalizedInput.category);
|
|
21389
|
+
const current = await this.store.getState(projectId, stateId);
|
|
21390
|
+
if (!current) throw new ProjectWorkError("PROJECT_WORK_STATE_NOT_FOUND", "work item state was not found");
|
|
21391
|
+
if (current.isDefault && normalizedInput.isDefault === false) throw new ProjectWorkError("PROJECT_WORK_STATE_INVALID", "choose another default state before clearing this one");
|
|
21392
|
+
if (normalizedInput.name && (await this.store.listStates(projectId)).some((state) => state.id !== stateId && state.name.toLocaleLowerCase() === normalizedInput.name.toLocaleLowerCase())) throw new ProjectWorkError("PROJECT_WORK_STATE_INVALID", "state name is already in use");
|
|
21393
|
+
const state = await this.store.updateState(projectId, stateId, normalizedInput);
|
|
21394
|
+
if (!state) throw new ProjectWorkError("PROJECT_WORK_STATE_NOT_FOUND", "work item state was not found");
|
|
21395
|
+
await this.emit(projectId, "state-config");
|
|
21396
|
+
return state;
|
|
21397
|
+
};
|
|
21398
|
+
deleteState = async (projectId, stateId, migrateToStateId, actor) => {
|
|
21399
|
+
await this.ensureProject(projectId);
|
|
21400
|
+
const states = await this.store.listStates(projectId);
|
|
21401
|
+
if (states.length <= 1) throw new ProjectWorkError("PROJECT_WORK_STATE_INVALID", "a project must keep at least one state");
|
|
21402
|
+
if (migrateToStateId === stateId) throw new ProjectWorkError("PROJECT_WORK_STATE_INVALID", "migration target must be a different state");
|
|
21403
|
+
if (migrateToStateId && !states.some((state) => state.id === migrateToStateId)) throw new ProjectWorkError("PROJECT_WORK_STATE_NOT_FOUND", "migration target state was not found");
|
|
21404
|
+
try {
|
|
21405
|
+
if (!await this.store.deleteState(projectId, stateId, migrateToStateId, this.normalizeActor(actor))) throw new ProjectWorkError("PROJECT_WORK_STATE_NOT_FOUND", "work item state was not found");
|
|
21406
|
+
} catch (error) {
|
|
21407
|
+
if (error instanceof Error && error.message === "PROJECT_WORK_STATE_IN_USE") throw new ProjectWorkError("PROJECT_WORK_STATE_IN_USE", "state is used by work items; choose a migration target");
|
|
21408
|
+
throw error;
|
|
21409
|
+
}
|
|
21410
|
+
await this.emit(projectId, "state-config");
|
|
21411
|
+
};
|
|
21412
|
+
linkArtifact = async (params) => {
|
|
21413
|
+
const { actor, label, path, projectId, workItemId } = params;
|
|
21414
|
+
const project = await this.requireProject(projectId);
|
|
21415
|
+
await this.get(projectId, workItemId);
|
|
21416
|
+
const artifactPath = await this.normalizeArtifactPath(project.rootPath, path);
|
|
21417
|
+
const existing = (await this.store.listArtifacts(projectId, workItemId)).find((link) => link.path === artifactPath);
|
|
21418
|
+
if (existing) return existing;
|
|
21419
|
+
const link = await this.store.linkArtifact({
|
|
21420
|
+
projectId,
|
|
21421
|
+
workItemId,
|
|
21422
|
+
path: artifactPath,
|
|
21423
|
+
label: label?.trim() || void 0,
|
|
21424
|
+
actor: this.normalizeActor(actor)
|
|
21425
|
+
});
|
|
21426
|
+
await this.emit(projectId, "artifact", workItemId);
|
|
21427
|
+
return link;
|
|
21428
|
+
};
|
|
21429
|
+
unlinkArtifact = async (params) => {
|
|
21430
|
+
const { actor, artifactLinkId, projectId, workItemId } = params;
|
|
21431
|
+
await this.get(projectId, workItemId);
|
|
21432
|
+
if (!await this.store.unlinkArtifact({
|
|
21433
|
+
projectId,
|
|
21434
|
+
workItemId,
|
|
21435
|
+
artifactLinkId,
|
|
21436
|
+
actor: this.normalizeActor(actor)
|
|
21437
|
+
})) throw new ProjectWorkError("PROJECT_WORK_ARTIFACT_NOT_FOUND", "artifact link was not found");
|
|
21438
|
+
await this.emit(projectId, "artifact", workItemId);
|
|
21439
|
+
};
|
|
21440
|
+
setDeleted = async (projectId, workItemId, deleted, actor) => {
|
|
21441
|
+
await this.ensureProject(projectId);
|
|
21442
|
+
const current = await this.get(projectId, workItemId);
|
|
21443
|
+
if (current.deletedAt !== null === deleted) return current;
|
|
21444
|
+
if (!await this.store.setDeleted({
|
|
21445
|
+
projectId,
|
|
21446
|
+
workItemId,
|
|
21447
|
+
deleted,
|
|
21448
|
+
actor: this.normalizeActor(actor)
|
|
21449
|
+
})) throw new ProjectWorkError("PROJECT_WORK_ITEM_NOT_FOUND", "work item was not found");
|
|
21450
|
+
await this.emit(projectId, deleted ? "deleted" : "restored", workItemId);
|
|
21451
|
+
return await this.get(projectId, workItemId);
|
|
21452
|
+
};
|
|
21453
|
+
requireProject = async (projectId) => {
|
|
21454
|
+
const normalized = projectId.trim();
|
|
21455
|
+
const project = normalized ? await this.options.projectManager.getProjectById(normalized) : null;
|
|
21456
|
+
if (!project) throw new ProjectWorkError("PROJECT_NOT_FOUND", "project was not found");
|
|
21457
|
+
return project;
|
|
21458
|
+
};
|
|
21459
|
+
requireMappedState = (states, stateId) => {
|
|
21460
|
+
const state = states.get(stateId);
|
|
21461
|
+
if (!state) throw new ProjectWorkError("PROJECT_WORK_STATE_NOT_FOUND", "work item state was not found");
|
|
21462
|
+
return state;
|
|
21463
|
+
};
|
|
21464
|
+
requireName = (value, label) => {
|
|
21465
|
+
const normalized = typeof value === "string" ? value.trim() : "";
|
|
21466
|
+
if (!normalized) throw new ProjectWorkError("PROJECT_WORK_VALIDATION_FAILED", `${label} is required`);
|
|
21467
|
+
if (normalized.length > 200) throw new ProjectWorkError("PROJECT_WORK_VALIDATION_FAILED", `${label} is too long`);
|
|
21468
|
+
return normalized;
|
|
21469
|
+
};
|
|
21470
|
+
assertAttention = (value) => {
|
|
21471
|
+
if (!PROJECT_WORK_ATTENTION_VALUES.some((entry) => entry === value)) throw new ProjectWorkError("PROJECT_WORK_VALIDATION_FAILED", "work item attention is invalid");
|
|
21472
|
+
};
|
|
21473
|
+
assertCategory = (value) => {
|
|
21474
|
+
if (!PROJECT_WORK_STATE_CATEGORIES.some((entry) => entry === value)) throw new ProjectWorkError("PROJECT_WORK_VALIDATION_FAILED", "work item state category is invalid");
|
|
21475
|
+
};
|
|
21476
|
+
normalizeActor = (actor) => ({
|
|
21477
|
+
kind: actor.kind,
|
|
21478
|
+
...actor.id?.trim() ? { id: actor.id.trim() } : {},
|
|
21479
|
+
...actor.sessionId?.trim() ? { sessionId: actor.sessionId.trim() } : {}
|
|
21480
|
+
});
|
|
21481
|
+
normalizeArtifactPath = async (rootPath, inputPath) => {
|
|
21482
|
+
const requested = inputPath.trim();
|
|
21483
|
+
if (!requested) throw new ProjectWorkError("PROJECT_WORK_ARTIFACT_INVALID", "artifact path is required");
|
|
21484
|
+
const canonicalRoot = await realpath(rootPath);
|
|
21485
|
+
const candidate = await realpath(isAbsolute(requested) ? requested : resolve(canonicalRoot, requested)).catch(() => null);
|
|
21486
|
+
if (!candidate) throw new ProjectWorkError("PROJECT_WORK_ARTIFACT_NOT_FOUND", "artifact file does not exist");
|
|
21487
|
+
const relativePath = relative(canonicalRoot, candidate);
|
|
21488
|
+
if (!relativePath || relativePath === ".." || relativePath.startsWith(`..${sep}`) || isAbsolute(relativePath)) throw new ProjectWorkError("PROJECT_WORK_ARTIFACT_INVALID", "artifact must be a file inside the project root");
|
|
21489
|
+
if (!(await stat(candidate)).isFile()) throw new ProjectWorkError("PROJECT_WORK_ARTIFACT_INVALID", "artifact must be a file");
|
|
21490
|
+
return relativePath.split(sep).join("/");
|
|
21491
|
+
};
|
|
21492
|
+
emit = async (projectId, change, workItemId) => {
|
|
21493
|
+
await this.options.eventBus.emit(eventKeys$1.projectWorkChanged, {
|
|
21494
|
+
projectId,
|
|
21495
|
+
change,
|
|
21496
|
+
...workItemId ? { workItemId } : {}
|
|
21497
|
+
});
|
|
21498
|
+
};
|
|
21499
|
+
};
|
|
21500
|
+
//#endregion
|
|
21501
|
+
//#region src/features/projects/types/project-observation.types.ts
|
|
21502
|
+
const PROJECT_OBSERVATION_PROTOCOL = "nextclaw.project/v1";
|
|
21503
|
+
var ProjectObservationError = class extends Error {
|
|
21504
|
+
constructor(code, message) {
|
|
21505
|
+
super(message);
|
|
21506
|
+
this.code = code;
|
|
21507
|
+
this.name = "ProjectObservationError";
|
|
21508
|
+
}
|
|
21509
|
+
};
|
|
21510
|
+
function isProjectObservationError(error) {
|
|
21511
|
+
return error instanceof ProjectObservationError;
|
|
21512
|
+
}
|
|
21513
|
+
//#endregion
|
|
21514
|
+
//#region src/features/projects/utils/project-observation-config.utils.ts
|
|
21515
|
+
const isRecord$8 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
21516
|
+
const readString$7 = (value) => {
|
|
21517
|
+
if (typeof value !== "string") return null;
|
|
21518
|
+
return value.trim() || null;
|
|
21519
|
+
};
|
|
21520
|
+
const collectUnknownKeyIssues = (value, allowed, owner) => Object.keys(value).flatMap((key) => allowed.includes(key) ? [] : [{
|
|
21521
|
+
code: "PROJECT_CONFIG_UNKNOWN_FIELD",
|
|
21522
|
+
message: `${owner} contains unknown field '${key}'.`
|
|
21523
|
+
}]);
|
|
21524
|
+
function parseContext(value) {
|
|
21525
|
+
if (value === void 0) return {
|
|
21526
|
+
value: [],
|
|
21527
|
+
issues: []
|
|
21528
|
+
};
|
|
21529
|
+
if (!Array.isArray(value)) return {
|
|
21530
|
+
value: [],
|
|
21531
|
+
issues: [{
|
|
21532
|
+
code: "PROJECT_CONFIG_CONTEXT_INVALID",
|
|
21533
|
+
message: "project.context must be an array."
|
|
21534
|
+
}]
|
|
21535
|
+
};
|
|
21536
|
+
const issues = [];
|
|
21537
|
+
return {
|
|
21538
|
+
value: value.flatMap((entry, index) => {
|
|
21539
|
+
if (!isRecord$8(entry)) {
|
|
21540
|
+
issues.push({
|
|
21541
|
+
code: "PROJECT_CONFIG_CONTEXT_INVALID",
|
|
21542
|
+
message: `project.context[${index}] must be an object.`
|
|
21543
|
+
});
|
|
21544
|
+
return [];
|
|
21545
|
+
}
|
|
21546
|
+
issues.push(...collectUnknownKeyIssues(entry, [
|
|
21547
|
+
"id",
|
|
21548
|
+
"role",
|
|
21549
|
+
"source"
|
|
21550
|
+
], `project.context[${index}]`));
|
|
21551
|
+
const id = readString$7(entry.id);
|
|
21552
|
+
const role = readString$7(entry.role);
|
|
21553
|
+
const source = readString$7(entry.source);
|
|
21554
|
+
if (!id || !role || !source) {
|
|
21555
|
+
issues.push({
|
|
21556
|
+
code: "PROJECT_CONFIG_CONTEXT_INVALID",
|
|
21557
|
+
message: `project.context[${index}] requires id, role and source.`
|
|
21558
|
+
});
|
|
21559
|
+
return [];
|
|
21560
|
+
}
|
|
21561
|
+
return [{
|
|
21562
|
+
id,
|
|
21563
|
+
role,
|
|
21564
|
+
source
|
|
21565
|
+
}];
|
|
21566
|
+
}),
|
|
21567
|
+
issues
|
|
21568
|
+
};
|
|
21569
|
+
}
|
|
21570
|
+
function parseWorkflows(value) {
|
|
21571
|
+
if (value === void 0) return {
|
|
21572
|
+
value: [],
|
|
21573
|
+
issues: []
|
|
21574
|
+
};
|
|
21575
|
+
if (!Array.isArray(value)) return {
|
|
21576
|
+
value: [],
|
|
21577
|
+
issues: [{
|
|
21578
|
+
code: "PROJECT_CONFIG_WORKFLOWS_INVALID",
|
|
21579
|
+
message: "workflows must be an array."
|
|
21580
|
+
}]
|
|
21581
|
+
};
|
|
21582
|
+
const issues = [];
|
|
21583
|
+
return {
|
|
21584
|
+
value: value.flatMap((entry, workflowIndex) => {
|
|
21585
|
+
if (!isRecord$8(entry) || !Array.isArray(entry.stages)) {
|
|
21586
|
+
issues.push({
|
|
21587
|
+
code: "PROJECT_CONFIG_WORKFLOW_INVALID",
|
|
21588
|
+
message: `workflows[${workflowIndex}] requires a stages array.`
|
|
21589
|
+
});
|
|
21590
|
+
return [];
|
|
21591
|
+
}
|
|
21592
|
+
issues.push(...collectUnknownKeyIssues(entry, [
|
|
21593
|
+
"id",
|
|
21594
|
+
"label",
|
|
21595
|
+
"stages"
|
|
21596
|
+
], `workflows[${workflowIndex}]`));
|
|
21597
|
+
const id = readString$7(entry.id);
|
|
21598
|
+
const label = readString$7(entry.label);
|
|
21599
|
+
const stages = entry.stages.flatMap((stage, stageIndex) => {
|
|
21600
|
+
if (!isRecord$8(stage)) {
|
|
21601
|
+
issues.push({
|
|
21602
|
+
code: "PROJECT_CONFIG_STAGE_INVALID",
|
|
21603
|
+
message: `workflows[${workflowIndex}].stages[${stageIndex}] must be an object.`
|
|
21604
|
+
});
|
|
21605
|
+
return [];
|
|
21606
|
+
}
|
|
21607
|
+
issues.push(...collectUnknownKeyIssues(stage, ["id", "label"], `workflows[${workflowIndex}].stages[${stageIndex}]`));
|
|
21608
|
+
const stageId = readString$7(stage.id);
|
|
21609
|
+
const stageLabel = readString$7(stage.label);
|
|
21610
|
+
if (!stageId || !stageLabel) {
|
|
21611
|
+
issues.push({
|
|
21612
|
+
code: "PROJECT_CONFIG_STAGE_INVALID",
|
|
21613
|
+
message: `workflows[${workflowIndex}].stages[${stageIndex}] requires id and label.`
|
|
21614
|
+
});
|
|
21615
|
+
return [];
|
|
21616
|
+
}
|
|
21617
|
+
return [{
|
|
21618
|
+
id: stageId,
|
|
21619
|
+
label: stageLabel
|
|
21620
|
+
}];
|
|
21621
|
+
});
|
|
21622
|
+
if (!id || !label) {
|
|
21623
|
+
issues.push({
|
|
21624
|
+
code: "PROJECT_CONFIG_WORKFLOW_INVALID",
|
|
21625
|
+
message: `workflows[${workflowIndex}] requires id and label.`
|
|
21626
|
+
});
|
|
21627
|
+
return [];
|
|
21628
|
+
}
|
|
21629
|
+
return [{
|
|
21630
|
+
id,
|
|
21631
|
+
label,
|
|
21632
|
+
stages
|
|
21633
|
+
}];
|
|
21634
|
+
}),
|
|
21635
|
+
issues
|
|
21636
|
+
};
|
|
21637
|
+
}
|
|
21638
|
+
function parseArtifactCategories(value) {
|
|
21639
|
+
if (value === void 0) return {
|
|
21640
|
+
value: [],
|
|
21641
|
+
issues: []
|
|
21642
|
+
};
|
|
21643
|
+
if (!Array.isArray(value)) return {
|
|
21644
|
+
value: [],
|
|
21645
|
+
issues: [{
|
|
21646
|
+
code: "PROJECT_CONFIG_ARTIFACTS_INVALID",
|
|
21647
|
+
message: "observation.artifacts must be an array."
|
|
21648
|
+
}]
|
|
21649
|
+
};
|
|
21650
|
+
const issues = [];
|
|
21651
|
+
return {
|
|
21652
|
+
value: value.flatMap((entry, index) => {
|
|
21653
|
+
if (!isRecord$8(entry) || !Array.isArray(entry.include)) {
|
|
21654
|
+
issues.push({
|
|
21655
|
+
code: "PROJECT_CONFIG_ARTIFACT_INVALID",
|
|
21656
|
+
message: `observation.artifacts[${index}] requires an include array.`
|
|
21657
|
+
});
|
|
21658
|
+
return [];
|
|
21659
|
+
}
|
|
21660
|
+
issues.push(...collectUnknownKeyIssues(entry, [
|
|
21661
|
+
"id",
|
|
21662
|
+
"label",
|
|
21663
|
+
"include"
|
|
21664
|
+
], `observation.artifacts[${index}]`));
|
|
21665
|
+
const id = readString$7(entry.id);
|
|
21666
|
+
const label = readString$7(entry.label);
|
|
21667
|
+
const include = entry.include.map(readString$7).filter((pattern) => pattern !== null);
|
|
21668
|
+
if (!id || !label || include.length !== entry.include.length || include.length === 0) {
|
|
21669
|
+
issues.push({
|
|
21670
|
+
code: "PROJECT_CONFIG_ARTIFACT_INVALID",
|
|
21671
|
+
message: `observation.artifacts[${index}] requires id, label and non-empty string patterns.`
|
|
21672
|
+
});
|
|
21673
|
+
return [];
|
|
21674
|
+
}
|
|
21675
|
+
return [{
|
|
21676
|
+
id,
|
|
21677
|
+
label,
|
|
21678
|
+
include
|
|
21679
|
+
}];
|
|
21680
|
+
}),
|
|
21681
|
+
issues
|
|
21682
|
+
};
|
|
21683
|
+
}
|
|
21684
|
+
function parseSkillRoots(value) {
|
|
21685
|
+
if (value === void 0) return {
|
|
21686
|
+
value: [".agents/skills"],
|
|
21687
|
+
issues: []
|
|
21688
|
+
};
|
|
21689
|
+
if (!Array.isArray(value)) return {
|
|
21690
|
+
value: [],
|
|
21691
|
+
issues: [{
|
|
21692
|
+
code: "PROJECT_CONFIG_SKILLS_INVALID",
|
|
21693
|
+
message: "observation.skills must be an array."
|
|
21694
|
+
}]
|
|
21695
|
+
};
|
|
21696
|
+
const issues = [];
|
|
21697
|
+
return {
|
|
21698
|
+
value: value.flatMap((entry, index) => {
|
|
21699
|
+
if (!isRecord$8(entry)) {
|
|
21700
|
+
issues.push({
|
|
21701
|
+
code: "PROJECT_CONFIG_SKILL_INVALID",
|
|
21702
|
+
message: `observation.skills[${index}] must be an object.`
|
|
21703
|
+
});
|
|
21704
|
+
return [];
|
|
21705
|
+
}
|
|
21706
|
+
issues.push(...collectUnknownKeyIssues(entry, ["root"], `observation.skills[${index}]`));
|
|
21707
|
+
const root = readString$7(entry.root);
|
|
21708
|
+
if (!root) {
|
|
21709
|
+
issues.push({
|
|
21710
|
+
code: "PROJECT_CONFIG_SKILL_INVALID",
|
|
21711
|
+
message: `observation.skills[${index}] requires root.`
|
|
21712
|
+
});
|
|
21713
|
+
return [];
|
|
21714
|
+
}
|
|
21715
|
+
return [root];
|
|
21716
|
+
}),
|
|
21717
|
+
issues
|
|
21718
|
+
};
|
|
21719
|
+
}
|
|
21720
|
+
function parseProjectObservationConfig(source) {
|
|
21721
|
+
const issues = [];
|
|
21722
|
+
let parsed;
|
|
21723
|
+
try {
|
|
21724
|
+
parsed = parse(source);
|
|
21725
|
+
} catch (error) {
|
|
21726
|
+
return {
|
|
21727
|
+
config: null,
|
|
21728
|
+
issues: [{
|
|
21729
|
+
code: "PROJECT_CONFIG_YAML_INVALID",
|
|
21730
|
+
message: error instanceof Error ? error.message : "project.yaml is invalid YAML."
|
|
21731
|
+
}]
|
|
21732
|
+
};
|
|
21733
|
+
}
|
|
21734
|
+
if (!isRecord$8(parsed)) return {
|
|
21735
|
+
config: null,
|
|
21736
|
+
issues: [{
|
|
21737
|
+
code: "PROJECT_CONFIG_INVALID",
|
|
21738
|
+
message: "project.yaml must contain an object."
|
|
21739
|
+
}]
|
|
21740
|
+
};
|
|
21741
|
+
issues.push(...collectUnknownKeyIssues(parsed, [
|
|
21742
|
+
"schema_version",
|
|
21743
|
+
"project",
|
|
21744
|
+
"workflows",
|
|
21745
|
+
"observation"
|
|
21746
|
+
], "project.yaml"));
|
|
21747
|
+
if (parsed.schema_version !== 1) return {
|
|
21748
|
+
config: null,
|
|
21749
|
+
issues: [{
|
|
21750
|
+
code: "PROJECT_CONFIG_VERSION_UNSUPPORTED",
|
|
21751
|
+
message: "Only schema_version 1 is supported."
|
|
21752
|
+
}, ...issues]
|
|
21753
|
+
};
|
|
21754
|
+
const project = isRecord$8(parsed.project) ? parsed.project : {};
|
|
21755
|
+
const observation = isRecord$8(parsed.observation) ? parsed.observation : {};
|
|
21756
|
+
if (parsed.project !== void 0 && !isRecord$8(parsed.project)) issues.push({
|
|
21757
|
+
code: "PROJECT_CONFIG_PROJECT_INVALID",
|
|
21758
|
+
message: "project must be an object."
|
|
21759
|
+
});
|
|
21760
|
+
if (parsed.observation !== void 0 && !isRecord$8(parsed.observation)) issues.push({
|
|
21761
|
+
code: "PROJECT_CONFIG_OBSERVATION_INVALID",
|
|
21762
|
+
message: "observation must be an object."
|
|
21763
|
+
});
|
|
21764
|
+
issues.push(...collectUnknownKeyIssues(project, ["summary", "context"], "project"));
|
|
21765
|
+
issues.push(...collectUnknownKeyIssues(observation, [
|
|
21766
|
+
"markers",
|
|
21767
|
+
"work_items",
|
|
21768
|
+
"artifacts",
|
|
21769
|
+
"skills"
|
|
21770
|
+
], "observation"));
|
|
21771
|
+
if (observation.markers !== void 0) {
|
|
21772
|
+
if (!(Array.isArray(observation.markers) ? observation.markers : []).some((marker) => isRecord$8(marker) && marker.protocol === "nextclaw.project/v1")) issues.push({
|
|
21773
|
+
code: "PROJECT_CONFIG_MARKERS_INVALID",
|
|
21774
|
+
message: `observation.markers must enable protocol '${PROJECT_OBSERVATION_PROTOCOL}'.`
|
|
21775
|
+
});
|
|
21776
|
+
}
|
|
21777
|
+
const context = parseContext(project.context);
|
|
21778
|
+
const workflows = parseWorkflows(parsed.workflows);
|
|
21779
|
+
const artifactCategories = parseArtifactCategories(observation.artifacts);
|
|
21780
|
+
const skillRoots = parseSkillRoots(observation.skills);
|
|
21781
|
+
issues.push(...context.issues, ...workflows.issues, ...artifactCategories.issues, ...skillRoots.issues);
|
|
21782
|
+
return {
|
|
21783
|
+
config: {
|
|
21784
|
+
...readString$7(project.summary) ? { summary: readString$7(project.summary) } : {},
|
|
21785
|
+
context: context.value,
|
|
21786
|
+
workflows: workflows.value,
|
|
21787
|
+
artifactCategories: artifactCategories.value,
|
|
21788
|
+
skillRoots: skillRoots.value
|
|
21789
|
+
},
|
|
21790
|
+
issues
|
|
21791
|
+
};
|
|
21792
|
+
}
|
|
21793
|
+
//#endregion
|
|
21794
|
+
//#region src/features/projects/utils/project-observation-files.utils.ts
|
|
21795
|
+
const IGNORED_DIRECTORIES = new Set([
|
|
21796
|
+
".git",
|
|
21797
|
+
".hg",
|
|
21798
|
+
".svn",
|
|
21799
|
+
"node_modules"
|
|
21800
|
+
]);
|
|
21801
|
+
const MAX_ARTIFACT_FILES = 1e4;
|
|
21802
|
+
const MAX_DEPTH = 12;
|
|
21803
|
+
const toPortablePath = (value) => value.split(sep).join("/");
|
|
21804
|
+
function globSearchBase(pattern) {
|
|
21805
|
+
const segments = pattern.split("/");
|
|
21806
|
+
const firstDynamicSegment = segments.findIndex((segment) => segment.includes("*") || segment.includes("?"));
|
|
21807
|
+
if (firstDynamicSegment >= 0) return segments.slice(0, firstDynamicSegment).join("/");
|
|
21808
|
+
return segments.slice(0, -1).join("/");
|
|
21809
|
+
}
|
|
21810
|
+
function shouldVisitDirectory(relativeDirectory, searchBases) {
|
|
21811
|
+
if (!relativeDirectory) return true;
|
|
21812
|
+
return searchBases.some((base) => !base || base === relativeDirectory || base.startsWith(`${relativeDirectory}/`) || relativeDirectory.startsWith(`${base}/`));
|
|
21813
|
+
}
|
|
21814
|
+
function compareFileRecency(left, right) {
|
|
21815
|
+
return left.updatedAt.localeCompare(right.updatedAt) || left.relativePath.localeCompare(right.relativePath);
|
|
21816
|
+
}
|
|
21817
|
+
function siftOldestFileDown(files, index) {
|
|
21818
|
+
let parent = index;
|
|
21819
|
+
while (true) {
|
|
21820
|
+
const left = parent * 2 + 1;
|
|
21821
|
+
const right = left + 1;
|
|
21822
|
+
let oldest = parent;
|
|
21823
|
+
if (left < files.length && compareFileRecency(files[left], files[oldest]) < 0) oldest = left;
|
|
21824
|
+
if (right < files.length && compareFileRecency(files[right], files[oldest]) < 0) oldest = right;
|
|
21825
|
+
if (oldest === parent) return;
|
|
21826
|
+
[files[parent], files[oldest]] = [files[oldest], files[parent]];
|
|
21827
|
+
parent = oldest;
|
|
21828
|
+
}
|
|
21829
|
+
}
|
|
21830
|
+
function keepMostRecentArtifactFile(files, file, maxArtifactFiles) {
|
|
21831
|
+
if (files.length < maxArtifactFiles) {
|
|
21832
|
+
files.push(file);
|
|
21833
|
+
let child = files.length - 1;
|
|
21834
|
+
while (child > 0) {
|
|
21835
|
+
const parent = Math.floor((child - 1) / 2);
|
|
21836
|
+
if (compareFileRecency(files[child], files[parent]) >= 0) break;
|
|
21837
|
+
[files[child], files[parent]] = [files[parent], files[child]];
|
|
21838
|
+
child = parent;
|
|
21839
|
+
}
|
|
21840
|
+
return;
|
|
21841
|
+
}
|
|
21842
|
+
if (compareFileRecency(file, files[0]) <= 0) return;
|
|
21843
|
+
files[0] = file;
|
|
21844
|
+
siftOldestFileDown(files, 0);
|
|
21845
|
+
}
|
|
21846
|
+
function normalizeProjectRelativePath(value) {
|
|
21847
|
+
const normalized = value.trim().replaceAll("\\", "/").replace(/^\.\//, "");
|
|
21848
|
+
if (!normalized || normalized.startsWith("/") || isAbsolute(normalized)) return null;
|
|
21849
|
+
const segments = normalized.split("/");
|
|
21850
|
+
if (segments.some((segment) => segment === "" || segment === "." || segment === "..")) return null;
|
|
21851
|
+
return segments.join("/");
|
|
21852
|
+
}
|
|
21853
|
+
function resolveInsideProject(rootPath, projectRelativePath) {
|
|
21854
|
+
const normalized = normalizeProjectRelativePath(projectRelativePath);
|
|
21855
|
+
if (!normalized) return null;
|
|
21856
|
+
const target = resolve(rootPath, normalized);
|
|
21857
|
+
const targetRelative = relative(rootPath, target);
|
|
21858
|
+
return targetRelative && !targetRelative.startsWith(`..${sep}`) && targetRelative !== ".." ? target : null;
|
|
21859
|
+
}
|
|
21860
|
+
async function observeProjectFile(rootPath, projectRelativePath) {
|
|
21861
|
+
const target = resolveInsideProject(rootPath, projectRelativePath);
|
|
21862
|
+
if (!target) return null;
|
|
21863
|
+
try {
|
|
21864
|
+
const targetLstat = await lstat(target);
|
|
21865
|
+
if (targetLstat.isSymbolicLink() || !targetLstat.isFile()) return null;
|
|
21866
|
+
const canonicalRoot = await realpath(rootPath);
|
|
21867
|
+
const canonicalTarget = await realpath(target);
|
|
21868
|
+
const canonicalRelative = relative(canonicalRoot, canonicalTarget);
|
|
21869
|
+
if (canonicalRelative.startsWith(`..${sep}`) || canonicalRelative === "..") return null;
|
|
21870
|
+
const targetStat = await stat(canonicalTarget);
|
|
21871
|
+
return {
|
|
21872
|
+
createdAt: targetStat.birthtime.toISOString(),
|
|
21873
|
+
relativePath: normalizeProjectRelativePath(projectRelativePath),
|
|
21874
|
+
size: targetStat.size,
|
|
21875
|
+
updatedAt: targetStat.mtime.toISOString()
|
|
21876
|
+
};
|
|
21877
|
+
} catch {
|
|
21878
|
+
return null;
|
|
21879
|
+
}
|
|
21880
|
+
}
|
|
21881
|
+
function globToRegExp(pattern) {
|
|
21882
|
+
const normalized = normalizeProjectRelativePath(pattern);
|
|
21883
|
+
if (!normalized) return null;
|
|
21884
|
+
let source = "^";
|
|
21885
|
+
for (let index = 0; index < normalized.length; index += 1) {
|
|
21886
|
+
const character = normalized[index];
|
|
21887
|
+
const next = normalized[index + 1];
|
|
21888
|
+
if (character === "*" && next === "*") {
|
|
21889
|
+
const after = normalized[index + 2];
|
|
21890
|
+
source += after === "/" ? "(?:.*/)?" : ".*";
|
|
21891
|
+
index += after === "/" ? 2 : 1;
|
|
21892
|
+
continue;
|
|
21893
|
+
}
|
|
21894
|
+
if (character === "*") {
|
|
21895
|
+
source += "[^/]*";
|
|
21896
|
+
continue;
|
|
21897
|
+
}
|
|
21898
|
+
if (character === "?") {
|
|
21899
|
+
source += "[^/]";
|
|
21900
|
+
continue;
|
|
21901
|
+
}
|
|
21902
|
+
source += /[|\\{}()[\]^$+?.]/.test(character) ? `\\${character}` : character;
|
|
21903
|
+
}
|
|
21904
|
+
return new RegExp(`${source}$`);
|
|
21905
|
+
}
|
|
21906
|
+
async function scanProjectArtifactFiles(rootPath, categories, options = {}) {
|
|
21907
|
+
const issues = [];
|
|
21908
|
+
const compiled = categories.flatMap((category) => category.include.flatMap((pattern) => {
|
|
21909
|
+
const matcher = globToRegExp(pattern);
|
|
21910
|
+
if (!matcher) {
|
|
21911
|
+
issues.push({
|
|
21912
|
+
code: "PROJECT_ARTIFACT_PATTERN_INVALID",
|
|
21913
|
+
message: `Artifact pattern '${pattern}' is not a safe project-relative glob.`,
|
|
21914
|
+
projectRelativePath: pattern
|
|
21915
|
+
});
|
|
21916
|
+
return [];
|
|
21917
|
+
}
|
|
21918
|
+
return [{
|
|
21919
|
+
...category,
|
|
21920
|
+
pattern,
|
|
21921
|
+
matcher
|
|
21922
|
+
}];
|
|
21923
|
+
}));
|
|
21924
|
+
if (compiled.length === 0) return {
|
|
21925
|
+
matches: [],
|
|
21926
|
+
issues
|
|
21927
|
+
};
|
|
21928
|
+
const files = [];
|
|
21929
|
+
let matchedFileCount = 0;
|
|
21930
|
+
const maxArtifactFiles = options.maxArtifactFiles ?? MAX_ARTIFACT_FILES;
|
|
21931
|
+
const searchBases = [...new Set(compiled.map((rule) => globSearchBase(rule.pattern)))];
|
|
21932
|
+
const visit = async (directory, depth) => {
|
|
21933
|
+
if (depth > MAX_DEPTH) return;
|
|
21934
|
+
const entries = await readdir(directory, { withFileTypes: true });
|
|
21935
|
+
for (const entry of entries) {
|
|
21936
|
+
if (entry.isSymbolicLink()) continue;
|
|
21937
|
+
const absolutePath = resolve(directory, entry.name);
|
|
21938
|
+
if (entry.isDirectory()) {
|
|
21939
|
+
const relativeDirectory = toPortablePath(relative(rootPath, absolutePath));
|
|
21940
|
+
if (!IGNORED_DIRECTORIES.has(entry.name) && shouldVisitDirectory(relativeDirectory, searchBases)) await visit(absolutePath, depth + 1);
|
|
21941
|
+
continue;
|
|
21942
|
+
}
|
|
21943
|
+
if (!entry.isFile()) continue;
|
|
21944
|
+
const relativePath = toPortablePath(relative(rootPath, absolutePath));
|
|
21945
|
+
if (!compiled.some((rule) => rule.matcher.test(relativePath))) continue;
|
|
21946
|
+
const fileStat = await stat(absolutePath);
|
|
21947
|
+
matchedFileCount += 1;
|
|
21948
|
+
keepMostRecentArtifactFile(files, {
|
|
21949
|
+
createdAt: fileStat.birthtime.toISOString(),
|
|
21950
|
+
relativePath,
|
|
21951
|
+
size: fileStat.size,
|
|
21952
|
+
updatedAt: fileStat.mtime.toISOString()
|
|
21953
|
+
}, maxArtifactFiles);
|
|
21954
|
+
}
|
|
21955
|
+
};
|
|
21956
|
+
try {
|
|
21957
|
+
await visit(rootPath, 0);
|
|
21958
|
+
} catch (error) {
|
|
21959
|
+
issues.push({
|
|
21960
|
+
code: "PROJECT_ARTIFACT_SCAN_FAILED",
|
|
21961
|
+
message: error instanceof Error ? error.message : "Project artifact scan failed."
|
|
21962
|
+
});
|
|
21963
|
+
}
|
|
21964
|
+
if (matchedFileCount > maxArtifactFiles) issues.push({
|
|
21965
|
+
code: "PROJECT_ARTIFACT_SCAN_LIMIT",
|
|
21966
|
+
message: `Showing the ${maxArtifactFiles} most recently updated files out of ${matchedFileCount} matched artifacts.`
|
|
21967
|
+
});
|
|
21968
|
+
const matchesByPath = /* @__PURE__ */ new Map();
|
|
21969
|
+
for (const rule of compiled) for (const file of files) {
|
|
21970
|
+
if (!rule.matcher.test(file.relativePath) || matchesByPath.has(file.relativePath)) continue;
|
|
21971
|
+
matchesByPath.set(file.relativePath, {
|
|
21972
|
+
...file,
|
|
21973
|
+
categoryId: rule.id,
|
|
21974
|
+
categoryLabel: rule.label
|
|
21975
|
+
});
|
|
21976
|
+
}
|
|
21977
|
+
return {
|
|
21978
|
+
matches: [...matchesByPath.values()],
|
|
21979
|
+
issues
|
|
21980
|
+
};
|
|
21981
|
+
}
|
|
21982
|
+
//#endregion
|
|
21983
|
+
//#region src/features/projects/utils/project-observation-projection.utils.ts
|
|
21984
|
+
const projectObservationConfigReference = (asOf) => ({
|
|
21985
|
+
kind: "project-config",
|
|
21986
|
+
label: "项目配置",
|
|
21987
|
+
observedAt: asOf,
|
|
21988
|
+
projectRelativePath: ".nextclaw/project.yaml"
|
|
21989
|
+
});
|
|
21990
|
+
const projectObservationFileReference = (path, observedAt) => ({
|
|
21991
|
+
kind: "file-observation",
|
|
21992
|
+
label: "文件观测",
|
|
21993
|
+
observedAt,
|
|
21994
|
+
projectRelativePath: path
|
|
21995
|
+
});
|
|
21996
|
+
const markerReference = (marker) => ({
|
|
21997
|
+
kind: "ai-report",
|
|
21998
|
+
label: "AI 报告",
|
|
21999
|
+
observedAt: marker.timestamp,
|
|
22000
|
+
sessionId: marker.sessionId,
|
|
22001
|
+
messageId: marker.messageId
|
|
22002
|
+
});
|
|
22003
|
+
const markerActivityMessage = (marker) => {
|
|
22004
|
+
switch (marker.kind) {
|
|
22005
|
+
case "work-item": return `AI 报告工作项“${marker.name}”为 ${marker.status}`;
|
|
22006
|
+
case "artifact": return `AI 报告产物 ${marker.path}`;
|
|
22007
|
+
case "schedule": return `AI 报告 ${marker.itemId} 的计划信息`;
|
|
22008
|
+
case "signal": return marker.message;
|
|
22009
|
+
case "request": return marker.prompt;
|
|
22010
|
+
}
|
|
22011
|
+
};
|
|
22012
|
+
const readOptionalString$9 = (value) => {
|
|
22013
|
+
if (typeof value !== "string") return void 0;
|
|
22014
|
+
return value.trim() || void 0;
|
|
22015
|
+
};
|
|
22016
|
+
const readProjectRunState = (session) => {
|
|
22017
|
+
const state = (session.metadata?.last_activity_preview)?.state;
|
|
22018
|
+
if (state === "running" || state === "completed" || state === "failed" || state === "cancelled" || state === "idle") return state;
|
|
22019
|
+
return session.status === "running" ? "running" : "idle";
|
|
22020
|
+
};
|
|
22021
|
+
function projectObservedRuns(sessions, markers) {
|
|
22022
|
+
const latestWorkItemIdBySession = /* @__PURE__ */ new Map();
|
|
22023
|
+
for (const marker of markers) if (marker.kind === "work-item") latestWorkItemIdBySession.set(marker.sessionId, marker.id);
|
|
22024
|
+
return sessions.map((session) => {
|
|
22025
|
+
const preview = session.metadata?.last_activity_preview;
|
|
22026
|
+
const updatedAt = readOptionalString$9(preview?.timestamp) ?? session.lastMessageAt ?? session.updatedAt;
|
|
22027
|
+
const model = readOptionalString$9(session.metadata?.preferred_model) ?? readOptionalString$9(session.metadata?.preferredModel) ?? readOptionalString$9(session.metadata?.model);
|
|
22028
|
+
const label = readOptionalString$9(session.metadata?.label);
|
|
22029
|
+
const statusText = readOptionalString$9(preview?.statusText);
|
|
22030
|
+
const workItemId = latestWorkItemIdBySession.get(session.sessionId);
|
|
22031
|
+
return {
|
|
22032
|
+
sessionId: session.sessionId,
|
|
22033
|
+
state: readProjectRunState(session),
|
|
22034
|
+
updatedAt,
|
|
22035
|
+
...session.agentId ? { agentId: session.agentId } : {},
|
|
22036
|
+
...model ? { model } : {},
|
|
22037
|
+
...label ? { label } : {},
|
|
22038
|
+
...statusText ? { statusText } : {},
|
|
22039
|
+
...workItemId ? { workItemId } : {},
|
|
22040
|
+
reference: {
|
|
22041
|
+
kind: "system-record",
|
|
22042
|
+
label: "会话运行",
|
|
22043
|
+
observedAt: updatedAt,
|
|
22044
|
+
sessionId: session.sessionId
|
|
22045
|
+
}
|
|
22046
|
+
};
|
|
22047
|
+
}).sort((left, right) => {
|
|
22048
|
+
return Number(right.state === "running") - Number(left.state === "running") || right.updatedAt.localeCompare(left.updatedAt);
|
|
22049
|
+
});
|
|
22050
|
+
}
|
|
22051
|
+
function projectObservedWorkItems(markers, workflows, addDiagnostic) {
|
|
22052
|
+
const workItems = /* @__PURE__ */ new Map();
|
|
22053
|
+
const schedules = /* @__PURE__ */ new Map();
|
|
22054
|
+
for (const marker of markers) {
|
|
22055
|
+
if (marker.kind === "work-item") workItems.set(marker.id, marker);
|
|
22056
|
+
if (marker.kind === "schedule") schedules.set(marker.itemId, marker);
|
|
22057
|
+
}
|
|
22058
|
+
return [...workItems.values()].map((marker) => {
|
|
22059
|
+
const inferredWorkflows = marker.stageId ? workflows.filter((entry) => entry.stages.some((stage) => stage.id === marker.stageId)) : [];
|
|
22060
|
+
const workflow = marker.workflowId ? workflows.find((entry) => entry.id === marker.workflowId) : inferredWorkflows.length === 1 ? inferredWorkflows[0] : void 0;
|
|
22061
|
+
if (marker.workflowId && !workflow) addDiagnostic("sessions", "warning", "PROJECT_MARKER_WORKFLOW_UNKNOWN", `Work item '${marker.id}' references unknown workflow '${marker.workflowId}'.`, {
|
|
22062
|
+
sessionId: marker.sessionId,
|
|
22063
|
+
messageId: marker.messageId
|
|
22064
|
+
});
|
|
22065
|
+
if (marker.stageId && (!workflow || !workflow.stages.some((stage) => stage.id === marker.stageId))) addDiagnostic("sessions", "warning", "PROJECT_MARKER_STAGE_UNKNOWN", `Work item '${marker.id}' references unknown stage '${marker.stageId}'.`, {
|
|
22066
|
+
sessionId: marker.sessionId,
|
|
22067
|
+
messageId: marker.messageId
|
|
22068
|
+
});
|
|
22069
|
+
const schedule = schedules.get(marker.id);
|
|
22070
|
+
return {
|
|
22071
|
+
id: marker.id,
|
|
22072
|
+
name: marker.name,
|
|
22073
|
+
status: marker.status,
|
|
22074
|
+
...workflow ? { workflowId: workflow.id } : {},
|
|
22075
|
+
...workflow && marker.stageId && workflow.stages.some((stage) => stage.id === marker.stageId) ? { stageId: marker.stageId } : {},
|
|
22076
|
+
...schedule ? { schedule: {
|
|
22077
|
+
...schedule.start ? { start: schedule.start } : {},
|
|
22078
|
+
...schedule.end ? { end: schedule.end } : {},
|
|
22079
|
+
milestone: schedule.milestone,
|
|
22080
|
+
dependsOn: schedule.dependsOn
|
|
22081
|
+
} } : {},
|
|
22082
|
+
updatedAt: marker.timestamp,
|
|
22083
|
+
reference: markerReference(marker)
|
|
22084
|
+
};
|
|
22085
|
+
}).sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
|
|
22086
|
+
}
|
|
22087
|
+
async function mergeProjectObservedArtifacts(rootPath, observed, config, markers, addDiagnostic) {
|
|
22088
|
+
const artifacts = new Map(observed.map((artifact) => [`${artifact.categoryId}:${artifact.path}`, artifact]));
|
|
22089
|
+
for (const marker of markers) {
|
|
22090
|
+
if (marker.kind !== "artifact") continue;
|
|
22091
|
+
const category = config?.artifactCategories.find((entry) => entry.id === marker.categoryId);
|
|
22092
|
+
const path = normalizeProjectRelativePath(marker.path);
|
|
22093
|
+
const file = path ? await observeProjectFile(rootPath, path) : null;
|
|
22094
|
+
if (!category) addDiagnostic("sessions", "warning", "PROJECT_MARKER_CATEGORY_UNKNOWN", `Artifact marker references unknown category '${marker.categoryId}'.`, {
|
|
22095
|
+
sessionId: marker.sessionId,
|
|
22096
|
+
messageId: marker.messageId
|
|
22097
|
+
});
|
|
22098
|
+
if (!file) addDiagnostic("files", "warning", "PROJECT_MARKER_ARTIFACT_UNAVAILABLE", `Artifact '${marker.path}' is missing or outside the project.`, {
|
|
22099
|
+
projectRelativePath: marker.path,
|
|
22100
|
+
sessionId: marker.sessionId,
|
|
22101
|
+
messageId: marker.messageId
|
|
22102
|
+
});
|
|
22103
|
+
const normalizedPath = path ?? marker.path;
|
|
22104
|
+
const key = `${marker.categoryId}:${normalizedPath}`;
|
|
22105
|
+
const existing = artifacts.get(key);
|
|
22106
|
+
if (existing) {
|
|
22107
|
+
artifacts.set(key, {
|
|
22108
|
+
...existing,
|
|
22109
|
+
itemId: marker.itemId,
|
|
22110
|
+
references: [...existing.references, markerReference(marker)]
|
|
22111
|
+
});
|
|
22112
|
+
continue;
|
|
22113
|
+
}
|
|
22114
|
+
artifacts.set(key, {
|
|
22115
|
+
id: key,
|
|
22116
|
+
path: normalizedPath,
|
|
22117
|
+
categoryId: marker.categoryId,
|
|
22118
|
+
categoryLabel: category?.label ?? marker.categoryId,
|
|
22119
|
+
exists: file !== null,
|
|
22120
|
+
itemId: marker.itemId,
|
|
22121
|
+
...file ? {
|
|
22122
|
+
size: file.size,
|
|
22123
|
+
fileCreatedAt: file.createdAt,
|
|
22124
|
+
fileUpdatedAt: file.updatedAt
|
|
22125
|
+
} : {},
|
|
22126
|
+
references: [markerReference(marker), ...file ? [projectObservationFileReference(file.relativePath, file.updatedAt)] : []]
|
|
22127
|
+
});
|
|
22128
|
+
}
|
|
22129
|
+
return [...artifacts.values()].sort((left, right) => left.path.localeCompare(right.path));
|
|
22130
|
+
}
|
|
22131
|
+
function projectObservedSignals(markers) {
|
|
22132
|
+
const signals = /* @__PURE__ */ new Map();
|
|
22133
|
+
for (const marker of markers) if (marker.kind === "signal") signals.set(marker.id, marker);
|
|
22134
|
+
return [...signals.values()].map((marker) => ({
|
|
22135
|
+
id: marker.id,
|
|
22136
|
+
...marker.itemId ? { itemId: marker.itemId } : {},
|
|
22137
|
+
status: marker.status,
|
|
22138
|
+
level: marker.level,
|
|
22139
|
+
message: marker.message,
|
|
22140
|
+
updatedAt: marker.timestamp,
|
|
22141
|
+
reference: markerReference(marker)
|
|
22142
|
+
})).sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
|
|
22143
|
+
}
|
|
22144
|
+
function projectObservedRequests(markers, responses) {
|
|
22145
|
+
const requests = /* @__PURE__ */ new Map();
|
|
22146
|
+
for (const marker of markers) if (marker.kind === "request") requests.set(marker.id, marker);
|
|
22147
|
+
return [...requests.values()].map((marker) => ({
|
|
22148
|
+
id: marker.id,
|
|
22149
|
+
...marker.itemId ? { itemId: marker.itemId } : {},
|
|
22150
|
+
status: marker.status,
|
|
22151
|
+
response: marker.response,
|
|
22152
|
+
prompt: marker.prompt,
|
|
22153
|
+
updatedAt: marker.timestamp,
|
|
22154
|
+
...responses.get(marker.id) ? { reply: responses.get(marker.id) } : {},
|
|
22155
|
+
reference: markerReference(marker)
|
|
22156
|
+
})).sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
|
|
22157
|
+
}
|
|
22158
|
+
const projectObservedActivity = (markers) => markers.map((marker) => ({
|
|
22159
|
+
id: `${marker.messageId}:${marker.line}:${marker.kind}`,
|
|
22160
|
+
kind: marker.kind,
|
|
22161
|
+
message: markerActivityMessage(marker),
|
|
22162
|
+
at: marker.timestamp,
|
|
22163
|
+
...marker.kind === "work-item" ? { itemId: marker.id } : marker.kind === "artifact" || marker.kind === "schedule" ? { itemId: marker.itemId } : marker.itemId ? { itemId: marker.itemId } : {},
|
|
22164
|
+
reference: markerReference(marker)
|
|
22165
|
+
})).sort((left, right) => right.at.localeCompare(left.at));
|
|
22166
|
+
//#endregion
|
|
22167
|
+
//#region src/features/projects/utils/project-observation-marker.utils.ts
|
|
22168
|
+
function createProjectObservationMarkerParseState() {
|
|
22169
|
+
return {
|
|
22170
|
+
currentWorkItemIdBySession: /* @__PURE__ */ new Map(),
|
|
22171
|
+
requestsById: /* @__PURE__ */ new Map(),
|
|
22172
|
+
workItemsById: /* @__PURE__ */ new Map()
|
|
22173
|
+
};
|
|
22174
|
+
}
|
|
22175
|
+
const ID_VALUE = "[a-z0-9][a-z0-9._-]*";
|
|
22176
|
+
const ID = `(${ID_VALUE})`;
|
|
22177
|
+
const OPTIONAL_ID = `(${ID_VALUE}|none)`;
|
|
22178
|
+
const TEXT = "([^\"\\]\\r\\n]{1,240})";
|
|
22179
|
+
const DATE = "(\\d{4}-\\d{2}-\\d{2}|none)";
|
|
22180
|
+
const PREFIX = "\\[nextclaw\\.project/v1 ";
|
|
22181
|
+
const LEGACY_WORK_ITEM_PATTERN = new RegExp(`^${PREFIX}kind=work-item id=${ID} title="${TEXT}" workflow=${OPTIONAL_ID} stage=${OPTIONAL_ID} status=(active|blocked|completed|cancelled)\\]$`);
|
|
22182
|
+
const LEGACY_ARTIFACT_PATTERN = new RegExp(`^${PREFIX}kind=artifact item=${ID} path="${TEXT}" category=${ID}\\]$`);
|
|
22183
|
+
const LEGACY_SCHEDULE_PATTERN = new RegExp(`^${PREFIX}kind=schedule item=${ID} start=${DATE} end=${DATE} milestone=(true|false) depends-on=([a-z0-9._,-]+|none)\\]$`);
|
|
22184
|
+
const LEGACY_SIGNAL_PATTERN = new RegExp(`^${PREFIX}kind=signal id=${ID} item=${OPTIONAL_ID} status=(open|resolved) level=(info|attention|warning) message="${TEXT}"\\]$`);
|
|
22185
|
+
const LEGACY_REQUEST_PATTERN = new RegExp(`^${PREFIX}kind=request id=${ID} item=${OPTIONAL_ID} status=(open|resolved|expired) response=(confirm-reject|open-session) prompt="${TEXT}"\\]$`);
|
|
22186
|
+
const ID_PATTERN = new RegExp(`^${ID_VALUE}$`);
|
|
22187
|
+
const FIELD_PATTERN = /([a-z][a-z-]*)=(?:"([^"\\\]\r\n]{1,240})"|([^\s\]]+))/y;
|
|
22188
|
+
const WORK_ITEM_STATUSES = new Set([
|
|
22189
|
+
"active",
|
|
22190
|
+
"blocked",
|
|
22191
|
+
"completed",
|
|
22192
|
+
"cancelled"
|
|
22193
|
+
]);
|
|
22194
|
+
const REQUEST_STATUSES = new Set([
|
|
22195
|
+
"open",
|
|
22196
|
+
"resolved",
|
|
22197
|
+
"expired"
|
|
22198
|
+
]);
|
|
22199
|
+
const REQUEST_RESPONSES = new Set(["confirm-reject", "open-session"]);
|
|
22200
|
+
const optional = (value) => value === "none" ? void 0 : value;
|
|
22201
|
+
function parseFields(value) {
|
|
22202
|
+
const fields = /* @__PURE__ */ new Map();
|
|
22203
|
+
let cursor = 0;
|
|
22204
|
+
while (cursor < value.length) {
|
|
22205
|
+
FIELD_PATTERN.lastIndex = cursor;
|
|
22206
|
+
const match = FIELD_PATTERN.exec(value);
|
|
22207
|
+
if (!match || fields.has(match[1])) return null;
|
|
22208
|
+
fields.set(match[1], match[2] ?? match[3]);
|
|
22209
|
+
cursor = FIELD_PATTERN.lastIndex;
|
|
22210
|
+
if (cursor === value.length) break;
|
|
22211
|
+
if (value[cursor] !== " ") return null;
|
|
22212
|
+
cursor += 1;
|
|
22213
|
+
}
|
|
22214
|
+
return fields;
|
|
22215
|
+
}
|
|
22216
|
+
function hasOnlyFields(fields, allowed) {
|
|
22217
|
+
const allowedSet = new Set(allowed);
|
|
22218
|
+
return [...fields.keys()].every((key) => allowedSet.has(key));
|
|
22219
|
+
}
|
|
22220
|
+
function rememberWorkItem(marker, state) {
|
|
22221
|
+
state.currentWorkItemIdBySession.set(marker.sessionId, marker.id);
|
|
22222
|
+
state.workItemsById.set(marker.id, {
|
|
22223
|
+
id: marker.id,
|
|
22224
|
+
name: marker.name,
|
|
22225
|
+
...marker.workflowId ? { workflowId: marker.workflowId } : {},
|
|
22226
|
+
...marker.stageId ? { stageId: marker.stageId } : {},
|
|
22227
|
+
status: marker.status
|
|
22228
|
+
});
|
|
22229
|
+
}
|
|
22230
|
+
function rememberRequest(marker, state) {
|
|
22231
|
+
state.requestsById.set(marker.id, {
|
|
22232
|
+
id: marker.id,
|
|
22233
|
+
...marker.itemId ? { itemId: marker.itemId } : {},
|
|
22234
|
+
status: marker.status,
|
|
22235
|
+
response: marker.response,
|
|
22236
|
+
prompt: marker.prompt
|
|
22237
|
+
});
|
|
22238
|
+
}
|
|
22239
|
+
function parseLegacyMarkerLine(line, source, state) {
|
|
22240
|
+
let match = LEGACY_WORK_ITEM_PATTERN.exec(line);
|
|
22241
|
+
if (match) {
|
|
22242
|
+
const marker = {
|
|
22243
|
+
...source,
|
|
22244
|
+
kind: "work-item",
|
|
22245
|
+
id: match[1],
|
|
22246
|
+
name: match[2],
|
|
22247
|
+
...optional(match[3]) ? { workflowId: optional(match[3]) } : {},
|
|
22248
|
+
...optional(match[4]) ? { stageId: optional(match[4]) } : {},
|
|
22249
|
+
status: match[5]
|
|
22250
|
+
};
|
|
22251
|
+
rememberWorkItem(marker, state);
|
|
22252
|
+
return marker;
|
|
22253
|
+
}
|
|
22254
|
+
match = LEGACY_ARTIFACT_PATTERN.exec(line);
|
|
22255
|
+
if (match) return {
|
|
22256
|
+
...source,
|
|
22257
|
+
kind: "artifact",
|
|
22258
|
+
itemId: match[1],
|
|
22259
|
+
path: match[2],
|
|
22260
|
+
categoryId: match[3]
|
|
22261
|
+
};
|
|
22262
|
+
match = LEGACY_SCHEDULE_PATTERN.exec(line);
|
|
22263
|
+
if (match) return {
|
|
22264
|
+
...source,
|
|
22265
|
+
kind: "schedule",
|
|
22266
|
+
itemId: match[1],
|
|
22267
|
+
...optional(match[2]) ? { start: optional(match[2]) } : {},
|
|
22268
|
+
...optional(match[3]) ? { end: optional(match[3]) } : {},
|
|
22269
|
+
milestone: match[4] === "true",
|
|
22270
|
+
dependsOn: match[5] === "none" ? [] : match[5].split(",")
|
|
22271
|
+
};
|
|
22272
|
+
match = LEGACY_SIGNAL_PATTERN.exec(line);
|
|
22273
|
+
if (match) return {
|
|
22274
|
+
...source,
|
|
22275
|
+
kind: "signal",
|
|
22276
|
+
id: match[1],
|
|
22277
|
+
...optional(match[2]) ? { itemId: optional(match[2]) } : {},
|
|
22278
|
+
status: match[3],
|
|
22279
|
+
level: match[4],
|
|
22280
|
+
message: match[5]
|
|
22281
|
+
};
|
|
22282
|
+
match = LEGACY_REQUEST_PATTERN.exec(line);
|
|
22283
|
+
if (match) {
|
|
22284
|
+
const marker = {
|
|
22285
|
+
...source,
|
|
22286
|
+
kind: "request",
|
|
22287
|
+
id: match[1],
|
|
22288
|
+
...optional(match[2]) ? { itemId: optional(match[2]) } : {},
|
|
22289
|
+
status: match[3],
|
|
22290
|
+
response: match[4],
|
|
22291
|
+
prompt: match[5]
|
|
22292
|
+
};
|
|
22293
|
+
rememberRequest(marker, state);
|
|
22294
|
+
return marker;
|
|
22295
|
+
}
|
|
22296
|
+
return null;
|
|
22297
|
+
}
|
|
22298
|
+
function parseCompactWorkItem(body, source, state) {
|
|
22299
|
+
const fields = parseFields(body);
|
|
22300
|
+
if (!fields || fields.size === 0 || !hasOnlyFields(fields, [
|
|
22301
|
+
"id",
|
|
22302
|
+
"name",
|
|
22303
|
+
"workflow",
|
|
22304
|
+
"stage",
|
|
22305
|
+
"status"
|
|
22306
|
+
])) return null;
|
|
22307
|
+
const explicitId = fields.get("id");
|
|
22308
|
+
if (explicitId && !ID_PATTERN.test(explicitId)) return null;
|
|
22309
|
+
const id = explicitId ?? state.currentWorkItemIdBySession.get(source.sessionId);
|
|
22310
|
+
if (!id) return null;
|
|
22311
|
+
const previous = state.workItemsById.get(id);
|
|
22312
|
+
const explicitName = fields.get("name");
|
|
22313
|
+
if (!previous && (!explicitName || !fields.has("stage"))) return null;
|
|
22314
|
+
if (previous && explicitName && explicitName !== previous.name) return null;
|
|
22315
|
+
const statusValue = fields.get("status");
|
|
22316
|
+
if (statusValue && !WORK_ITEM_STATUSES.has(statusValue)) return null;
|
|
22317
|
+
const workflowValue = fields.get("workflow");
|
|
22318
|
+
const stageValue = fields.get("stage");
|
|
22319
|
+
if (workflowValue && workflowValue !== "none" && !ID_PATTERN.test(workflowValue) || stageValue && stageValue !== "none" && !ID_PATTERN.test(stageValue)) return null;
|
|
22320
|
+
const workflowId = workflowValue === void 0 ? previous?.workflowId : optional(workflowValue);
|
|
22321
|
+
const stageId = stageValue === void 0 ? previous?.stageId : optional(stageValue);
|
|
22322
|
+
const marker = {
|
|
22323
|
+
...source,
|
|
22324
|
+
kind: "work-item",
|
|
22325
|
+
id,
|
|
22326
|
+
name: explicitName ?? previous.name,
|
|
22327
|
+
...workflowId ? { workflowId } : {},
|
|
22328
|
+
...stageId ? { stageId } : {},
|
|
22329
|
+
status: statusValue ?? (fields.has("stage") ? "active" : previous?.status ?? "active")
|
|
22330
|
+
};
|
|
22331
|
+
rememberWorkItem(marker, state);
|
|
22332
|
+
return marker;
|
|
22333
|
+
}
|
|
22334
|
+
function parseCompactArtifact(body, source, state) {
|
|
22335
|
+
const fields = parseFields(body);
|
|
22336
|
+
if (!fields || !hasOnlyFields(fields, [
|
|
22337
|
+
"path",
|
|
22338
|
+
"category",
|
|
22339
|
+
"work-id"
|
|
22340
|
+
])) return null;
|
|
22341
|
+
const path = fields.get("path");
|
|
22342
|
+
const categoryId = fields.get("category");
|
|
22343
|
+
const itemId = fields.get("work-id") ?? state.currentWorkItemIdBySession.get(source.sessionId);
|
|
22344
|
+
if (!path || !categoryId || !itemId || !ID_PATTERN.test(categoryId) || !ID_PATTERN.test(itemId)) return null;
|
|
22345
|
+
return {
|
|
22346
|
+
...source,
|
|
22347
|
+
kind: "artifact",
|
|
22348
|
+
itemId,
|
|
22349
|
+
path,
|
|
22350
|
+
categoryId
|
|
22351
|
+
};
|
|
22352
|
+
}
|
|
22353
|
+
function parseCompactRequest(body, source, state) {
|
|
22354
|
+
const fields = parseFields(body);
|
|
22355
|
+
if (!fields || !hasOnlyFields(fields, [
|
|
22356
|
+
"request",
|
|
22357
|
+
"work-id",
|
|
22358
|
+
"status",
|
|
22359
|
+
"response",
|
|
22360
|
+
"prompt"
|
|
22361
|
+
])) return null;
|
|
22362
|
+
const id = fields.get("request");
|
|
22363
|
+
if (!id || !ID_PATTERN.test(id)) return null;
|
|
22364
|
+
const previous = state.requestsById.get(id);
|
|
22365
|
+
const itemId = fields.get("work-id") ?? previous?.itemId ?? state.currentWorkItemIdBySession.get(source.sessionId);
|
|
22366
|
+
const statusValue = fields.get("status") ?? previous?.status ?? "open";
|
|
22367
|
+
const responseValue = fields.get("response") ?? previous?.response;
|
|
22368
|
+
const prompt = fields.get("prompt") ?? previous?.prompt;
|
|
22369
|
+
if (!responseValue || !prompt || !REQUEST_STATUSES.has(statusValue) || !REQUEST_RESPONSES.has(responseValue) || itemId && !ID_PATTERN.test(itemId)) return null;
|
|
22370
|
+
const marker = {
|
|
22371
|
+
...source,
|
|
22372
|
+
kind: "request",
|
|
22373
|
+
id,
|
|
22374
|
+
...itemId ? { itemId } : {},
|
|
22375
|
+
status: statusValue,
|
|
22376
|
+
response: responseValue,
|
|
22377
|
+
prompt
|
|
22378
|
+
};
|
|
22379
|
+
rememberRequest(marker, state);
|
|
22380
|
+
return marker;
|
|
22381
|
+
}
|
|
22382
|
+
function parseCompactMarkerLine(line, source, state) {
|
|
22383
|
+
const prefix = `[${PROJECT_OBSERVATION_PROTOCOL} `;
|
|
22384
|
+
if (!line.startsWith(prefix) || !line.endsWith("]")) return null;
|
|
22385
|
+
const body = line.slice(prefix.length, -1);
|
|
22386
|
+
if (body.startsWith("artifact ")) return parseCompactArtifact(body.slice(9), source, state);
|
|
22387
|
+
if (body.startsWith("request=")) return parseCompactRequest(body, source, state);
|
|
22388
|
+
return parseCompactWorkItem(body, source, state);
|
|
22389
|
+
}
|
|
22390
|
+
function parseProjectObservationMarkers(params) {
|
|
22391
|
+
const markers = [];
|
|
22392
|
+
const issues = [];
|
|
22393
|
+
const state = params.state ?? createProjectObservationMarkerParseState();
|
|
22394
|
+
let insideFencedCodeBlock = false;
|
|
22395
|
+
params.text.split(/\r?\n/).forEach((rawLine, index) => {
|
|
22396
|
+
const line = rawLine.trim();
|
|
22397
|
+
if (line.startsWith("```")) {
|
|
22398
|
+
insideFencedCodeBlock = !insideFencedCodeBlock;
|
|
22399
|
+
return;
|
|
22400
|
+
}
|
|
22401
|
+
if (insideFencedCodeBlock) return;
|
|
22402
|
+
if (!line.startsWith(`[nextclaw.project/v1`)) return;
|
|
22403
|
+
const source = {
|
|
22404
|
+
sessionId: params.sessionId,
|
|
22405
|
+
messageId: params.messageId,
|
|
22406
|
+
timestamp: params.timestamp,
|
|
22407
|
+
line: index + 1
|
|
22408
|
+
};
|
|
22409
|
+
const marker = line.includes(" kind=") ? parseLegacyMarkerLine(line, source, state) : parseCompactMarkerLine(line, source, state);
|
|
22410
|
+
if (marker) {
|
|
22411
|
+
markers.push(marker);
|
|
22412
|
+
return;
|
|
22413
|
+
}
|
|
22414
|
+
if (params.reportInvalid !== false) issues.push({
|
|
22415
|
+
code: "PROJECT_MARKER_INVALID",
|
|
22416
|
+
message: `Invalid ${PROJECT_OBSERVATION_PROTOCOL} marker at line ${index + 1}.`,
|
|
22417
|
+
line: index + 1
|
|
22418
|
+
});
|
|
22419
|
+
});
|
|
22420
|
+
return {
|
|
22421
|
+
markers,
|
|
22422
|
+
issues
|
|
22423
|
+
};
|
|
22424
|
+
}
|
|
22425
|
+
const isRecord$7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
22426
|
+
function readProjectObservationResponseMetadata(metadata) {
|
|
22427
|
+
const response = metadata?.project_observation_response;
|
|
22428
|
+
if (!isRecord$7(response) || response.protocol !== "nextclaw.project/v1" || typeof response.requestId !== "string" || response.decision !== "confirmed" && response.decision !== "rejected") return null;
|
|
22429
|
+
return {
|
|
22430
|
+
protocol: PROJECT_OBSERVATION_PROTOCOL,
|
|
22431
|
+
requestId: response.requestId,
|
|
22432
|
+
decision: response.decision
|
|
22433
|
+
};
|
|
22434
|
+
}
|
|
22435
|
+
//#endregion
|
|
22436
|
+
//#region src/features/projects/services/project-observation.service.ts
|
|
22437
|
+
const SOURCE_LABELS = {
|
|
22438
|
+
config: "项目配置",
|
|
22439
|
+
files: "项目文件",
|
|
22440
|
+
sessions: "项目会话",
|
|
22441
|
+
skills: "项目 Skills"
|
|
22442
|
+
};
|
|
22443
|
+
const readOptionalString$8 = (value) => {
|
|
22444
|
+
if (typeof value !== "string") return;
|
|
22445
|
+
return value.trim() || void 0;
|
|
22446
|
+
};
|
|
22447
|
+
const readSessionProjectRoot$1 = (session) => readOptionalString$8(session.metadata?.project_root) ?? readOptionalString$8(session.metadata?.projectRoot);
|
|
22448
|
+
const readMessageText = (message) => message.parts.flatMap((part) => part.type === "text" || part.type === "rich-text" ? [part.text] : []).join("\n");
|
|
22449
|
+
const compareMarkerSource = (left, right) => left.timestamp.localeCompare(right.timestamp) || left.sessionId.localeCompare(right.sessionId) || left.messageId.localeCompare(right.messageId) || left.line - right.line;
|
|
22450
|
+
const markerConflictKey = (marker) => {
|
|
22451
|
+
switch (marker.kind) {
|
|
22452
|
+
case "work-item":
|
|
22453
|
+
case "signal":
|
|
22454
|
+
case "request": return `${marker.kind}:${marker.id}`;
|
|
22455
|
+
case "schedule": return `${marker.kind}:${marker.itemId}`;
|
|
22456
|
+
case "artifact": return null;
|
|
22457
|
+
}
|
|
22458
|
+
};
|
|
22459
|
+
const markerSemanticValue = (marker) => {
|
|
22460
|
+
const { line: _line, messageId: _messageId, sessionId: _sessionId, timestamp: _timestamp, ...semantic } = marker;
|
|
22461
|
+
return JSON.stringify(semantic);
|
|
22462
|
+
};
|
|
22463
|
+
const parseProjectSessionMessage = (sessionId, message, state) => {
|
|
22464
|
+
if (message.role === "assistant") {
|
|
22465
|
+
const parsed = parseProjectObservationMarkers({
|
|
22466
|
+
text: readMessageText(message),
|
|
22467
|
+
sessionId,
|
|
22468
|
+
messageId: message.id,
|
|
22469
|
+
timestamp: message.timestamp,
|
|
22470
|
+
reportInvalid: message.status === "final" || message.status === "error",
|
|
22471
|
+
state
|
|
22472
|
+
});
|
|
22473
|
+
return {
|
|
22474
|
+
markers: parsed.markers,
|
|
22475
|
+
markerIssues: parsed.issues
|
|
22476
|
+
};
|
|
22477
|
+
}
|
|
22478
|
+
if (message.role !== "user") return {
|
|
22479
|
+
markers: [],
|
|
22480
|
+
markerIssues: []
|
|
22481
|
+
};
|
|
22482
|
+
const response = readProjectObservationResponseMetadata(message.metadata);
|
|
22483
|
+
return {
|
|
22484
|
+
markers: [],
|
|
22485
|
+
markerIssues: [],
|
|
22486
|
+
...response ? { response: {
|
|
22487
|
+
requestId: response.requestId,
|
|
22488
|
+
decision: response.decision,
|
|
22489
|
+
sentAt: message.timestamp,
|
|
22490
|
+
messageId: message.id
|
|
22491
|
+
} } : {}
|
|
22492
|
+
};
|
|
22493
|
+
};
|
|
22494
|
+
const mergeLatestResponse = (responses, response) => {
|
|
22495
|
+
const next = new Map(responses);
|
|
22496
|
+
const current = next.get(response.requestId);
|
|
22497
|
+
if (!current || current.sentAt.localeCompare(response.sentAt) <= 0) {
|
|
22498
|
+
const { requestId, ...value } = response;
|
|
22499
|
+
next.set(requestId, value);
|
|
22500
|
+
}
|
|
22501
|
+
return next;
|
|
22502
|
+
};
|
|
22503
|
+
var ProjectObservationService = class {
|
|
22504
|
+
now;
|
|
22505
|
+
constructor(options) {
|
|
22506
|
+
this.options = options;
|
|
22507
|
+
this.now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
22508
|
+
}
|
|
22509
|
+
observe = async (rootPath) => {
|
|
22510
|
+
let project;
|
|
22511
|
+
try {
|
|
22512
|
+
project = await this.options.projectManager.getRegisteredProject(rootPath);
|
|
22513
|
+
} catch (error) {
|
|
22514
|
+
throw new ProjectObservationError("PROJECT_OBSERVATION_INVALID_ROOT", error instanceof Error ? error.message : "Project root is invalid.");
|
|
22515
|
+
}
|
|
22516
|
+
if (!project) throw new ProjectObservationError("PROJECT_NOT_REGISTERED", "Project is not registered.");
|
|
22517
|
+
const asOf = this.now().toISOString();
|
|
22518
|
+
const diagnostics = [];
|
|
22519
|
+
const sourceCounts = /* @__PURE__ */ new Map();
|
|
22520
|
+
const sourceErrors = /* @__PURE__ */ new Set();
|
|
22521
|
+
const addDiagnostic = (source, level, code, message, details = {}) => {
|
|
22522
|
+
if (level === "error") sourceErrors.add(source);
|
|
22523
|
+
diagnostics.push({
|
|
22524
|
+
id: `${source}:${code}:${diagnostics.filter((entry) => entry.source === source && entry.code === code).length + 1}`,
|
|
22525
|
+
source,
|
|
22526
|
+
level,
|
|
22527
|
+
code,
|
|
22528
|
+
message,
|
|
22529
|
+
...details
|
|
22530
|
+
});
|
|
22531
|
+
};
|
|
22532
|
+
const addSourceCount = (source, count) => {
|
|
22533
|
+
sourceCounts.set(source, (sourceCounts.get(source) ?? 0) + count);
|
|
22534
|
+
};
|
|
22535
|
+
const config = await this.readConfig(project.rootPath, addDiagnostic);
|
|
22536
|
+
const context = await this.observeContext(project.rootPath, config, asOf, addDiagnostic);
|
|
22537
|
+
addSourceCount("config", (config ? 1 : 0) + context.length + (config?.workflows.length ?? 0));
|
|
22538
|
+
const artifacts = await this.observeArtifacts(project.rootPath, config, asOf, addDiagnostic);
|
|
22539
|
+
addSourceCount("files", artifacts.length);
|
|
22540
|
+
const sessionObservation = await this.observeSessions(project.rootPath, addDiagnostic);
|
|
22541
|
+
addSourceCount("sessions", sessionObservation.sessions.length);
|
|
22542
|
+
const skills = this.observeSkills(project.rootPath, config, asOf, addDiagnostic);
|
|
22543
|
+
addSourceCount("skills", skills.length);
|
|
22544
|
+
const workflows = (config?.workflows ?? []).map((workflow) => ({
|
|
22545
|
+
...workflow,
|
|
22546
|
+
reference: projectObservationConfigReference(asOf)
|
|
22547
|
+
}));
|
|
22548
|
+
const workItems = projectObservedWorkItems(sessionObservation.markers, workflows, addDiagnostic);
|
|
22549
|
+
const runs = projectObservedRuns(sessionObservation.sessions, sessionObservation.markers);
|
|
22550
|
+
const mergedArtifacts = await mergeProjectObservedArtifacts(project.rootPath, artifacts, config, sessionObservation.markers, addDiagnostic);
|
|
22551
|
+
const signals = projectObservedSignals(sessionObservation.markers);
|
|
22552
|
+
const requests = projectObservedRequests(sessionObservation.markers, sessionObservation.responses);
|
|
22553
|
+
const activity = projectObservedActivity(sessionObservation.markers);
|
|
22554
|
+
const sources = Object.keys(SOURCE_LABELS).map((source) => this.createSourceStatus(source, asOf, sourceCounts.get(source) ?? 0, sourceErrors.has(source), diagnostics));
|
|
22555
|
+
const errorSourceCount = sources.filter((source) => source.status === "error").length;
|
|
22556
|
+
return {
|
|
22557
|
+
asOf,
|
|
22558
|
+
project: {
|
|
22559
|
+
name: project.name,
|
|
22560
|
+
rootPath: project.rootPath,
|
|
22561
|
+
...config?.summary ? { summary: config.summary } : {},
|
|
22562
|
+
context
|
|
22563
|
+
},
|
|
22564
|
+
sources,
|
|
22565
|
+
workflows,
|
|
22566
|
+
runs,
|
|
22567
|
+
workItems,
|
|
22568
|
+
artifactCategories: (config?.artifactCategories ?? []).map(({ id, label }) => ({
|
|
22569
|
+
id,
|
|
22570
|
+
label
|
|
22571
|
+
})),
|
|
22572
|
+
artifacts: mergedArtifacts,
|
|
22573
|
+
signals,
|
|
22574
|
+
requests,
|
|
22575
|
+
activity,
|
|
22576
|
+
skills,
|
|
22577
|
+
diagnostics,
|
|
22578
|
+
dataQuality: errorSourceCount === sources.length ? "unavailable" : diagnostics.some((entry) => entry.level === "error" || entry.level === "warning") ? "partial" : "complete"
|
|
22579
|
+
};
|
|
22580
|
+
};
|
|
22581
|
+
readConfig = async (rootPath, addDiagnostic) => {
|
|
22582
|
+
const path = ".nextclaw/project.yaml";
|
|
22583
|
+
if (!await observeProjectFile(rootPath, path)) {
|
|
22584
|
+
addDiagnostic("config", "info", "PROJECT_CONFIG_NOT_FOUND", "No .nextclaw/project.yaml was found.", { projectRelativePath: path });
|
|
22585
|
+
return null;
|
|
22586
|
+
}
|
|
22587
|
+
try {
|
|
22588
|
+
const result = parseProjectObservationConfig(await readFile(join(rootPath, path), "utf8"));
|
|
22589
|
+
for (const issue of result.issues) addDiagnostic("config", result.config ? "warning" : "error", issue.code, issue.message, { projectRelativePath: path });
|
|
22590
|
+
return result.config;
|
|
22591
|
+
} catch (error) {
|
|
22592
|
+
addDiagnostic("config", "error", "PROJECT_CONFIG_READ_FAILED", error instanceof Error ? error.message : "Project config could not be read.", { projectRelativePath: path });
|
|
22593
|
+
return null;
|
|
22594
|
+
}
|
|
22595
|
+
};
|
|
22596
|
+
observeContext = async (rootPath, config, asOf, addDiagnostic) => await Promise.all((config?.context ?? []).map(async (entry) => {
|
|
22597
|
+
const path = normalizeProjectRelativePath(entry.source);
|
|
22598
|
+
const observed = path ? await observeProjectFile(rootPath, path) : null;
|
|
22599
|
+
if (!observed) addDiagnostic("files", "warning", "PROJECT_CONTEXT_SOURCE_UNAVAILABLE", `Context source '${entry.source}' is unavailable or outside the project.`, { projectRelativePath: entry.source });
|
|
22600
|
+
return {
|
|
22601
|
+
...entry,
|
|
22602
|
+
accessible: observed !== null,
|
|
22603
|
+
reference: observed ? projectObservationFileReference(observed.relativePath, asOf) : projectObservationConfigReference(asOf)
|
|
22604
|
+
};
|
|
22605
|
+
}));
|
|
22606
|
+
observeArtifacts = async (rootPath, config, asOf, addDiagnostic) => {
|
|
22607
|
+
const result = await scanProjectArtifactFiles(rootPath, config?.artifactCategories ?? []);
|
|
22608
|
+
for (const issue of result.issues) addDiagnostic("files", "warning", issue.code, issue.message, { ...issue.projectRelativePath ? { projectRelativePath: issue.projectRelativePath } : {} });
|
|
22609
|
+
return result.matches.map((match) => ({
|
|
22610
|
+
id: `${match.categoryId}:${match.relativePath}`,
|
|
22611
|
+
path: match.relativePath,
|
|
22612
|
+
categoryId: match.categoryId,
|
|
22613
|
+
categoryLabel: match.categoryLabel,
|
|
22614
|
+
exists: true,
|
|
22615
|
+
size: match.size,
|
|
22616
|
+
fileCreatedAt: match.createdAt,
|
|
22617
|
+
fileUpdatedAt: match.updatedAt,
|
|
22618
|
+
references: [projectObservationFileReference(match.relativePath, asOf)]
|
|
22619
|
+
}));
|
|
22620
|
+
};
|
|
22621
|
+
observeSessions = async (rootPath, addDiagnostic) => {
|
|
22622
|
+
const markers = [];
|
|
22623
|
+
let responses = /* @__PURE__ */ new Map();
|
|
22624
|
+
let sessions;
|
|
22625
|
+
try {
|
|
22626
|
+
sessions = (await this.options.sessionManager.listSessions()).filter((session) => readSessionProjectRoot$1(session) === rootPath);
|
|
22627
|
+
} catch (error) {
|
|
22628
|
+
addDiagnostic("sessions", "error", "PROJECT_SESSIONS_READ_FAILED", error instanceof Error ? error.message : "Project sessions could not be listed.");
|
|
22629
|
+
return {
|
|
22630
|
+
markers,
|
|
22631
|
+
responses,
|
|
22632
|
+
sessions: []
|
|
22633
|
+
};
|
|
22634
|
+
}
|
|
22635
|
+
const messages = [];
|
|
22636
|
+
for (const session of sessions) try {
|
|
22637
|
+
messages.push(...(await this.options.sessionManager.listSessionMessages(session.sessionId)).map((message) => ({
|
|
22638
|
+
message,
|
|
22639
|
+
sessionId: session.sessionId
|
|
22640
|
+
})));
|
|
22641
|
+
} catch (error) {
|
|
22642
|
+
addDiagnostic("sessions", "error", "PROJECT_SESSION_MESSAGES_READ_FAILED", error instanceof Error ? error.message : "Project session messages could not be read.", { sessionId: session.sessionId });
|
|
22643
|
+
}
|
|
22644
|
+
messages.sort((left, right) => left.message.timestamp.localeCompare(right.message.timestamp) || left.sessionId.localeCompare(right.sessionId) || left.message.id.localeCompare(right.message.id));
|
|
22645
|
+
const parseState = createProjectObservationMarkerParseState();
|
|
22646
|
+
for (const { message, sessionId } of messages) {
|
|
22647
|
+
const parsed = parseProjectSessionMessage(sessionId, message, parseState);
|
|
22648
|
+
markers.push(...parsed.markers);
|
|
22649
|
+
for (const issue of parsed.markerIssues) addDiagnostic("sessions", "warning", issue.code, issue.message, {
|
|
22650
|
+
sessionId,
|
|
22651
|
+
messageId: message.id
|
|
22652
|
+
});
|
|
22653
|
+
if (parsed.response) responses = mergeLatestResponse(responses, parsed.response);
|
|
22654
|
+
}
|
|
22655
|
+
markers.sort(compareMarkerSource);
|
|
22656
|
+
this.addMarkerConflictDiagnostics(markers, addDiagnostic);
|
|
22657
|
+
return {
|
|
22658
|
+
markers,
|
|
22659
|
+
responses,
|
|
22660
|
+
sessions
|
|
22661
|
+
};
|
|
22662
|
+
};
|
|
22663
|
+
addMarkerConflictDiagnostics = (markers, addDiagnostic) => {
|
|
22664
|
+
const latestAtByKey = /* @__PURE__ */ new Map();
|
|
22665
|
+
for (const marker of markers) {
|
|
22666
|
+
const key = markerConflictKey(marker);
|
|
22667
|
+
if (!key) continue;
|
|
22668
|
+
const current = latestAtByKey.get(key);
|
|
22669
|
+
const value = markerSemanticValue(marker);
|
|
22670
|
+
if (current?.timestamp === marker.timestamp && (current.sessionId !== marker.sessionId || current.messageId !== marker.messageId) && current.value !== value) addDiagnostic("sessions", "warning", "PROJECT_MARKER_CONFLICT", `Conflicting '${key}' markers share the same observation time; a deterministic source order was used.`, {
|
|
22671
|
+
sessionId: marker.sessionId,
|
|
22672
|
+
messageId: marker.messageId
|
|
22673
|
+
});
|
|
22674
|
+
latestAtByKey.set(key, {
|
|
22675
|
+
messageId: marker.messageId,
|
|
22676
|
+
sessionId: marker.sessionId,
|
|
22677
|
+
timestamp: marker.timestamp,
|
|
22678
|
+
value
|
|
22679
|
+
});
|
|
22680
|
+
}
|
|
22681
|
+
};
|
|
22682
|
+
observeSkills = (rootPath, config, asOf, addDiagnostic) => {
|
|
22683
|
+
const output = /* @__PURE__ */ new Map();
|
|
22684
|
+
for (const skillRoot of config?.skillRoots ?? [".agents/skills"]) {
|
|
22685
|
+
const normalizedRoot = normalizeProjectRelativePath(skillRoot);
|
|
22686
|
+
if (!normalizedRoot) {
|
|
22687
|
+
addDiagnostic("skills", "warning", "PROJECT_SKILL_ROOT_INVALID", `Skill root '${skillRoot}' is outside the project.`, { projectRelativePath: skillRoot });
|
|
22688
|
+
continue;
|
|
22689
|
+
}
|
|
22690
|
+
try {
|
|
22691
|
+
const loader = new SkillsLoader({
|
|
22692
|
+
workspace: this.options.workspacePath,
|
|
22693
|
+
projectRoot: rootPath,
|
|
22694
|
+
projectSkillsDirName: normalizedRoot,
|
|
22695
|
+
includeBuiltin: false,
|
|
22696
|
+
includeGlobal: false
|
|
22697
|
+
});
|
|
22698
|
+
for (const skill of loader.listSkills(false).filter((entry) => entry.scope === "project")) {
|
|
22699
|
+
const metadata = loader.getSkillMetadata(skill) ?? {};
|
|
22700
|
+
const relativePath = normalizeProjectRelativePath(skill.path.slice(rootPath.length + 1)) ?? `${normalizedRoot}/${skill.name}/SKILL.md`;
|
|
22701
|
+
output.set(skill.ref, {
|
|
22702
|
+
ref: skill.ref,
|
|
22703
|
+
name: skill.name,
|
|
22704
|
+
...metadata.description ? { description: metadata.description } : {},
|
|
22705
|
+
source: "project",
|
|
22706
|
+
path: relativePath,
|
|
22707
|
+
readable: true,
|
|
22708
|
+
reference: projectObservationFileReference(relativePath, asOf)
|
|
22709
|
+
});
|
|
22710
|
+
}
|
|
22711
|
+
} catch (error) {
|
|
22712
|
+
addDiagnostic("skills", "error", "PROJECT_SKILLS_READ_FAILED", error instanceof Error ? error.message : "Project skills could not be read.", { projectRelativePath: normalizedRoot });
|
|
22713
|
+
}
|
|
22714
|
+
}
|
|
22715
|
+
return [...output.values()].sort((left, right) => left.name.localeCompare(right.name));
|
|
22716
|
+
};
|
|
22717
|
+
createSourceStatus = (source, asOf, itemCount, hasError, diagnostics) => ({
|
|
22718
|
+
id: source,
|
|
22719
|
+
label: SOURCE_LABELS[source],
|
|
22720
|
+
status: hasError ? "error" : itemCount > 0 ? "available" : "empty",
|
|
22721
|
+
itemCount,
|
|
22722
|
+
observedAt: asOf,
|
|
22723
|
+
diagnosticIds: diagnostics.filter((entry) => entry.source === source).map((entry) => entry.id)
|
|
22724
|
+
});
|
|
22725
|
+
};
|
|
22726
|
+
//#endregion
|
|
22727
|
+
//#region src/stores/ncp-agent-session-journal-summary-recovery.store.ts
|
|
22728
|
+
async function loadNcpAgentSessionSummary(params) {
|
|
22729
|
+
const journalStat = await stat(params.journalPath);
|
|
22730
|
+
const projectionMeta = await params.projectionStore.readMeta(params.sessionId);
|
|
22731
|
+
if (projectionMeta && projectionMeta.projectedJournalOffset <= journalStat.size) {
|
|
22732
|
+
const metadata = await params.metadataStore.read(params.sessionId, {
|
|
22733
|
+
createdAt: params.updatedAt,
|
|
22734
|
+
updatedAt: params.updatedAt,
|
|
22735
|
+
metadata: {}
|
|
22736
|
+
});
|
|
22737
|
+
const page = await params.projectionStore.listPage({
|
|
22738
|
+
sessionId: params.sessionId,
|
|
22739
|
+
limit: 1
|
|
22740
|
+
});
|
|
22741
|
+
return {
|
|
22742
|
+
...createNcpAgentSessionSummary({
|
|
22743
|
+
sessionId: params.sessionId,
|
|
22744
|
+
...metadata.agentId ? { agentId: metadata.agentId } : {},
|
|
22745
|
+
createdAt: metadata.createdAt,
|
|
22746
|
+
updatedAt: metadata.updatedAt,
|
|
22747
|
+
metadata: metadata.metadata,
|
|
22748
|
+
messages: page?.messages ?? []
|
|
22749
|
+
}),
|
|
22750
|
+
messageCount: page?.total ?? projectionMeta.total
|
|
22751
|
+
};
|
|
22752
|
+
}
|
|
22753
|
+
return await loadNcpAgentSessionSummaryWithoutReplay({
|
|
22754
|
+
sessionId: params.sessionId,
|
|
22755
|
+
journalPath: params.journalPath,
|
|
22756
|
+
updatedAt: params.updatedAt,
|
|
22757
|
+
metadataStore: params.metadataStore
|
|
22758
|
+
});
|
|
22759
|
+
}
|
|
22760
|
+
async function loadNcpAgentSessionSummaryWithoutReplay(params) {
|
|
22761
|
+
const stream = createReadStream(params.journalPath, { encoding: "utf-8" });
|
|
22762
|
+
const lines = createInterface({
|
|
22763
|
+
input: stream,
|
|
22764
|
+
crlfDelay: Infinity
|
|
22765
|
+
});
|
|
22766
|
+
const state = {
|
|
22767
|
+
createdAt: params.updatedAt,
|
|
22768
|
+
metadata: {},
|
|
22769
|
+
messageCount: 0,
|
|
22770
|
+
messageIds: /* @__PURE__ */ new Set(),
|
|
22771
|
+
requiresReplay: false
|
|
22772
|
+
};
|
|
22773
|
+
try {
|
|
22774
|
+
for await (const line of lines) applyJournalSummaryLine(state, line);
|
|
22775
|
+
} catch {
|
|
22776
|
+
return null;
|
|
22777
|
+
} finally {
|
|
22778
|
+
lines.close();
|
|
22779
|
+
stream.destroy();
|
|
22780
|
+
}
|
|
22781
|
+
if (state.requiresReplay) return null;
|
|
22782
|
+
const snapshot = await params.metadataStore.read(params.sessionId, {
|
|
22783
|
+
...state.agentId ? { agentId: state.agentId } : {},
|
|
22784
|
+
createdAt: state.createdAt,
|
|
22785
|
+
updatedAt: params.updatedAt,
|
|
22786
|
+
metadata: state.metadata
|
|
22787
|
+
});
|
|
22788
|
+
return {
|
|
22789
|
+
...createNcpAgentSessionSummary({
|
|
22790
|
+
sessionId: params.sessionId,
|
|
22791
|
+
...snapshot.agentId ? { agentId: snapshot.agentId } : {},
|
|
22792
|
+
createdAt: snapshot.createdAt,
|
|
22793
|
+
updatedAt: snapshot.updatedAt,
|
|
22794
|
+
metadata: snapshot.metadata,
|
|
22795
|
+
messages: []
|
|
22796
|
+
}),
|
|
22797
|
+
messageCount: state.messageCount,
|
|
22798
|
+
...state.lastMessageAt ? { lastMessageAt: state.lastMessageAt } : {}
|
|
22799
|
+
};
|
|
22800
|
+
}
|
|
22801
|
+
function applyJournalSummaryLine(state, line) {
|
|
22802
|
+
const parsed = parseJournalLine(line);
|
|
22803
|
+
if (!parsed) return;
|
|
22804
|
+
if (parsed._type === "metadata") {
|
|
22805
|
+
state.createdAt = toIsoString(parsed.created_at, state.createdAt);
|
|
22806
|
+
state.agentId = typeof parsed.agent_id === "string" ? parsed.agent_id : state.agentId;
|
|
22807
|
+
if (isRecord$11(parsed.metadata)) state.metadata = structuredClone(parsed.metadata);
|
|
22808
|
+
return;
|
|
22809
|
+
}
|
|
22810
|
+
if (parsed._type !== "event" || !isRecord$11(parsed.event)) return;
|
|
22811
|
+
const event = parsed.event;
|
|
22812
|
+
const payload = isRecord$11(event.payload) ? event.payload : null;
|
|
22813
|
+
const message = payload && isRecord$11(payload.message) ? payload.message : null;
|
|
22814
|
+
const messageId = typeof message?.id === "string" ? message.id : typeof payload?.messageId === "string" ? payload.messageId : null;
|
|
22815
|
+
if (!isMessageStartEvent(event.type) || !messageId || state.messageIds.has(messageId)) return;
|
|
22816
|
+
state.messageIds.add(messageId);
|
|
22817
|
+
state.messageCount += 1;
|
|
22818
|
+
if (isRecord$11(message?.metadata) && message.metadata.nextclaw_timeline_kind === "context_compaction") state.requiresReplay = true;
|
|
22819
|
+
if (!isFinalMessageEvent(event.type)) return;
|
|
22820
|
+
const timestamp = toIsoString(message?.timestamp, "");
|
|
22821
|
+
if (timestamp && (!state.lastMessageAt || timestamp > state.lastMessageAt)) state.lastMessageAt = timestamp;
|
|
22822
|
+
}
|
|
22823
|
+
function parseJournalLine(line) {
|
|
22824
|
+
if (!line.trim()) return null;
|
|
22825
|
+
try {
|
|
22826
|
+
const parsed = JSON.parse(line);
|
|
22827
|
+
return isRecord$11(parsed) ? parsed : null;
|
|
22828
|
+
} catch {
|
|
22829
|
+
return null;
|
|
22830
|
+
}
|
|
22831
|
+
}
|
|
22832
|
+
function isMessageStartEvent(type) {
|
|
22833
|
+
return type === NcpEventType.MessageSent || type === NcpEventType.MessageCompleted || type === "session.snapshot.message" || type === NcpEventType.MessageTextStart || type === NcpEventType.MessageReasoningStart;
|
|
22834
|
+
}
|
|
22835
|
+
function isFinalMessageEvent(type) {
|
|
22836
|
+
return type === NcpEventType.MessageSent || type === NcpEventType.MessageCompleted || type === "session.snapshot.message";
|
|
22837
|
+
}
|
|
22838
|
+
//#endregion
|
|
22839
|
+
//#region src/utils/ncp-agent-unfinished-run.utils.ts
|
|
22840
|
+
function applyNcpAgentRunLifecycleEvent(sessionId, activeRun, event) {
|
|
22841
|
+
if (event.type === NcpEventType.RunStarted) return {
|
|
22842
|
+
sessionId,
|
|
22843
|
+
...event.payload.messageId ? { messageId: event.payload.messageId } : {},
|
|
22844
|
+
...event.payload.runId ? { runId: event.payload.runId } : {},
|
|
22845
|
+
...event.payload.startedAt ? { startedAt: event.payload.startedAt } : {}
|
|
22846
|
+
};
|
|
22847
|
+
if (activeRun && (event.type === NcpEventType.RunFinished || event.type === NcpEventType.RunError || event.type === NcpEventType.MessageAbort) && (!event.payload.runId || !activeRun.runId || event.payload.runId === activeRun.runId)) return null;
|
|
22848
|
+
return activeRun;
|
|
22849
|
+
}
|
|
22850
|
+
//#endregion
|
|
22851
|
+
//#region src/stores/ncp-agent-unfinished-run.store.ts
|
|
22852
|
+
var NcpAgentUnfinishedRunStore = class {
|
|
22853
|
+
constructor(journalDir, listSessionIds) {
|
|
22854
|
+
this.journalDir = journalDir;
|
|
22855
|
+
this.listSessionIds = listSessionIds;
|
|
22856
|
+
}
|
|
22857
|
+
list = async () => {
|
|
22858
|
+
const runs = [];
|
|
20556
22859
|
for (const sessionId of await this.listSessionIds()) {
|
|
20557
22860
|
const run = await this.read(sessionId);
|
|
20558
22861
|
if (run) runs.push(run);
|
|
@@ -20575,7 +22878,7 @@ var NcpAgentUnfinishedRunStore = class {
|
|
|
20575
22878
|
} catch {
|
|
20576
22879
|
continue;
|
|
20577
22880
|
}
|
|
20578
|
-
if (!isRecord$
|
|
22881
|
+
if (!isRecord$11(parsed) || parsed._type !== "event" || !isRecord$11(parsed.event)) continue;
|
|
20579
22882
|
activeRun = applyNcpAgentRunLifecycleEvent(sessionId, activeRun, parsed.event);
|
|
20580
22883
|
}
|
|
20581
22884
|
return activeRun;
|
|
@@ -20596,11 +22899,11 @@ function isNcpAgentSessionMessageProjectionBoundaryEvent(event) {
|
|
|
20596
22899
|
function serializeNcpAgentSessionJournalEntry(entry) {
|
|
20597
22900
|
const serialized = JSON.stringify(entry);
|
|
20598
22901
|
if (!serialized) throw new Error("ncp agent session journal entry serialization produced empty output");
|
|
20599
|
-
if (!isRecord$
|
|
22902
|
+
if (!isRecord$11(JSON.parse(serialized))) throw new Error("ncp agent session journal entry serialization produced a non-object entry");
|
|
20600
22903
|
return serialized;
|
|
20601
22904
|
}
|
|
20602
22905
|
function attachNcpAgentSessionJournalTimestamp(event, timestamp) {
|
|
20603
|
-
if (!("payload" in event) || !isRecord$
|
|
22906
|
+
if (!("payload" in event) || !isRecord$11(event.payload)) return event;
|
|
20604
22907
|
return {
|
|
20605
22908
|
...event,
|
|
20606
22909
|
payload: {
|
|
@@ -20625,7 +22928,7 @@ var NcpAgentSessionJournalParser = class {
|
|
|
20625
22928
|
this.applyMetadata(parsed);
|
|
20626
22929
|
return;
|
|
20627
22930
|
}
|
|
20628
|
-
if (parsed._type === "event" && isRecord$
|
|
22931
|
+
if (parsed._type === "event" && isRecord$11(parsed.event)) this.applyEvent(parsed, lineEndOffset);
|
|
20629
22932
|
};
|
|
20630
22933
|
finish = () => ({
|
|
20631
22934
|
metadata: this.metadata,
|
|
@@ -20639,14 +22942,14 @@ var NcpAgentSessionJournalParser = class {
|
|
|
20639
22942
|
parseLine = (line, index) => {
|
|
20640
22943
|
try {
|
|
20641
22944
|
const parsed = JSON.parse(line);
|
|
20642
|
-
return isRecord$
|
|
22945
|
+
return isRecord$11(parsed) ? parsed : null;
|
|
20643
22946
|
} catch (error) {
|
|
20644
22947
|
console.warn(`[ncp-agent-session-journal] skipped corrupted journal line ${index + 1}: ${error instanceof Error ? error.message : String(error)}`);
|
|
20645
22948
|
return null;
|
|
20646
22949
|
}
|
|
20647
22950
|
};
|
|
20648
22951
|
applyMetadata = (entry) => {
|
|
20649
|
-
this.metadata = isRecord$
|
|
22952
|
+
this.metadata = isRecord$11(entry.metadata) ? structuredClone(entry.metadata) : {};
|
|
20650
22953
|
this.agentId = normalizeNcpAgentId(typeof entry.agent_id === "string" ? entry.agent_id : void 0);
|
|
20651
22954
|
this.createdAt = toIsoString(entry.created_at, this.createdAt);
|
|
20652
22955
|
this.updatedAt = toIsoString(entry.updated_at, this.updatedAt);
|
|
@@ -20693,7 +22996,7 @@ var NcpAgentSessionMetadataStore = class {
|
|
|
20693
22996
|
read = async (sessionId, activitySnapshot) => {
|
|
20694
22997
|
try {
|
|
20695
22998
|
const parsed = JSON.parse(await readFile(this.metadataPath(sessionId), "utf-8"));
|
|
20696
|
-
if (!isRecord$
|
|
22999
|
+
if (!isRecord$11(parsed) || parsed._type !== "metadata" || !isRecord$11(parsed.metadata)) throw new Error(`invalid ncp agent session metadata sidecar: ${sessionId}`);
|
|
20697
23000
|
const createdAt = toIsoString(parsed.created_at, activitySnapshot.createdAt);
|
|
20698
23001
|
const agentId = normalizeNcpAgentId(typeof parsed.agent_id === "string" ? parsed.agent_id : void 0);
|
|
20699
23002
|
return {
|
|
@@ -20763,7 +23066,7 @@ function deduplicateNcpAgentSessionTailMessages(messages) {
|
|
|
20763
23066
|
function isNcpAgentSessionMessageProjectionMeta(value, sessionId) {
|
|
20764
23067
|
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
20765
23068
|
const meta = value;
|
|
20766
|
-
return meta.version === 7 && meta.sessionId === sessionId && Number.isSafeInteger(meta.total) && Number.isSafeInteger(meta.projectedJournalOffset) && Number.isSafeInteger(meta.dataBytes) && (meta.activeMessageId === null || typeof meta.activeMessageId === "string") && Array.isArray(meta.pendingCompactionMessageIds) && meta.pendingCompactionMessageIds.every((id) => typeof id === "string") && (meta.contextWindow === null || isRecord$
|
|
23069
|
+
return meta.version === 7 && meta.sessionId === sessionId && Number.isSafeInteger(meta.total) && Number.isSafeInteger(meta.projectedJournalOffset) && Number.isSafeInteger(meta.dataBytes) && (meta.activeMessageId === null || typeof meta.activeMessageId === "string") && Array.isArray(meta.pendingCompactionMessageIds) && meta.pendingCompactionMessageIds.every((id) => typeof id === "string") && (meta.contextWindow === null || isRecord$6(meta.contextWindow));
|
|
20767
23070
|
}
|
|
20768
23071
|
function readActiveAssistantMessageId(messages) {
|
|
20769
23072
|
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
@@ -20786,12 +23089,12 @@ function mergePendingCompactionMessageIds(current, messages) {
|
|
|
20786
23089
|
}
|
|
20787
23090
|
return pending;
|
|
20788
23091
|
}
|
|
20789
|
-
function isRecord$
|
|
23092
|
+
function isRecord$6(value) {
|
|
20790
23093
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
20791
23094
|
}
|
|
20792
23095
|
function readCompactionStatus(message) {
|
|
20793
23096
|
const checkpoint = message.metadata?.checkpoint;
|
|
20794
|
-
if (message.metadata?.nextclaw_timeline_kind !== "context_compaction" || !isRecord$
|
|
23097
|
+
if (message.metadata?.nextclaw_timeline_kind !== "context_compaction" || !isRecord$6(checkpoint)) return null;
|
|
20795
23098
|
return typeof checkpoint.status === "string" ? checkpoint.status : null;
|
|
20796
23099
|
}
|
|
20797
23100
|
//#endregion
|
|
@@ -21299,151 +23602,6 @@ async function readLegacyRecords(journalDir, diagnostics) {
|
|
|
21299
23602
|
return /* @__PURE__ */ new Map();
|
|
21300
23603
|
}
|
|
21301
23604
|
//#endregion
|
|
21302
|
-
//#region src/stores/ncp-session-catalog-sqlite-driver.store.ts
|
|
21303
|
-
const require = createRequire(import.meta.url);
|
|
21304
|
-
const portableDatabases = /* @__PURE__ */ new Map();
|
|
21305
|
-
async function openSessionCatalogSqliteDatabase(databasePath) {
|
|
21306
|
-
try {
|
|
21307
|
-
return new (require("node:sqlite")).DatabaseSync(databasePath);
|
|
21308
|
-
} catch (error) {
|
|
21309
|
-
if (!isMissingNodeSqlite(error)) throw error;
|
|
21310
|
-
return await openPortableDatabase(databasePath);
|
|
21311
|
-
}
|
|
21312
|
-
}
|
|
21313
|
-
function runSessionCatalogSqliteTransaction(database, operation, mode = "DEFERRED") {
|
|
21314
|
-
database.exec(`BEGIN ${mode}`);
|
|
21315
|
-
try {
|
|
21316
|
-
const result = operation();
|
|
21317
|
-
database.exec("COMMIT");
|
|
21318
|
-
return result;
|
|
21319
|
-
} catch (error) {
|
|
21320
|
-
database.exec("ROLLBACK");
|
|
21321
|
-
throw error;
|
|
21322
|
-
}
|
|
21323
|
-
}
|
|
21324
|
-
function isMissingNodeSqlite(error) {
|
|
21325
|
-
const code = typeof error === "object" && error !== null ? error.code : void 0;
|
|
21326
|
-
return code === "ERR_UNKNOWN_BUILTIN_MODULE" || code === "MODULE_NOT_FOUND";
|
|
21327
|
-
}
|
|
21328
|
-
async function openPortableDatabase(databasePath) {
|
|
21329
|
-
const existing = portableDatabases.get(databasePath);
|
|
21330
|
-
if (existing) return (await existing).acquire();
|
|
21331
|
-
const opening = createPortableDatabase(databasePath).catch((error) => {
|
|
21332
|
-
portableDatabases.delete(databasePath);
|
|
21333
|
-
throw error;
|
|
21334
|
-
});
|
|
21335
|
-
portableDatabases.set(databasePath, opening);
|
|
21336
|
-
return (await opening).acquire();
|
|
21337
|
-
}
|
|
21338
|
-
async function createPortableDatabase(databasePath) {
|
|
21339
|
-
const initialize = (await import("sql.js")).default;
|
|
21340
|
-
const sqlite = await initialize({ locateFile: (file) => require.resolve(`sql.js/dist/${file}`) });
|
|
21341
|
-
return new PortableSessionCatalogSqliteDatabase(databasePath, existsSync(databasePath) ? new sqlite.Database(readFileSync(databasePath)) : new sqlite.Database());
|
|
21342
|
-
}
|
|
21343
|
-
var PortableSessionCatalogSqliteDatabase = class {
|
|
21344
|
-
inTransaction = false;
|
|
21345
|
-
dirty = false;
|
|
21346
|
-
leaseCount = 0;
|
|
21347
|
-
constructor(databasePath, database) {
|
|
21348
|
-
this.databasePath = databasePath;
|
|
21349
|
-
this.database = database;
|
|
21350
|
-
}
|
|
21351
|
-
acquire = () => {
|
|
21352
|
-
this.leaseCount += 1;
|
|
21353
|
-
return new PortableSessionCatalogSqliteLease(this);
|
|
21354
|
-
};
|
|
21355
|
-
exec = (sql) => {
|
|
21356
|
-
const command = sql.trim().toUpperCase();
|
|
21357
|
-
this.database.run(sql);
|
|
21358
|
-
if (command.startsWith("BEGIN")) {
|
|
21359
|
-
this.inTransaction = true;
|
|
21360
|
-
return;
|
|
21361
|
-
}
|
|
21362
|
-
if (command.startsWith("ROLLBACK")) {
|
|
21363
|
-
this.inTransaction = false;
|
|
21364
|
-
this.dirty = false;
|
|
21365
|
-
return;
|
|
21366
|
-
}
|
|
21367
|
-
if (command.startsWith("COMMIT")) {
|
|
21368
|
-
this.inTransaction = false;
|
|
21369
|
-
this.persistIfDirty();
|
|
21370
|
-
return;
|
|
21371
|
-
}
|
|
21372
|
-
if (!command.startsWith("PRAGMA")) this.markDirty();
|
|
21373
|
-
};
|
|
21374
|
-
prepare = (sql) => new PortableSessionCatalogSqliteStatement(this, sql);
|
|
21375
|
-
release = () => {
|
|
21376
|
-
this.leaseCount -= 1;
|
|
21377
|
-
if (this.leaseCount > 0) return;
|
|
21378
|
-
this.persistIfDirty();
|
|
21379
|
-
this.database.close();
|
|
21380
|
-
portableDatabases.delete(this.databasePath);
|
|
21381
|
-
};
|
|
21382
|
-
run = (sql, params) => {
|
|
21383
|
-
this.database.run(sql, normalizeParams(params));
|
|
21384
|
-
this.markDirty();
|
|
21385
|
-
};
|
|
21386
|
-
readRows = (sql, params) => {
|
|
21387
|
-
const statement = this.database.prepare(sql);
|
|
21388
|
-
try {
|
|
21389
|
-
bindStatement(statement, params);
|
|
21390
|
-
const rows = [];
|
|
21391
|
-
while (statement.step()) rows.push(statement.getAsObject());
|
|
21392
|
-
return rows;
|
|
21393
|
-
} finally {
|
|
21394
|
-
statement.free();
|
|
21395
|
-
}
|
|
21396
|
-
};
|
|
21397
|
-
markDirty = () => {
|
|
21398
|
-
this.dirty = true;
|
|
21399
|
-
if (!this.inTransaction) this.persistIfDirty();
|
|
21400
|
-
};
|
|
21401
|
-
persistIfDirty = () => {
|
|
21402
|
-
if (!this.dirty) return;
|
|
21403
|
-
const temporaryPath = `${this.databasePath}.tmp`;
|
|
21404
|
-
writeFileSync(temporaryPath, this.database.export());
|
|
21405
|
-
renameSync(temporaryPath, this.databasePath);
|
|
21406
|
-
this.dirty = false;
|
|
21407
|
-
};
|
|
21408
|
-
};
|
|
21409
|
-
var PortableSessionCatalogSqliteLease = class {
|
|
21410
|
-
closed = false;
|
|
21411
|
-
constructor(owner) {
|
|
21412
|
-
this.owner = owner;
|
|
21413
|
-
}
|
|
21414
|
-
close = () => {
|
|
21415
|
-
if (this.closed) return;
|
|
21416
|
-
this.closed = true;
|
|
21417
|
-
this.owner.release();
|
|
21418
|
-
};
|
|
21419
|
-
exec = (sql) => this.owner.exec(sql);
|
|
21420
|
-
prepare = (sql) => this.owner.prepare(sql);
|
|
21421
|
-
};
|
|
21422
|
-
var PortableSessionCatalogSqliteStatement = class {
|
|
21423
|
-
constructor(database, sql) {
|
|
21424
|
-
this.database = database;
|
|
21425
|
-
this.sql = sql;
|
|
21426
|
-
}
|
|
21427
|
-
all = (...params) => this.database.readRows(this.sql, params);
|
|
21428
|
-
get = (...params) => this.all(...params)[0];
|
|
21429
|
-
run = (...params) => {
|
|
21430
|
-
this.database.run(this.sql, params);
|
|
21431
|
-
return {};
|
|
21432
|
-
};
|
|
21433
|
-
};
|
|
21434
|
-
function bindStatement(statement, params) {
|
|
21435
|
-
const normalized = normalizeParams(params);
|
|
21436
|
-
if (Array.isArray(normalized) && normalized.length === 0) return;
|
|
21437
|
-
statement.bind(normalized);
|
|
21438
|
-
}
|
|
21439
|
-
function normalizeParams(params) {
|
|
21440
|
-
if (params.length !== 1 || !isRecord$6(params[0])) return params;
|
|
21441
|
-
return Object.fromEntries(Object.entries(params[0]).map(([key, value]) => [`@${key}`, value]));
|
|
21442
|
-
}
|
|
21443
|
-
function isRecord$6(value) {
|
|
21444
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
21445
|
-
}
|
|
21446
|
-
//#endregion
|
|
21447
23605
|
//#region src/stores/ncp-agent-session-summary-index.store.ts
|
|
21448
23606
|
const SQLITE_DATABASE_FILE = ".ncp-agent-session-catalog.sqlite";
|
|
21449
23607
|
const CATALOG_SCHEMA_VERSION = 1;
|
|
@@ -21548,7 +23706,7 @@ var NcpAgentSessionSummaryIndexStore = class {
|
|
|
21548
23706
|
const { sessionId: rawSessionId, event, updatedAt } = params;
|
|
21549
23707
|
const sessionId = normalizeNcpSessionId(rawSessionId);
|
|
21550
23708
|
const currentRow = this.db().prepare("SELECT * FROM sessions WHERE session_id = ? LIMIT 1").get(sessionId);
|
|
21551
|
-
const
|
|
23709
|
+
const { deleted_at: _deletedAt, ...upsertParams } = summaryToRow({
|
|
21552
23710
|
...upsertNcpAgentSessionSummaryEvent({
|
|
21553
23711
|
current: currentRow && currentRow.deleted_at === null ? rowToSummary(currentRow) : void 0,
|
|
21554
23712
|
sessionId,
|
|
@@ -21577,13 +23735,13 @@ var NcpAgentSessionSummaryIndexStore = class {
|
|
|
21577
23735
|
END,
|
|
21578
23736
|
message_count = sessions.message_count + excluded.message_count,
|
|
21579
23737
|
status = excluded.status
|
|
21580
|
-
WHERE sessions.deleted_at IS NULL`).run(
|
|
23738
|
+
WHERE sessions.deleted_at IS NULL`).run(upsertParams);
|
|
21581
23739
|
};
|
|
21582
23740
|
remove = async (sessionId) => {
|
|
21583
23741
|
await this.ensureReady();
|
|
21584
23742
|
const normalizedSessionId = normalizeNcpSessionId(sessionId);
|
|
21585
23743
|
const deletedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
21586
|
-
|
|
23744
|
+
runSqliteTransaction(this.db(), () => {
|
|
21587
23745
|
if (this.db().prepare("SELECT session_id FROM sessions WHERE session_id = ? LIMIT 1").get(normalizedSessionId)) {
|
|
21588
23746
|
this.db().prepare("UPDATE sessions SET deleted_at = ? WHERE session_id = ?").run(deletedAt, normalizedSessionId);
|
|
21589
23747
|
return;
|
|
@@ -21608,7 +23766,7 @@ var NcpAgentSessionSummaryIndexStore = class {
|
|
|
21608
23766
|
};
|
|
21609
23767
|
initializeCatalog = async () => {
|
|
21610
23768
|
await mkdir(this.journalDir, { recursive: true });
|
|
21611
|
-
this.database = await
|
|
23769
|
+
this.database = await openSqliteDatabase(resolve(this.journalDir, SQLITE_DATABASE_FILE));
|
|
21612
23770
|
this.database.exec(`
|
|
21613
23771
|
PRAGMA busy_timeout = 10000;
|
|
21614
23772
|
PRAGMA journal_mode = WAL;
|
|
@@ -21648,7 +23806,7 @@ var NcpAgentSessionSummaryIndexStore = class {
|
|
|
21648
23806
|
loadSession: (sessionId) => this.loadSession(sessionId),
|
|
21649
23807
|
loadSessionSummary: this.loadSessionSummary
|
|
21650
23808
|
});
|
|
21651
|
-
|
|
23809
|
+
runSqliteTransaction(this.db(), () => {
|
|
21652
23810
|
if (this.db().prepare("SELECT value FROM storage_meta WHERE key = ? LIMIT 1").get(MIGRATION_STATUS_KEY)?.value === MIGRATION_COMPLETE) {
|
|
21653
23811
|
this.reconcileRecords(scan.records);
|
|
21654
23812
|
return;
|
|
@@ -22663,7 +24821,6 @@ function resolveSessionChannelContext(params) {
|
|
|
22663
24821
|
}
|
|
22664
24822
|
//#endregion
|
|
22665
24823
|
//#region src/features/native-runtime/utils/nextclaw-ncp-run-context.utils.ts
|
|
22666
|
-
const REQUESTED_SKILLS_METADATA_READER = new RequestedSkillsMetadataReader();
|
|
22667
24824
|
function resolveRequestedToolNames(metadata) {
|
|
22668
24825
|
const rawValue = metadata.requested_tools ?? metadata.requestedTools;
|
|
22669
24826
|
if (!Array.isArray(rawValue)) return [];
|
|
@@ -22700,7 +24857,6 @@ function buildNextclawNcpRunContext(params) {
|
|
|
22700
24857
|
requestMetadata
|
|
22701
24858
|
});
|
|
22702
24859
|
const sessionMetadata = channelContext.metadata;
|
|
22703
|
-
const requestedSkills = REQUESTED_SKILLS_METADATA_READER.readSelection(requestMetadata);
|
|
22704
24860
|
const runtimeThinking = resolveThinkingLevel({
|
|
22705
24861
|
config,
|
|
22706
24862
|
agentId: profile.agentId,
|
|
@@ -22715,7 +24871,6 @@ function buildNextclawNcpRunContext(params) {
|
|
|
22715
24871
|
effectiveWorkspace,
|
|
22716
24872
|
profile,
|
|
22717
24873
|
requestMetadata,
|
|
22718
|
-
requestedSkills,
|
|
22719
24874
|
requestedToolNames: resolveRequestedToolNames(requestMetadata),
|
|
22720
24875
|
runtimeThinking,
|
|
22721
24876
|
sessionKey: sessionId,
|
|
@@ -23513,13 +25668,12 @@ var ReplyFormatContextProvider = class {
|
|
|
23513
25668
|
};
|
|
23514
25669
|
//#endregion
|
|
23515
25670
|
//#region src/contributions/context-provider/providers/skills-context.provider.ts
|
|
23516
|
-
function
|
|
25671
|
+
function renderAlwaysOnSkillsSection(skills, skillSelectors) {
|
|
23517
25672
|
const manifest = skills.buildSkillsManifest(skillSelectors);
|
|
23518
25673
|
if (!manifest) return "";
|
|
23519
25674
|
return [
|
|
23520
|
-
"#
|
|
23521
|
-
"These
|
|
23522
|
-
"For NextClaw self-management, use the active built-in self-management skill before unrelated generic skills.",
|
|
25675
|
+
"# Always-on Skills",
|
|
25676
|
+
"These skills are always active. Read each `Root/<name>/SKILL.md` before following it.",
|
|
23523
25677
|
manifest
|
|
23524
25678
|
].join("\n");
|
|
23525
25679
|
}
|
|
@@ -23554,7 +25708,7 @@ var SkillsContextProvider = class {
|
|
|
23554
25708
|
this.context = context;
|
|
23555
25709
|
}
|
|
23556
25710
|
provide = async (request) => {
|
|
23557
|
-
const { projectContext
|
|
25711
|
+
const { projectContext } = await this.context.resolve(request);
|
|
23558
25712
|
const skills = new SkillsLoader({
|
|
23559
25713
|
workspace: projectContext.hostWorkspace,
|
|
23560
25714
|
projectRoot: projectContext.projectRoot,
|
|
@@ -23564,10 +25718,10 @@ var SkillsContextProvider = class {
|
|
|
23564
25718
|
hostWorkspace: projectContext.hostWorkspace,
|
|
23565
25719
|
projectSkillsRoot: projectContext.projectSkillsRoot
|
|
23566
25720
|
})];
|
|
23567
|
-
const
|
|
23568
|
-
if (
|
|
23569
|
-
const
|
|
23570
|
-
if (
|
|
25721
|
+
const alwaysOnSkills = skills.getAlwaysSkills();
|
|
25722
|
+
if (alwaysOnSkills.length) {
|
|
25723
|
+
const alwaysOnSection = renderAlwaysOnSkillsSection(skills, alwaysOnSkills);
|
|
25724
|
+
if (alwaysOnSection) blocks.push(alwaysOnSection);
|
|
23571
25725
|
}
|
|
23572
25726
|
const availableSkillsSection = renderAvailableSkillsSection(skills);
|
|
23573
25727
|
if (availableSkillsSection) blocks.push(availableSkillsSection);
|
|
@@ -24198,7 +26352,6 @@ const LEARNING_LOOP_LAST_TOOL_CALL_COUNT_METADATA_KEY = "learning_loop_last_tool
|
|
|
24198
26352
|
const LEARNING_LOOP_LAST_REQUESTED_AT_METADATA_KEY = "learning_loop_last_requested_at";
|
|
24199
26353
|
const LEARNING_LOOP_LAST_REVIEW_SESSION_ID_METADATA_KEY = "learning_loop_last_review_session_id";
|
|
24200
26354
|
const LEARNING_LOOP_SOURCE_SESSION_ID_METADATA_KEY = "learning_loop_source_session_id";
|
|
24201
|
-
const LEARNING_LOOP_REQUESTED_SKILLS = ["skill-creator"];
|
|
24202
26355
|
function readLearningLoopRuntimeConfig(config) {
|
|
24203
26356
|
return {
|
|
24204
26357
|
enabled: config.agents.learningLoop.enabled,
|
|
@@ -24311,7 +26464,6 @@ var LearningLoopContribution = class extends Contribution$1 {
|
|
|
24311
26464
|
sourceSessionId: sessionId,
|
|
24312
26465
|
sourceSessionMetadata: metadata,
|
|
24313
26466
|
metadataOverrides: {
|
|
24314
|
-
requested_skills: LEARNING_LOOP_REQUESTED_SKILLS,
|
|
24315
26467
|
[LEARNING_LOOP_DISABLED_METADATA_KEY]: true,
|
|
24316
26468
|
[LEARNING_LOOP_SOURCE_SESSION_ID_METADATA_KEY]: sessionId
|
|
24317
26469
|
},
|
|
@@ -24509,12 +26661,234 @@ var ProjectsCreateTool = class {
|
|
|
24509
26661
|
};
|
|
24510
26662
|
};
|
|
24511
26663
|
//#endregion
|
|
26664
|
+
//#region src/tools/project-work.tools.ts
|
|
26665
|
+
function requiredString$1(value, key) {
|
|
26666
|
+
if (typeof value !== "string" || !value.trim()) throw new Error(`${key} must be a non-empty string.`);
|
|
26667
|
+
return value.trim();
|
|
26668
|
+
}
|
|
26669
|
+
function optionalString$1(value) {
|
|
26670
|
+
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
26671
|
+
}
|
|
26672
|
+
function optionalBoolean(value) {
|
|
26673
|
+
return typeof value === "boolean" ? value : void 0;
|
|
26674
|
+
}
|
|
26675
|
+
function optionalNumber(value) {
|
|
26676
|
+
return typeof value === "number" && Number.isInteger(value) ? value : void 0;
|
|
26677
|
+
}
|
|
26678
|
+
function agentActor(context) {
|
|
26679
|
+
return {
|
|
26680
|
+
kind: "agent",
|
|
26681
|
+
...context.agentId ? { id: context.agentId } : {},
|
|
26682
|
+
sessionId: context.sessionId
|
|
26683
|
+
};
|
|
26684
|
+
}
|
|
26685
|
+
var ProjectWorkListTool = class {
|
|
26686
|
+
name = "project_work_list";
|
|
26687
|
+
description = "List persistent work items and custom states for the current project.";
|
|
26688
|
+
parameters = {
|
|
26689
|
+
type: "object",
|
|
26690
|
+
properties: { include_deleted: {
|
|
26691
|
+
type: "boolean",
|
|
26692
|
+
description: "Include deleted work items."
|
|
26693
|
+
} },
|
|
26694
|
+
additionalProperties: false
|
|
26695
|
+
};
|
|
26696
|
+
constructor(work, context) {
|
|
26697
|
+
this.work = work;
|
|
26698
|
+
this.context = context;
|
|
26699
|
+
}
|
|
26700
|
+
execute = async (args) => {
|
|
26701
|
+
const params = normalizeToolParams(args);
|
|
26702
|
+
return JSON.stringify(await this.work.list(this.context.projectId, optionalBoolean(params.include_deleted) ?? false), null, 2);
|
|
26703
|
+
};
|
|
26704
|
+
};
|
|
26705
|
+
var ProjectWorkGetTool = class {
|
|
26706
|
+
name = "project_work_get";
|
|
26707
|
+
description = "Get one current-project work item with artifact links and recent immutable activity.";
|
|
26708
|
+
parameters = {
|
|
26709
|
+
type: "object",
|
|
26710
|
+
properties: {
|
|
26711
|
+
id: {
|
|
26712
|
+
type: "string",
|
|
26713
|
+
description: "Work item id."
|
|
26714
|
+
},
|
|
26715
|
+
activity_limit: {
|
|
26716
|
+
type: "integer",
|
|
26717
|
+
minimum: 1,
|
|
26718
|
+
maximum: 100,
|
|
26719
|
+
description: "Recent activity entries. Defaults to 20."
|
|
26720
|
+
}
|
|
26721
|
+
},
|
|
26722
|
+
required: ["id"],
|
|
26723
|
+
additionalProperties: false
|
|
26724
|
+
};
|
|
26725
|
+
constructor(work, context) {
|
|
26726
|
+
this.work = work;
|
|
26727
|
+
this.context = context;
|
|
26728
|
+
}
|
|
26729
|
+
execute = async (args) => {
|
|
26730
|
+
const params = normalizeToolParams(args);
|
|
26731
|
+
const id = requiredString$1(params.id, "id");
|
|
26732
|
+
const [item, activity] = await Promise.all([this.work.get(this.context.projectId, id), this.work.listActivities(this.context.projectId, id, { limit: optionalNumber(params.activity_limit) ?? 20 })]);
|
|
26733
|
+
return JSON.stringify({
|
|
26734
|
+
item,
|
|
26735
|
+
activity
|
|
26736
|
+
}, null, 2);
|
|
26737
|
+
};
|
|
26738
|
+
};
|
|
26739
|
+
var ProjectWorkCreateTool = class {
|
|
26740
|
+
name = "project_work_create";
|
|
26741
|
+
description = "Create a persistent work item in the current project.";
|
|
26742
|
+
parameters = {
|
|
26743
|
+
type: "object",
|
|
26744
|
+
properties: {
|
|
26745
|
+
title: {
|
|
26746
|
+
type: "string",
|
|
26747
|
+
description: "Concise work item title."
|
|
26748
|
+
},
|
|
26749
|
+
description: {
|
|
26750
|
+
type: "string",
|
|
26751
|
+
description: "Optional durable context and acceptance notes."
|
|
26752
|
+
},
|
|
26753
|
+
state_id: {
|
|
26754
|
+
type: "string",
|
|
26755
|
+
description: "Optional custom state id. The project default is used when omitted."
|
|
26756
|
+
},
|
|
26757
|
+
attention: {
|
|
26758
|
+
type: "string",
|
|
26759
|
+
enum: [
|
|
26760
|
+
"none",
|
|
26761
|
+
"blocked",
|
|
26762
|
+
"awaiting-user"
|
|
26763
|
+
]
|
|
26764
|
+
}
|
|
26765
|
+
},
|
|
26766
|
+
required: ["title"],
|
|
26767
|
+
additionalProperties: false
|
|
26768
|
+
};
|
|
26769
|
+
constructor(work, context) {
|
|
26770
|
+
this.work = work;
|
|
26771
|
+
this.context = context;
|
|
26772
|
+
}
|
|
26773
|
+
execute = async (args) => {
|
|
26774
|
+
const params = normalizeToolParams(args);
|
|
26775
|
+
return JSON.stringify(await this.work.create(this.context.projectId, {
|
|
26776
|
+
title: requiredString$1(params.title, "title"),
|
|
26777
|
+
...optionalString$1(params.description) ? { description: optionalString$1(params.description) } : {},
|
|
26778
|
+
...optionalString$1(params.state_id) ? { stateId: optionalString$1(params.state_id) } : {},
|
|
26779
|
+
...optionalString$1(params.attention) ? { attention: optionalString$1(params.attention) } : {}
|
|
26780
|
+
}, agentActor(this.context)), null, 2);
|
|
26781
|
+
};
|
|
26782
|
+
};
|
|
26783
|
+
var ProjectWorkUpdateTool = class {
|
|
26784
|
+
name = "project_work_update";
|
|
26785
|
+
description = "Update, soft-delete, or restore a current-project work item, or add/remove its artifact links. Status changes use state_id; there is no separate start tool.";
|
|
26786
|
+
parameters = {
|
|
26787
|
+
type: "object",
|
|
26788
|
+
properties: {
|
|
26789
|
+
id: {
|
|
26790
|
+
type: "string",
|
|
26791
|
+
description: "Work item id."
|
|
26792
|
+
},
|
|
26793
|
+
title: { type: "string" },
|
|
26794
|
+
description: { type: "string" },
|
|
26795
|
+
state_id: { type: "string" },
|
|
26796
|
+
attention: {
|
|
26797
|
+
type: "string",
|
|
26798
|
+
enum: [
|
|
26799
|
+
"none",
|
|
26800
|
+
"blocked",
|
|
26801
|
+
"awaiting-user"
|
|
26802
|
+
]
|
|
26803
|
+
},
|
|
26804
|
+
expected_version: {
|
|
26805
|
+
type: "integer",
|
|
26806
|
+
minimum: 1
|
|
26807
|
+
},
|
|
26808
|
+
deleted: {
|
|
26809
|
+
type: "boolean",
|
|
26810
|
+
description: "True to soft-delete the item; false to restore it."
|
|
26811
|
+
},
|
|
26812
|
+
add_artifact: {
|
|
26813
|
+
type: "object",
|
|
26814
|
+
properties: {
|
|
26815
|
+
path: { type: "string" },
|
|
26816
|
+
label: { type: "string" }
|
|
26817
|
+
},
|
|
26818
|
+
required: ["path"],
|
|
26819
|
+
additionalProperties: false
|
|
26820
|
+
},
|
|
26821
|
+
remove_artifact_id: { type: "string" }
|
|
26822
|
+
},
|
|
26823
|
+
required: ["id"],
|
|
26824
|
+
additionalProperties: false
|
|
26825
|
+
};
|
|
26826
|
+
constructor(work, context) {
|
|
26827
|
+
this.work = work;
|
|
26828
|
+
this.context = context;
|
|
26829
|
+
}
|
|
26830
|
+
execute = async (args) => {
|
|
26831
|
+
const params = normalizeToolParams(args);
|
|
26832
|
+
const id = requiredString$1(params.id, "id");
|
|
26833
|
+
const actor = agentActor(this.context);
|
|
26834
|
+
const patch = {
|
|
26835
|
+
...params.title !== void 0 ? { title: requiredString$1(params.title, "title") } : {},
|
|
26836
|
+
...typeof params.description === "string" ? { description: params.description } : {},
|
|
26837
|
+
...optionalString$1(params.state_id) ? { stateId: optionalString$1(params.state_id) } : {},
|
|
26838
|
+
...optionalString$1(params.attention) ? { attention: optionalString$1(params.attention) } : {},
|
|
26839
|
+
...optionalNumber(params.expected_version) ? { expectedVersion: optionalNumber(params.expected_version) } : {}
|
|
26840
|
+
};
|
|
26841
|
+
if (Object.keys(patch).length > 0) await this.work.update(this.context.projectId, id, patch, actor);
|
|
26842
|
+
const deleted = optionalBoolean(params.deleted);
|
|
26843
|
+
if (deleted !== void 0) await (deleted ? this.work.delete(this.context.projectId, id, actor) : this.work.restore(this.context.projectId, id, actor));
|
|
26844
|
+
if (params.add_artifact && typeof params.add_artifact === "object" && !Array.isArray(params.add_artifact)) {
|
|
26845
|
+
const artifact = params.add_artifact;
|
|
26846
|
+
await this.work.linkArtifact({
|
|
26847
|
+
projectId: this.context.projectId,
|
|
26848
|
+
workItemId: id,
|
|
26849
|
+
path: requiredString$1(artifact.path, "add_artifact.path"),
|
|
26850
|
+
...optionalString$1(artifact.label) ? { label: optionalString$1(artifact.label) } : {},
|
|
26851
|
+
actor
|
|
26852
|
+
});
|
|
26853
|
+
}
|
|
26854
|
+
const removeArtifactId = optionalString$1(params.remove_artifact_id);
|
|
26855
|
+
if (removeArtifactId) await this.work.unlinkArtifact({
|
|
26856
|
+
projectId: this.context.projectId,
|
|
26857
|
+
workItemId: id,
|
|
26858
|
+
artifactLinkId: removeArtifactId,
|
|
26859
|
+
actor
|
|
26860
|
+
});
|
|
26861
|
+
return JSON.stringify(await this.work.get(this.context.projectId, id), null, 2);
|
|
26862
|
+
};
|
|
26863
|
+
};
|
|
26864
|
+
//#endregion
|
|
24512
26865
|
//#region src/contributions/tool-provider/providers/project-tool.provider.ts
|
|
24513
26866
|
var ProjectToolProvider = class {
|
|
24514
|
-
constructor(projectManager) {
|
|
26867
|
+
constructor(runContext, projectManager, projectWork) {
|
|
26868
|
+
this.runContext = runContext;
|
|
24515
26869
|
this.projectManager = projectManager;
|
|
26870
|
+
this.projectWork = projectWork;
|
|
24516
26871
|
}
|
|
24517
|
-
provide = (
|
|
26872
|
+
provide = async (request) => {
|
|
26873
|
+
const globalTools = [new ProjectsListTool(this.projectManager), new ProjectsCreateTool(this.projectManager)];
|
|
26874
|
+
const resolved = await this.runContext.resolve(request);
|
|
26875
|
+
const projectId = readProjectId(resolved.session?.metadata);
|
|
26876
|
+
const projectRoot = readProjectRoot(resolved.session?.metadata);
|
|
26877
|
+
const project = projectId ? await this.projectManager.getProjectById(projectId) : projectRoot ? await this.projectManager.getRegisteredProject(projectRoot) : null;
|
|
26878
|
+
if (!project || !resolved.sessionId) return globalTools;
|
|
26879
|
+
const context = {
|
|
26880
|
+
projectId: project.id,
|
|
26881
|
+
sessionId: resolved.sessionId,
|
|
26882
|
+
...resolved.session?.agentId ? { agentId: resolved.session.agentId } : {}
|
|
26883
|
+
};
|
|
26884
|
+
return [
|
|
26885
|
+
...globalTools,
|
|
26886
|
+
new ProjectWorkListTool(this.projectWork, context),
|
|
26887
|
+
new ProjectWorkGetTool(this.projectWork, context),
|
|
26888
|
+
new ProjectWorkCreateTool(this.projectWork, context),
|
|
26889
|
+
new ProjectWorkUpdateTool(this.projectWork, context)
|
|
26890
|
+
];
|
|
26891
|
+
};
|
|
24518
26892
|
};
|
|
24519
26893
|
//#endregion
|
|
24520
26894
|
//#region src/tools/session-history.tools.ts
|
|
@@ -26351,7 +28725,7 @@ var ToolProviderContribution = class extends Contribution$1 {
|
|
|
26351
28725
|
new DesktopToolProvider(runContextService, this.kernel.extensions.getDesktopHost()),
|
|
26352
28726
|
new CoreToolProvider(runContextService, this.kernel.getGatewayController),
|
|
26353
28727
|
new MessagingToolProvider(runContextService, this.kernel.channels, this.kernel.automation, this.kernel.extensions),
|
|
26354
|
-
new ProjectToolProvider(this.kernel.projectManager),
|
|
28728
|
+
new ProjectToolProvider(runContextService, this.kernel.projectManager, this.kernel.projectWorkManager),
|
|
26355
28729
|
new SessionToolProvider(runContextService, this.kernel.sessionManager, this.kernel.sessionRequests, this.kernel.sessionSearch),
|
|
26356
28730
|
new AssetToolProvider(this.kernel.assetStore),
|
|
26357
28731
|
new ServiceActionToolProvider(runContextService, this.kernel.serviceAppManager),
|
|
@@ -26391,6 +28765,7 @@ function createKernelServiceAppManagers(params) {
|
|
|
26391
28765
|
appHomeDirectory,
|
|
26392
28766
|
configManager,
|
|
26393
28767
|
listPackageComponentSources: appPackageManager.listActiveComponentSources,
|
|
28768
|
+
assertDocumentAccess: appPackageManager.assertDocumentAccess,
|
|
26394
28769
|
capabilityGrantManager,
|
|
26395
28770
|
hasAgent,
|
|
26396
28771
|
providerManager,
|
|
@@ -26447,16 +28822,26 @@ function createPortableRuntimeAcceptanceServices(params) {
|
|
|
26447
28822
|
};
|
|
26448
28823
|
}
|
|
26449
28824
|
function createKernelSessionManagers(params) {
|
|
26450
|
-
const { agentContextWindowManager, agentManager, configManager, eventBus, ingress, observationStorePath, projectStorePath, sessionsDir } = params;
|
|
28825
|
+
const { agentContextWindowManager, agentManager, configManager, eventBus, ingress, observationStorePath, projectStorePath, projectWorkStorePath, sessionsDir } = params;
|
|
26451
28826
|
const sessionSearch = new SessionSearchService({
|
|
26452
28827
|
databasePath: resolve(getDataDir(), "session-search.db"),
|
|
26453
28828
|
sessionsDir
|
|
26454
28829
|
});
|
|
26455
28830
|
const journalStore = new NcpAgentSessionJournalStore(resolve(sessionsDir, ".ncp-agent-journal"));
|
|
28831
|
+
const projectWorkOwner = { current: null };
|
|
26456
28832
|
const projectManager = new ProjectManager({
|
|
26457
28833
|
storePath: projectStorePath,
|
|
26458
|
-
getDefaultWorkspacePath: () => getWorkspacePathFromConfig(configManager.config)
|
|
28834
|
+
getDefaultWorkspacePath: () => getWorkspacePathFromConfig(configManager.config),
|
|
28835
|
+
onProjectRegistered: async (project) => {
|
|
28836
|
+
await projectWorkOwner.current?.ensureProject(project.id);
|
|
28837
|
+
}
|
|
28838
|
+
});
|
|
28839
|
+
const projectWorkManager = new ProjectWorkManager({
|
|
28840
|
+
databasePath: projectWorkStorePath,
|
|
28841
|
+
eventBus,
|
|
28842
|
+
projectManager
|
|
26459
28843
|
});
|
|
28844
|
+
projectWorkOwner.current = projectWorkManager;
|
|
26460
28845
|
const observationOwner = { current: null };
|
|
26461
28846
|
const sessionManager = new SessionManager({
|
|
26462
28847
|
agentContextWindowManager,
|
|
@@ -26478,11 +28863,18 @@ function createKernelSessionManagers(params) {
|
|
|
26478
28863
|
ingress,
|
|
26479
28864
|
eventBus
|
|
26480
28865
|
});
|
|
28866
|
+
const projectObservation = new ProjectObservationService({
|
|
28867
|
+
projectManager,
|
|
28868
|
+
sessionManager,
|
|
28869
|
+
workspacePath: getWorkspacePathFromConfig(configManager.config)
|
|
28870
|
+
});
|
|
26481
28871
|
observationOwner.current = observations;
|
|
26482
28872
|
return {
|
|
26483
28873
|
journalStore,
|
|
26484
28874
|
observations,
|
|
26485
28875
|
projectManager,
|
|
28876
|
+
projectObservation,
|
|
28877
|
+
projectWorkManager,
|
|
26486
28878
|
sessionManager,
|
|
26487
28879
|
sessionSearch
|
|
26488
28880
|
};
|
|
@@ -26511,6 +28903,10 @@ function installKernelAppPackageRuntimeHooks(params) {
|
|
|
26511
28903
|
panelAppManager.deactivatePackageComponents(sources);
|
|
26512
28904
|
await serviceAppManager.deactivatePackageComponents(sources);
|
|
26513
28905
|
},
|
|
28906
|
+
prepareCapabilityChange: async (sources) => await serviceAppManager.preparePackageComponentDeactivation(sources),
|
|
28907
|
+
afterCapabilityChange: async (sources) => {
|
|
28908
|
+
await serviceAppManager.activatePackageComponents(sources);
|
|
28909
|
+
},
|
|
26514
28910
|
beforeUninstall: async (sources) => {
|
|
26515
28911
|
const rollbacks = [];
|
|
26516
28912
|
try {
|
|
@@ -26578,6 +28974,8 @@ var NextclawKernel = class {
|
|
|
26578
28974
|
panelAppManager;
|
|
26579
28975
|
preferenceManager;
|
|
26580
28976
|
projectManager;
|
|
28977
|
+
projectObservation;
|
|
28978
|
+
projectWorkManager;
|
|
26581
28979
|
serviceAppManager;
|
|
26582
28980
|
extensions;
|
|
26583
28981
|
agentRuntimeManager = new AgentRuntimeManager();
|
|
@@ -26623,7 +29021,7 @@ var NextclawKernel = class {
|
|
|
26623
29021
|
homeDir: options.homeDir
|
|
26624
29022
|
});
|
|
26625
29023
|
this.capabilityGrantLegacyMigration = this.createCapabilityGrantLegacyMigration(options);
|
|
26626
|
-
({journalStore: this.ncpAgentSessionJournalStore, observations: this.observations, projectManager: this.projectManager, sessionManager: this.sessionManager, sessionSearch: this.sessionSearch} = createKernelSessionManagers({
|
|
29024
|
+
({journalStore: this.ncpAgentSessionJournalStore, observations: this.observations, projectManager: this.projectManager, projectObservation: this.projectObservation, projectWorkManager: this.projectWorkManager, sessionManager: this.sessionManager, sessionSearch: this.sessionSearch} = createKernelSessionManagers({
|
|
26627
29025
|
agentContextWindowManager: this.agentContextWindowManager,
|
|
26628
29026
|
agentManager: this.agents,
|
|
26629
29027
|
configManager: this.configManager,
|
|
@@ -26631,6 +29029,7 @@ var NextclawKernel = class {
|
|
|
26631
29029
|
ingress: this.ingress,
|
|
26632
29030
|
observationStorePath: resolveKernelObservationStorePath(options),
|
|
26633
29031
|
projectStorePath: resolveKernelProjectStorePath(options),
|
|
29032
|
+
projectWorkStorePath: resolveKernelProjectWorkStorePath(options),
|
|
26634
29033
|
sessionsDir
|
|
26635
29034
|
}));
|
|
26636
29035
|
this.inboxDeliveryManager = new InboxDeliveryManager({
|
|
@@ -26730,7 +29129,9 @@ var NextclawKernel = class {
|
|
|
26730
29129
|
this.sessionSearch.start();
|
|
26731
29130
|
this.mcpManager.start();
|
|
26732
29131
|
this.providerModelCatalog.start();
|
|
29132
|
+
await this.projectManager.migrateLegacyProjects();
|
|
26733
29133
|
await this.projectManager.importSessionProjects((await this.sessionManager.listSessions()).map((session) => readProjectRoot(session.metadata)));
|
|
29134
|
+
await this.projectWorkManager.initialize();
|
|
26734
29135
|
await this.sessionManager.start();
|
|
26735
29136
|
for (const contribution of this.contributions) await contribution.start();
|
|
26736
29137
|
this.agentRunRequestManager.start();
|
|
@@ -26750,6 +29151,7 @@ var NextclawKernel = class {
|
|
|
26750
29151
|
await this.mcpManager.dispose();
|
|
26751
29152
|
await this.serviceAppManager.dispose();
|
|
26752
29153
|
await this.sessionSearch.dispose();
|
|
29154
|
+
this.projectWorkManager.dispose();
|
|
26753
29155
|
};
|
|
26754
29156
|
};
|
|
26755
29157
|
//#endregion
|
|
@@ -27892,6 +30294,6 @@ function resolveLegacyEventType(message) {
|
|
|
27892
30294
|
return `message.${role || "other"}`;
|
|
27893
30295
|
}
|
|
27894
30296
|
//#endregion
|
|
27895
|
-
export { AUTOMATIC_UPDATE_CHECK_INTERVAL_MS, AccessManager, AgentManager, AgentRunClient, AppDataError, AppDataManager, AppPackageError, AppPackageManager, AutomationManager, BuiltinNarpRuntimeProviderService, CONTEXT_COMPACTION_CONTINUATION_TEXT, CONTEXT_COMPACTION_PROJECTION_KIND, CONTEXT_COMPACTION_PROJECTION_METADATA_KEY, CONTEXT_COMPACTION_SYSTEM_PREAMBLE, CONTEXT_COMPACTION_TIMELINE_KIND, CapabilityGrantLegacyMigrationService, CapabilityGrantManager, CapabilityGrantStore, ChannelManager, CommandRegistry, ConfigManager, ContextCompactionJournalRecoveryService, ContextCompactionPreflightService, Contribution, DEFAULT_AGENT_RUNTIME_ENTRY_ID, DEFAULT_SERVICE_ACTION_RISK, DESKTOP_HOST_ACCESS, DESKTOP_HOST_PROTOCOL_VERSION, DesktopHostCapabilityManager, DesktopNodeReplService, DesktopSessionStateService, EventBus, ExtensionManager, FeatureControlsService, GatewayInboundProcessor, InboxDeliveryError, InboxDeliveryManager, Ingress, LlmProviderManager, LlmUsageManager, LlmUsageStore, MAX_INBOX_DELIVERY_CONTENT_LENGTH, McpManager, McpServiceAppRuntimeService, NARP_HTTP_RUNTIME_KIND, NARP_STDIO_RUNTIME_KIND, NEXTCLAW_TIMELINE_KIND_METADATA_KEY, NcpAgentSessionJournalStore, NextclawHarness, NextclawHarnessError, NextclawKernel, ObservationManager, PANEL_APP_AGENT_CAPABILITIES, PORTABLE_RUNTIME_ACCEPTANCE_CONTRACT, PORTABLE_RUNTIME_ACCEPTANCE_CONTRACT_FINGERPRINT, PORTABLE_RUNTIME_ACCEPTANCE_LOCALES, PORTABLE_RUNTIME_ACCEPTANCE_PLATFORMS, PORTABLE_RUNTIME_ACCEPTANCE_PRESENTATION, PORTABLE_RUNTIME_ACCEPTANCE_REFERENCE_APP_ID, PROJECT_TEMPLATE_IDS, PROVIDER_MODEL_CATALOG_REFRESH_INTERVAL_MS, PanelAppAssetTokenService, PanelAppError, PanelAppManager, PortableRuntimeAcceptanceIdentityService, PortableRuntimeAcceptanceManager, PreferenceError, PreferenceManager, ProjectError, ProjectManager, ProviderManagerNcpLLMApi, ProviderModelCatalogManager, SERVICE_APP_MANIFEST_FILE_NAME, ServiceAppAiCapabilityService, ServiceAppError, ServiceAppJobJournalService, ServiceAppManager, ServiceAppResidentEventInboxService, ServiceAppRuntimeService, SessionContextCompactionError, SessionContextCompactionManager, SessionManager, SessionMessageCursorError, SessionRequestManager, SessionSettingsError, SkillManager, SystemObjectReferenceError, SystemObjectReferenceManager, UnavailableDesktopHost, UpdateManifestReader, VerificationRecordService, assertObservationJsonValue, assertObservationPredicate, buildAgentRunSendPayload, buildContextCompactionModelProjection, buildContextCompactionTimelineNcpMessage, buildLlmUsageSummary, buildLocalizedTextMap, buildNextclawNcpRunContext, buildObservationEventModelMessage, buildServiceActionId, buildSessionRequestCompletionMessage, capabilityGrantCovers, createAgentRuntimeSessionRequestDispatcher, createAgentRuntimeSessionRequestSourceNotifier, createAssetTools, createCapabilityDeclarationFingerprint, createContextCompactionMessageId, createContextWindowSignature, createCronJobSystemObjectProvider, createDesktopHostError, createInboxDeliverySystemObjectProvider, createLlmUsageRecord, createPanelAppAgentGrantRequest, createPanelAppClientGrantRequest, createServiceActionGrantRequest, createServiceAppAgentSlotGrantRequest, createServiceAppModelSlotGrantRequest, createTypedKey, describeAgentRuntimeSessionTypes, dispatchAgentRuntimeSessionRequest, dispatchChannelReplyRoute, dispatchPromptOverNcp, dispatchPromptOverNcpResult, evaluateObservationEventAdmission, evaluatePortableRuntimeAcceptance, evaluatePortableRuntimeAcceptanceArtifact, eventKeys, getAutomaticUpdateCheckDelay, getCapabilityGrantKey, getServiceActionName, getServiceAppManifestPath, getUiContentParamsBootstrapScript, getUnsignedUpdateManifest, hasLlmUsageTelemetry, ingressKeys, injectUiContentParamsBootstrap, isAppDataError, isAppPackageError, isContextCompactionProjectionMessage, isContextCompactionTimelineMessage, isContextWindowSnapshot, isInboxDeliveryError, isPanelAppAgentCapability, isPanelAppError, isPreferenceError, isProjectError, isReplyCapableChannel, isServiceAppError, isSessionContextCompactionError, isSessionMessageCursorError, isSessionSettingsError, isSystemObjectReferenceError, listExtensionChannelIds, listServiceAppManifestActions, matchesCapabilityGrantFilter, matchesObservationPredicate, mergeServiceAppRuntimeActions, normalizeAgentRuntimeSessionTypeIcon, normalizeCapabilityGrantRequest, normalizeLlmUsageModel, normalizeOptionalString, normalizePortableRuntimeEnvironment, parseObservationDuration, parsePortableRuntimeAcceptanceEvidenceArtifact, parseServiceAppManifest, parseSkillFrontmatter, presentPortableRuntimeAcceptanceDefinition, readContextCompactionCheckpoint, readContextWindowEventSessionId, readJsonPointer, readLatestContextCompactionCheckpoint, readLearningLoopRuntimeConfig, readMetadataModel, readMetadataThinking, readServiceActionTargetId, readServiceAppManifest, readServiceAppSlotTarget, resolveAgentRuntimeEntries, resolveAutomaticUpdateCheckIntervalMs, resolveChannelReplyRoute, resolveEffectiveModel, resolveLegacyEventType, resolvePortableRuntimeAcceptanceLocale, resolveSessionChannelContext, runGatewayInboundLoop, runNextclawTask, sanitizeLlmUsage, serializeContextTail, serializeUnsignedUpdateManifest, shouldRefreshContextWindowDuringStream, shouldRefreshContextWindowImmediately, startPromptOverNcpExecution, stripSkillFrontmatter, syncSessionThinkingPreference, toBoundedJson, toNcpMessages, waitForAgentRuntimeSessionReply };
|
|
30297
|
+
export { AUTOMATIC_UPDATE_CHECK_INTERVAL_MS, AccessManager, AgentManager, AgentRunClient, AppDataError, AppDataManager, AppPackageError, AppPackageManager, AutomationManager, BuiltinNarpRuntimeProviderService, CONTEXT_COMPACTION_CONTINUATION_TEXT, CONTEXT_COMPACTION_PROJECTION_KIND, CONTEXT_COMPACTION_PROJECTION_METADATA_KEY, CONTEXT_COMPACTION_SYSTEM_PREAMBLE, CONTEXT_COMPACTION_TIMELINE_KIND, CapabilityGrantLegacyMigrationService, CapabilityGrantManager, CapabilityGrantStore, ChannelManager, CommandRegistry, ConfigManager, ContextCompactionJournalRecoveryService, ContextCompactionPreflightService, Contribution, DEFAULT_AGENT_RUNTIME_ENTRY_ID, DEFAULT_SERVICE_ACTION_RISK, DESKTOP_HOST_ACCESS, DESKTOP_HOST_PROTOCOL_VERSION, DesktopHostCapabilityManager, DesktopNodeReplService, DesktopSessionStateService, EventBus, ExtensionManager, FeatureControlsService, GatewayInboundProcessor, InboxDeliveryError, InboxDeliveryManager, Ingress, LlmProviderManager, LlmUsageManager, LlmUsageStore, MAX_INBOX_DELIVERY_CONTENT_LENGTH, McpManager, McpServiceAppRuntimeService, NARP_HTTP_RUNTIME_KIND, NARP_STDIO_RUNTIME_KIND, NEXTCLAW_TIMELINE_KIND_METADATA_KEY, NcpAgentSessionJournalStore, NextclawHarness, NextclawHarnessError, NextclawKernel, ObservationManager, PANEL_APP_AGENT_CAPABILITIES, PORTABLE_RUNTIME_ACCEPTANCE_CONTRACT, PORTABLE_RUNTIME_ACCEPTANCE_CONTRACT_FINGERPRINT, PORTABLE_RUNTIME_ACCEPTANCE_LOCALES, PORTABLE_RUNTIME_ACCEPTANCE_PLATFORMS, PORTABLE_RUNTIME_ACCEPTANCE_PRESENTATION, PORTABLE_RUNTIME_ACCEPTANCE_REFERENCE_APP_ID, PROJECT_OBSERVATION_PROTOCOL, PROJECT_TEMPLATE_IDS, PROJECT_WORK_ATTENTION_VALUES, PROJECT_WORK_STATE_CATEGORIES, PROVIDER_MODEL_CATALOG_REFRESH_INTERVAL_MS, PanelAppAssetTokenService, PanelAppError, PanelAppManager, PortableRuntimeAcceptanceIdentityService, PortableRuntimeAcceptanceManager, PreferenceError, PreferenceManager, ProjectError, ProjectManager, ProjectObservationError, ProjectObservationService, ProjectWorkError, ProjectWorkManager, ProviderManagerNcpLLMApi, ProviderModelCatalogManager, SERVICE_APP_MANIFEST_FILE_NAME, ServiceAppAiCapabilityService, ServiceAppError, ServiceAppJobJournalService, ServiceAppManager, ServiceAppResidentEventInboxService, ServiceAppRuntimeService, SessionContextCompactionError, SessionContextCompactionManager, SessionManager, SessionMessageCursorError, SessionRequestManager, SessionSettingsError, SkillManager, SystemObjectReferenceError, SystemObjectReferenceManager, UnavailableDesktopHost, UpdateManifestReader, VerificationRecordService, assertObservationJsonValue, assertObservationPredicate, buildAgentRunSendPayload, buildContextCompactionModelProjection, buildContextCompactionTimelineNcpMessage, buildLlmUsageSummary, buildLocalizedTextMap, buildNextclawNcpRunContext, buildObservationEventModelMessage, buildServiceActionId, buildSessionRequestCompletionMessage, capabilityGrantCovers, createAgentRuntimeSessionRequestDispatcher, createAgentRuntimeSessionRequestSourceNotifier, createAssetTools, createCapabilityDeclarationFingerprint, createContextCompactionMessageId, createContextWindowSignature, createCronJobSystemObjectProvider, createDesktopHostError, createInboxDeliverySystemObjectProvider, createLlmUsageRecord, createPanelAppAgentGrantRequest, createPanelAppClientGrantRequest, createServiceActionGrantRequest, createServiceAppAgentSlotGrantRequest, createServiceAppModelSlotGrantRequest, createTypedKey, describeAgentRuntimeSessionTypes, dispatchAgentRuntimeSessionRequest, dispatchChannelReplyRoute, dispatchPromptOverNcp, dispatchPromptOverNcpResult, evaluateObservationEventAdmission, evaluatePortableRuntimeAcceptance, evaluatePortableRuntimeAcceptanceArtifact, eventKeys, getAutomaticUpdateCheckDelay, getCapabilityGrantKey, getServiceActionName, getServiceAppManifestPath, getUiContentParamsBootstrapScript, getUnsignedUpdateManifest, hasLlmUsageTelemetry, ingressKeys, injectUiContentParamsBootstrap, isAppDataError, isAppPackageError, isContextCompactionProjectionMessage, isContextCompactionTimelineMessage, isContextWindowSnapshot, isInboxDeliveryError, isPanelAppAgentCapability, isPanelAppError, isPreferenceError, isProjectError, isProjectObservationError, isProjectWorkError, isReplyCapableChannel, isServiceAppError, isSessionContextCompactionError, isSessionMessageCursorError, isSessionSettingsError, isSystemObjectReferenceError, listExtensionChannelIds, listServiceAppManifestActions, matchesCapabilityGrantFilter, matchesObservationPredicate, mergeServiceAppRuntimeActions, normalizeAgentRuntimeSessionTypeIcon, normalizeCapabilityGrantRequest, normalizeLlmUsageModel, normalizeOptionalString, normalizePortableRuntimeEnvironment, parseObservationDuration, parsePortableRuntimeAcceptanceEvidenceArtifact, parseServiceAppManifest, parseSkillFrontmatter, presentPortableRuntimeAcceptanceDefinition, readContextCompactionCheckpoint, readContextWindowEventSessionId, readJsonPointer, readLatestContextCompactionCheckpoint, readLearningLoopRuntimeConfig, readMetadataModel, readMetadataThinking, readServiceActionTargetId, readServiceAppManifest, readServiceAppSlotTarget, resolveAgentRuntimeEntries, resolveAutomaticUpdateCheckIntervalMs, resolveChannelReplyRoute, resolveEffectiveModel, resolveLegacyEventType, resolvePortableRuntimeAcceptanceLocale, resolveSessionChannelContext, runGatewayInboundLoop, runNextclawTask, sanitizeLlmUsage, serializeContextTail, serializeUnsignedUpdateManifest, shouldRefreshContextWindowDuringStream, shouldRefreshContextWindowImmediately, startPromptOverNcpExecution, stripSkillFrontmatter, syncSessionThinkingPreference, toBoundedJson, toNcpMessages, waitForAgentRuntimeSessionReply };
|
|
27896
30298
|
|
|
27897
30299
|
//# sourceMappingURL=index.js.map
|