@openspecui/server 7.0.1 → 9.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{api-B4zHqR-f.mjs → api-C_H23KUr.mjs} +1 -1
- package/dist/index.mjs +340 -134
- package/package.json +4 -4
|
@@ -2,9 +2,9 @@ import m, { createRequire } from "node:module";
|
|
|
2
2
|
import a from "node:path";
|
|
3
3
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
4
4
|
import se, { writeSync } from "node:fs";
|
|
5
|
+
import Ht from "node:crypto";
|
|
5
6
|
import o from "node:os";
|
|
6
7
|
import p from "node:net";
|
|
7
|
-
import Ht from "node:crypto";
|
|
8
8
|
import { inspect } from "node:util";
|
|
9
9
|
import { MessageChannel } from "node:worker_threads";
|
|
10
10
|
import { createRequire as createRequire$1 } from "module";
|
package/dist/index.mjs
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { a as resolveCt2ModelDownloadPlanFromRepositoryFiles } from "./src-Dh_UAz5C.mjs";
|
|
2
2
|
import { t as resolveGgufModelDownloadPlanFromRepositoryFiles } from "./src-CZfiVL_-.mjs";
|
|
3
|
-
import {
|
|
3
|
+
import { ActiveRootMutationSchema, ActiveRootRevisionSchema, AgentDeliveryPolicyUpdateSchema, BatchTranslateInputSchema, CliExecutor, CliProjectionCommandError, CliProjectionNoticeSchema, CodeEditorThemeSchema, ConfigManager, CustomSoundHashSchema, DASHBOARD_METRIC_KEYS, DashboardConfigSchema, DashboardGitSnapshotSchema, DashboardSummaryInvalidationSchema, DashboardSummaryProjectionStateSchema, DashboardTrendsProjectionSchema, DocumentTranslationConfigUpdateSchema, EnvironmentDefaultStoreUpdateSchema, EnvironmentGlobalConfigValueSchema, EnvironmentGlobalFileProjectionDataSchema, EnvironmentGlobalFileProjectionStateSchema, EnvironmentGlobalProjectionDataSchema, EnvironmentGlobalProjectionStateSchema, GitConfigSchema, GlobalSettingsManager, LocalModelAssetStateSchema, LocalModelLifecycleFileStateSchema, LocalModelLifecycleGroupStateSchema, LocalModelProfileManifestSchema, MarkdownParser, NotificationPublishInputSchema, NotificationSettingsSchema, OPENSPECUI_DOCUMENT_HOOK_VERSION, OPENSPECUI_WORKFLOW_HOOK_VERSION, OPSX_ARTIFACT_INPUT_ACTIONS, OPSX_CHANGE_INPUT_ACTIONS, OPSX_COMMAND_CAPABLE_WORKFLOWS, OPSX_CORE_PROFILE_WORKFLOWS, OPSX_TEXT_INPUT_ACTIONS, OpenSpecAdapter, OpenSpecDataHomeObserver, OpenSpecUIGlobalSettingsUpdateSchema, OpenSpecWatcher, OpsxConfigSchema, OpsxKernel, OwnedSpecIdentitySchema, PlanningCliProjectionDataSchema, PlanningCliProjectionSelectorSchema, PlanningCliProjectionStateSchema, ProjectBindingUpdateSchema, ProjectWatcher, PtyClientMessageSchema, RUNTIME_INVALIDATION_FACETS, ReactiveContext, ReactiveObservationEnvironment, ReactiveState, RuntimeInvalidationIndex, RuntimeRootInvalidationRegistry, ServiceTranslationEngineIdSchema, SpecIdentitySchema, StoreDoctorResultSchema, StoreListResultSchema, StoreMutationLifecycleEventSchema, StoreMutationStartResponseSchema, TRANSLATION_ENGINE_MANIFESTS, TerminalConfigSchema, TerminalControlParser, TerminalRendererEngineSchema, TranslationCacheReadInputSchema, TranslationCacheWriteInputSchema, TranslationEngineIdSchema, TranslationEngineLifecycleStatusSchema, acquireWatcher, applyAgentDeliveryPolicy, buildBackendHealthPayload, buildLocalDownloadPlanFromRepositoryFiles, buildRuntimePackageInstallCommand, checkLocalDirectionalModelLanguagePair, classifyStoreCliResult, clearCache, compareAndWriteAtomicPhysicalReactiveFile, computeEnvUri, constantTimeEqual, createCleanCliEnv, createPhysicalReactiveDirectory, createToolInitStateProjection, createTranslationEngineLifecycleStatus, detectRuntimePackageManager, getAllTools, getAvailableTools, getConfiguredTools, getDefaultCliCommandString, getDetectedProjectTools, getExternalAgentSkillsObservationRoots, getExternalCodexCommandObservationRoot, getManagedLocalTranslationEngineManifest, getOpsxEntityRootRelativePath, getRootContextCliSelector, getToolInitStates, getTranslationEngineLifecycleMessage, getWatcherRuntimeStatus, inferFileMime, inferFilePreviewKind, inspectActiveRootOfficialConfig, inspectEnvironmentDefaultStore, inspectProjectBinding, isDirectionalManagedLocalTranslationEngineId, isManagedLocalTranslationEngineId, isTerminalMutationStatus, isTranslationEngineDependencyReady, isTranslationEngineRuntimeReady, loadOpenSpecAgentCommandContents, normalizeAgentDeliveryPolicy, normalizeBatchTranslationError, parseCliCommandResult, parseOpsxEntityMetadata, parseOpsxSchemaDetail, patchActiveRootOfficialFields, reactiveReadFile, reactiveStat, removePhysicalReactivePath, replaceFileAtomically, requireCanonicalOpenSpecEntityId, requireCanonicalOpenSpecSpecId, requireOpenSpecEntityRelativePath, resolveCommandInvocation, resolveOpenSpecDataScope, resolveRootContext, resolveRuntimePackageInstallStrategy, resolveTerminalShellDefaults, runPhysicalReactivePathMutation, selectAgentDeliveryRegistry, selectLocalDownloadGroup, shouldShowTranslationEngineInstallGate, sniffGlobalCli, spawnSafe, subscribeWatcherRuntimeStatus, terminalNotificationEventToPublishInput, terminateChildProcessTree, toCliProjectionCommandEvidence, toCliProjectionFailure, toCliProjectionNotice, toStoreFeatureResult, toggleMarkdownTask, updateProjectBindingContent, updateReactiveFileCache, validateActiveRootRawYaml, writePhysicalReactiveFile } from "@openspecui/core";
|
|
4
4
|
import { basename, dirname, extname, isAbsolute, join, matchesGlob, relative, resolve, sep } from "node:path";
|
|
5
|
-
import { access, copyFile, lstat, mkdir, open, readFile, readlink, realpath, rename, rm, stat, symlink, unlink, writeFile } from "node:fs/promises";
|
|
5
|
+
import { access, copyFile, link, lstat, mkdir, open, readFile, readlink, realpath, rename, rm, stat, symlink, unlink, writeFile } from "node:fs/promises";
|
|
6
6
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
7
7
|
import { downloadFile, fileDownloadInfo, listFiles, modelInfo } from "@huggingface/hub";
|
|
8
8
|
import { observable } from "@trpc/server/observable";
|
|
9
9
|
import { existsSync, readFileSync, realpathSync, statSync, write, writeFileSync } from "node:fs";
|
|
10
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
10
11
|
import { freemem, homedir, hostname, platform, totalmem } from "node:os";
|
|
11
12
|
import { z } from "zod";
|
|
12
13
|
import { Agent, EnvHttpProxyAgent, setGlobalDispatcher } from "undici";
|
|
@@ -20,7 +21,6 @@ import { cors } from "hono/cors";
|
|
|
20
21
|
import { WebSocketServer } from "ws";
|
|
21
22
|
import { Buffer as Buffer$1 } from "node:buffer";
|
|
22
23
|
import { CustomSoundHashSchema as CustomSoundHashSchema$1, CustomSoundIdSchema, CustomSoundMetadataFileSchema, customHashFromSoundId, soundIdFromCustomHash } from "@openspecui/core/sounds";
|
|
23
|
-
import { createHash, randomUUID } from "node:crypto";
|
|
24
24
|
import { execFile, spawn } from "node:child_process";
|
|
25
25
|
import { promisify } from "node:util";
|
|
26
26
|
import { NotificationGroupKeySchema, NotificationPublishInputSchema as NotificationPublishInputSchema$1, getNotificationGroupKey } from "@openspecui/core/notifications";
|
|
@@ -33,6 +33,7 @@ import { NodeWorkerSearchProvider } from "@openspecui/search/node";
|
|
|
33
33
|
import * as pty from "@lydell/node-pty";
|
|
34
34
|
import { EventEmitter as EventEmitter$1 } from "events";
|
|
35
35
|
import { HostedCliProjectionNoticeSchema, HostedRootContextProjectionStateSchema, HostedRootContextResolvedStateSchema, HostedStoreContentChangesProjectionStateSchema, HostedStoreContentSpecsProjectionStateSchema, HostedStoreDoctorEnvelopeSchema, HostedStoreDoctorProjectionStateSchema, HostedStoreListEnvelopeSchema, HostedStoreListProjectionStateSchema } from "@openspecui/core/hosted-contract";
|
|
36
|
+
import { deriveOpenSpecCliCapabilities, parseOpenSpecCliVersion } from "@openspecui/core/openspec-compat";
|
|
36
37
|
import { StoreContentChangeListSchema, StoreContentSpecListSchema } from "@openspecui/core/store-content-projection";
|
|
37
38
|
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
|
|
38
39
|
import { NodeSDK } from "@opentelemetry/sdk-node";
|
|
@@ -502,7 +503,7 @@ var ProjectHookRuntime = class {
|
|
|
502
503
|
}
|
|
503
504
|
async loadFresh() {
|
|
504
505
|
if (!await pathExists$1(this.hooksPath)) return {};
|
|
505
|
-
const { tsImport } = await import("./api-
|
|
506
|
+
const { tsImport } = await import("./api-C_H23KUr.mjs");
|
|
506
507
|
return normalizeHooksModule(await tsImport(`${pathToFileURL(this.hooksPath).href}?t=${Date.now()}`, { parentURL: pathToFileURL(this.hooksPath).href }));
|
|
507
508
|
}
|
|
508
509
|
};
|
|
@@ -524,6 +525,14 @@ function buildHuggingFaceApiBaseUrl(endpoint) {
|
|
|
524
525
|
|
|
525
526
|
//#endregion
|
|
526
527
|
//#region src/local-model-asset-store.ts
|
|
528
|
+
/**
|
|
529
|
+
* Orthogonal intents (updated 2026-08-05 Asia/Shanghai):
|
|
530
|
+
* 1. Persist normalized local-model lifecycle state through complete JSON replacements.
|
|
531
|
+
* 2. Survive transient Windows target locks without exposing partial metadata.
|
|
532
|
+
* 3. Retire reactive caches only after the replacement commits.
|
|
533
|
+
*
|
|
534
|
+
* Original request (2026-08-05): Continue the Windows adaptation and fix equivalent failures together.
|
|
535
|
+
*/
|
|
527
536
|
const LocalModelAssetIndexSchema = LocalModelAssetStateSchema.array();
|
|
528
537
|
var LocalModelAssetStore = class {
|
|
529
538
|
constructor(options) {
|
|
@@ -549,9 +558,13 @@ var LocalModelAssetStore = class {
|
|
|
549
558
|
const normalized = LocalModelAssetIndexSchema.parse([...states].sort((left, right) => left.modelId.localeCompare(right.modelId)));
|
|
550
559
|
const serialized = JSON.stringify(normalized, null, 2);
|
|
551
560
|
await mkdir(dirname(this.options.indexPath), { recursive: true });
|
|
552
|
-
const tempPath = `${this.options.indexPath}.${process.pid}.${
|
|
553
|
-
|
|
554
|
-
|
|
561
|
+
const tempPath = `${this.options.indexPath}.${process.pid}.${randomUUID()}.tmp`;
|
|
562
|
+
try {
|
|
563
|
+
await writeFile(tempPath, `${serialized}\n`, "utf8");
|
|
564
|
+
await replaceFileAtomically(tempPath, this.options.indexPath);
|
|
565
|
+
} finally {
|
|
566
|
+
await rm(tempPath, { force: true }).catch(() => void 0);
|
|
567
|
+
}
|
|
555
568
|
clearCache();
|
|
556
569
|
}
|
|
557
570
|
async upsert(state) {
|
|
@@ -759,6 +772,14 @@ function getTransformersFileCacheModelPath(cacheDir, modelId) {
|
|
|
759
772
|
|
|
760
773
|
//#endregion
|
|
761
774
|
//#region src/local-model-profile-manifest-store.ts
|
|
775
|
+
/**
|
|
776
|
+
* Orthogonal intents (updated 2026-08-05 Asia/Shanghai):
|
|
777
|
+
* 1. Persist normalized local-model profile manifests through complete JSON replacements.
|
|
778
|
+
* 2. Survive transient Windows target locks without exposing partial metadata.
|
|
779
|
+
* 3. Retire reactive caches only after the replacement commits.
|
|
780
|
+
*
|
|
781
|
+
* Original request (2026-08-05): Continue the Windows adaptation and fix equivalent failures together.
|
|
782
|
+
*/
|
|
762
783
|
const LocalModelProfileManifestIndexSchema = z.object({
|
|
763
784
|
version: z.literal(1).default(1),
|
|
764
785
|
manifests: z.array(LocalModelProfileManifestSchema).default([])
|
|
@@ -786,9 +807,13 @@ var LocalModelProfileManifestStore = class {
|
|
|
786
807
|
});
|
|
787
808
|
const serialized = JSON.stringify(normalized, null, 2);
|
|
788
809
|
await mkdir(dirname(this.options.manifestPath), { recursive: true });
|
|
789
|
-
const tempPath = `${this.options.manifestPath}.${process.pid}.${
|
|
790
|
-
|
|
791
|
-
|
|
810
|
+
const tempPath = `${this.options.manifestPath}.${process.pid}.${randomUUID()}.tmp`;
|
|
811
|
+
try {
|
|
812
|
+
await writeFile(tempPath, `${serialized}\n`, "utf8");
|
|
813
|
+
await replaceFileAtomically(tempPath, this.options.manifestPath);
|
|
814
|
+
} finally {
|
|
815
|
+
await rm(tempPath, { force: true }).catch(() => void 0);
|
|
816
|
+
}
|
|
792
817
|
clearCache();
|
|
793
818
|
}
|
|
794
819
|
async upsert(manifest) {
|
|
@@ -1390,6 +1415,16 @@ function isRecord$2(value) {
|
|
|
1390
1415
|
|
|
1391
1416
|
//#endregion
|
|
1392
1417
|
//#region src/local-model-asset-service.ts
|
|
1418
|
+
/**
|
|
1419
|
+
* Orthogonal intents (updated 2026-08-09 Asia/Shanghai):
|
|
1420
|
+
* 1. Resolve local translation model catalogs and versioned profile manifests.
|
|
1421
|
+
* 2. Own resumable download, pause, cancellation, retry, progress, and bounded Windows finalization.
|
|
1422
|
+
* 3. Materialize downloaded profiles and platform-appropriate Hugging Face cache pointers.
|
|
1423
|
+
* 4. Preserve selected-group authority while background work settles.
|
|
1424
|
+
* 5. Publish typed lifecycle snapshots and bounded diagnostics.
|
|
1425
|
+
*
|
|
1426
|
+
* Original request (2026-08-04): "Adapt macOS-first development and runtime paths to Windows."
|
|
1427
|
+
*/
|
|
1393
1428
|
const DEFAULT_NETWORK_RETRY_LIMIT$2 = Number.POSITIVE_INFINITY;
|
|
1394
1429
|
const DEFAULT_NETWORK_RETRY_DELAY_MS$2 = 500;
|
|
1395
1430
|
const DEFAULT_NETWORK_RETRY_DELAY_MAX_MS$2 = 5e3;
|
|
@@ -2742,10 +2777,10 @@ async function streamDownloadToIncompleteFile(input) {
|
|
|
2742
2777
|
}
|
|
2743
2778
|
return true;
|
|
2744
2779
|
}
|
|
2780
|
+
/** Commit one complete model file without deleting the previous target before replacement succeeds. */
|
|
2745
2781
|
async function finalizeDownloadedFile$1(input) {
|
|
2746
2782
|
await mkdir(dirname(input.targetPath), { recursive: true });
|
|
2747
|
-
await
|
|
2748
|
-
await rename(input.incompletePath, input.targetPath);
|
|
2783
|
+
await replaceFileAtomically(input.incompletePath, input.targetPath);
|
|
2749
2784
|
}
|
|
2750
2785
|
async function mirrorDownloadedFileToHubCache(input) {
|
|
2751
2786
|
await mkdir(dirname(input.cachePaths.blobPath), { recursive: true });
|
|
@@ -2753,7 +2788,8 @@ async function mirrorDownloadedFileToHubCache(input) {
|
|
|
2753
2788
|
await rm(input.cachePaths.blobPath, { force: true });
|
|
2754
2789
|
await copyFile(input.targetPath, input.cachePaths.blobPath);
|
|
2755
2790
|
await unlink(input.cachePaths.pointerPath).catch(() => void 0);
|
|
2756
|
-
await
|
|
2791
|
+
if (process.platform === "win32") await link(input.cachePaths.blobPath, input.cachePaths.pointerPath);
|
|
2792
|
+
else await symlink(input.cachePaths.blobPath, input.cachePaths.pointerPath);
|
|
2757
2793
|
}
|
|
2758
2794
|
function getHubCacheFilePaths(input) {
|
|
2759
2795
|
const repoPath = getHubCacheRepoPath(input.cacheDir, input.modelId);
|
|
@@ -2948,13 +2984,15 @@ function resolveDefaultServerHostIdentity() {
|
|
|
2948
2984
|
//#endregion
|
|
2949
2985
|
//#region src/agent-delivery-projection-service.ts
|
|
2950
2986
|
/**
|
|
2951
|
-
* Orthogonal intents (
|
|
2952
|
-
* 1. Combine the complete OpenSpec 1.
|
|
2987
|
+
* Orthogonal intents (updated 2026-08-15 Asia/Shanghai):
|
|
2988
|
+
* 1. Combine the complete OpenSpec 1.9 Agent registry with authoritative Environment delivery policy.
|
|
2953
2989
|
* 2. Expose fresh one-shot physical projections through Core `getToolInitStates`.
|
|
2954
2990
|
* 3. Retain reactive physical projections through Core `createToolInitStateProjection`.
|
|
2955
2991
|
* 4. Rebind retained work when Environment policy changes and retire all work on dispose.
|
|
2992
|
+
* 5. Observe user-global skill roots (MiniMax Code) beside the project root and Codex prompt root.
|
|
2956
2993
|
*
|
|
2957
2994
|
* Original request (2026-08-01): "新增 Agent delivery projection service 及 checked tests。"
|
|
2995
|
+
* Original request (2026-08-15): "v9的适配需要同时适配 1.8和1.9。"
|
|
2958
2996
|
*/
|
|
2959
2997
|
/** Raised when the CLI-owned Environment projection cannot provide an executable Agent policy. */
|
|
2960
2998
|
var AgentDeliveryPolicyUnavailableError = class extends Error {
|
|
@@ -2999,8 +3037,8 @@ function cloneCleanup(cleanup) {
|
|
|
2999
3037
|
replacementLabel: cleanup.replacementLabel
|
|
3000
3038
|
};
|
|
3001
3039
|
}
|
|
3002
|
-
function cloneRegistry() {
|
|
3003
|
-
return
|
|
3040
|
+
function cloneRegistry(registry) {
|
|
3041
|
+
return registry.map((tool) => ({
|
|
3004
3042
|
name: tool.name,
|
|
3005
3043
|
value: tool.value,
|
|
3006
3044
|
available: tool.available,
|
|
@@ -3011,6 +3049,9 @@ function cloneRegistry() {
|
|
|
3011
3049
|
...tool.detectionPaths ? { detectionPaths: [...tool.detectionPaths] } : {},
|
|
3012
3050
|
...tool.setupNote !== void 0 ? { setupNote: tool.setupNote } : {},
|
|
3013
3051
|
...tool.aliases ? { aliases: [...tool.aliases] } : {},
|
|
3052
|
+
...tool.legacySkillsDirs ? { legacySkillsDirs: [...tool.legacySkillsDirs] } : {},
|
|
3053
|
+
...tool.globalSkillsDir !== void 0 && tool.globalSkillsDir !== null ? { globalSkillsDir: tool.globalSkillsDir } : {},
|
|
3054
|
+
...tool.requiresIdeRestart !== void 0 ? { requiresIdeRestart: tool.requiresIdeRestart } : {},
|
|
3014
3055
|
...tool.cleanup ? { cleanup: cloneCleanup(tool.cleanup) } : {},
|
|
3015
3056
|
...tool.migrations ? { migrations: tool.migrations.map((migration) => ({ ...migration })) } : {}
|
|
3016
3057
|
}));
|
|
@@ -3032,13 +3073,13 @@ function policyFingerprint(policy) {
|
|
|
3032
3073
|
async function resolveGeneratorEvidence(cliExecutor, cliCommandAuthority, workflows) {
|
|
3033
3074
|
const availability = await cliExecutor.checkAvailability();
|
|
3034
3075
|
return {
|
|
3035
|
-
version: availability.available && availability.version ? availability.version :
|
|
3076
|
+
version: availability.available && availability.version ? availability.version : null,
|
|
3036
3077
|
commandContents: availability.available ? await loadOpenSpecAgentCommandContents(await cliCommandAuthority.getCliCommand(), workflows) : null
|
|
3037
3078
|
};
|
|
3038
3079
|
}
|
|
3039
|
-
function createProjection(policy, states) {
|
|
3080
|
+
function createProjection(policy, states, generatorEvidence) {
|
|
3040
3081
|
return {
|
|
3041
|
-
registry: cloneRegistry(),
|
|
3082
|
+
registry: cloneRegistry(selectAgentDeliveryRegistry(generatorEvidence.version)),
|
|
3042
3083
|
policy: {
|
|
3043
3084
|
profile: policy.profile,
|
|
3044
3085
|
delivery: policy.delivery,
|
|
@@ -3112,7 +3153,7 @@ var RetainedAgentDeliveryProjection = class {
|
|
|
3112
3153
|
});
|
|
3113
3154
|
}
|
|
3114
3155
|
startPhysicalProjection(policy, generatorEvidence, force) {
|
|
3115
|
-
const fingerprint = `${policyFingerprint(policy)}:${generatorEvidence.version}`;
|
|
3156
|
+
const fingerprint = `${policyFingerprint(policy)}:${generatorEvidence.version ?? "no-cli"}`;
|
|
3116
3157
|
if (!force && this.physicalController && fingerprint === this.currentPolicyFingerprint) return;
|
|
3117
3158
|
this.stopPhysicalProjection();
|
|
3118
3159
|
this.currentPolicyFingerprint = fingerprint;
|
|
@@ -3122,8 +3163,10 @@ var RetainedAgentDeliveryProjection = class {
|
|
|
3122
3163
|
const context = new ReactiveContext();
|
|
3123
3164
|
const projectToolInitStates = createToolInitStateProjection(this.options.projectDir, {
|
|
3124
3165
|
...policy,
|
|
3125
|
-
generatorVersion: generatorEvidence.version,
|
|
3126
|
-
commandContents: generatorEvidence.commandContents
|
|
3166
|
+
generatorVersion: generatorEvidence.version ?? void 0,
|
|
3167
|
+
commandContents: generatorEvidence.commandContents?.catalog ?? null,
|
|
3168
|
+
unavailableCommandTools: generatorEvidence.commandContents?.unavailableTools ?? null,
|
|
3169
|
+
registry: selectAgentDeliveryRegistry(generatorEvidence.version)
|
|
3127
3170
|
});
|
|
3128
3171
|
(async () => {
|
|
3129
3172
|
try {
|
|
@@ -3131,7 +3174,7 @@ var RetainedAgentDeliveryProjection = class {
|
|
|
3131
3174
|
if (this.disposed || controller.signal.aborted || generation !== this.physicalGeneration) return;
|
|
3132
3175
|
this.listener({
|
|
3133
3176
|
type: "snapshot",
|
|
3134
|
-
projection: createProjection(policy, states)
|
|
3177
|
+
projection: createProjection(policy, states, generatorEvidence)
|
|
3135
3178
|
});
|
|
3136
3179
|
}
|
|
3137
3180
|
} catch (cause) {
|
|
@@ -3168,11 +3211,13 @@ var AgentDeliveryProjectionService = class {
|
|
|
3168
3211
|
const generatorEvidence = await resolveGeneratorEvidence(this.options.cliExecutor, this.options.cliCommandAuthority, policy.workflows);
|
|
3169
3212
|
const states = await getToolInitStates(this.options.projectDir, {
|
|
3170
3213
|
...policy,
|
|
3171
|
-
generatorVersion: generatorEvidence.version,
|
|
3172
|
-
commandContents: generatorEvidence.commandContents
|
|
3214
|
+
generatorVersion: generatorEvidence.version ?? void 0,
|
|
3215
|
+
commandContents: generatorEvidence.commandContents?.catalog ?? null,
|
|
3216
|
+
unavailableCommandTools: generatorEvidence.commandContents?.unavailableTools ?? null,
|
|
3217
|
+
registry: selectAgentDeliveryRegistry(generatorEvidence.version)
|
|
3173
3218
|
});
|
|
3174
3219
|
this.assertActive();
|
|
3175
|
-
return createProjection(policy, states);
|
|
3220
|
+
return createProjection(policy, states, generatorEvidence);
|
|
3176
3221
|
}
|
|
3177
3222
|
/** Retain Environment policy and physical Agent artifact observation until unsubscribed. */
|
|
3178
3223
|
subscribe(listener) {
|
|
@@ -3214,6 +3259,7 @@ var AgentDeliveryProjectionService = class {
|
|
|
3214
3259
|
try {
|
|
3215
3260
|
releases.push(await this.options.observationEnvironment.acquireRoot(this.options.projectDir));
|
|
3216
3261
|
releases.push(await this.options.observationEnvironment.acquireRoot(getExternalCodexCommandObservationRoot()));
|
|
3262
|
+
for (const globalSkillsRoot of getExternalAgentSkillsObservationRoots()) releases.push(await this.options.observationEnvironment.acquireRoot(globalSkillsRoot));
|
|
3217
3263
|
if (this.disposed) {
|
|
3218
3264
|
await Promise.all(releases.map((release) => release()));
|
|
3219
3265
|
return;
|
|
@@ -4081,13 +4127,26 @@ var ChangesProjectionService = class {
|
|
|
4081
4127
|
load: async (context) => {
|
|
4082
4128
|
context.reportStage("root-ready");
|
|
4083
4129
|
const ids = await this.options.adapter.listChanges();
|
|
4130
|
+
const cliEntries = await this.options.adapter.readCliChangeListEntries?.() ?? /* @__PURE__ */ new Map();
|
|
4084
4131
|
const rows = [];
|
|
4085
4132
|
const errors = [];
|
|
4086
4133
|
const total = ids.length;
|
|
4087
4134
|
for (const [index, id] of ids.entries()) {
|
|
4088
4135
|
if (context.signal.aborted) throw new DOMException("Change projection was cancelled.", "AbortError");
|
|
4089
4136
|
try {
|
|
4090
|
-
const
|
|
4137
|
+
const meta = await this.options.adapter.readChangeMeta(id);
|
|
4138
|
+
const entry = cliEntries.get(id);
|
|
4139
|
+
const row = entry ? {
|
|
4140
|
+
...meta,
|
|
4141
|
+
cliTaskSummary: {
|
|
4142
|
+
completedTasks: entry.completedTasks,
|
|
4143
|
+
totalTasks: entry.totalTasks,
|
|
4144
|
+
status: entry.status
|
|
4145
|
+
}
|
|
4146
|
+
} : {
|
|
4147
|
+
...meta,
|
|
4148
|
+
cliTaskSummary: meta.cliTaskSummary ?? null
|
|
4149
|
+
};
|
|
4091
4150
|
rows.push(row);
|
|
4092
4151
|
context.emitBatch({
|
|
4093
4152
|
rows: [row],
|
|
@@ -6504,7 +6563,10 @@ var EnvironmentGlobalProjectionService = class {
|
|
|
6504
6563
|
* 2. Distinguish a valid non-repository path from an identity-command failure.
|
|
6505
6564
|
* 3. Canonicalize repository paths for binding identity and stale-intent checks.
|
|
6506
6565
|
* 4. Propagate cooperative cancellation into Git subprocess ownership.
|
|
6566
|
+
* 5. Hide the Git subprocess console window (`windowsHide`) so Git executed by a console-less
|
|
6567
|
+
* Windows daemon never flashes a cmd window.
|
|
6507
6568
|
*
|
|
6569
|
+
* Original request (2026-08-14): "在Windows平台上,执行命令总是会弹出cmd窗口,这个可否统一隐藏,你先调查一下原因"
|
|
6508
6570
|
* Original request (2026-07-19): "代码已经提交,开始review。如果有问题,那么可更新change。"
|
|
6509
6571
|
* Derived requirement (2026-07-19): Checkpoint 6.11 preserves objective Planning Git failures.
|
|
6510
6572
|
* Original request (2026-07-31): "Code Git Snapshot,它非常慢,有时候甚至要十几秒"
|
|
@@ -6521,7 +6583,8 @@ async function defaultRunGit(cwd, args, signal) {
|
|
|
6521
6583
|
cwd,
|
|
6522
6584
|
encoding: "utf8",
|
|
6523
6585
|
maxBuffer: 8 * 1024 * 1024,
|
|
6524
|
-
signal
|
|
6586
|
+
signal,
|
|
6587
|
+
windowsHide: true
|
|
6525
6588
|
});
|
|
6526
6589
|
return {
|
|
6527
6590
|
ok: true,
|
|
@@ -9076,7 +9139,10 @@ async function buildDashboardGitSnapshot(options) {
|
|
|
9076
9139
|
* 2. Preserve backend-issued Code binding provenance and observable Git task lifecycle.
|
|
9077
9140
|
* 3. Trigger explicit Git snapshot invalidation through a reactive stamp without broad Dashboard reloads.
|
|
9078
9141
|
* 4. Propagate Projection Work cancellation into the Git subprocess tree.
|
|
9142
|
+
* 5. Hide Git identity-probe subprocess console windows (`windowsHide`) under the console-less
|
|
9143
|
+
* Windows daemon.
|
|
9079
9144
|
*
|
|
9145
|
+
* Original request (2026-08-14): "在Windows平台上,执行命令总是会弹出cmd窗口,这个可否统一隐藏,你先调查一下原因"
|
|
9080
9146
|
* Original request (2026-07-23): "现在页面数据的加载数据非常慢(比如dashboard页面、changes页面都要等待非常久,页面刷新后,似乎后台没有缓存一样,也要加载很久。"
|
|
9081
9147
|
* Derived requirement (2026-07-25): P4.3 settles a cached refresh stamp before Dashboard Git refresh returns.
|
|
9082
9148
|
* Original request (2026-07-31): "Code Git Snapshot,它非常慢,有时候甚至要十几秒"
|
|
@@ -9127,7 +9193,8 @@ async function resolveGitMetadataDir(projectDir) {
|
|
|
9127
9193
|
const { stdout } = await execFileAsync$1("git", ["rev-parse", "--git-dir"], {
|
|
9128
9194
|
cwd: projectDir,
|
|
9129
9195
|
maxBuffer: 1024 * 1024,
|
|
9130
|
-
encoding: "utf8"
|
|
9196
|
+
encoding: "utf8",
|
|
9197
|
+
windowsHide: true
|
|
9131
9198
|
});
|
|
9132
9199
|
const gitDirRaw = stdout.trim();
|
|
9133
9200
|
if (!gitDirRaw) return null;
|
|
@@ -9297,17 +9364,26 @@ function selectRecentDashboardArchives(items, limit = DASHBOARD_RECENT_LIST_LIMI
|
|
|
9297
9364
|
//#region src/dashboard-summary.ts
|
|
9298
9365
|
/** Read the objective files behind Dashboard Summary without invoking Git or workflow commands. */
|
|
9299
9366
|
async function loadDashboardPlanningFacts(ctx) {
|
|
9300
|
-
const [specMetas, changeMetas, archiveMetas] = await Promise.all([
|
|
9367
|
+
const [specMetas, changeMetas, archiveMetas, cliEntries] = await Promise.all([
|
|
9301
9368
|
ctx.adapter.listSpecsWithMeta(),
|
|
9302
9369
|
ctx.adapter.listChangesWithMeta(),
|
|
9303
|
-
ctx.adapter.listArchivedChangesWithMeta()
|
|
9370
|
+
ctx.adapter.listArchivedChangesWithMeta(),
|
|
9371
|
+
ctx.readCliChangeListEntries?.().catch(() => /* @__PURE__ */ new Map()) ?? Promise.resolve(/* @__PURE__ */ new Map())
|
|
9304
9372
|
]);
|
|
9305
|
-
const allActiveChanges = changeMetas.map((changeMeta) =>
|
|
9306
|
-
|
|
9307
|
-
|
|
9308
|
-
|
|
9309
|
-
|
|
9310
|
-
|
|
9373
|
+
const allActiveChanges = changeMetas.map((changeMeta) => {
|
|
9374
|
+
const entry = cliEntries.get(changeMeta.id);
|
|
9375
|
+
return {
|
|
9376
|
+
id: changeMeta.id,
|
|
9377
|
+
name: changeMeta.name ?? changeMeta.id,
|
|
9378
|
+
trackedTaskProgress: changeMeta.trackedTaskProgress,
|
|
9379
|
+
cliTaskSummary: entry ? {
|
|
9380
|
+
completedTasks: entry.completedTasks,
|
|
9381
|
+
totalTasks: entry.totalTasks,
|
|
9382
|
+
status: entry.status
|
|
9383
|
+
} : changeMeta.cliTaskSummary,
|
|
9384
|
+
updatedAt: changeMeta.updatedAt
|
|
9385
|
+
};
|
|
9386
|
+
});
|
|
9311
9387
|
return {
|
|
9312
9388
|
specMetas,
|
|
9313
9389
|
archiveMetas,
|
|
@@ -9667,12 +9743,13 @@ function resolveEntityEntryPath(input) {
|
|
|
9667
9743
|
//#endregion
|
|
9668
9744
|
//#region src/file-preview-service.ts
|
|
9669
9745
|
/**
|
|
9670
|
-
* Orthogonal intents (updated 2026-
|
|
9746
|
+
* Orthogonal intents (updated 2026-08-04 Asia/Shanghai):
|
|
9671
9747
|
* 1. Prepare source-bound preview sessions for supported entity files.
|
|
9672
|
-
* 2. Serve preview documents and bundled assets with confined path resolution.
|
|
9748
|
+
* 2. Serve preview documents and bundled assets with platform-neutral confined path resolution.
|
|
9673
9749
|
* 3. Retire every preview session when its owning Planning root is replaced.
|
|
9674
9750
|
*
|
|
9675
9751
|
* Original request (2026-07-16): "A -> B 后必须清退 A 的 preview。"
|
|
9752
|
+
* Original request (2026-08-04): "Make pnpm openspecui start and equivalent package scripts work on Windows."
|
|
9676
9753
|
*/
|
|
9677
9754
|
const PREVIEW_ENTRY_FILE_BY_KIND = {
|
|
9678
9755
|
image: "image-preview.html",
|
|
@@ -9696,6 +9773,10 @@ function toHash(input) {
|
|
|
9696
9773
|
function stripLeadingSlash(path) {
|
|
9697
9774
|
return path.replace(/^\/+/, "");
|
|
9698
9775
|
}
|
|
9776
|
+
function isPathInsideRoot(rootPath, candidatePath) {
|
|
9777
|
+
const relativePath$1 = relative(rootPath, candidatePath);
|
|
9778
|
+
return relativePath$1 === "" || relativePath$1 !== ".." && !relativePath$1.startsWith(`..${sep}`) && !isAbsolute(relativePath$1);
|
|
9779
|
+
}
|
|
9699
9780
|
function inferPreviewAssetContentType(path) {
|
|
9700
9781
|
switch (extname(path).toLowerCase()) {
|
|
9701
9782
|
case ".html": return "text/html";
|
|
@@ -9773,7 +9854,7 @@ var FilePreviewService = class {
|
|
|
9773
9854
|
const normalized = stripLeadingSlash(requestPath);
|
|
9774
9855
|
if (session.previewKind === "html") {
|
|
9775
9856
|
const absolutePath$1 = resolve(session.directoryPath, normalized);
|
|
9776
|
-
if (!
|
|
9857
|
+
if (!isPathInsideRoot(session.directoryPath, absolutePath$1)) return null;
|
|
9777
9858
|
if (!existsSync(absolutePath$1) || !statSync(absolutePath$1).isFile()) return null;
|
|
9778
9859
|
return {
|
|
9779
9860
|
content: readFileSync(absolutePath$1),
|
|
@@ -9783,7 +9864,7 @@ var FilePreviewService = class {
|
|
|
9783
9864
|
if (normalized.startsWith("resource/")) {
|
|
9784
9865
|
const resourcePath = normalized.slice(9);
|
|
9785
9866
|
const absolutePath$1 = resolve(session.directoryPath, resourcePath);
|
|
9786
|
-
if (!
|
|
9867
|
+
if (!isPathInsideRoot(session.directoryPath, absolutePath$1)) return null;
|
|
9787
9868
|
if (!existsSync(absolutePath$1) || !statSync(absolutePath$1).isFile()) return null;
|
|
9788
9869
|
return {
|
|
9789
9870
|
content: readFileSync(absolutePath$1),
|
|
@@ -9793,7 +9874,7 @@ var FilePreviewService = class {
|
|
|
9793
9874
|
const assetName = normalized || session.entryFileName;
|
|
9794
9875
|
if (!assetName) return null;
|
|
9795
9876
|
const absolutePath = resolve(this.previewAssetsDir, assetName);
|
|
9796
|
-
if (!
|
|
9877
|
+
if (!isPathInsideRoot(this.previewAssetsDir, absolutePath)) return null;
|
|
9797
9878
|
if (!existsSync(absolutePath) || !statSync(absolutePath).isFile()) return null;
|
|
9798
9879
|
if (isRewritablePreviewAsset(assetName)) {
|
|
9799
9880
|
const rewritten = rewritePreviewAssetPaths(readFileSync(absolutePath, "utf8"), hash);
|
|
@@ -10833,6 +10914,18 @@ var PlanningRootUnavailableError = class extends Error {
|
|
|
10833
10914
|
}
|
|
10834
10915
|
};
|
|
10835
10916
|
/** Serialized deep owner for one replaceable Planning-root service record. */
|
|
10917
|
+
/** Read CLI Change-list task facts once per call; failures degrade to an empty index. */
|
|
10918
|
+
function readCliChangeListEntriesFor(service) {
|
|
10919
|
+
return async () => {
|
|
10920
|
+
try {
|
|
10921
|
+
const data = await service.getCurrent({ kind: "opsx-change-list" });
|
|
10922
|
+
if (data.kind !== "opsx-change-list") return /* @__PURE__ */ new Map();
|
|
10923
|
+
return new Map(data.entries.map((entry) => [entry.name, entry]));
|
|
10924
|
+
} catch {
|
|
10925
|
+
return /* @__PURE__ */ new Map();
|
|
10926
|
+
}
|
|
10927
|
+
};
|
|
10928
|
+
}
|
|
10836
10929
|
var PlanningRootServiceManager = class {
|
|
10837
10930
|
activeRecord = null;
|
|
10838
10931
|
currentRootContextSnapshot = null;
|
|
@@ -10908,7 +11001,10 @@ var PlanningRootServiceManager = class {
|
|
|
10908
11001
|
},
|
|
10909
11002
|
codeGitBindingToken: this.options.codeBinding.bindingToken,
|
|
10910
11003
|
loaders: {
|
|
10911
|
-
loadSummary: () => loadDashboardSummary({
|
|
11004
|
+
loadSummary: () => loadDashboardSummary({
|
|
11005
|
+
adapter,
|
|
11006
|
+
readCliChangeListEntries: readCliChangeListEntriesFor(planningCliProjectionService)
|
|
11007
|
+
}),
|
|
10912
11008
|
loadTrends: () => loadDashboardTrends({
|
|
10913
11009
|
adapter,
|
|
10914
11010
|
configManager: this.options.configManager
|
|
@@ -10919,16 +11015,6 @@ var PlanningRootServiceManager = class {
|
|
|
10919
11015
|
}, "projection-work", signal)
|
|
10920
11016
|
}
|
|
10921
11017
|
});
|
|
10922
|
-
const changesProjectionService = new ChangesProjectionService({
|
|
10923
|
-
workOwner: this.changesProjectionWorkOwner,
|
|
10924
|
-
root: {
|
|
10925
|
-
path: projectDir,
|
|
10926
|
-
source: planningRoot.source,
|
|
10927
|
-
storeSelector: rootCliSelector.store ?? null,
|
|
10928
|
-
generation: gitBindingToken
|
|
10929
|
-
},
|
|
10930
|
-
adapter
|
|
10931
|
-
});
|
|
10932
11018
|
const planningCliProjectionService = new PlanningCliProjectionService({
|
|
10933
11019
|
rootContext: rootContextValue,
|
|
10934
11020
|
gitBindingToken,
|
|
@@ -10939,6 +11025,28 @@ var PlanningRootServiceManager = class {
|
|
|
10939
11025
|
storeObservation: this.options.storeObservation,
|
|
10940
11026
|
workOwner: this.planningCliProjectionWorkOwner
|
|
10941
11027
|
});
|
|
11028
|
+
const changesProjectionService = new ChangesProjectionService({
|
|
11029
|
+
workOwner: this.changesProjectionWorkOwner,
|
|
11030
|
+
root: {
|
|
11031
|
+
path: projectDir,
|
|
11032
|
+
source: planningRoot.source,
|
|
11033
|
+
storeSelector: rootCliSelector.store ?? null,
|
|
11034
|
+
generation: gitBindingToken
|
|
11035
|
+
},
|
|
11036
|
+
adapter: {
|
|
11037
|
+
listChanges: () => adapter.listChanges(),
|
|
11038
|
+
readChangeMeta: (id) => adapter.readChangeMeta(id),
|
|
11039
|
+
readCliChangeListEntries: async () => {
|
|
11040
|
+
try {
|
|
11041
|
+
const data = await planningCliProjectionService.getCurrent({ kind: "opsx-change-list" });
|
|
11042
|
+
if (data.kind !== "opsx-change-list") return /* @__PURE__ */ new Map();
|
|
11043
|
+
return new Map(data.entries.map((entry) => [entry.name, entry]));
|
|
11044
|
+
} catch {
|
|
11045
|
+
return /* @__PURE__ */ new Map();
|
|
11046
|
+
}
|
|
11047
|
+
}
|
|
11048
|
+
}
|
|
11049
|
+
});
|
|
10942
11050
|
const filePreviewService = new FilePreviewService(projectDir, this.options.previewAssetsDir);
|
|
10943
11051
|
const schemaMutationService = new SchemaMutationService({
|
|
10944
11052
|
planningRoot: projectDir,
|
|
@@ -11828,11 +11936,12 @@ var PtyInputWriter = class {
|
|
|
11828
11936
|
//#endregion
|
|
11829
11937
|
//#region src/pty-manager.ts
|
|
11830
11938
|
/**
|
|
11831
|
-
* Orthogonal intents (updated 2026-
|
|
11939
|
+
* Orthogonal intents (updated 2026-08-09 Asia/Shanghai):
|
|
11832
11940
|
* 1. Own PTY session process, ordered input, buffer, title, and lifecycle state.
|
|
11833
11941
|
* 2. Spawn each session at an explicitly resolved launch-project or planning-root cwd and retain its immutable Root generation.
|
|
11834
11942
|
* 3. Preserve the inherited backend environment, including XDG_DATA_HOME, across cwd targets.
|
|
11835
11943
|
* 4. List and close server-owned sessions for reconnect and teardown.
|
|
11944
|
+
* 5. Resolve Windows Agent launchers to native argv while retaining the requested display command.
|
|
11836
11945
|
*
|
|
11837
11946
|
* Original request (2026-07-16): "3.8 Terminal exposes explicit launch-project cwd and planning-root cwd while preserving inherited XDG_DATA_HOME"
|
|
11838
11947
|
* Owner-reported defect (2026-07-21): Pre-created Agent terminals are absent from Compose Send.
|
|
@@ -11919,7 +12028,11 @@ var PtySession = class extends EventEmitter$1 {
|
|
|
11919
12028
|
this.rootGeneration = opts.rootGeneration;
|
|
11920
12029
|
this.maxBufferLines = opts.scrollback ?? DEFAULT_SCROLLBACK;
|
|
11921
12030
|
this.maxBufferBytes = opts.maxBufferBytes ?? DEFAULT_MAX_BUFFER_BYTES;
|
|
11922
|
-
|
|
12031
|
+
const invocation = resolveCommandInvocation(this.command, this.args, {
|
|
12032
|
+
cwd: opts.cwd,
|
|
12033
|
+
env: process.env
|
|
12034
|
+
});
|
|
12035
|
+
this.process = pty.spawn(invocation.command, invocation.args, {
|
|
11923
12036
|
name: "xterm-256color",
|
|
11924
12037
|
cols: opts.cols ?? 80,
|
|
11925
12038
|
rows: opts.rows ?? 24,
|
|
@@ -14650,7 +14763,16 @@ const changeProjectionDataSchema = z.object({
|
|
|
14650
14763
|
remaining: z.number().int().nonnegative()
|
|
14651
14764
|
}),
|
|
14652
14765
|
createdAt: z.number(),
|
|
14653
|
-
updatedAt: z.number()
|
|
14766
|
+
updatedAt: z.number(),
|
|
14767
|
+
cliTaskSummary: z.object({
|
|
14768
|
+
completedTasks: z.number().int().nonnegative(),
|
|
14769
|
+
totalTasks: z.number().int().nonnegative(),
|
|
14770
|
+
status: z.enum([
|
|
14771
|
+
"no-tasks",
|
|
14772
|
+
"complete",
|
|
14773
|
+
"in-progress"
|
|
14774
|
+
])
|
|
14775
|
+
}).nullable()
|
|
14654
14776
|
})),
|
|
14655
14777
|
errors: z.array(changeProjectionRowErrorSchema)
|
|
14656
14778
|
});
|
|
@@ -15292,12 +15414,31 @@ const specRouter = router({
|
|
|
15292
15414
|
/**
|
|
15293
15415
|
* Change router - change proposal operations
|
|
15294
15416
|
*/
|
|
15417
|
+
/**
|
|
15418
|
+
* Join the CLI-owned Change-list task facts onto adapter ChangeMeta rows. The CLI entry is
|
|
15419
|
+
* the task-count authority (`openspec list` reports the same checkbox semantics as Apply);
|
|
15420
|
+
* rows the CLI did not list keep a null summary rather than a UI-side recomputation.
|
|
15421
|
+
*/
|
|
15422
|
+
async function listChangesWithCliTaskSummary(ctx, adapter) {
|
|
15423
|
+
const [rows, cliList] = await Promise.all([adapter.listChangesWithMeta(), runPlanningRootRead(ctx, ({ planningCliProjectionService }) => planningCliProjectionService.getCurrent({ kind: "opsx-change-list" })).then((data) => data.kind === "opsx-change-list" ? new Map(data.entries.map((entry) => [entry.name, entry])) : /* @__PURE__ */ new Map(), () => /* @__PURE__ */ new Map())]);
|
|
15424
|
+
return rows.map((row) => {
|
|
15425
|
+
const entry = cliList.get(row.id);
|
|
15426
|
+
return entry ? {
|
|
15427
|
+
...row,
|
|
15428
|
+
cliTaskSummary: {
|
|
15429
|
+
completedTasks: entry.completedTasks,
|
|
15430
|
+
totalTasks: entry.totalTasks,
|
|
15431
|
+
status: entry.status
|
|
15432
|
+
}
|
|
15433
|
+
} : row;
|
|
15434
|
+
});
|
|
15435
|
+
}
|
|
15295
15436
|
const changeRouter = router({
|
|
15296
15437
|
list: publicProcedure.query(async ({ ctx }) => {
|
|
15297
15438
|
return runPlanningRootRead(ctx, ({ adapter }) => adapter.listChanges());
|
|
15298
15439
|
}),
|
|
15299
15440
|
listWithMeta: publicProcedure.query(async ({ ctx }) => {
|
|
15300
|
-
return runPlanningRootRead(ctx, ({ adapter }) => adapter
|
|
15441
|
+
return runPlanningRootRead(ctx, ({ adapter }) => listChangesWithCliTaskSummary(ctx, adapter));
|
|
15301
15442
|
}),
|
|
15302
15443
|
listArchived: publicProcedure.query(async ({ ctx }) => {
|
|
15303
15444
|
return runPlanningRootRead(ctx, ({ adapter }) => adapter.listArchivedChanges());
|
|
@@ -15342,7 +15483,7 @@ const changeRouter = router({
|
|
|
15342
15483
|
return { success: true };
|
|
15343
15484
|
}),
|
|
15344
15485
|
subscribe: publicProcedure.subscription(({ ctx }) => {
|
|
15345
|
-
return createPlanningRootSubscription(ctx, ({ adapter }) => adapter
|
|
15486
|
+
return createPlanningRootSubscription(ctx, ({ adapter }) => listChangesWithCliTaskSummary(ctx, adapter));
|
|
15346
15487
|
}),
|
|
15347
15488
|
subscribeBatches: publicProcedure.subscription(({ ctx }) => createPlanningRootProjectionWorkSubscription(ctx, (services, listener) => services.changesProjectionService.subscribe(listener), (event) => changeProjectionEventSchema().parse(event))),
|
|
15348
15489
|
subscribeFiles: publicProcedure.input(z.object({ id: z.string() })).subscription(({ ctx, input }) => {
|
|
@@ -15620,32 +15761,45 @@ const cliRouter = router({
|
|
|
15620
15761
|
subscribeConfiguredTools: publicProcedure.subscription(({ ctx }) => {
|
|
15621
15762
|
return createReactiveSubscription(() => getConfiguredTools(ctx.projectDir));
|
|
15622
15763
|
}),
|
|
15623
|
-
validate: publicProcedure.input(z.discriminatedUnion("kind", [
|
|
15624
|
-
|
|
15625
|
-
|
|
15626
|
-
|
|
15627
|
-
|
|
15628
|
-
|
|
15629
|
-
|
|
15630
|
-
|
|
15631
|
-
"
|
|
15632
|
-
|
|
15633
|
-
|
|
15634
|
-
|
|
15635
|
-
|
|
15636
|
-
|
|
15637
|
-
|
|
15638
|
-
|
|
15639
|
-
|
|
15640
|
-
|
|
15641
|
-
|
|
15642
|
-
|
|
15643
|
-
|
|
15644
|
-
|
|
15645
|
-
|
|
15646
|
-
|
|
15647
|
-
|
|
15648
|
-
|
|
15764
|
+
validate: publicProcedure.input(z.discriminatedUnion("kind", [
|
|
15765
|
+
z.object({
|
|
15766
|
+
kind: z.literal("item"),
|
|
15767
|
+
id: z.string().min(1),
|
|
15768
|
+
type: z.enum(["spec", "change"]).optional(),
|
|
15769
|
+
strict: z.boolean().optional()
|
|
15770
|
+
}),
|
|
15771
|
+
z.object({
|
|
15772
|
+
kind: z.literal("scope"),
|
|
15773
|
+
scope: z.enum([
|
|
15774
|
+
"all",
|
|
15775
|
+
"changes",
|
|
15776
|
+
"specs"
|
|
15777
|
+
]),
|
|
15778
|
+
strict: z.boolean().optional()
|
|
15779
|
+
}),
|
|
15780
|
+
z.object({ kind: z.literal("archived") })
|
|
15781
|
+
])).mutation(async ({ ctx, input }) => {
|
|
15782
|
+
return runPlanningRoot(ctx, ({ rootContext }) => {
|
|
15783
|
+
if (input.kind === "archived") {
|
|
15784
|
+
const cli = rootContext.cli;
|
|
15785
|
+
if (!deriveOpenSpecCliCapabilities(cli.available ? parseOpenSpecCliVersion(cli.version) : null).archivedValidation) throw new TRPCError({
|
|
15786
|
+
code: "PRECONDITION_FAILED",
|
|
15787
|
+
message: "Archived task validation requires the detected OpenSpec CLI to declare the 1.9 archived-validation capability."
|
|
15788
|
+
});
|
|
15789
|
+
}
|
|
15790
|
+
return ctx.cliExecutor.contracts.validate({
|
|
15791
|
+
target: input.kind === "item" ? {
|
|
15792
|
+
kind: "item",
|
|
15793
|
+
id: input.id,
|
|
15794
|
+
type: input.type
|
|
15795
|
+
} : input.kind === "scope" ? {
|
|
15796
|
+
kind: "scope",
|
|
15797
|
+
scope: input.scope
|
|
15798
|
+
} : { kind: "archived" },
|
|
15799
|
+
strict: input.kind === "archived" ? void 0 : input.strict,
|
|
15800
|
+
...getRootContextCliSelector(rootContext)
|
|
15801
|
+
});
|
|
15802
|
+
});
|
|
15649
15803
|
}),
|
|
15650
15804
|
validateStream: publicProcedure.input(z.object({
|
|
15651
15805
|
type: z.enum(["spec", "change"]).optional(),
|
|
@@ -15699,7 +15853,7 @@ const cliRouter = router({
|
|
|
15699
15853
|
})
|
|
15700
15854
|
});
|
|
15701
15855
|
/** Config-owned Agent registry, policy, physical inventory, and CLI mutation boundary. */
|
|
15702
|
-
const agentIntegrationToolIdSchema = z.string().min(1).refine((toolId) => getAvailableTools().some((tool) => tool.value === toolId), "Agent tool must be an available OpenSpec 1.
|
|
15856
|
+
const agentIntegrationToolIdSchema = z.string().min(1).refine((toolId) => getAvailableTools().some((tool) => tool.value === toolId), "Agent tool must be an available OpenSpec 1.9 registry id.");
|
|
15703
15857
|
const agentIntegrationsRouter = router({
|
|
15704
15858
|
get: publicProcedure.query(({ ctx }) => ctx.agentDeliveryProjectionService.getCurrent()),
|
|
15705
15859
|
refresh: publicProcedure.query(({ ctx }) => ctx.agentDeliveryProjectionService.refresh()),
|
|
@@ -15727,6 +15881,14 @@ const agentIntegrationsRouter = router({
|
|
|
15727
15881
|
})).subscription(({ ctx, input }) => {
|
|
15728
15882
|
return createCliStreamObservable(async (onEvent) => {
|
|
15729
15883
|
const projection = await ctx.agentDeliveryProjectionService.getCurrent();
|
|
15884
|
+
if (Array.isArray(input.tools)) {
|
|
15885
|
+
const offered = new Set(projection.registry.map((tool) => tool.value));
|
|
15886
|
+
const unavailable = input.tools.filter((toolId) => !offered.has(toolId));
|
|
15887
|
+
if (unavailable.length > 0) throw new TRPCError({
|
|
15888
|
+
code: "PRECONDITION_FAILED",
|
|
15889
|
+
message: `Agent tool(s) unavailable on the admitted OpenSpec CLI line: ${unavailable.join(", ")}.`
|
|
15890
|
+
});
|
|
15891
|
+
}
|
|
15730
15892
|
return streamOpenSpecCliMutation(ctx, ["init"], (mutationEvent) => ctx.cliExecutor.initStream({
|
|
15731
15893
|
tools: input.tools,
|
|
15732
15894
|
profile: projection.policy.profile,
|
|
@@ -17774,10 +17936,12 @@ var TranslationCacheService = class {
|
|
|
17774
17936
|
//#endregion
|
|
17775
17937
|
//#region src/runtime-package-host.ts
|
|
17776
17938
|
/**
|
|
17777
|
-
*
|
|
17939
|
+
* Original request (2026-08-04): "The project was developed on macOS and now needs Windows adaptation."
|
|
17940
|
+
* Orthogonal intents (updated 2026-08-08 Asia/Shanghai):
|
|
17778
17941
|
* 1. Resolve the physical host manifest that owns on-demand runtime installation.
|
|
17779
17942
|
* 2. Read declared runtime ranges without treating optional peers as install-time dependencies.
|
|
17780
17943
|
* 3. Normalize legacy on-demand optional dependency records after package-manager installation.
|
|
17944
|
+
* 4. Execute the npm dependency probe through Core's shell-independent Windows-safe spawn owner.
|
|
17781
17945
|
*
|
|
17782
17946
|
* Original request (2026-07-31): "这个依赖好像会导致安装的时候仍然会被强制装上去,可能要改成 peerDependencies 会更好"
|
|
17783
17947
|
*/
|
|
@@ -17814,7 +17978,7 @@ function readRuntimeHostPackageDependencyRequest(input) {
|
|
|
17814
17978
|
return `${input.packageName}@${range}`;
|
|
17815
17979
|
}
|
|
17816
17980
|
async function readRuntimeHostPackageDependencyTree(input) {
|
|
17817
|
-
const
|
|
17981
|
+
const started = spawnSafe("npm", [
|
|
17818
17982
|
"list",
|
|
17819
17983
|
"--json",
|
|
17820
17984
|
"--omit=dev",
|
|
@@ -17822,9 +17986,10 @@ async function readRuntimeHostPackageDependencyTree(input) {
|
|
|
17822
17986
|
...input.packageNames
|
|
17823
17987
|
], {
|
|
17824
17988
|
cwd: input.runtimeHost.packageDir,
|
|
17825
|
-
shell: false,
|
|
17826
17989
|
env: createCleanCliEnv()
|
|
17827
17990
|
});
|
|
17991
|
+
if (!started.ok) throw new Error(`Unable to start npm list: ${started.error.message}`);
|
|
17992
|
+
const { child } = started;
|
|
17828
17993
|
const stdoutChunks = [];
|
|
17829
17994
|
const stderrChunks = [];
|
|
17830
17995
|
const exitCode = await new Promise((resolve$1, reject) => {
|
|
@@ -18066,6 +18231,17 @@ function buildRuntimeConfig(engineId, profile) {
|
|
|
18066
18231
|
|
|
18067
18232
|
//#endregion
|
|
18068
18233
|
//#region src/translation-engine-service.ts
|
|
18234
|
+
/**
|
|
18235
|
+
* Orthogonal intents (updated 2026-08-09 Asia/Shanghai):
|
|
18236
|
+
* 1. Project translation-engine inventory, selection, lifecycle, and immutable settings snapshots.
|
|
18237
|
+
* 2. Install and cancel managed local runtime dependencies through observable, bounded process settlement.
|
|
18238
|
+
* 3. Own local model search, download plans, persisted asset readiness, and profile validation.
|
|
18239
|
+
* 4. Execute browser, OpenAI, and managed-local batch translation with cache-aware events.
|
|
18240
|
+
* 5. Preserve CT2/llama runtime policy, directional language constraints, and resource limits.
|
|
18241
|
+
*
|
|
18242
|
+
* Original request (2026-05-22): "Server provides engine registry, installation sessions, installation logs, selection, and translation."
|
|
18243
|
+
* Original request (2026-08-04): "Make pnpm openspecui start and equivalent package scripts work on Windows."
|
|
18244
|
+
*/
|
|
18069
18245
|
const __dirname$2 = dirname(fileURLToPath(import.meta.url));
|
|
18070
18246
|
var TranslationEngineService = class {
|
|
18071
18247
|
projectDir;
|
|
@@ -18796,55 +18972,82 @@ async function installManagedLocalRuntime(engineId, input) {
|
|
|
18796
18972
|
input.onLifecycle?.(finalLifecycle);
|
|
18797
18973
|
return finalLifecycle;
|
|
18798
18974
|
}
|
|
18975
|
+
/** Run one package-manager installation with bounded cross-platform tree cancellation. */
|
|
18799
18976
|
async function runRuntimeInstallCommand(input) {
|
|
18800
|
-
|
|
18801
|
-
|
|
18802
|
-
|
|
18803
|
-
|
|
18804
|
-
|
|
18805
|
-
|
|
18806
|
-
|
|
18807
|
-
}
|
|
18808
|
-
|
|
18809
|
-
|
|
18810
|
-
|
|
18811
|
-
|
|
18812
|
-
|
|
18813
|
-
} catch {}
|
|
18814
|
-
};
|
|
18815
|
-
input.signal?.addEventListener("abort", abort, { once: true });
|
|
18816
|
-
try {
|
|
18817
|
-
await new Promise((resolve$1, reject) => {
|
|
18818
|
-
child.stdout?.on("data", (data) => {
|
|
18819
|
-
input.onLog?.({
|
|
18820
|
-
stream: "stdout",
|
|
18821
|
-
text: data.toString()
|
|
18822
|
-
});
|
|
18823
|
-
});
|
|
18824
|
-
child.stderr?.on("data", (data) => {
|
|
18825
|
-
input.onLog?.({
|
|
18826
|
-
stream: "stderr",
|
|
18827
|
-
text: data.toString()
|
|
18828
|
-
});
|
|
18829
|
-
});
|
|
18830
|
-
child.on("error", (error) => {
|
|
18831
|
-
reject(error);
|
|
18977
|
+
if (input.signal?.aborted) return "Translation runtime installation cancelled.";
|
|
18978
|
+
const started = spawnSafe(input.command.cmd, input.command.args, {
|
|
18979
|
+
cwd: input.cwd,
|
|
18980
|
+
shell: false,
|
|
18981
|
+
env: createCleanCliEnv()
|
|
18982
|
+
});
|
|
18983
|
+
if (!started.ok) return started.error.message;
|
|
18984
|
+
const { child } = started;
|
|
18985
|
+
const completion = new Promise((resolve$1) => {
|
|
18986
|
+
child.stdout?.on("data", (data) => {
|
|
18987
|
+
input.onLog?.({
|
|
18988
|
+
stream: "stdout",
|
|
18989
|
+
text: data.toString()
|
|
18832
18990
|
});
|
|
18833
|
-
|
|
18834
|
-
|
|
18835
|
-
|
|
18836
|
-
|
|
18837
|
-
|
|
18838
|
-
reject(/* @__PURE__ */ new Error(`${input.command.displayCommand} exited with code ${exitCode ?? "unknown"}.`));
|
|
18991
|
+
});
|
|
18992
|
+
child.stderr?.on("data", (data) => {
|
|
18993
|
+
input.onLog?.({
|
|
18994
|
+
stream: "stderr",
|
|
18995
|
+
text: data.toString()
|
|
18839
18996
|
});
|
|
18840
18997
|
});
|
|
18841
|
-
|
|
18842
|
-
|
|
18843
|
-
|
|
18998
|
+
child.once("error", (error) => resolve$1(error.message));
|
|
18999
|
+
child.once("close", (exitCode) => {
|
|
19000
|
+
resolve$1(exitCode === 0 ? null : `${input.command.displayCommand} exited with code ${exitCode ?? "unknown"}.`);
|
|
19001
|
+
});
|
|
19002
|
+
});
|
|
19003
|
+
let requestAbort = null;
|
|
19004
|
+
const abortRequested = new Promise((resolve$1) => {
|
|
19005
|
+
requestAbort = resolve$1;
|
|
19006
|
+
});
|
|
19007
|
+
const abort = () => requestAbort?.();
|
|
19008
|
+
input.signal?.addEventListener("abort", abort, { once: true });
|
|
19009
|
+
try {
|
|
19010
|
+
const outcome = await Promise.race([completion.then((result) => ({
|
|
19011
|
+
kind: "complete",
|
|
19012
|
+
result
|
|
19013
|
+
})), abortRequested.then(() => ({ kind: "abort" }))]);
|
|
19014
|
+
if (outcome.kind === "complete") return outcome.result;
|
|
19015
|
+
return settleRuntimeInstallCancellation(child, completion, input.terminationTimeoutMs ?? 5e3);
|
|
18844
19016
|
} finally {
|
|
18845
19017
|
input.signal?.removeEventListener("abort", abort);
|
|
18846
19018
|
}
|
|
18847
19019
|
}
|
|
19020
|
+
async function settleRuntimeInstallCancellation(child, completion, timeoutMs) {
|
|
19021
|
+
const failures = [];
|
|
19022
|
+
const terminate = async (signal) => {
|
|
19023
|
+
let timer = null;
|
|
19024
|
+
try {
|
|
19025
|
+
await Promise.race([terminateChildProcessTree(child, signal), new Promise((_resolve, reject) => {
|
|
19026
|
+
timer = setTimeout(() => reject(/* @__PURE__ */ new Error(`Timed out requesting ${signal} for runtime installation.`)), timeoutMs);
|
|
19027
|
+
})]);
|
|
19028
|
+
} catch (error) {
|
|
19029
|
+
failures.push(error);
|
|
19030
|
+
} finally {
|
|
19031
|
+
if (timer) clearTimeout(timer);
|
|
19032
|
+
}
|
|
19033
|
+
};
|
|
19034
|
+
const waitForCompletion = async () => {
|
|
19035
|
+
let timer = null;
|
|
19036
|
+
try {
|
|
19037
|
+
return await Promise.race([completion.then(() => true), new Promise((resolve$1) => {
|
|
19038
|
+
timer = setTimeout(() => resolve$1(false), timeoutMs);
|
|
19039
|
+
})]);
|
|
19040
|
+
} finally {
|
|
19041
|
+
if (timer) clearTimeout(timer);
|
|
19042
|
+
}
|
|
19043
|
+
};
|
|
19044
|
+
await terminate("SIGTERM");
|
|
19045
|
+
if (await waitForCompletion()) return "Translation runtime installation cancelled.";
|
|
19046
|
+
await terminate("SIGKILL");
|
|
19047
|
+
if (await waitForCompletion()) return "Translation runtime installation cancelled.";
|
|
19048
|
+
const failureText = failures.map((error) => error instanceof Error ? error.message : String(error)).join(" ");
|
|
19049
|
+
return `Translation runtime installation cancellation did not settle.${failureText ? ` ${failureText}` : ""}`;
|
|
19050
|
+
}
|
|
18848
19051
|
function getManagedLocalRuntimeSpec(engineId) {
|
|
18849
19052
|
if (engineId === "local") return {
|
|
18850
19053
|
packageNames: ["@huggingface/transformers", "onnxruntime-node"],
|
|
@@ -18957,7 +19160,9 @@ function readCurrentRuntimeMemory() {
|
|
|
18957
19160
|
* 2. Execute translation engines in isolated worker-thread or process hosts.
|
|
18958
19161
|
* 3. Enforce cancellation and process memory limits while preserving batch evidence.
|
|
18959
19162
|
* 4. Route worker-thread payloads by an explicit translation protocol kind.
|
|
19163
|
+
* 5. Hide the process-host bootstrap console window (`windowsHide`) under a console-less Windows daemon.
|
|
18960
19164
|
*
|
|
19165
|
+
* Original request (2026-08-14): "在Windows平台上,执行命令总是会弹出cmd窗口,这个可否统一隐藏,你先调查一下原因"
|
|
18961
19166
|
* Original request (2026-07-24): "Production worker kinds must not claim each other's payloads."
|
|
18962
19167
|
*/
|
|
18963
19168
|
const execFileAsync = promisify(execFile);
|
|
@@ -19253,7 +19458,8 @@ function createManagedLocalTranslationProcess(input) {
|
|
|
19253
19458
|
"inherit",
|
|
19254
19459
|
"inherit",
|
|
19255
19460
|
"ipc"
|
|
19256
|
-
]
|
|
19461
|
+
],
|
|
19462
|
+
windowsHide: true
|
|
19257
19463
|
});
|
|
19258
19464
|
}
|
|
19259
19465
|
function resolveProcessEntryUrl() {
|
|
@@ -19410,11 +19616,11 @@ if (!isMainThread) {
|
|
|
19410
19616
|
//#endregion
|
|
19411
19617
|
//#region src/server.ts
|
|
19412
19618
|
/**
|
|
19413
|
-
* Orthogonal intents (updated 2026-08-
|
|
19619
|
+
* Orthogonal intents (updated 2026-08-04 Asia/Shanghai):
|
|
19414
19620
|
* 1. Bootstrap HTTP/tRPC, WebSocket, PTY, and launch-project runtime services.
|
|
19415
19621
|
* 2. Delegate planning-root, Store, Environment Global, Agent, and Root Context projection ownership.
|
|
19416
19622
|
* 3. Host notifications, sound, preview-resource, translation, and telemetry boundaries.
|
|
19417
|
-
* 4. Admit dynamic loopback App origins and
|
|
19623
|
+
* 4. Admit dynamic loopback App origins and await deterministic native-handle teardown.
|
|
19418
19624
|
* 5. Bridge settled machine-global config changes into Root Context replacement.
|
|
19419
19625
|
*
|
|
19420
19626
|
* Original request (2026-07-15): "你先负责后端(内核)的开发。"
|
|
@@ -19936,7 +20142,7 @@ async function createWebSocketServer(server, httpServer, config) {
|
|
|
19936
20142
|
() => ptyManager.closeAll(),
|
|
19937
20143
|
() => closeWebSocketServer(ptyWss),
|
|
19938
20144
|
() => closeWebSocketServer(wss),
|
|
19939
|
-
() => server.watcher?.
|
|
20145
|
+
() => server.watcher?.close()
|
|
19940
20146
|
]);
|
|
19941
20147
|
await settleCleanupPhase(failures, [() => server.storeObservationFallback.dispose()]);
|
|
19942
20148
|
await settleCleanupPhase(failures, [() => server.storeMutationService.dispose()]);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openspecui/server",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "9.0.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.mjs",
|
|
6
6
|
"exports": {
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"typecheck:nested-spec-tests": "tsc -p tsconfig.nested-spec-tests.json --noEmit",
|
|
26
26
|
"typecheck:project-initialization-tests": "tsc -p tsconfig.project-initialization-tests.json --noEmit",
|
|
27
27
|
"typecheck:default-store-tests": "tsc -p tsconfig.default-store-tests.json --noEmit",
|
|
28
|
-
"dev": "
|
|
28
|
+
"dev": "tsx watch --conditions=development --include '../core/dist/**' --include '../search/dist/**' src/standalone.ts",
|
|
29
29
|
"test": "vitest run",
|
|
30
30
|
"test:watch": "vitest"
|
|
31
31
|
},
|
|
@@ -33,8 +33,8 @@
|
|
|
33
33
|
"@hono/node-server": "^1.14.1",
|
|
34
34
|
"@huggingface/hub": "^2.12.0",
|
|
35
35
|
"@lydell/node-pty": "^1.1.0",
|
|
36
|
-
"@openspecui/core": "
|
|
37
|
-
"@openspecui/search": "
|
|
36
|
+
"@openspecui/core": "9.0.0",
|
|
37
|
+
"@openspecui/search": "9.0.0",
|
|
38
38
|
"@opentelemetry/api": "^1.9.1",
|
|
39
39
|
"@opentelemetry/exporter-trace-otlp-http": "^0.221.0",
|
|
40
40
|
"@opentelemetry/sdk-node": "^0.221.0",
|