@openspecui/server 3.2.3 → 3.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.mjs +601 -24
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
+
import { CliExecutor, CodeEditorThemeSchema, ConfigManager, DASHBOARD_METRIC_KEYS, DashboardConfigSchema, GitConfigSchema, HOSTED_SHELL_PROTOCOL_VERSION, MarkdownParser, OPENSPECUI_HOOKS_VERSION, OpenSpecAdapter, OpenSpecWatcher, OpsxConfigSchema, OpsxKernel, PtyClientMessageSchema, ReactiveContext, TerminalConfigSchema, TerminalRendererEngineSchema, getAllTools, getAvailableTools, getConfiguredTools, getDefaultCliCommandString, getDetectedProjectTools, getToolInitStates, getWatcherRuntimeStatus, initWatcherPool, isWatcherPoolInitialized, resolveTerminalShellDefaults, sniffGlobalCli, subscribeWatcherRuntimeStatus } from "@openspecui/core";
|
|
2
|
+
import { basename, dirname, join, relative, resolve, sep } from "node:path";
|
|
3
|
+
import { access, mkdir, readFile, realpath, rm, stat, writeFile } from "node:fs/promises";
|
|
4
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
1
5
|
import { createServer as createServer$1 } from "node:net";
|
|
2
6
|
import { serve } from "@hono/node-server";
|
|
3
|
-
import { CliExecutor, CodeEditorThemeSchema, ConfigManager, DASHBOARD_METRIC_KEYS, DashboardConfigSchema, GitConfigSchema, HOSTED_SHELL_PROTOCOL_VERSION, OpenSpecAdapter, OpenSpecWatcher, OpsxConfigSchema, OpsxKernel, PtyClientMessageSchema, ReactiveContext, TerminalConfigSchema, TerminalRendererEngineSchema, getAllTools, getAvailableTools, getConfiguredTools, getDefaultCliCommandString, getDetectedProjectTools, getToolInitStates, getWatcherRuntimeStatus, initWatcherPool, isWatcherPoolInitialized, sniffGlobalCli, subscribeWatcherRuntimeStatus } from "@openspecui/core";
|
|
4
7
|
import { fetchRequestHandler } from "@trpc/server/adapters/fetch";
|
|
5
8
|
import { applyWSSHandler } from "@trpc/server/adapters/ws";
|
|
6
9
|
import { Hono } from "hono";
|
|
7
10
|
import { cors } from "hono/cors";
|
|
8
11
|
import { readFileSync } from "node:fs";
|
|
9
|
-
import { basename, dirname, join, relative, resolve, sep } from "node:path";
|
|
10
|
-
import { fileURLToPath } from "node:url";
|
|
11
12
|
import { WebSocketServer } from "ws";
|
|
12
13
|
import { EventEmitter } from "node:events";
|
|
13
14
|
import { execFile } from "node:child_process";
|
|
14
|
-
import { mkdir, readFile, realpath, rm, stat, writeFile } from "node:fs/promises";
|
|
15
15
|
import { promisify } from "node:util";
|
|
16
16
|
import * as pty from "@lydell/node-pty";
|
|
17
17
|
import { EventEmitter as EventEmitter$1 } from "events";
|
|
@@ -21,6 +21,284 @@ import { observable } from "@trpc/server/observable";
|
|
|
21
21
|
import { z } from "zod";
|
|
22
22
|
import { NodeWorkerSearchProvider } from "@openspecui/search/node";
|
|
23
23
|
|
|
24
|
+
//#region src/document-service.ts
|
|
25
|
+
function toErrorDiagnostic$1(error) {
|
|
26
|
+
return {
|
|
27
|
+
level: "error",
|
|
28
|
+
message: error instanceof Error ? error.message : String(error)
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function isNotNull(value) {
|
|
32
|
+
return value !== null;
|
|
33
|
+
}
|
|
34
|
+
var DocumentService = class {
|
|
35
|
+
parser = new MarkdownParser();
|
|
36
|
+
constructor(projectDir, adapter, hookRuntime) {
|
|
37
|
+
this.projectDir = projectDir;
|
|
38
|
+
this.adapter = adapter;
|
|
39
|
+
this.hookRuntime = hookRuntime;
|
|
40
|
+
}
|
|
41
|
+
async readProjectMd(consumer = "view", mode = "processed") {
|
|
42
|
+
const source = await this.adapter.readProjectMd();
|
|
43
|
+
if (source === null) return null;
|
|
44
|
+
return this.processDocument({
|
|
45
|
+
consumer,
|
|
46
|
+
mode,
|
|
47
|
+
document: {
|
|
48
|
+
stage: "project",
|
|
49
|
+
kind: "project",
|
|
50
|
+
relativePath: "openspec/project.md",
|
|
51
|
+
absolutePath: join(this.projectDir, "openspec", "project.md")
|
|
52
|
+
},
|
|
53
|
+
source
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
async readSpecRaw(specId, consumer = "view", mode = "processed") {
|
|
57
|
+
const source = await this.adapter.readSpecRaw(specId);
|
|
58
|
+
if (source === null) return null;
|
|
59
|
+
return this.processDocument({
|
|
60
|
+
consumer,
|
|
61
|
+
mode,
|
|
62
|
+
document: {
|
|
63
|
+
stage: "main",
|
|
64
|
+
kind: "spec",
|
|
65
|
+
specId,
|
|
66
|
+
relativePath: `openspec/specs/${specId}/spec.md`,
|
|
67
|
+
absolutePath: join(this.projectDir, "openspec", "specs", specId, "spec.md")
|
|
68
|
+
},
|
|
69
|
+
source
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
async readSpec(specId, consumer = "view", mode = "processed") {
|
|
73
|
+
try {
|
|
74
|
+
const content = await this.readSpecRaw(specId, consumer, mode);
|
|
75
|
+
if (!content) return null;
|
|
76
|
+
return this.parser.parseSpec(specId, content.markdown);
|
|
77
|
+
} catch {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
async readChangeRaw(changeId, consumer = "view", mode = "processed") {
|
|
82
|
+
const raw = await this.adapter.readChangeRaw(changeId);
|
|
83
|
+
if (!raw) return null;
|
|
84
|
+
const process$1 = (kind, relativePath$1, source) => this.processDocument({
|
|
85
|
+
consumer,
|
|
86
|
+
mode,
|
|
87
|
+
document: {
|
|
88
|
+
stage: "change",
|
|
89
|
+
kind,
|
|
90
|
+
changeId,
|
|
91
|
+
relativePath: relativePath$1,
|
|
92
|
+
absolutePath: join(this.projectDir, relativePath$1)
|
|
93
|
+
},
|
|
94
|
+
source
|
|
95
|
+
});
|
|
96
|
+
const [proposal, tasks, design, deltaSpecs] = await Promise.all([
|
|
97
|
+
process$1("proposal", `openspec/changes/${changeId}/proposal.md`, raw.proposal),
|
|
98
|
+
process$1("tasks", `openspec/changes/${changeId}/tasks.md`, raw.tasks),
|
|
99
|
+
raw.design ? process$1("design", `openspec/changes/${changeId}/design.md`, raw.design) : Promise.resolve(void 0),
|
|
100
|
+
Promise.all(raw.deltaSpecs.map(async (deltaSpec) => {
|
|
101
|
+
const result = await process$1("delta-spec", `openspec/changes/${changeId}/specs/${deltaSpec.specId}/spec.md`, deltaSpec.content);
|
|
102
|
+
return {
|
|
103
|
+
specId: deltaSpec.specId,
|
|
104
|
+
content: result.markdown,
|
|
105
|
+
sourceContent: deltaSpec.content
|
|
106
|
+
};
|
|
107
|
+
}))
|
|
108
|
+
]);
|
|
109
|
+
return {
|
|
110
|
+
proposal,
|
|
111
|
+
tasks,
|
|
112
|
+
design,
|
|
113
|
+
deltaSpecs
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
async readChange(changeId, consumer = "view", mode = "processed") {
|
|
117
|
+
try {
|
|
118
|
+
const raw = await this.readChangeRaw(changeId, consumer, mode);
|
|
119
|
+
if (!raw) return null;
|
|
120
|
+
return this.parser.parseChange(changeId, raw.proposal.markdown, raw.tasks.markdown, {
|
|
121
|
+
design: raw.design?.markdown,
|
|
122
|
+
deltaSpecs: raw.deltaSpecs
|
|
123
|
+
});
|
|
124
|
+
} catch {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
async readArchivedChangeRaw(changeId, consumer = "view", mode = "processed") {
|
|
129
|
+
const raw = await this.adapter.readArchivedChangeRaw(changeId);
|
|
130
|
+
if (!raw) return null;
|
|
131
|
+
const process$1 = (kind, relativePath$1, source) => this.processDocument({
|
|
132
|
+
consumer,
|
|
133
|
+
mode,
|
|
134
|
+
document: {
|
|
135
|
+
stage: "archive",
|
|
136
|
+
kind,
|
|
137
|
+
changeId,
|
|
138
|
+
relativePath: relativePath$1,
|
|
139
|
+
absolutePath: join(this.projectDir, relativePath$1)
|
|
140
|
+
},
|
|
141
|
+
source
|
|
142
|
+
});
|
|
143
|
+
const [proposal, tasks, design, deltaSpecs] = await Promise.all([
|
|
144
|
+
process$1("proposal", `openspec/changes/archive/${changeId}/proposal.md`, raw.proposal),
|
|
145
|
+
process$1("tasks", `openspec/changes/archive/${changeId}/tasks.md`, raw.tasks),
|
|
146
|
+
raw.design ? process$1("design", `openspec/changes/archive/${changeId}/design.md`, raw.design) : Promise.resolve(void 0),
|
|
147
|
+
Promise.all(raw.deltaSpecs.map(async (deltaSpec) => {
|
|
148
|
+
const result = await process$1("delta-spec", `openspec/changes/archive/${changeId}/specs/${deltaSpec.specId}/spec.md`, deltaSpec.content);
|
|
149
|
+
return {
|
|
150
|
+
specId: deltaSpec.specId,
|
|
151
|
+
content: result.markdown,
|
|
152
|
+
sourceContent: deltaSpec.content
|
|
153
|
+
};
|
|
154
|
+
}))
|
|
155
|
+
]);
|
|
156
|
+
return {
|
|
157
|
+
proposal,
|
|
158
|
+
tasks,
|
|
159
|
+
design,
|
|
160
|
+
deltaSpecs
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
async readArchivedChange(changeId, consumer = "view", mode = "processed") {
|
|
164
|
+
try {
|
|
165
|
+
const raw = await this.readArchivedChangeRaw(changeId, consumer, mode);
|
|
166
|
+
if (!raw) return null;
|
|
167
|
+
return this.parser.parseChange(changeId, raw.proposal.markdown, raw.tasks.markdown, {
|
|
168
|
+
design: raw.design?.markdown,
|
|
169
|
+
deltaSpecs: raw.deltaSpecs
|
|
170
|
+
});
|
|
171
|
+
} catch {
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
async readChangeFiles(changeId, consumer = "view", mode = "processed") {
|
|
176
|
+
const files = await this.adapter.readChangeFiles(changeId);
|
|
177
|
+
return this.processChangeFiles("change", changeId, files, consumer, mode);
|
|
178
|
+
}
|
|
179
|
+
async readArchivedChangeFiles(changeId, consumer = "view", mode = "processed") {
|
|
180
|
+
const files = await this.adapter.readArchivedChangeFiles(changeId);
|
|
181
|
+
return this.processChangeFiles("archive", changeId, files, consumer, mode);
|
|
182
|
+
}
|
|
183
|
+
async processChangeFiles(stage, changeId, files, consumer, mode) {
|
|
184
|
+
const root = stage === "change" ? `openspec/changes/${changeId}` : `openspec/changes/archive/${changeId}`;
|
|
185
|
+
return (await Promise.all(files.map(async (file) => {
|
|
186
|
+
if (file.type !== "file" || file.content === void 0 || !file.path.endsWith(".md")) return file;
|
|
187
|
+
const kind = this.inferChangeFileKind(file.path);
|
|
188
|
+
if (!kind) return file;
|
|
189
|
+
const result = await this.processDocument({
|
|
190
|
+
consumer,
|
|
191
|
+
mode,
|
|
192
|
+
document: {
|
|
193
|
+
stage,
|
|
194
|
+
kind,
|
|
195
|
+
changeId,
|
|
196
|
+
relativePath: `${root}/${file.path}`,
|
|
197
|
+
absolutePath: join(this.projectDir, root, file.path)
|
|
198
|
+
},
|
|
199
|
+
source: file.content
|
|
200
|
+
});
|
|
201
|
+
return {
|
|
202
|
+
...file,
|
|
203
|
+
content: result.markdown
|
|
204
|
+
};
|
|
205
|
+
}))).filter(isNotNull);
|
|
206
|
+
}
|
|
207
|
+
inferChangeFileKind(path) {
|
|
208
|
+
if (path === "proposal.md") return "proposal";
|
|
209
|
+
if (path === "tasks.md") return "tasks";
|
|
210
|
+
if (path === "design.md") return "design";
|
|
211
|
+
if (/^specs\/[^/]+\/spec\.md$/.test(path)) return "delta-spec";
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
async processDocument(input) {
|
|
215
|
+
const read = async () => ({
|
|
216
|
+
markdown: input.source,
|
|
217
|
+
sourceMarkdown: input.source
|
|
218
|
+
});
|
|
219
|
+
if (input.mode === "source") return read();
|
|
220
|
+
const hooks = await this.hookRuntime.load();
|
|
221
|
+
if (!hooks.onReadDocument) return read();
|
|
222
|
+
try {
|
|
223
|
+
return {
|
|
224
|
+
...await hooks.onReadDocument({
|
|
225
|
+
version: OPENSPECUI_HOOKS_VERSION,
|
|
226
|
+
projectDir: this.projectDir,
|
|
227
|
+
consumer: input.consumer,
|
|
228
|
+
document: input.document,
|
|
229
|
+
signal: new AbortController().signal,
|
|
230
|
+
lifecycle: this.hookRuntime
|
|
231
|
+
}, read),
|
|
232
|
+
sourceMarkdown: input.source
|
|
233
|
+
};
|
|
234
|
+
} catch (error) {
|
|
235
|
+
const fallback = await read();
|
|
236
|
+
return {
|
|
237
|
+
...fallback,
|
|
238
|
+
diagnostics: [...fallback.diagnostics ?? [], toErrorDiagnostic$1(error)]
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
//#endregion
|
|
245
|
+
//#region src/hook-runtime.ts
|
|
246
|
+
const OPENSPECUI_HOOKS_RELATIVE_PATH = "openspec/openspecui.hooks.ts";
|
|
247
|
+
function isOnReadDocumentHook(value) {
|
|
248
|
+
return typeof value === "function";
|
|
249
|
+
}
|
|
250
|
+
function isOnRunWorkflowHook(value) {
|
|
251
|
+
return typeof value === "function";
|
|
252
|
+
}
|
|
253
|
+
function normalizeHooksModule(moduleValue) {
|
|
254
|
+
if (!moduleValue || typeof moduleValue !== "object") return {};
|
|
255
|
+
const record = moduleValue;
|
|
256
|
+
const defaultRecord = record.default && typeof record.default === "object" ? record.default : {};
|
|
257
|
+
const moduleExportsRecord = record["module.exports"] && typeof record["module.exports"] === "object" ? record["module.exports"] : {};
|
|
258
|
+
return {
|
|
259
|
+
onReadDocument: isOnReadDocumentHook(record.onReadDocument) ? record.onReadDocument : isOnReadDocumentHook(defaultRecord.onReadDocument) ? defaultRecord.onReadDocument : isOnReadDocumentHook(moduleExportsRecord.onReadDocument) ? moduleExportsRecord.onReadDocument : void 0,
|
|
260
|
+
onRunWorkflow: isOnRunWorkflowHook(record.onRunWorkflow) ? record.onRunWorkflow : isOnRunWorkflowHook(defaultRecord.onRunWorkflow) ? defaultRecord.onRunWorkflow : isOnRunWorkflowHook(moduleExportsRecord.onRunWorkflow) ? moduleExportsRecord.onRunWorkflow : void 0
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
async function pathExists$1(path) {
|
|
264
|
+
try {
|
|
265
|
+
await access(path);
|
|
266
|
+
return true;
|
|
267
|
+
} catch {
|
|
268
|
+
return false;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
var ProjectHookRuntime = class {
|
|
272
|
+
hooksPath;
|
|
273
|
+
hooksPromise = null;
|
|
274
|
+
disposeCallbacks = /* @__PURE__ */ new Set();
|
|
275
|
+
constructor(projectDir) {
|
|
276
|
+
this.hooksPath = join(projectDir, OPENSPECUI_HOOKS_RELATIVE_PATH);
|
|
277
|
+
}
|
|
278
|
+
async load() {
|
|
279
|
+
if (this.hooksPromise) return this.hooksPromise;
|
|
280
|
+
this.hooksPromise = this.loadFresh().catch(() => ({}));
|
|
281
|
+
return this.hooksPromise;
|
|
282
|
+
}
|
|
283
|
+
onDispose(cleanup) {
|
|
284
|
+
this.disposeCallbacks.add(cleanup);
|
|
285
|
+
}
|
|
286
|
+
async dispose() {
|
|
287
|
+
const callbacks = [...this.disposeCallbacks];
|
|
288
|
+
this.disposeCallbacks.clear();
|
|
289
|
+
await Promise.allSettled(callbacks.map((cleanup) => cleanup()));
|
|
290
|
+
}
|
|
291
|
+
async loadFresh() {
|
|
292
|
+
if (!await pathExists$1(this.hooksPath)) return {};
|
|
293
|
+
const { tsImport } = await import("tsx/esm/api");
|
|
294
|
+
return normalizeHooksModule(await tsImport(`${pathToFileURL(this.hooksPath).href}?t=${Date.now()}`, { parentURL: pathToFileURL(this.hooksPath).href }));
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
function createHookRuntime(projectDir) {
|
|
298
|
+
return new ProjectHookRuntime(projectDir);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
//#endregion
|
|
24
302
|
//#region src/port-utils.ts
|
|
25
303
|
/**
|
|
26
304
|
* Check if a port is available by trying to listen on it.
|
|
@@ -1145,6 +1423,15 @@ function resolveDefaultShell(platform, env) {
|
|
|
1145
1423
|
if (platform === "windows") return env.ComSpec?.trim() || "cmd.exe";
|
|
1146
1424
|
return env.SHELL?.trim() || "/bin/sh";
|
|
1147
1425
|
}
|
|
1426
|
+
function resolvePtyShellDefaults(opts) {
|
|
1427
|
+
return resolveTerminalShellDefaults({
|
|
1428
|
+
platform: opts.platform,
|
|
1429
|
+
env: {
|
|
1430
|
+
SHELL: opts.env.SHELL,
|
|
1431
|
+
ComSpec: opts.env.ComSpec
|
|
1432
|
+
}
|
|
1433
|
+
});
|
|
1434
|
+
}
|
|
1148
1435
|
function resolvePtyCommand(opts) {
|
|
1149
1436
|
const command = opts.command?.trim();
|
|
1150
1437
|
if (command) return {
|
|
@@ -1282,6 +1569,12 @@ var PtyManager = class {
|
|
|
1282
1569
|
this.defaultCwd = defaultCwd;
|
|
1283
1570
|
this.platform = detectPtyPlatform();
|
|
1284
1571
|
}
|
|
1572
|
+
getShellDefaults() {
|
|
1573
|
+
return resolvePtyShellDefaults({
|
|
1574
|
+
platform: this.platform,
|
|
1575
|
+
env: process.env
|
|
1576
|
+
});
|
|
1577
|
+
}
|
|
1285
1578
|
create(opts) {
|
|
1286
1579
|
const id = `pty-${++this.idCounter}`;
|
|
1287
1580
|
const session = new PtySession(id, {
|
|
@@ -2313,6 +2606,47 @@ const gitEntrySelectorSchema = z.discriminatedUnion("type", [z.object({ type: z.
|
|
|
2313
2606
|
type: z.literal("commit"),
|
|
2314
2607
|
hash: z.string().min(1)
|
|
2315
2608
|
})]);
|
|
2609
|
+
const workflowRequestedModeSchema = z.enum([
|
|
2610
|
+
"compose",
|
|
2611
|
+
"command",
|
|
2612
|
+
"direct"
|
|
2613
|
+
]);
|
|
2614
|
+
const runWorkflowInputSchema = z.discriminatedUnion("action", [
|
|
2615
|
+
z.object({
|
|
2616
|
+
action: z.enum(["explore", "propose"]),
|
|
2617
|
+
text: z.string()
|
|
2618
|
+
}),
|
|
2619
|
+
z.object({
|
|
2620
|
+
action: z.literal("new"),
|
|
2621
|
+
changeId: z.string(),
|
|
2622
|
+
schema: z.string().optional(),
|
|
2623
|
+
description: z.string().optional(),
|
|
2624
|
+
extraArgs: z.array(z.string()).default([])
|
|
2625
|
+
}),
|
|
2626
|
+
z.object({
|
|
2627
|
+
action: z.enum(["continue", "ff"]),
|
|
2628
|
+
changeId: z.string(),
|
|
2629
|
+
artifactId: z.string(),
|
|
2630
|
+
schema: z.string().optional()
|
|
2631
|
+
}),
|
|
2632
|
+
z.object({
|
|
2633
|
+
action: z.enum([
|
|
2634
|
+
"apply",
|
|
2635
|
+
"archive",
|
|
2636
|
+
"verify",
|
|
2637
|
+
"sync"
|
|
2638
|
+
]),
|
|
2639
|
+
changeId: z.string(),
|
|
2640
|
+
schema: z.string().optional(),
|
|
2641
|
+
strict: z.boolean().optional()
|
|
2642
|
+
}),
|
|
2643
|
+
z.object({
|
|
2644
|
+
action: z.literal("bulk-archive"),
|
|
2645
|
+
changeIds: z.array(z.string()).optional(),
|
|
2646
|
+
schema: z.string().optional()
|
|
2647
|
+
}),
|
|
2648
|
+
z.object({ action: z.literal("onboard") })
|
|
2649
|
+
]);
|
|
2316
2650
|
function requireChangeId(changeId) {
|
|
2317
2651
|
if (!changeId) throw new Error("change is required");
|
|
2318
2652
|
return changeId;
|
|
@@ -2494,7 +2828,7 @@ const specRouter = router({
|
|
|
2494
2828
|
return ctx.adapter.listSpecsWithMeta();
|
|
2495
2829
|
}),
|
|
2496
2830
|
get: publicProcedure.input(z.object({ id: z.string() })).query(async ({ ctx, input }) => {
|
|
2497
|
-
return ctx.
|
|
2831
|
+
return ctx.documentService.readSpec(input.id);
|
|
2498
2832
|
}),
|
|
2499
2833
|
getRaw: publicProcedure.input(z.object({ id: z.string() })).query(async ({ ctx, input }) => {
|
|
2500
2834
|
return ctx.adapter.readSpecRaw(input.id);
|
|
@@ -2513,7 +2847,7 @@ const specRouter = router({
|
|
|
2513
2847
|
return createReactiveSubscription(() => ctx.adapter.listSpecsWithMeta());
|
|
2514
2848
|
}),
|
|
2515
2849
|
subscribeOne: publicProcedure.input(z.object({ id: z.string() })).subscription(({ ctx, input }) => {
|
|
2516
|
-
return createReactiveSubscriptionWithInput((id) => ctx.
|
|
2850
|
+
return createReactiveSubscriptionWithInput((id) => ctx.documentService.readSpec(id))(input.id);
|
|
2517
2851
|
}),
|
|
2518
2852
|
subscribeRaw: publicProcedure.input(z.object({ id: z.string() })).subscription(({ ctx, input }) => {
|
|
2519
2853
|
return createReactiveSubscriptionWithInput((id) => ctx.adapter.readSpecRaw(id))(input.id);
|
|
@@ -2533,7 +2867,7 @@ const changeRouter = router({
|
|
|
2533
2867
|
return ctx.adapter.listArchivedChanges();
|
|
2534
2868
|
}),
|
|
2535
2869
|
get: publicProcedure.input(z.object({ id: z.string() })).query(async ({ ctx, input }) => {
|
|
2536
|
-
return ctx.
|
|
2870
|
+
return ctx.documentService.readChange(input.id);
|
|
2537
2871
|
}),
|
|
2538
2872
|
getRaw: publicProcedure.input(z.object({ id: z.string() })).query(async ({ ctx, input }) => {
|
|
2539
2873
|
return ctx.adapter.readChangeRaw(input.id);
|
|
@@ -2585,7 +2919,7 @@ const archiveRouter = router({
|
|
|
2585
2919
|
return ctx.adapter.listArchivedChangesWithMeta();
|
|
2586
2920
|
}),
|
|
2587
2921
|
get: publicProcedure.input(z.object({ id: z.string() })).query(async ({ ctx, input }) => {
|
|
2588
|
-
return ctx.
|
|
2922
|
+
return ctx.documentService.readArchivedChange(input.id);
|
|
2589
2923
|
}),
|
|
2590
2924
|
getRaw: publicProcedure.input(z.object({ id: z.string() })).query(async ({ ctx, input }) => {
|
|
2591
2925
|
return ctx.adapter.readArchivedChangeRaw(input.id);
|
|
@@ -2594,7 +2928,7 @@ const archiveRouter = router({
|
|
|
2594
2928
|
return createReactiveSubscription(() => ctx.adapter.listArchivedChangesWithMeta());
|
|
2595
2929
|
}),
|
|
2596
2930
|
subscribeOne: publicProcedure.input(z.object({ id: z.string() })).subscription(({ ctx, input }) => {
|
|
2597
|
-
return createReactiveSubscriptionWithInput((id) => ctx.
|
|
2931
|
+
return createReactiveSubscriptionWithInput((id) => ctx.documentService.readArchivedChange(id))(input.id);
|
|
2598
2932
|
}),
|
|
2599
2933
|
subscribeFiles: publicProcedure.input(z.object({ id: z.string() })).subscription(({ ctx, input }) => {
|
|
2600
2934
|
return createReactiveSubscriptionWithInput((id) => ctx.adapter.readArchivedChangeFiles(id))(input.id);
|
|
@@ -2720,6 +3054,15 @@ const configRouter = router({
|
|
|
2720
3054
|
}),
|
|
2721
3055
|
subscribe: publicProcedure.subscription(({ ctx }) => {
|
|
2722
3056
|
return createReactiveSubscription(() => ctx.configManager.readConfig());
|
|
3057
|
+
}),
|
|
3058
|
+
getTerminalShellDefaults: publicProcedure.query(async () => {
|
|
3059
|
+
return resolveTerminalShellDefaults({
|
|
3060
|
+
platform: process.platform === "win32" ? "windows" : process.platform === "darwin" ? "macos" : "common",
|
|
3061
|
+
env: {
|
|
3062
|
+
SHELL: process.env.SHELL,
|
|
3063
|
+
ComSpec: process.env.ComSpec
|
|
3064
|
+
}
|
|
3065
|
+
});
|
|
2723
3066
|
})
|
|
2724
3067
|
});
|
|
2725
3068
|
/**
|
|
@@ -2882,6 +3225,12 @@ const cliRouter = router({
|
|
|
2882
3225
|
* OPSX router - CLI-driven workflow data
|
|
2883
3226
|
*/
|
|
2884
3227
|
const opsxRouter = router({
|
|
3228
|
+
runWorkflow: publicProcedure.input(z.object({
|
|
3229
|
+
requestedMode: workflowRequestedModeSchema,
|
|
3230
|
+
input: runWorkflowInputSchema
|
|
3231
|
+
})).mutation(async ({ ctx, input }) => {
|
|
3232
|
+
return ctx.workflowInvocationService.runWorkflow(input.input, input.requestedMode);
|
|
3233
|
+
}),
|
|
2885
3234
|
status: publicProcedure.input(z.object({
|
|
2886
3235
|
change: z.string().optional(),
|
|
2887
3236
|
schema: z.string().optional()
|
|
@@ -3350,11 +3699,11 @@ const appRouter = router({
|
|
|
3350
3699
|
function joinParts(parts) {
|
|
3351
3700
|
return parts.map((part) => part?.trim() ?? "").filter((part) => part.length > 0).join("\n\n");
|
|
3352
3701
|
}
|
|
3353
|
-
async function collectSearchDocuments(adapter) {
|
|
3702
|
+
async function collectSearchDocuments(adapter, documentService) {
|
|
3354
3703
|
const docs = [];
|
|
3355
3704
|
const specs = await adapter.listSpecsWithMeta();
|
|
3356
3705
|
for (const spec of specs) {
|
|
3357
|
-
const raw = await adapter.readSpecRaw(spec.id);
|
|
3706
|
+
const raw = documentService ? await documentService.readSpecRaw(spec.id, "search", "processed") : await adapter.readSpecRaw(spec.id);
|
|
3358
3707
|
if (!raw) continue;
|
|
3359
3708
|
docs.push({
|
|
3360
3709
|
id: `spec:${spec.id}`,
|
|
@@ -3362,13 +3711,13 @@ async function collectSearchDocuments(adapter) {
|
|
|
3362
3711
|
title: spec.name,
|
|
3363
3712
|
href: `/specs/${encodeURIComponent(spec.id)}`,
|
|
3364
3713
|
path: `openspec/specs/${spec.id}/spec.md`,
|
|
3365
|
-
content: raw,
|
|
3714
|
+
content: typeof raw === "string" ? raw : raw.markdown,
|
|
3366
3715
|
updatedAt: spec.updatedAt
|
|
3367
3716
|
});
|
|
3368
3717
|
}
|
|
3369
3718
|
const changes = await adapter.listChangesWithMeta();
|
|
3370
3719
|
for (const change of changes) {
|
|
3371
|
-
const raw = await adapter.readChangeRaw(change.id);
|
|
3720
|
+
const raw = documentService ? await documentService.readChangeRaw(change.id, "search", "processed") : await adapter.readChangeRaw(change.id);
|
|
3372
3721
|
if (!raw) continue;
|
|
3373
3722
|
docs.push({
|
|
3374
3723
|
id: `change:${change.id}`,
|
|
@@ -3377,9 +3726,9 @@ async function collectSearchDocuments(adapter) {
|
|
|
3377
3726
|
href: `/changes/${encodeURIComponent(change.id)}`,
|
|
3378
3727
|
path: `openspec/changes/${change.id}`,
|
|
3379
3728
|
content: joinParts([
|
|
3380
|
-
raw.proposal,
|
|
3381
|
-
raw.tasks,
|
|
3382
|
-
raw.design,
|
|
3729
|
+
typeof raw.proposal === "string" ? raw.proposal : raw.proposal.markdown,
|
|
3730
|
+
typeof raw.tasks === "string" ? raw.tasks : raw.tasks.markdown,
|
|
3731
|
+
typeof raw.design === "string" ? raw.design : raw.design?.markdown,
|
|
3383
3732
|
...raw.deltaSpecs.map((deltaSpec) => deltaSpec.content)
|
|
3384
3733
|
]),
|
|
3385
3734
|
updatedAt: change.updatedAt
|
|
@@ -3387,7 +3736,7 @@ async function collectSearchDocuments(adapter) {
|
|
|
3387
3736
|
}
|
|
3388
3737
|
const archives = await adapter.listArchivedChangesWithMeta();
|
|
3389
3738
|
for (const archive of archives) {
|
|
3390
|
-
const raw = await adapter.readArchivedChangeRaw(archive.id);
|
|
3739
|
+
const raw = documentService ? await documentService.readArchivedChangeRaw(archive.id, "search", "processed") : await adapter.readArchivedChangeRaw(archive.id);
|
|
3391
3740
|
if (!raw) continue;
|
|
3392
3741
|
docs.push({
|
|
3393
3742
|
id: `archive:${archive.id}`,
|
|
@@ -3396,9 +3745,9 @@ async function collectSearchDocuments(adapter) {
|
|
|
3396
3745
|
href: `/archive/${encodeURIComponent(archive.id)}`,
|
|
3397
3746
|
path: `openspec/changes/archive/${archive.id}`,
|
|
3398
3747
|
content: joinParts([
|
|
3399
|
-
raw.proposal,
|
|
3400
|
-
raw.tasks,
|
|
3401
|
-
raw.design,
|
|
3748
|
+
typeof raw.proposal === "string" ? raw.proposal : raw.proposal.markdown,
|
|
3749
|
+
typeof raw.tasks === "string" ? raw.tasks : raw.tasks.markdown,
|
|
3750
|
+
typeof raw.design === "string" ? raw.design : raw.design?.markdown,
|
|
3402
3751
|
...raw.deltaSpecs.map((deltaSpec) => deltaSpec.content)
|
|
3403
3752
|
]),
|
|
3404
3753
|
updatedAt: archive.updatedAt
|
|
@@ -3416,8 +3765,9 @@ var SearchService = class {
|
|
|
3416
3765
|
initPromise = null;
|
|
3417
3766
|
rebuildPromise = null;
|
|
3418
3767
|
rebuildTimer = null;
|
|
3419
|
-
constructor(adapter, watcher, provider = new NodeWorkerSearchProvider()) {
|
|
3768
|
+
constructor(adapter, watcher, provider = new NodeWorkerSearchProvider(), documentService) {
|
|
3420
3769
|
this.adapter = adapter;
|
|
3770
|
+
this.documentService = documentService;
|
|
3421
3771
|
this.provider = provider;
|
|
3422
3772
|
watcher?.on("change", () => {
|
|
3423
3773
|
this.scheduleRebuild();
|
|
@@ -3463,7 +3813,7 @@ var SearchService = class {
|
|
|
3463
3813
|
if (!forceInit && !this.initialized) return;
|
|
3464
3814
|
if (this.rebuildPromise) return this.rebuildPromise;
|
|
3465
3815
|
this.rebuildPromise = (async () => {
|
|
3466
|
-
const docs = await collectSearchDocuments(this.adapter);
|
|
3816
|
+
const docs = await collectSearchDocuments(this.adapter, this.documentService);
|
|
3467
3817
|
if (this.initialized) await this.provider.replaceAll(docs);
|
|
3468
3818
|
else {
|
|
3469
3819
|
await this.provider.init(docs);
|
|
@@ -3478,6 +3828,218 @@ var SearchService = class {
|
|
|
3478
3828
|
}
|
|
3479
3829
|
};
|
|
3480
3830
|
|
|
3831
|
+
//#endregion
|
|
3832
|
+
//#region src/workflow-invocation-service.ts
|
|
3833
|
+
const COMMAND_CAPABLE_ACTIONS = new Set([
|
|
3834
|
+
"propose",
|
|
3835
|
+
"apply",
|
|
3836
|
+
"archive"
|
|
3837
|
+
]);
|
|
3838
|
+
const COMMAND_FALLBACK_REASONS = {
|
|
3839
|
+
continue: "Continue uses the selected artifact context, so compose mode is required.",
|
|
3840
|
+
ff: "Fast-forward from a change page uses the selected ready artifact, so compose mode is required."
|
|
3841
|
+
};
|
|
3842
|
+
function toErrorDiagnostic(error) {
|
|
3843
|
+
return {
|
|
3844
|
+
level: "error",
|
|
3845
|
+
message: error instanceof Error ? error.message : String(error)
|
|
3846
|
+
};
|
|
3847
|
+
}
|
|
3848
|
+
function withDiagnostics(result, diagnostics) {
|
|
3849
|
+
return {
|
|
3850
|
+
...result,
|
|
3851
|
+
diagnostics: [...result.diagnostics ?? [], ...diagnostics]
|
|
3852
|
+
};
|
|
3853
|
+
}
|
|
3854
|
+
function resolveInvocationMode(action, requestedMode) {
|
|
3855
|
+
if (requestedMode !== "command" || COMMAND_CAPABLE_ACTIONS.has(action)) return {
|
|
3856
|
+
requestedMode,
|
|
3857
|
+
actualMode: requestedMode,
|
|
3858
|
+
fallbackReason: null
|
|
3859
|
+
};
|
|
3860
|
+
return {
|
|
3861
|
+
requestedMode,
|
|
3862
|
+
actualMode: "compose",
|
|
3863
|
+
fallbackReason: COMMAND_FALLBACK_REASONS[action] ?? "This action requires compose mode."
|
|
3864
|
+
};
|
|
3865
|
+
}
|
|
3866
|
+
function buildProposeComposePrompt(text) {
|
|
3867
|
+
const normalized = text.trim();
|
|
3868
|
+
if (normalized.length === 0) return ["Propose a new OpenSpec change.", "Ask me what to build before creating files if the request is unclear."].join("\n");
|
|
3869
|
+
return [
|
|
3870
|
+
`Propose a new OpenSpec change for: ${normalized}`,
|
|
3871
|
+
"",
|
|
3872
|
+
"Use the OpenSpec propose workflow. If an openspec-propose skill is available, follow it. Otherwise derive a kebab-case change name, run `openspec new change \"<name>\"`, inspect `openspec status --change \"<name>\" --json`, and create every apply-required artifact using `openspec instructions <artifact-id> --change \"<name>\" --json`."
|
|
3873
|
+
].join("\n");
|
|
3874
|
+
}
|
|
3875
|
+
function buildSlashCommand(input) {
|
|
3876
|
+
switch (input.action) {
|
|
3877
|
+
case "propose": {
|
|
3878
|
+
const normalized = input.text.trim();
|
|
3879
|
+
if (normalized.length === 0) return "/opsx:propose";
|
|
3880
|
+
if (normalized.startsWith("/opsx:")) return normalized;
|
|
3881
|
+
return `/opsx:propose ${normalized}`;
|
|
3882
|
+
}
|
|
3883
|
+
case "apply":
|
|
3884
|
+
case "archive": return `/opsx:${input.action} ${input.changeId.trim()}`;
|
|
3885
|
+
default: return null;
|
|
3886
|
+
}
|
|
3887
|
+
}
|
|
3888
|
+
async function captureCliText(execute, args, fallback) {
|
|
3889
|
+
const result = await execute(args);
|
|
3890
|
+
const text = result.stdout.trim().length > 0 ? result.stdout.trim() : fallback;
|
|
3891
|
+
if (result.success) return { text };
|
|
3892
|
+
return {
|
|
3893
|
+
text,
|
|
3894
|
+
diagnostics: [{
|
|
3895
|
+
level: "warning",
|
|
3896
|
+
message: result.stderr || `openspec command exited with code ${result.exitCode ?? "null"}`
|
|
3897
|
+
}]
|
|
3898
|
+
};
|
|
3899
|
+
}
|
|
3900
|
+
function buildFallbackPrompt(input) {
|
|
3901
|
+
switch (input.action) {
|
|
3902
|
+
case "continue": return `Continue artifact ${input.artifactId} for change ${input.changeId}.`;
|
|
3903
|
+
case "ff": return `Fast-forward artifact ${input.artifactId} for change ${input.changeId}.`;
|
|
3904
|
+
case "apply": return `Apply change ${input.changeId} based on current completed artifacts.`;
|
|
3905
|
+
case "archive": return `Archive change ${input.changeId} after verifying completion and risks.`;
|
|
3906
|
+
case "sync": return `Sync specs for change ${input.changeId}.`;
|
|
3907
|
+
case "verify": return `Verify change ${input.changeId}.`;
|
|
3908
|
+
case "bulk-archive": return `Archive completed changes${input.changeIds?.length ? `: ${input.changeIds.join(", ")}` : ""}.`;
|
|
3909
|
+
case "explore":
|
|
3910
|
+
case "propose": return buildProposeComposePrompt(input.text);
|
|
3911
|
+
case "new": return `Create OpenSpec change ${input.changeId}.`;
|
|
3912
|
+
case "onboard": return "Start OpenSpec onboarding for this project.";
|
|
3913
|
+
}
|
|
3914
|
+
}
|
|
3915
|
+
function buildArchivePrompt(changeId, statusText) {
|
|
3916
|
+
const normalized = statusText.trim();
|
|
3917
|
+
return [
|
|
3918
|
+
`Archive planning for change "${changeId}".`,
|
|
3919
|
+
"",
|
|
3920
|
+
"Current openspec status:",
|
|
3921
|
+
"```text",
|
|
3922
|
+
normalized.length > 0 ? normalized : "(no status output)",
|
|
3923
|
+
"```",
|
|
3924
|
+
"",
|
|
3925
|
+
"Please confirm archive readiness, highlight risks, and provide the exact next steps."
|
|
3926
|
+
].join("\n");
|
|
3927
|
+
}
|
|
3928
|
+
var WorkflowInvocationService = class {
|
|
3929
|
+
constructor(options) {
|
|
3930
|
+
this.options = options;
|
|
3931
|
+
}
|
|
3932
|
+
async runWorkflow(input, requestedMode, signal = new AbortController().signal) {
|
|
3933
|
+
const mode = resolveInvocationMode(input.action, requestedMode);
|
|
3934
|
+
const run = () => this.runDefault(input, mode);
|
|
3935
|
+
const hooks = await this.options.hookRuntime.load();
|
|
3936
|
+
if (!hooks.onRunWorkflow) return run();
|
|
3937
|
+
try {
|
|
3938
|
+
return await hooks.onRunWorkflow({
|
|
3939
|
+
version: OPENSPECUI_HOOKS_VERSION,
|
|
3940
|
+
projectDir: this.options.projectDir,
|
|
3941
|
+
action: input.action,
|
|
3942
|
+
requestedMode,
|
|
3943
|
+
input,
|
|
3944
|
+
signal,
|
|
3945
|
+
lifecycle: this.options.hookRuntime
|
|
3946
|
+
}, run);
|
|
3947
|
+
} catch (error) {
|
|
3948
|
+
return withDiagnostics(await run(), [toErrorDiagnostic(error)]);
|
|
3949
|
+
}
|
|
3950
|
+
}
|
|
3951
|
+
async runDefault(input, mode) {
|
|
3952
|
+
if (mode.actualMode === "command") {
|
|
3953
|
+
const text = buildSlashCommand(input);
|
|
3954
|
+
if (text) return {
|
|
3955
|
+
kind: "agent-command",
|
|
3956
|
+
text,
|
|
3957
|
+
mode
|
|
3958
|
+
};
|
|
3959
|
+
}
|
|
3960
|
+
if (input.action === "new") {
|
|
3961
|
+
const args = [
|
|
3962
|
+
"new",
|
|
3963
|
+
"change",
|
|
3964
|
+
input.changeId.trim()
|
|
3965
|
+
];
|
|
3966
|
+
const schema = input.schema?.trim();
|
|
3967
|
+
const description = input.description?.trim();
|
|
3968
|
+
if (schema) args.push("--schema", schema);
|
|
3969
|
+
if (description) args.push("--description", description);
|
|
3970
|
+
args.push(...input.extraArgs.map((arg) => arg.trim()).filter((arg) => arg.length > 0));
|
|
3971
|
+
return {
|
|
3972
|
+
kind: "cli-command",
|
|
3973
|
+
command: "openspec",
|
|
3974
|
+
args,
|
|
3975
|
+
mode
|
|
3976
|
+
};
|
|
3977
|
+
}
|
|
3978
|
+
if (input.action === "verify") {
|
|
3979
|
+
const args = [
|
|
3980
|
+
"validate",
|
|
3981
|
+
input.changeId,
|
|
3982
|
+
"--type",
|
|
3983
|
+
"change"
|
|
3984
|
+
];
|
|
3985
|
+
if (input.strict) args.push("--strict");
|
|
3986
|
+
return {
|
|
3987
|
+
kind: "cli-command",
|
|
3988
|
+
command: "openspec",
|
|
3989
|
+
args,
|
|
3990
|
+
mode
|
|
3991
|
+
};
|
|
3992
|
+
}
|
|
3993
|
+
if (input.action === "propose" || input.action === "explore") return {
|
|
3994
|
+
kind: "agent-prompt",
|
|
3995
|
+
text: buildProposeComposePrompt(input.text),
|
|
3996
|
+
format: "markdown",
|
|
3997
|
+
mode
|
|
3998
|
+
};
|
|
3999
|
+
const executeCli = this.options.executeCli;
|
|
4000
|
+
if (executeCli && (input.action === "continue" || input.action === "ff" || input.action === "apply" || input.action === "archive")) {
|
|
4001
|
+
if ((input.action === "continue" || input.action === "ff") && !input.artifactId.trim()) return {
|
|
4002
|
+
kind: "agent-prompt",
|
|
4003
|
+
text: buildFallbackPrompt(input),
|
|
4004
|
+
format: "markdown",
|
|
4005
|
+
mode,
|
|
4006
|
+
diagnostics: [{
|
|
4007
|
+
level: "warning",
|
|
4008
|
+
message: "Artifact id is required for this action."
|
|
4009
|
+
}]
|
|
4010
|
+
};
|
|
4011
|
+
const captured = await captureCliText(executeCli, input.action === "continue" || input.action === "ff" ? [
|
|
4012
|
+
"instructions",
|
|
4013
|
+
input.artifactId,
|
|
4014
|
+
"--change",
|
|
4015
|
+
input.changeId
|
|
4016
|
+
] : input.action === "apply" ? [
|
|
4017
|
+
"instructions",
|
|
4018
|
+
"apply",
|
|
4019
|
+
"--change",
|
|
4020
|
+
input.changeId
|
|
4021
|
+
] : [
|
|
4022
|
+
"status",
|
|
4023
|
+
"--change",
|
|
4024
|
+
input.changeId
|
|
4025
|
+
], buildFallbackPrompt(input));
|
|
4026
|
+
return {
|
|
4027
|
+
kind: "agent-prompt",
|
|
4028
|
+
text: input.action === "archive" ? buildArchivePrompt(input.changeId, captured.text) : captured.text,
|
|
4029
|
+
format: "markdown",
|
|
4030
|
+
mode,
|
|
4031
|
+
diagnostics: captured.diagnostics
|
|
4032
|
+
};
|
|
4033
|
+
}
|
|
4034
|
+
return {
|
|
4035
|
+
kind: "agent-prompt",
|
|
4036
|
+
text: buildFallbackPrompt(input),
|
|
4037
|
+
format: "markdown",
|
|
4038
|
+
mode
|
|
4039
|
+
};
|
|
4040
|
+
}
|
|
4041
|
+
};
|
|
4042
|
+
|
|
3481
4043
|
//#endregion
|
|
3482
4044
|
//#region src/server.ts
|
|
3483
4045
|
/**
|
|
@@ -3514,8 +4076,15 @@ function createServer(config) {
|
|
|
3514
4076
|
const configManager = new ConfigManager(config.projectDir);
|
|
3515
4077
|
const cliExecutor = new CliExecutor(configManager, config.projectDir);
|
|
3516
4078
|
const kernel = config.kernel;
|
|
4079
|
+
const hookRuntime = createHookRuntime(config.projectDir);
|
|
4080
|
+
const documentService = new DocumentService(config.projectDir, adapter, hookRuntime);
|
|
4081
|
+
const workflowInvocationService = new WorkflowInvocationService({
|
|
4082
|
+
projectDir: config.projectDir,
|
|
4083
|
+
hookRuntime,
|
|
4084
|
+
executeCli: (args) => cliExecutor.execute(args)
|
|
4085
|
+
});
|
|
3517
4086
|
const watcher = config.enableWatcher !== false ? new OpenSpecWatcher(config.projectDir) : void 0;
|
|
3518
|
-
const searchService = new SearchService(adapter, watcher);
|
|
4087
|
+
const searchService = new SearchService(adapter, watcher, void 0, documentService);
|
|
3519
4088
|
const dashboardOverviewService = new DashboardOverviewService((reason) => loadDashboardOverview({
|
|
3520
4089
|
adapter,
|
|
3521
4090
|
configManager,
|
|
@@ -3550,8 +4119,10 @@ function createServer(config) {
|
|
|
3550
4119
|
createContext: () => ({
|
|
3551
4120
|
adapter,
|
|
3552
4121
|
configManager,
|
|
4122
|
+
documentService,
|
|
3553
4123
|
cliExecutor,
|
|
3554
4124
|
kernel,
|
|
4125
|
+
workflowInvocationService,
|
|
3555
4126
|
searchService,
|
|
3556
4127
|
dashboardOverviewService,
|
|
3557
4128
|
projectRecoveryService,
|
|
@@ -3564,8 +4135,10 @@ function createServer(config) {
|
|
|
3564
4135
|
const createContext = () => ({
|
|
3565
4136
|
adapter,
|
|
3566
4137
|
configManager,
|
|
4138
|
+
documentService,
|
|
3567
4139
|
cliExecutor,
|
|
3568
4140
|
kernel,
|
|
4141
|
+
workflowInvocationService,
|
|
3569
4142
|
searchService,
|
|
3570
4143
|
dashboardOverviewService,
|
|
3571
4144
|
projectRecoveryService,
|
|
@@ -3577,11 +4150,14 @@ function createServer(config) {
|
|
|
3577
4150
|
app,
|
|
3578
4151
|
adapter,
|
|
3579
4152
|
configManager,
|
|
4153
|
+
documentService,
|
|
3580
4154
|
cliExecutor,
|
|
3581
4155
|
kernel,
|
|
4156
|
+
workflowInvocationService,
|
|
3582
4157
|
searchService,
|
|
3583
4158
|
dashboardOverviewService,
|
|
3584
4159
|
projectRecoveryService,
|
|
4160
|
+
hookRuntime,
|
|
3585
4161
|
watcher,
|
|
3586
4162
|
createContext,
|
|
3587
4163
|
port: config.port ?? 3100
|
|
@@ -3675,6 +4251,7 @@ async function startServer(config, setupApp) {
|
|
|
3675
4251
|
preferredPort,
|
|
3676
4252
|
close: async () => {
|
|
3677
4253
|
kernel.dispose();
|
|
4254
|
+
await server.hookRuntime.dispose();
|
|
3678
4255
|
wsServer.close();
|
|
3679
4256
|
httpServer.close();
|
|
3680
4257
|
}
|
|
@@ -3682,4 +4259,4 @@ async function startServer(config, setupApp) {
|
|
|
3682
4259
|
}
|
|
3683
4260
|
|
|
3684
4261
|
//#endregion
|
|
3685
|
-
export { createServer, createWebSocketServer, findAvailablePort, isPortAvailable, startServer };
|
|
4262
|
+
export { DocumentService, OPENSPECUI_HOOKS_RELATIVE_PATH, ProjectHookRuntime, WorkflowInvocationService, createHookRuntime, createServer, createWebSocketServer, findAvailablePort, isPortAvailable, startServer };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openspecui/server",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.4.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.mjs",
|
|
6
6
|
"exports": {
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
"@openspecui/search": "workspace:*",
|
|
26
26
|
"@trpc/server": "^11.0.0",
|
|
27
27
|
"hono": "^4.7.3",
|
|
28
|
+
"tsx": "^4.19.2",
|
|
28
29
|
"ws": "^8.18.0",
|
|
29
30
|
"yaml": "^2.8.0",
|
|
30
31
|
"yargs": "^18.0.0",
|
|
@@ -35,7 +36,6 @@
|
|
|
35
36
|
"@types/ws": "^8.5.13",
|
|
36
37
|
"@types/yargs": "^17.0.35",
|
|
37
38
|
"tsdown": "^0.16.6",
|
|
38
|
-
"tsx": "^4.19.2",
|
|
39
39
|
"typescript": "^5.7.2",
|
|
40
40
|
"vitest": "^4.1.0"
|
|
41
41
|
},
|