pi-harness-runtime 1.1.367 → 1.1.369
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/AGENTS.md +33 -0
- package/package.json +1 -1
- package/packages/a2a-adapter/package.json +1 -1
- package/packages/agent-policy/dist/extension.d.ts.map +1 -1
- package/packages/agent-policy/dist/extension.js +15 -2
- package/packages/agent-policy/dist/extension.js.map +1 -1
- package/packages/agent-policy/package.json +1 -1
- package/packages/agent-policy/src/extension.ts +15 -2
- package/packages/architecture-generator/package.json +1 -1
- package/packages/auth/package.json +1 -1
- package/packages/autonomous-refactor/package.json +1 -1
- package/packages/autonomous-runtime/package.json +1 -1
- package/packages/cache-strategy/package.json +1 -1
- package/packages/capability-registry/package.json +1 -1
- package/packages/checkpoint/package.json +1 -1
- package/packages/cli-plugin-sdk/package.json +1 -1
- package/packages/clipboard/package.json +1 -1
- package/packages/clipboard-plugin/package.json +1 -1
- package/packages/code-generation/package.json +1 -1
- package/packages/code-review/package.json +1 -1
- package/packages/codex-adapter/package.json +1 -1
- package/packages/codex-watcher/package.json +1 -1
- package/packages/config-capture/package.json +1 -1
- package/packages/connection-runtime/package.json +1 -1
- package/packages/context-compiler/package.json +1 -1
- package/packages/context-discovery/package.json +1 -1
- package/packages/context-manager/package.json +1 -1
- package/packages/contracts/dist/workspace-env.d.ts +35 -14
- package/packages/contracts/dist/workspace-env.js +333 -132
- package/packages/contracts/package.json +1 -1
- package/packages/contracts/src/workspace-env.ts +411 -157
- package/packages/cookie-sanitizer/package.json +1 -1
- package/packages/cost-optimizer/package.json +1 -1
- package/packages/dependency-analyzer/package.json +1 -1
- package/packages/django-plugin/package.json +1 -1
- package/packages/doc-generator/package.json +1 -1
- package/packages/durable-review/package.json +1 -1
- package/packages/e2e-tester/package.json +1 -1
- package/packages/evaluation-engine/package.json +1 -1
- package/packages/evaluation-runner/package.json +1 -1
- package/packages/event-bus/package.json +1 -1
- package/packages/event-sink/package.json +1 -1
- package/packages/event-store/package.json +1 -1
- package/packages/experience-replay/package.json +1 -1
- package/packages/feedback-collector/package.json +1 -1
- package/packages/file-copy-helper/package.json +1 -1
- package/packages/framework-detector/package.json +1 -1
- package/packages/framework-plugin-sdk/package.json +1 -1
- package/packages/frappe-plugin/package.json +1 -1
- package/packages/generic-web-plugin/package.json +1 -1
- package/packages/health-monitor/package.json +1 -1
- package/packages/intent-analyzer/package.json +1 -1
- package/packages/jev-judge/package.json +1 -1
- package/packages/knowledge-graph/package.json +1 -1
- package/packages/knowledge-retrieval/package.json +1 -1
- package/packages/laravel-plugin/package.json +1 -1
- package/packages/learning-engine/package.json +1 -1
- package/packages/mcp-adapter/package.json +1 -1
- package/packages/memory-engine/package.json +1 -1
- package/packages/milestone-manager/package.json +1 -1
- package/packages/model-registry/package.json +1 -1
- package/packages/nextjs-plugin/package.json +1 -1
- package/packages/notification/package.json +1 -1
- package/packages/observability/package.json +1 -1
- package/packages/okf-indexer/package.json +1 -1
- package/packages/paperclip-integration/package.json +1 -1
- package/packages/performance-optimizer/package.json +1 -1
- package/packages/permission-mode/package.json +1 -1
- package/packages/privilege-broker/package.json +1 -1
- package/packages/project-analyzer/package.json +1 -1
- package/packages/project-bootstrap/package.json +1 -1
- package/packages/projection-engine/package.json +1 -1
- package/packages/prompt-compiler/package.json +1 -1
- package/packages/prompt-versioning/package.json +1 -1
- package/packages/provider-adapter-sdk/package.json +1 -1
- package/packages/provider-router/package.json +1 -1
- package/packages/provider-selector/package.json +1 -1
- package/packages/providers/package.json +1 -1
- package/packages/qdrant-skills/package.json +1 -1
- package/packages/quota-manager/package.json +1 -1
- package/packages/rate-limiter/package.json +1 -1
- package/packages/react-vite-plugin/package.json +1 -1
- package/packages/relay/package.json +1 -1
- package/packages/release-manager/package.json +1 -1
- package/packages/requirement-compiler/package.json +1 -1
- package/packages/runtime/package.json +1 -1
- package/packages/scheduler/package.json +1 -1
- package/packages/scheduler-adapter/package.json +1 -1
- package/packages/session/package.json +1 -1
- package/packages/session-api/package.json +1 -1
- package/packages/session-export/package.json +1 -1
- package/packages/shared-context/package.json +1 -1
- package/packages/skill-mcp-client/package.json +1 -1
- package/packages/skill-registry/package.json +1 -1
- package/packages/skills/package.json +1 -1
- package/packages/sprint-planner/package.json +1 -1
- package/packages/subscription-engine/package.json +1 -1
- package/packages/task-compiler/package.json +1 -1
- package/packages/telegram/package.json +1 -1
- package/packages/tencentdb-memory/package.json +1 -1
- package/packages/tencentdb-sync/package.json +1 -1
- package/packages/test-data-generator/package.json +1 -1
- package/packages/test-generator/package.json +1 -1
- package/packages/todo-bd-sync/package.json +1 -1
- package/packages/token-estimation/package.json +1 -1
- package/packages/token-optimizer/package.json +1 -1
- package/packages/trajectory/package.json +1 -1
- package/packages/tui/package.json +1 -1
- package/packages/types/package.json +1 -1
- package/packages/voice-api/package.json +1 -1
- package/packages/webmcp-tester/package.json +1 -1
- package/packages/wiki/package.json +1 -1
- package/packages/workflow-events/package.json +1 -1
- package/packages/workspace-scanner/package.json +1 -1
- package/packages/worktree/package.json +1 -1
- package/packages/write-review/package.json +1 -1
|
@@ -2,25 +2,24 @@
|
|
|
2
2
|
* Workspace Environment Detector
|
|
3
3
|
*
|
|
4
4
|
* Detects if the workspace is a development environment by checking:
|
|
5
|
-
* 1. .env
|
|
6
|
-
* 2.
|
|
7
|
-
* 3. .
|
|
5
|
+
* 1. process.env.NODE_ENV (for build processes)
|
|
6
|
+
* 2. .env file existence and NODE_ENV value
|
|
7
|
+
* 3. package.json "dev" script presence
|
|
8
8
|
*
|
|
9
9
|
* Stores detected environment in SQLite for agent reference.
|
|
10
|
+
* Uses synchronous directory creation and exception-safe operations.
|
|
10
11
|
*/
|
|
11
12
|
import { DatabaseSync } from "node:sqlite";
|
|
12
|
-
import { existsSync, readFileSync } from "fs";
|
|
13
|
-
import { join
|
|
14
|
-
import {
|
|
15
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
import { existsSync, readFileSync, mkdirSync } from "fs";
|
|
14
|
+
import { join } from "path";
|
|
15
|
+
import { platform, totalmem, freemem } from "os";
|
|
16
16
|
const STORE_DIR = join(process.env.HOME || "/home/frappe", ".pi-harness-runtime");
|
|
17
17
|
/**
|
|
18
|
-
* Ensure store directory exists
|
|
18
|
+
* Ensure store directory exists synchronously
|
|
19
19
|
*/
|
|
20
20
|
function ensureStoreDir() {
|
|
21
21
|
if (!existsSync(STORE_DIR)) {
|
|
22
|
-
|
|
23
|
-
import("fs").then((fs) => fs.mkdirSync(STORE_DIR, { recursive: true }));
|
|
22
|
+
mkdirSync(STORE_DIR, { recursive: true });
|
|
24
23
|
}
|
|
25
24
|
}
|
|
26
25
|
/**
|
|
@@ -34,15 +33,23 @@ function getDbPath() {
|
|
|
34
33
|
* Initialize workspace environment database
|
|
35
34
|
*/
|
|
36
35
|
function initDatabase() {
|
|
36
|
+
ensureStoreDir();
|
|
37
37
|
const db = new DatabaseSync(getDbPath());
|
|
38
38
|
db.exec(`
|
|
39
39
|
CREATE TABLE IF NOT EXISTS workspace_env (
|
|
40
40
|
workspace_path TEXT PRIMARY KEY,
|
|
41
|
-
|
|
41
|
+
mode TEXT NOT NULL DEFAULT 'unknown',
|
|
42
|
+
evidence TEXT NOT NULL DEFAULT '[]',
|
|
42
43
|
has_dev_script INTEGER NOT NULL DEFAULT 0,
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
has_build_script INTEGER NOT NULL DEFAULT 0,
|
|
45
|
+
package_manager TEXT,
|
|
46
|
+
dev_server_running INTEGER NOT NULL DEFAULT 0,
|
|
47
|
+
build_server_running INTEGER NOT NULL DEFAULT 0,
|
|
48
|
+
ram_total_gb REAL,
|
|
49
|
+
ram_available_gb REAL,
|
|
50
|
+
ram_effective_limit_gb REAL,
|
|
51
|
+
low_ram_warning INTEGER NOT NULL DEFAULT 0,
|
|
52
|
+
build_allowed INTEGER NOT NULL DEFAULT -1,
|
|
46
53
|
detected_at INTEGER NOT NULL,
|
|
47
54
|
updated_at INTEGER NOT NULL
|
|
48
55
|
)
|
|
@@ -50,107 +57,310 @@ function initDatabase() {
|
|
|
50
57
|
return db;
|
|
51
58
|
}
|
|
52
59
|
/**
|
|
53
|
-
*
|
|
60
|
+
* Parse dotenv file without loading secrets
|
|
61
|
+
* Returns only NODE_ENV value, ignoring comments and other vars
|
|
54
62
|
*/
|
|
55
|
-
function
|
|
63
|
+
function parseNodeEnvFromDotenv(content) {
|
|
64
|
+
const lines = content.split("\n");
|
|
65
|
+
for (const line of lines) {
|
|
66
|
+
// Skip empty lines and comments
|
|
67
|
+
const trimmed = line.trim();
|
|
68
|
+
if (!trimmed || trimmed.startsWith("#"))
|
|
69
|
+
continue;
|
|
70
|
+
// Match NODE_ENV= followed by value (handle quotes, spaces)
|
|
71
|
+
// Allow: NODE_ENV=development, NODE_ENV="production", NODE_ENV: development
|
|
72
|
+
const match = trimmed.match(/^NODE_ENV\s*[=:]\s*['"]?([^\s'";]+)['"]?/);
|
|
73
|
+
if (match) {
|
|
74
|
+
return match[1];
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Detect RAM information
|
|
81
|
+
*/
|
|
82
|
+
function detectRamInfo() {
|
|
56
83
|
try {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
84
|
+
const totalBytes = totalmem();
|
|
85
|
+
const freeBytes = freemem();
|
|
86
|
+
const totalGb = Math.round((totalBytes / (1024 ** 3)) * 10) / 10;
|
|
87
|
+
const availableGb = Math.round((freeBytes / (1024 ** 3)) * 10) / 10;
|
|
88
|
+
const LOW_RAM_GB = 16;
|
|
89
|
+
return {
|
|
90
|
+
total: totalGb,
|
|
91
|
+
available: availableGb,
|
|
92
|
+
lowWarning: totalGb < LOW_RAM_GB || availableGb < 4,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return { total: null, available: null, lowWarning: false };
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Check if dev/build server is running
|
|
101
|
+
*/
|
|
102
|
+
function checkProcessRunning(pattern) {
|
|
103
|
+
try {
|
|
104
|
+
if (platform() === "linux") {
|
|
105
|
+
const { execSync } = require("child_process");
|
|
106
|
+
const out = execSync(`pgrep -f "${pattern}" 2>/dev/null || true`, {
|
|
107
|
+
encoding: "utf8",
|
|
108
|
+
timeout: 2000,
|
|
109
|
+
});
|
|
110
|
+
return out.trim().split("\n").filter(Boolean).length > 0;
|
|
63
111
|
}
|
|
64
|
-
else if (
|
|
65
|
-
const
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
112
|
+
else if (platform() === "darwin") {
|
|
113
|
+
const { execSync } = require("child_process");
|
|
114
|
+
const out = execSync(`pgrep -f "${pattern}" 2>/dev/null || true`, {
|
|
115
|
+
encoding: "utf8",
|
|
116
|
+
timeout: 2000,
|
|
117
|
+
});
|
|
118
|
+
return out.trim().split("\n").filter(Boolean).length > 0;
|
|
70
119
|
}
|
|
71
120
|
}
|
|
72
121
|
catch {
|
|
73
|
-
//
|
|
122
|
+
// Process check failed - assume unknown
|
|
74
123
|
}
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Detect package manager from lock file
|
|
128
|
+
*/
|
|
129
|
+
function detectPackageManager(workspacePath) {
|
|
130
|
+
if (existsSync(join(workspacePath, "pnpm-lock.yaml")))
|
|
131
|
+
return "pnpm";
|
|
132
|
+
if (existsSync(join(workspacePath, "yarn.lock")))
|
|
133
|
+
return "yarn";
|
|
134
|
+
if (existsSync(join(workspacePath, "package-lock.json")))
|
|
135
|
+
return "npm";
|
|
75
136
|
return null;
|
|
76
137
|
}
|
|
138
|
+
/**
|
|
139
|
+
* Determine workspace mode with proper precedence
|
|
140
|
+
* Never assume production - unknown requires investigation
|
|
141
|
+
*/
|
|
142
|
+
function determineMode(processEnv, dotenvNodeEnv, evidence) {
|
|
143
|
+
// Explicit process.env takes highest priority (for build processes)
|
|
144
|
+
if (processEnv) {
|
|
145
|
+
const env = processEnv.toLowerCase();
|
|
146
|
+
if (env === "production") {
|
|
147
|
+
evidence.push({
|
|
148
|
+
source: "process.env",
|
|
149
|
+
key: "NODE_ENV",
|
|
150
|
+
value: env,
|
|
151
|
+
timestamp: Date.now(),
|
|
152
|
+
});
|
|
153
|
+
return "production";
|
|
154
|
+
}
|
|
155
|
+
if (env === "development" || env === "dev") {
|
|
156
|
+
evidence.push({
|
|
157
|
+
source: "process.env",
|
|
158
|
+
key: "NODE_ENV",
|
|
159
|
+
value: env,
|
|
160
|
+
timestamp: Date.now(),
|
|
161
|
+
});
|
|
162
|
+
return "development";
|
|
163
|
+
}
|
|
164
|
+
if (env === "test") {
|
|
165
|
+
evidence.push({
|
|
166
|
+
source: "process.env",
|
|
167
|
+
key: "NODE_ENV",
|
|
168
|
+
value: env,
|
|
169
|
+
timestamp: Date.now(),
|
|
170
|
+
});
|
|
171
|
+
return "test";
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
// Check dotenv file if present
|
|
175
|
+
if (dotenvNodeEnv) {
|
|
176
|
+
const env = dotenvNodeEnv.toLowerCase();
|
|
177
|
+
evidence.push({
|
|
178
|
+
source: ".env file",
|
|
179
|
+
key: "NODE_ENV",
|
|
180
|
+
value: env,
|
|
181
|
+
timestamp: Date.now(),
|
|
182
|
+
});
|
|
183
|
+
if (env === "production")
|
|
184
|
+
return "production";
|
|
185
|
+
if (env === "development" || env === "dev")
|
|
186
|
+
return "development";
|
|
187
|
+
if (env === "test")
|
|
188
|
+
return "test";
|
|
189
|
+
}
|
|
190
|
+
// Note: presence of dev script is a capability, not proof of mode
|
|
191
|
+
// Mode detection requires explicit NODE_ENV
|
|
192
|
+
return "unknown";
|
|
193
|
+
}
|
|
77
194
|
/**
|
|
78
195
|
* Detect and store workspace environment
|
|
79
196
|
*/
|
|
80
197
|
export function detectWorkspaceEnv(workspacePath) {
|
|
81
198
|
const now = Date.now();
|
|
82
|
-
const
|
|
83
|
-
//
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
199
|
+
const evidence = [];
|
|
200
|
+
// Get process.env.NODE_ENV (for build process context)
|
|
201
|
+
const processNodeEnv = process.env.NODE_ENV;
|
|
202
|
+
// Detect RAM info
|
|
203
|
+
const ramInfo = detectRamInfo();
|
|
204
|
+
// Check for .env files and parse NODE_ENV
|
|
205
|
+
let dotenvNodeEnv = null;
|
|
206
|
+
const dotenvFiles = [
|
|
207
|
+
join(workspacePath, ".env"),
|
|
208
|
+
join(workspacePath, ".env.development"),
|
|
209
|
+
join(workspacePath, ".env.dev"),
|
|
210
|
+
];
|
|
211
|
+
for (const envFile of dotenvFiles) {
|
|
212
|
+
if (existsSync(envFile)) {
|
|
213
|
+
try {
|
|
214
|
+
const content = readFileSync(envFile, "utf8");
|
|
215
|
+
dotenvNodeEnv = parseNodeEnvFromDotenv(content);
|
|
216
|
+
if (dotenvNodeEnv) {
|
|
217
|
+
evidence.push({
|
|
218
|
+
source: ".env file",
|
|
219
|
+
key: "NODE_ENV",
|
|
220
|
+
value: dotenvNodeEnv,
|
|
221
|
+
timestamp: now,
|
|
222
|
+
});
|
|
223
|
+
break;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
catch {
|
|
227
|
+
// Ignore read errors
|
|
97
228
|
}
|
|
98
|
-
}
|
|
99
|
-
catch {
|
|
100
|
-
// Ignore read errors
|
|
101
229
|
}
|
|
102
230
|
}
|
|
103
|
-
// Check for dev
|
|
231
|
+
// Check for dev/build scripts in package.json
|
|
104
232
|
let hasDevScript = false;
|
|
233
|
+
let hasBuildScript = false;
|
|
105
234
|
const pkgJsonPath = join(workspacePath, "package.json");
|
|
106
235
|
if (existsSync(pkgJsonPath)) {
|
|
107
236
|
try {
|
|
108
237
|
const pkg = JSON.parse(readFileSync(pkgJsonPath, "utf8"));
|
|
109
|
-
hasDevScript = !!(pkg.scripts && (pkg.scripts.dev || pkg.scripts.development));
|
|
238
|
+
hasDevScript = !!(pkg.scripts && (pkg.scripts.dev || pkg.scripts.development || pkg.scripts["dev:next"]));
|
|
239
|
+
hasBuildScript = !!(pkg.scripts && (pkg.scripts.build || pkg.scripts["build:next"]));
|
|
240
|
+
if (hasDevScript) {
|
|
241
|
+
evidence.push({
|
|
242
|
+
source: "package.json",
|
|
243
|
+
key: "scripts.dev",
|
|
244
|
+
value: "present",
|
|
245
|
+
timestamp: now,
|
|
246
|
+
});
|
|
247
|
+
}
|
|
110
248
|
}
|
|
111
249
|
catch {
|
|
112
250
|
// Ignore parse errors
|
|
113
251
|
}
|
|
114
252
|
}
|
|
115
|
-
//
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
253
|
+
// Detect package manager
|
|
254
|
+
const packageManager = detectPackageManager(workspacePath);
|
|
255
|
+
// Check for running dev/build processes
|
|
256
|
+
const devServerRunning = checkProcessRunning("(next dev|nuxt| vite |webpack|remix dev)");
|
|
257
|
+
const buildServerRunning = checkProcessRunning("(next build|nuxt build|webpack build)");
|
|
258
|
+
// Determine mode - NEVER assume production
|
|
259
|
+
const mode = determineMode(processNodeEnv, dotenvNodeEnv, evidence);
|
|
260
|
+
// Determine build permission
|
|
261
|
+
let buildAllowed = false;
|
|
262
|
+
if (mode === "production") {
|
|
263
|
+
buildAllowed = true; // Production build is allowed
|
|
264
|
+
}
|
|
265
|
+
else if (mode === "development") {
|
|
266
|
+
buildAllowed = !ramInfo.lowWarning; // Allow if not low RAM
|
|
267
|
+
}
|
|
268
|
+
else {
|
|
269
|
+
buildAllowed = false; // Unknown requires investigation
|
|
270
|
+
}
|
|
271
|
+
// Determine low RAM warning
|
|
272
|
+
const lowRamWarning = ramInfo.lowWarning;
|
|
273
|
+
// Build result
|
|
274
|
+
const result = {
|
|
127
275
|
workspacePath,
|
|
128
|
-
|
|
276
|
+
mode,
|
|
277
|
+
evidence,
|
|
129
278
|
hasDevScript,
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
279
|
+
hasBuildScript,
|
|
280
|
+
packageManager,
|
|
281
|
+
devServerRunning,
|
|
282
|
+
buildServerRunning,
|
|
283
|
+
ramTotalGb: ramInfo.total,
|
|
284
|
+
ramAvailableGb: ramInfo.available,
|
|
285
|
+
ramEffectiveLimitGb: ramInfo.total, // Could be enhanced with cgroup detection
|
|
286
|
+
lowRamWarning,
|
|
287
|
+
buildAllowed,
|
|
133
288
|
detectedAt: now,
|
|
134
289
|
};
|
|
290
|
+
// Store in SQLite (synchronously, exception-safe)
|
|
291
|
+
let db = null;
|
|
292
|
+
try {
|
|
293
|
+
db = initDatabase();
|
|
294
|
+
db.prepare(`
|
|
295
|
+
INSERT OR REPLACE INTO workspace_env
|
|
296
|
+
(workspace_path, mode, evidence, has_dev_script, has_build_script, package_manager,
|
|
297
|
+
dev_server_running, build_server_running, ram_total_gb, ram_available_gb,
|
|
298
|
+
ram_effective_limit_gb, low_ram_warning, build_allowed, detected_at, updated_at)
|
|
299
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
300
|
+
`).run(workspacePath, mode, JSON.stringify(evidence), hasDevScript ? 1 : 0, hasBuildScript ? 1 : 0, packageManager, devServerRunning ? 1 : 0, buildServerRunning ? 1 : 0, ramInfo.total, ramInfo.available, ramInfo.total, lowRamWarning ? 1 : 0, buildAllowed ? 1 : 0, now, now);
|
|
301
|
+
}
|
|
302
|
+
catch (err) {
|
|
303
|
+
// SQLite failure - continue with in-memory result
|
|
304
|
+
console.error("[WorkspaceEnv] SQLite persistence failed:", err);
|
|
305
|
+
}
|
|
306
|
+
finally {
|
|
307
|
+
if (db) {
|
|
308
|
+
try {
|
|
309
|
+
db.close();
|
|
310
|
+
}
|
|
311
|
+
catch {
|
|
312
|
+
// Ignore close errors
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
return result;
|
|
135
317
|
}
|
|
136
318
|
/**
|
|
137
319
|
* Load cached workspace environment
|
|
138
320
|
*/
|
|
139
321
|
export function loadWorkspaceEnv(workspacePath) {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
322
|
+
let db = null;
|
|
323
|
+
try {
|
|
324
|
+
db = initDatabase();
|
|
325
|
+
const row = db.prepare("SELECT * FROM workspace_env WHERE workspace_path = ?").get(workspacePath);
|
|
326
|
+
if (!row)
|
|
327
|
+
return null;
|
|
328
|
+
// Check cache freshness (5 minutes for dev workspaces)
|
|
329
|
+
const ageMs = Date.now() - row.detected_at;
|
|
330
|
+
const MAX_CACHE_AGE_MS = 5 * 60 * 1000; // 5 minutes
|
|
331
|
+
if (ageMs > MAX_CACHE_AGE_MS) {
|
|
332
|
+
return null; // Cache stale
|
|
333
|
+
}
|
|
334
|
+
return {
|
|
335
|
+
workspacePath: row.workspace_path,
|
|
336
|
+
mode: row.mode,
|
|
337
|
+
evidence: JSON.parse(row.evidence || "[]"),
|
|
338
|
+
hasDevScript: row.has_dev_script === 1,
|
|
339
|
+
hasBuildScript: row.has_build_script === 1,
|
|
340
|
+
packageManager: row.package_manager,
|
|
341
|
+
devServerRunning: row.dev_server_running === 1,
|
|
342
|
+
buildServerRunning: row.build_server_running === 1,
|
|
343
|
+
ramTotalGb: row.ram_total_gb,
|
|
344
|
+
ramAvailableGb: row.ram_available_gb,
|
|
345
|
+
ramEffectiveLimitGb: row.ram_effective_limit_gb,
|
|
346
|
+
lowRamWarning: row.low_ram_warning === 1,
|
|
347
|
+
buildAllowed: row.build_allowed === 1,
|
|
348
|
+
detectedAt: row.detected_at,
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
catch {
|
|
144
352
|
return null;
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
353
|
+
}
|
|
354
|
+
finally {
|
|
355
|
+
if (db) {
|
|
356
|
+
try {
|
|
357
|
+
db.close();
|
|
358
|
+
}
|
|
359
|
+
catch {
|
|
360
|
+
// Ignore close errors
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
154
364
|
}
|
|
155
365
|
/**
|
|
156
366
|
* Get or detect workspace environment
|
|
@@ -159,73 +369,64 @@ export function getWorkspaceEnv(workspacePath) {
|
|
|
159
369
|
// Try to load cached result first
|
|
160
370
|
const cached = loadWorkspaceEnv(workspacePath);
|
|
161
371
|
if (cached) {
|
|
162
|
-
|
|
163
|
-
const ageMs = Date.now() - cached.detectedAt;
|
|
164
|
-
if (ageMs < 60 * 60 * 1000) {
|
|
165
|
-
return cached;
|
|
166
|
-
}
|
|
372
|
+
return cached;
|
|
167
373
|
}
|
|
168
374
|
// Detect fresh
|
|
169
375
|
return detectWorkspaceEnv(workspacePath);
|
|
170
376
|
}
|
|
171
377
|
/**
|
|
172
|
-
*
|
|
378
|
+
* Revalidate workspace environment before impactful commands
|
|
173
379
|
*/
|
|
174
|
-
export function
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
const LOW_RAM_GB = 16;
|
|
178
|
-
if (!env.isDevEnv) {
|
|
179
|
-
return {
|
|
180
|
-
preferDev: false,
|
|
181
|
-
reason: "Not detected as dev environment",
|
|
182
|
-
ramGb: env.ramGb,
|
|
183
|
-
};
|
|
184
|
-
}
|
|
185
|
-
if (env.ramGb !== null && env.ramGb < LOW_RAM_GB) {
|
|
186
|
-
return {
|
|
187
|
-
preferDev: true,
|
|
188
|
-
reason: `Low RAM (${env.ramGb}GB < ${LOW_RAM_GB}GB) - avoid parallel build + dev`,
|
|
189
|
-
ramGb: env.ramGb,
|
|
190
|
-
};
|
|
191
|
-
}
|
|
192
|
-
if (env.hasDevScript) {
|
|
193
|
-
return {
|
|
194
|
-
preferDev: true,
|
|
195
|
-
reason: `Dev script detected - hot reload available`,
|
|
196
|
-
ramGb: env.ramGb,
|
|
197
|
-
};
|
|
198
|
-
}
|
|
199
|
-
return {
|
|
200
|
-
preferDev: false,
|
|
201
|
-
reason: "Dev environment but no dev script found",
|
|
202
|
-
ramGb: env.ramGb,
|
|
203
|
-
};
|
|
380
|
+
export function revalidateWorkspaceEnv(workspacePath) {
|
|
381
|
+
// Force fresh detection for command decisions
|
|
382
|
+
return detectWorkspaceEnv(workspacePath);
|
|
204
383
|
}
|
|
205
384
|
/**
|
|
206
385
|
* Format workspace env as agent-readable message
|
|
207
386
|
*/
|
|
208
387
|
export function formatEnvForAgent(workspacePath) {
|
|
209
|
-
const env =
|
|
210
|
-
const details = getWorkspaceEnv(workspacePath);
|
|
388
|
+
const env = getWorkspaceEnv(workspacePath);
|
|
211
389
|
let message = `[Workspace Environment]\n`;
|
|
212
|
-
message +=
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
message += `- Has .env file: ${details.hasEnvFile ? "Yes" : "No"}\n`;
|
|
217
|
-
message += `- RAM: ${details.ramGb ? `${details.ramGb}GB` : "unknown"}\n`;
|
|
218
|
-
message += `\n`;
|
|
219
|
-
if (env.preferDev) {
|
|
220
|
-
message += `RECOMMENDATION: Use \`dev\` mode (no build needed)\n`;
|
|
221
|
-
message += `Reason: ${env.reason}\n`;
|
|
222
|
-
message += `→ Run: \`bun run dev\` (or \`npm run dev\`)\n`;
|
|
223
|
-
message += `→ DON'T: Run \`build\` unless explicitly requested\n`;
|
|
390
|
+
message += `Mode: ${env.mode.toUpperCase()}\n`;
|
|
391
|
+
if (env.evidence.length > 0) {
|
|
392
|
+
const sources = env.evidence.map((e) => `${e.source}:${e.value}`).join(", ");
|
|
393
|
+
message += `Evidence: ${sources}\n`;
|
|
224
394
|
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
message +=
|
|
395
|
+
message += `Dev script: ${env.hasDevScript ? "yes" : "no"}\n`;
|
|
396
|
+
message += `Build script: ${env.hasBuildScript ? "yes" : "no"}\n`;
|
|
397
|
+
if (env.devServerRunning) {
|
|
398
|
+
message += `Dev server: running\n`;
|
|
399
|
+
}
|
|
400
|
+
if (env.buildServerRunning) {
|
|
401
|
+
message += `Build server: running\n`;
|
|
402
|
+
}
|
|
403
|
+
if (env.ramTotalGb !== null) {
|
|
404
|
+
const ramStr = env.ramAvailableGb !== null
|
|
405
|
+
? `${env.ramAvailableGb}/${env.ramTotalGb}GB available`
|
|
406
|
+
: `${env.ramTotalGb}GB total`;
|
|
407
|
+
message += `RAM: ${ramStr}\n`;
|
|
408
|
+
if (env.lowRamWarning) {
|
|
409
|
+
message += `WARNING: Low RAM - avoid parallel build + dev\n`;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
message += `\n`;
|
|
413
|
+
message += `Build allowed: ${env.buildAllowed ? "yes" : "no - requires explicit request"}\n`;
|
|
414
|
+
if (env.mode === "unknown") {
|
|
415
|
+
message += `\nNOTE: Environment unknown. Explicitly ask user before build.\n`;
|
|
229
416
|
}
|
|
230
417
|
return message;
|
|
231
418
|
}
|
|
419
|
+
/**
|
|
420
|
+
* Format workspace env as compact status line for UI
|
|
421
|
+
*/
|
|
422
|
+
export function formatEnvStatusLine(env) {
|
|
423
|
+
const mode = env.mode.toUpperCase();
|
|
424
|
+
const devStatus = env.devServerRunning ? "running" : (env.hasDevScript ? "available" : "none");
|
|
425
|
+
const buildStatus = env.buildServerRunning ? "running" : (env.hasBuildScript ? "explicit request" : "none");
|
|
426
|
+
const ramStr = env.ramAvailableGb !== null ? `${env.ramAvailableGb}/${env.ramTotalGb}` : "";
|
|
427
|
+
let status = `Workspace: ${mode} | dev server: ${devStatus} | build: ${buildStatus}`;
|
|
428
|
+
if (ramStr) {
|
|
429
|
+
status += ` | RAM: ${ramStr}`;
|
|
430
|
+
}
|
|
431
|
+
return status;
|
|
432
|
+
}
|