@documentation-robotics/cli 0.1.7 → 0.1.9
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/README.md +18 -17
- package/dist/audit/nodes/ai/prompts.js +1 -0
- package/dist/audit/nodes/ai/prompts.js.map +2 -2
- package/dist/audit/relationships/ai/prompts.js +20 -29
- package/dist/audit/relationships/ai/prompts.js.map +2 -2
- package/dist/audit/relationships/analysis/gap-analyzer.js.map +1 -1
- package/dist/audit/relationships/analysis/layer-templates.js +36 -0
- package/dist/audit/relationships/analysis/layer-templates.js.map +2 -2
- package/dist/cli.js +8 -8
- package/dist/cli.js.map +1 -1
- package/dist/commands/reports.js +3 -3
- package/dist/commands/reports.js.map +1 -1
- package/dist/commands/show.js.map +1 -1
- package/dist/commands/upgrade.js +1 -1
- package/dist/commands/upgrade.js.map +1 -1
- package/dist/commands/validate.js.map +1 -1
- package/dist/commands/version.js +1 -1
- package/dist/commands/visualize.js +2 -8
- package/dist/commands/visualize.js.map +2 -2
- package/dist/core/layers.js +1 -0
- package/dist/core/layers.js.map +2 -2
- package/dist/core/migration-registry.js +59 -2
- package/dist/core/migration-registry.js.map +2 -2
- package/dist/core/model.js +3 -16
- package/dist/core/model.js.map +2 -2
- package/dist/core/spec-loader.js.map +1 -1
- package/dist/core/stats-collector.js +2 -0
- package/dist/core/stats-collector.js.map +2 -2
- package/dist/export/archimate-exporter.js.map +2 -2
- package/dist/export/json-schema-exporter.js.map +1 -1
- package/dist/export/markdown-exporter.js.map +1 -1
- package/dist/export/markdown-utils.js +1 -0
- package/dist/export/markdown-utils.js.map +2 -2
- package/dist/export/mermaid-markdown-exporter.js.map +1 -1
- package/dist/export/openapi-exporter.js.map +1 -1
- package/dist/export/plantuml-exporter.js.map +1 -1
- package/dist/export/report-formatters.js +2 -2
- package/dist/export/report-formatters.js.map +1 -1
- package/dist/export/types.js +1 -0
- package/dist/export/types.js.map +2 -2
- package/dist/generated/layer-registry.js +44 -31
- package/dist/generated/layer-registry.js.map +2 -2
- package/dist/generated/layer-types.js +1 -1
- package/dist/generated/layer-types.js.map +2 -2
- package/dist/generated/node-types.js +45 -0
- package/dist/generated/node-types.js.map +2 -2
- package/dist/generated/relationship-index.js +201 -19
- package/dist/generated/relationship-index.js.map +2 -2
- package/dist/import/archimate-importer.js.map +1 -1
- package/dist/import/openapi-importer.js.map +1 -1
- package/dist/integrations/claude_code/agents/dr-advisor.md +14 -13
- package/dist/integrations/claude_code/agents/dr-extractor.md +2 -2
- package/dist/integrations/claude_code/commands/dr-info.md +1 -1
- package/dist/integrations/claude_code/commands/dr-init.md +3 -3
- package/dist/integrations/claude_code/commands/dr-map.md +2 -2
- package/dist/integrations/claude_code/commands/dr-model.md +1 -1
- package/dist/integrations/claude_code/commands/dr-relate.md +14 -4
- package/dist/integrations/claude_code/docs/END_TO_END_WALKTHROUGH.md +3 -3
- package/dist/integrations/claude_code/docs/QUICK_START.md +1 -1
- package/dist/integrations/claude_code/docs/README.md +2 -2
- package/dist/integrations/claude_code/skills/dr_01_motivation_layer/SKILL.md +2 -2
- package/dist/integrations/claude_code/skills/dr_03_product_layer/SKILL.md +679 -0
- package/dist/integrations/claude_code/skills/{dr_08_dataset_layer → dr_09_dataset_layer}/SKILL.md +1 -1
- package/dist/integrations/claude_code/skills/dr_engineering_guide/SKILL.md +2 -2
- package/dist/integrations/github_copilot/agents/dr-advisor.md +14 -13
- package/dist/integrations/github_copilot/agents/dr-architect.md +15 -14
- package/dist/integrations/github_copilot/skills/dr_01_motivation_layer/SKILL.md +2 -2
- package/dist/integrations/github_copilot/skills/dr_03_product_layer/SKILL.md +253 -0
- package/dist/reports/model-report-orchestrator.js +3 -3
- package/dist/reports/model-report-orchestrator.js.map +1 -1
- package/dist/schemas/bundled/analyzers/cbm.json +2 -2
- package/dist/schemas/bundled/analyzers/manifest.json +2 -2
- package/dist/schemas/bundled/api.json +3 -3
- package/dist/schemas/bundled/apm.json +3 -3
- package/dist/schemas/bundled/application.json +3 -3
- package/dist/schemas/bundled/base.json +29 -1
- package/dist/schemas/bundled/business.json +1 -1
- package/dist/schemas/bundled/data-model.json +3 -3
- package/dist/schemas/bundled/data-store.json +3 -3
- package/dist/schemas/bundled/manifest.json +20 -13
- package/dist/schemas/bundled/motivation.json +1 -1
- package/dist/schemas/bundled/navigation.json +3 -3
- package/dist/schemas/bundled/product.json +469 -0
- package/dist/schemas/bundled/security.json +3 -3
- package/dist/schemas/bundled/technology.json +3 -3
- package/dist/schemas/bundled/testing.json +3 -3
- package/dist/schemas/bundled/ux.json +3 -3
- package/dist/server/server.js +148 -101
- package/dist/server/server.js.map +2 -2
- package/dist/server-entry.js.map +2 -2
- package/dist/telemetry/index.js +1 -1
- package/dist/utils/spec-installer.js +2 -2
- package/dist/utils/spec-installer.js.map +1 -1
- package/dist/utils/spec-version.js +1 -1
- package/dist/utils/spec-version.js.map +1 -1
- package/dist/viewer/README.md +6 -6
- package/dist/viewer/assets/main-D114QwJo.css +1 -0
- package/dist/viewer/assets/main-DyuokeQj.js +81 -0
- package/dist/viewer/index.html +2 -2
- package/dist/viewer/manifest.json +10 -10
- package/dist/viewer/version.json +1 -1
- package/package.json +4 -1
- package/dist/viewer/assets/main-CY0Xufuj.css +0 -1
- package/dist/viewer/assets/main-DtgErnwF.js +0 -81
- /package/dist/integrations/claude_code/skills/{dr_03_security_layer → dr_04_security_layer}/SKILL.md +0 -0
- /package/dist/integrations/claude_code/skills/{dr_04_application_layer → dr_05_application_layer}/SKILL.md +0 -0
- /package/dist/integrations/claude_code/skills/{dr_05_technology_layer → dr_06_technology_layer}/SKILL.md +0 -0
- /package/dist/integrations/claude_code/skills/{dr_06_api_layer → dr_07_api_layer}/SKILL.md +0 -0
- /package/dist/integrations/claude_code/skills/{dr_07_data_model_layer → dr_08_data_model_layer}/SKILL.md +0 -0
- /package/dist/integrations/claude_code/skills/{dr_09_ux_layer → dr_10_ux_layer}/SKILL.md +0 -0
- /package/dist/integrations/claude_code/skills/{dr_10_navigation_layer → dr_11_navigation_layer}/SKILL.md +0 -0
- /package/dist/integrations/claude_code/skills/{dr_11_apm_layer → dr_12_apm_layer}/SKILL.md +0 -0
- /package/dist/integrations/claude_code/skills/{dr_12_testing_layer → dr_13_testing_layer}/SKILL.md +0 -0
- /package/dist/integrations/github_copilot/skills/{dr_03_security_layer → dr_04_security_layer}/SKILL.md +0 -0
- /package/dist/integrations/github_copilot/skills/{dr_04_application_layer → dr_05_application_layer}/SKILL.md +0 -0
- /package/dist/integrations/github_copilot/skills/{dr_05_technology_layer → dr_06_technology_layer}/SKILL.md +0 -0
- /package/dist/integrations/github_copilot/skills/{dr_06_api_layer → dr_07_api_layer}/SKILL.md +0 -0
- /package/dist/integrations/github_copilot/skills/{dr_07_data_model_layer → dr_08_data_model_layer}/SKILL.md +0 -0
- /package/dist/integrations/github_copilot/skills/{dr_08_dataset_layer → dr_09_dataset_layer}/SKILL.md +0 -0
- /package/dist/integrations/github_copilot/skills/{dr_09_ux_layer → dr_10_ux_layer}/SKILL.md +0 -0
- /package/dist/integrations/github_copilot/skills/{dr_10_navigation_layer → dr_11_navigation_layer}/SKILL.md +0 -0
- /package/dist/integrations/github_copilot/skills/{dr_11_apm_layer → dr_12_apm_layer}/SKILL.md +0 -0
- /package/dist/integrations/github_copilot/skills/{dr_12_testing_layer → dr_13_testing_layer}/SKILL.md +0 -0
package/dist/server/server.js
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
import { OpenAPIHono, createRoute, z } from "@hono/zod-openapi";
|
|
2
2
|
import { cors } from "hono/cors";
|
|
3
3
|
import { swaggerUI } from "@hono/swagger-ui";
|
|
4
|
+
import { serve } from "@hono/node-server";
|
|
5
|
+
import { createNodeWebSocket } from "@hono/node-ws";
|
|
6
|
+
import { spawn, spawnSync } from "child_process";
|
|
7
|
+
import chokidar from "chokidar";
|
|
4
8
|
import { randomBytes } from "crypto";
|
|
5
9
|
import { createRequire } from "module";
|
|
6
10
|
import { fileURLToPath } from "url";
|
|
7
11
|
import path from "path";
|
|
8
|
-
let upgradeWebSocket, websocket;
|
|
9
12
|
const require2 = createRequire(import.meta.url);
|
|
10
13
|
import { Model } from "../core/model.js";
|
|
11
14
|
import { StagedChangesetStorage } from "../core/staged-changeset-storage.js";
|
|
15
|
+
import { CANONICAL_LAYER_NAMES } from "../core/layers.js";
|
|
12
16
|
import { telemetryMiddleware } from "./telemetry-middleware.js";
|
|
13
17
|
import { ClaudeCodeClient } from "../coding-agents/claude-code-client.js";
|
|
14
18
|
import { CopilotClient } from "../coding-agents/copilot-client.js";
|
|
@@ -31,7 +35,9 @@ class VisualizationServer {
|
|
|
31
35
|
app;
|
|
32
36
|
model;
|
|
33
37
|
_server;
|
|
34
|
-
//
|
|
38
|
+
// Node HTTP server returned by @hono/node-server
|
|
39
|
+
upgradeWebSocket;
|
|
40
|
+
injectWebSocket;
|
|
35
41
|
clients = /* @__PURE__ */ new Set();
|
|
36
42
|
watcher;
|
|
37
43
|
annotations = /* @__PURE__ */ new Map();
|
|
@@ -50,7 +56,7 @@ class VisualizationServer {
|
|
|
50
56
|
viewerPath;
|
|
51
57
|
// Optional custom viewer path
|
|
52
58
|
activeChatProcesses = /* @__PURE__ */ new Map();
|
|
53
|
-
// conversationId ->
|
|
59
|
+
// conversationId -> spawned chat process
|
|
54
60
|
chatConversationCounter = 0;
|
|
55
61
|
selectedChatClient;
|
|
56
62
|
// Selected chat client for server
|
|
@@ -70,21 +76,8 @@ class VisualizationServer {
|
|
|
70
76
|
* If any file cannot be loaded, falls back to a minimal safe set
|
|
71
77
|
*/
|
|
72
78
|
static loadValidSpecNodeIds() {
|
|
73
|
-
const result = {},
|
|
74
|
-
|
|
75
|
-
"business",
|
|
76
|
-
"security",
|
|
77
|
-
"application",
|
|
78
|
-
"technology",
|
|
79
|
-
"api",
|
|
80
|
-
"data-model",
|
|
81
|
-
"data-store",
|
|
82
|
-
"ux",
|
|
83
|
-
"navigation",
|
|
84
|
-
"apm",
|
|
85
|
-
"testing"
|
|
86
|
-
], __dirname = path.dirname(fileURLToPath(import.meta.url)), bundledDir = path.join(__dirname, "..", "schemas", "bundled");
|
|
87
|
-
for (const layer of requiredLayers)
|
|
79
|
+
const result = {}, __dirname = path.dirname(fileURLToPath(import.meta.url)), bundledDir = path.join(__dirname, "..", "schemas", "bundled");
|
|
80
|
+
for (const layer of CANONICAL_LAYER_NAMES)
|
|
88
81
|
try {
|
|
89
82
|
const specFilePath = path.join(bundledDir, `${layer}.json`), nodeTypes = require2(specFilePath).layer?.node_types;
|
|
90
83
|
Array.isArray(nodeTypes) && nodeTypes.length > 0 ? result[layer] = nodeTypes : (console.warn(`[SPEC-LOAD-001] Layer ${layer} has no node_types in spec file`), result[layer] = []);
|
|
@@ -99,6 +92,7 @@ class VisualizationServer {
|
|
|
99
92
|
* or found through other resolution strategies
|
|
100
93
|
*/
|
|
101
94
|
static LAYER_FALLBACK = {
|
|
95
|
+
product: "business.businessservice",
|
|
102
96
|
security: "business.businessprocess",
|
|
103
97
|
technology: "application.applicationservice",
|
|
104
98
|
api: "business.businessprocess",
|
|
@@ -119,7 +113,7 @@ class VisualizationServer {
|
|
|
119
113
|
}), ids;
|
|
120
114
|
})();
|
|
121
115
|
constructor(model, options) {
|
|
122
|
-
this.app = new OpenAPIHono(), this.model = model, this.
|
|
116
|
+
this.app = new OpenAPIHono(), this.model = model, this.loadWebSocketAdapter(), this.authEnabled = options?.authEnabled ?? process.env.DR_AUTH_ENABLED !== "false", this.authToken = options?.authToken || process.env.DR_AUTH_TOKEN || this.generateAuthToken(), this.withDanger = options?.withDanger || !1, this.viewerPath = options?.viewerPath || new URL("../viewer", import.meta.url).pathname, !this.authEnabled && options?.authToken && console.warn("[VisualizationServer] Warning: authToken provided but authEnabled is false. The token will be generated but not used for authentication. Set authEnabled: true to enable authentication."), this.app.use("/*", cors()), this.app.use("/*", telemetryMiddleware), this.app.use("/api/*", async (c, next) => {
|
|
123
117
|
if (!this.authEnabled)
|
|
124
118
|
return next();
|
|
125
119
|
const authHeader = c.req.header("Authorization"), queryToken = c.req.query("token");
|
|
@@ -130,19 +124,21 @@ class VisualizationServer {
|
|
|
130
124
|
});
|
|
131
125
|
}
|
|
132
126
|
/**
|
|
133
|
-
*
|
|
134
|
-
* This is called in the constructor to support WebSocket functionality
|
|
135
|
-
*
|
|
127
|
+
* Set up the WebSocket adapter for this server instance
|
|
128
|
+
* This is called in the constructor to support WebSocket functionality via @hono/node-ws,
|
|
129
|
+
* which works identically under Node.js and Bun (both implement Node's http module).
|
|
130
|
+
* Wrapped in try/catch as a defensive guard so a WS setup failure would degrade the
|
|
131
|
+
* server gracefully (no /ws route) rather than crash construction entirely, in case
|
|
132
|
+
* this ever becomes fallible again in the future.
|
|
136
133
|
*/
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
}
|
|
134
|
+
loadWebSocketAdapter() {
|
|
135
|
+
try {
|
|
136
|
+
const { injectWebSocket, upgradeWebSocket } = createNodeWebSocket({ app: this.app });
|
|
137
|
+
this.injectWebSocket = injectWebSocket, this.upgradeWebSocket = upgradeWebSocket;
|
|
138
|
+
} catch (error) {
|
|
139
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
140
|
+
console.warn(`[Server] WARNING: Could not set up WebSocket adapter: ${message}. WebSocket endpoints (/ws) will not be available.`), process.env.VERBOSE && error instanceof Error && error.stack && console.debug("[Server] Stack trace:", error.stack);
|
|
141
|
+
}
|
|
146
142
|
}
|
|
147
143
|
/**
|
|
148
144
|
* Detect and initialize available chat clients
|
|
@@ -858,7 +854,7 @@ class VisualizationServer {
|
|
|
858
854
|
{ name: "Annotations", description: "User annotations on model elements" },
|
|
859
855
|
{ name: "WebSocket", description: "Real-time updates via WebSocket" }
|
|
860
856
|
]
|
|
861
|
-
}), this.app.get("/api-docs", swaggerUI({ url: "/api-spec.yaml" })), upgradeWebSocket && this.app.get("/ws", async (c, next) => {
|
|
857
|
+
}), this.app.get("/api-docs", swaggerUI({ url: "/api-spec.yaml" })), this.upgradeWebSocket && this.app.get("/ws", async (c, next) => {
|
|
862
858
|
if (this.authEnabled) {
|
|
863
859
|
const queryToken = c.req.query("token"), wsProtocol = c.req.header("Sec-WebSocket-Protocol");
|
|
864
860
|
let protocolToken = null;
|
|
@@ -876,7 +872,7 @@ class VisualizationServer {
|
|
|
876
872
|
protocolToken && c.header("Sec-WebSocket-Protocol", "token");
|
|
877
873
|
}
|
|
878
874
|
return next();
|
|
879
|
-
}, upgradeWebSocket(() => ({
|
|
875
|
+
}, this.upgradeWebSocket(() => ({
|
|
880
876
|
onOpen: async (_evt, ws) => {
|
|
881
877
|
await this.recordWebSocketEvent("ws.connection.open", {
|
|
882
878
|
"ws.client_count": this.clients.size + 1
|
|
@@ -1274,23 +1270,33 @@ class VisualizationServer {
|
|
|
1274
1270
|
try {
|
|
1275
1271
|
const cmd = ["claude", "--agent", "dr-architect", "--print"];
|
|
1276
1272
|
this.withDanger && cmd.push("--dangerously-skip-permissions"), cmd.push("--verbose", "--output-format", "stream-json");
|
|
1277
|
-
const proc =
|
|
1278
|
-
cmd,
|
|
1273
|
+
const proc = spawn(cmd[0], cmd.slice(1), {
|
|
1279
1274
|
cwd: this.model.rootPath,
|
|
1280
|
-
|
|
1281
|
-
stdout: "pipe",
|
|
1282
|
-
stderr: "pipe"
|
|
1275
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
1283
1276
|
});
|
|
1284
|
-
|
|
1285
|
-
|
|
1277
|
+
proc.on("error", (err) => {
|
|
1278
|
+
const errorMsg = getErrorMessage(err);
|
|
1279
|
+
console.error(`[Claude Code] Failed to spawn subprocess for conversation ${conversationId}: ${errorMsg}`), this.activeChatProcesses.delete(conversationId);
|
|
1280
|
+
try {
|
|
1281
|
+
ws.send(JSON.stringify({
|
|
1282
|
+
jsonrpc: "2.0",
|
|
1283
|
+
error: {
|
|
1284
|
+
code: JSONRPC_ERRORS.INTERNAL_ERROR,
|
|
1285
|
+
message: `Failed to launch Claude Code: ${errorMsg}`
|
|
1286
|
+
},
|
|
1287
|
+
id: requestId
|
|
1288
|
+
}));
|
|
1289
|
+
} catch (sendError) {
|
|
1290
|
+
console.error(`[Claude Code] Failed to send spawn-error notification: ${getErrorMessage(sendError)}`);
|
|
1291
|
+
}
|
|
1292
|
+
}), proc.stdin?.on("error", () => {
|
|
1293
|
+
}), this.activeChatProcesses.set(conversationId, proc), proc.stdin?.write(message), proc.stdin?.end();
|
|
1294
|
+
const decoder = new TextDecoder();
|
|
1286
1295
|
(async () => {
|
|
1287
1296
|
let accumulatedText = "", buffer = "";
|
|
1288
1297
|
try {
|
|
1289
|
-
for (
|
|
1290
|
-
const
|
|
1291
|
-
if (done)
|
|
1292
|
-
break;
|
|
1293
|
-
const chunk = decoder.decode(value, { stream: !0 });
|
|
1298
|
+
for await (const chunkBuf of proc.stdout) {
|
|
1299
|
+
const chunk = decoder.decode(chunkBuf, { stream: !0 });
|
|
1294
1300
|
buffer += chunk;
|
|
1295
1301
|
const lines = buffer.split(`
|
|
1296
1302
|
`);
|
|
@@ -1354,7 +1360,7 @@ class VisualizationServer {
|
|
|
1354
1360
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1355
1361
|
}
|
|
1356
1362
|
}));
|
|
1357
|
-
const exitCode = await proc
|
|
1363
|
+
const exitCode = await this.waitForExit(proc);
|
|
1358
1364
|
this.activeChatProcesses.delete(conversationId), ws.send(JSON.stringify({
|
|
1359
1365
|
jsonrpc: "2.0",
|
|
1360
1366
|
result: {
|
|
@@ -1367,6 +1373,8 @@ class VisualizationServer {
|
|
|
1367
1373
|
id: requestId
|
|
1368
1374
|
}));
|
|
1369
1375
|
} catch (error) {
|
|
1376
|
+
if (!proc.pid)
|
|
1377
|
+
return;
|
|
1370
1378
|
const errorMsg = getErrorMessage(error);
|
|
1371
1379
|
ws.send(JSON.stringify({
|
|
1372
1380
|
jsonrpc: "2.0",
|
|
@@ -1416,28 +1424,37 @@ class VisualizationServer {
|
|
|
1416
1424
|
async launchCopilotChat(ws, conversationId, message, requestId) {
|
|
1417
1425
|
try {
|
|
1418
1426
|
let cmd;
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
const proc = Bun.spawn({
|
|
1425
|
-
cmd,
|
|
1427
|
+
const ghResult = spawnSync("gh", ["copilot", "--version"], {
|
|
1428
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
1429
|
+
});
|
|
1430
|
+
ghResult.error ? console.warn(`[Copilot] 'gh copilot --version' probe failed to spawn: ${getErrorMessage(ghResult.error)}`) : ghResult.status !== 0 && process.env.VERBOSE && console.log(`[Copilot] 'gh copilot' unavailable (exit ${ghResult.status}), falling back to standalone 'copilot'`), ghResult.status === 0 ? (cmd = ["gh", "copilot", "explain"], this.withDanger && cmd.push("--allow-all-tools"), cmd.push(message)) : (cmd = ["copilot", "explain"], this.withDanger && cmd.push("--allow-all-tools"), cmd.push(message));
|
|
1431
|
+
const proc = spawn(cmd[0], cmd.slice(1), {
|
|
1426
1432
|
cwd: this.model.rootPath,
|
|
1427
|
-
|
|
1428
|
-
stdout: "pipe",
|
|
1429
|
-
stderr: "pipe"
|
|
1433
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
1430
1434
|
});
|
|
1431
|
-
|
|
1432
|
-
|
|
1435
|
+
proc.on("error", (err) => {
|
|
1436
|
+
const errorMsg = getErrorMessage(err);
|
|
1437
|
+
console.error(`[Copilot] Failed to spawn subprocess for conversation ${conversationId}: ${errorMsg}`), this.activeChatProcesses.delete(conversationId);
|
|
1438
|
+
try {
|
|
1439
|
+
ws.send(JSON.stringify({
|
|
1440
|
+
jsonrpc: "2.0",
|
|
1441
|
+
error: {
|
|
1442
|
+
code: JSONRPC_ERRORS.INTERNAL_ERROR,
|
|
1443
|
+
message: `Failed to launch GitHub Copilot: ${errorMsg}`
|
|
1444
|
+
},
|
|
1445
|
+
id: requestId
|
|
1446
|
+
}));
|
|
1447
|
+
} catch (sendError) {
|
|
1448
|
+
console.error(`[Copilot] Failed to send spawn-error notification: ${getErrorMessage(sendError)}`);
|
|
1449
|
+
}
|
|
1450
|
+
}), proc.stdin?.on("error", () => {
|
|
1451
|
+
}), this.activeChatProcesses.set(conversationId, proc);
|
|
1452
|
+
const decoder = new TextDecoder();
|
|
1433
1453
|
(async () => {
|
|
1434
1454
|
let accumulatedText = "";
|
|
1435
1455
|
try {
|
|
1436
|
-
for (
|
|
1437
|
-
const
|
|
1438
|
-
if (done)
|
|
1439
|
-
break;
|
|
1440
|
-
const chunk = decoder.decode(value, { stream: !0 });
|
|
1456
|
+
for await (const chunkBuf of proc.stdout) {
|
|
1457
|
+
const chunk = decoder.decode(chunkBuf, { stream: !0 });
|
|
1441
1458
|
accumulatedText += chunk, ws.send(JSON.stringify({
|
|
1442
1459
|
jsonrpc: "2.0",
|
|
1443
1460
|
method: "chat.response.chunk",
|
|
@@ -1449,7 +1466,7 @@ class VisualizationServer {
|
|
|
1449
1466
|
}
|
|
1450
1467
|
}));
|
|
1451
1468
|
}
|
|
1452
|
-
const exitCode = await proc
|
|
1469
|
+
const exitCode = await this.waitForExit(proc);
|
|
1453
1470
|
this.activeChatProcesses.delete(conversationId), ws.send(JSON.stringify({
|
|
1454
1471
|
jsonrpc: "2.0",
|
|
1455
1472
|
result: {
|
|
@@ -1462,6 +1479,8 @@ class VisualizationServer {
|
|
|
1462
1479
|
id: requestId
|
|
1463
1480
|
}));
|
|
1464
1481
|
} catch (error) {
|
|
1482
|
+
if (!proc.pid)
|
|
1483
|
+
return;
|
|
1465
1484
|
const errorMsg = getErrorMessage(error);
|
|
1466
1485
|
ws.send(JSON.stringify({
|
|
1467
1486
|
jsonrpc: "2.0",
|
|
@@ -1496,35 +1515,33 @@ class VisualizationServer {
|
|
|
1496
1515
|
* Setup file watcher for model changes
|
|
1497
1516
|
*/
|
|
1498
1517
|
setupFileWatcher() {
|
|
1499
|
-
const modelPath = `${this.model.rootPath}/documentation-robotics/model
|
|
1500
|
-
if (!bunWatch) {
|
|
1501
|
-
console.warn("[Watcher] Bun.watch not available, file watching disabled");
|
|
1502
|
-
return;
|
|
1503
|
-
}
|
|
1518
|
+
const modelPath = `${this.model.rootPath}/documentation-robotics/model`;
|
|
1504
1519
|
try {
|
|
1505
|
-
this.watcher =
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
}
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
}
|
|
1520
|
+
this.watcher = chokidar.watch(modelPath, {
|
|
1521
|
+
persistent: !0,
|
|
1522
|
+
ignoreInitial: !0
|
|
1523
|
+
}), this.watcher.on("all", async (_event, path2) => {
|
|
1524
|
+
process.env.VERBOSE && console.log(`[Watcher] File changed: ${path2}`);
|
|
1525
|
+
try {
|
|
1526
|
+
this.model = await Model.load(this.model.rootPath, { lazyLoad: !1 }), await this.loadChangesetsFromDisk();
|
|
1527
|
+
const modelData = await this.serializeModel(), message = JSON.stringify({
|
|
1528
|
+
type: "model.updated",
|
|
1529
|
+
data: modelData,
|
|
1530
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1531
|
+
});
|
|
1532
|
+
for (const client of this.clients)
|
|
1533
|
+
try {
|
|
1534
|
+
client.send(message);
|
|
1535
|
+
} catch (error) {
|
|
1536
|
+
const msg = getErrorMessage(error);
|
|
1537
|
+
console.warn(`[Watcher] Failed to send update: ${msg}`);
|
|
1538
|
+
}
|
|
1539
|
+
} catch (error) {
|
|
1540
|
+
const message = getErrorMessage(error);
|
|
1541
|
+
console.error(`[Watcher] Failed to reload model: ${message}`);
|
|
1527
1542
|
}
|
|
1543
|
+
}), this.watcher.on("error", (err) => {
|
|
1544
|
+
console.warn(`[Watcher] Could not watch ${modelPath}: ${getErrorMessage(err)}`);
|
|
1528
1545
|
});
|
|
1529
1546
|
} catch (err) {
|
|
1530
1547
|
console.warn(`[Watcher] Could not watch ${modelPath}: ${err}`);
|
|
@@ -2226,14 +2243,14 @@ class VisualizationServer {
|
|
|
2226
2243
|
* Start the server
|
|
2227
2244
|
*/
|
|
2228
2245
|
async start(port = 8080) {
|
|
2229
|
-
this.changesetStorage = new StagedChangesetStorage(this.model.rootPath), await this.loadChangesetsFromDisk(), this.setupFileWatcher()
|
|
2230
|
-
const { serve } = await import("bun");
|
|
2231
|
-
this._server = serve({
|
|
2246
|
+
this.changesetStorage = new StagedChangesetStorage(this.model.rootPath), await this.loadChangesetsFromDisk(), this.setupFileWatcher(), this._server = serve({
|
|
2232
2247
|
port,
|
|
2233
|
-
fetch: this.app.fetch
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2248
|
+
fetch: this.app.fetch
|
|
2249
|
+
}), await new Promise((resolve, reject) => {
|
|
2250
|
+
this._server.once("listening", resolve), this._server.once("error", (err) => {
|
|
2251
|
+
err.code === "EADDRINUSE" ? reject(new Error(`Port ${port} is already in use. Try a different port with --port <port>.`)) : reject(err);
|
|
2252
|
+
});
|
|
2253
|
+
}), this.injectWebSocket?.(this._server), console.log(`\u2713 Visualization server running at http://localhost:${port}`);
|
|
2237
2254
|
}
|
|
2238
2255
|
/**
|
|
2239
2256
|
* Get the current auth token
|
|
@@ -2247,6 +2264,34 @@ class VisualizationServer {
|
|
|
2247
2264
|
isAuthEnabled() {
|
|
2248
2265
|
return this.authEnabled;
|
|
2249
2266
|
}
|
|
2267
|
+
/**
|
|
2268
|
+
* Wait for a spawned child process to exit and resolve with its exit code
|
|
2269
|
+
* Node's ChildProcess doesn't expose a Bun-style `.exited` promise, so this
|
|
2270
|
+
* wraps the "exit" event (or the already-set exitCode, if it fired before
|
|
2271
|
+
* we started listening).
|
|
2272
|
+
*/
|
|
2273
|
+
waitForExit(proc) {
|
|
2274
|
+
return new Promise((resolve) => {
|
|
2275
|
+
if (proc.exitCode !== null) {
|
|
2276
|
+
resolve(proc.exitCode);
|
|
2277
|
+
return;
|
|
2278
|
+
}
|
|
2279
|
+
proc.once("exit", (code) => resolve(code ?? 0));
|
|
2280
|
+
});
|
|
2281
|
+
}
|
|
2282
|
+
/**
|
|
2283
|
+
* Whether a child process has actually terminated.
|
|
2284
|
+
*
|
|
2285
|
+
* NOTE: Node's `ChildProcess.killed` means "kill() was successfully used to send a
|
|
2286
|
+
* signal" — it does NOT mean the process has exited (a process can ignore SIGTERM
|
|
2287
|
+
* and keep running with `.killed === true`). This differs from Bun's
|
|
2288
|
+
* `Subprocess.killed`, which does mean "has the process exited". Use exitCode/
|
|
2289
|
+
* signalCode (set only once the process has actually terminated) instead of
|
|
2290
|
+
* `.killed` to decide whether retry/escalation is still needed.
|
|
2291
|
+
*/
|
|
2292
|
+
hasExited(proc) {
|
|
2293
|
+
return proc.exitCode !== null || proc.signalCode !== null;
|
|
2294
|
+
}
|
|
2250
2295
|
/**
|
|
2251
2296
|
* Kill a process with retry logic and exponential backoff
|
|
2252
2297
|
* Attempts graceful SIGTERM first, then force SIGKILL if needed
|
|
@@ -2256,7 +2301,7 @@ class VisualizationServer {
|
|
|
2256
2301
|
const baseDelay = 100;
|
|
2257
2302
|
for (; attempt < 3; ) {
|
|
2258
2303
|
try {
|
|
2259
|
-
if (proc
|
|
2304
|
+
if (this.hasExited(proc) || (proc.kill("SIGTERM"), await new Promise((resolve) => setTimeout(resolve, 500)), this.hasExited(proc)))
|
|
2260
2305
|
return;
|
|
2261
2306
|
} catch {
|
|
2262
2307
|
}
|
|
@@ -2266,7 +2311,7 @@ class VisualizationServer {
|
|
|
2266
2311
|
}
|
|
2267
2312
|
}
|
|
2268
2313
|
try {
|
|
2269
|
-
proc
|
|
2314
|
+
this.hasExited(proc) || (proc.kill("SIGKILL"), console.warn(`[${label}] Process for conversation ${conversationId} did not respond to SIGTERM, force killed with SIGKILL`));
|
|
2270
2315
|
} catch (error) {
|
|
2271
2316
|
throw new Error(`Failed to kill process: ${error instanceof Error ? error.message : String(error)}`);
|
|
2272
2317
|
}
|
|
@@ -2282,7 +2327,9 @@ class VisualizationServer {
|
|
|
2282
2327
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2283
2328
|
errorMessage.includes("already closed") || console.warn(`[SERVER] Unexpected error closing WebSocket client: ${errorMessage}`);
|
|
2284
2329
|
}
|
|
2285
|
-
this.clients.clear(), this.watcher && this.watcher.close?.(), this._server && this._server.
|
|
2330
|
+
this.clients.clear(), this.watcher && this.watcher.close?.(), this._server && this._server.close((error) => {
|
|
2331
|
+
error && process.env.VERBOSE && console.warn(`[SERVER] Error while closing HTTP server: ${getErrorMessage(error)}`);
|
|
2332
|
+
});
|
|
2286
2333
|
}
|
|
2287
2334
|
}
|
|
2288
2335
|
export {
|