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,28 +2,27 @@
|
|
|
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
|
|
|
12
13
|
import { DatabaseSync } from "node:sqlite";
|
|
13
|
-
import { existsSync, readFileSync } from "fs";
|
|
14
|
-
import { join
|
|
15
|
-
import {
|
|
14
|
+
import { existsSync, readFileSync, mkdirSync } from "fs";
|
|
15
|
+
import { join } from "path";
|
|
16
|
+
import { platform, totalmem, freemem } from "os";
|
|
16
17
|
|
|
17
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
18
18
|
const STORE_DIR = join(process.env.HOME || "/home/frappe", ".pi-harness-runtime");
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
|
-
* Ensure store directory exists
|
|
21
|
+
* Ensure store directory exists synchronously
|
|
22
22
|
*/
|
|
23
23
|
function ensureStoreDir(): void {
|
|
24
24
|
if (!existsSync(STORE_DIR)) {
|
|
25
|
-
|
|
26
|
-
import("fs").then((fs) => fs.mkdirSync(STORE_DIR, { recursive: true }));
|
|
25
|
+
mkdirSync(STORE_DIR, { recursive: true });
|
|
27
26
|
}
|
|
28
27
|
}
|
|
29
28
|
|
|
@@ -39,15 +38,23 @@ function getDbPath(): string {
|
|
|
39
38
|
* Initialize workspace environment database
|
|
40
39
|
*/
|
|
41
40
|
function initDatabase(): DatabaseSync {
|
|
41
|
+
ensureStoreDir();
|
|
42
42
|
const db = new DatabaseSync(getDbPath());
|
|
43
43
|
db.exec(`
|
|
44
44
|
CREATE TABLE IF NOT EXISTS workspace_env (
|
|
45
45
|
workspace_path TEXT PRIMARY KEY,
|
|
46
|
-
|
|
46
|
+
mode TEXT NOT NULL DEFAULT 'unknown',
|
|
47
|
+
evidence TEXT NOT NULL DEFAULT '[]',
|
|
47
48
|
has_dev_script INTEGER NOT NULL DEFAULT 0,
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
has_build_script INTEGER NOT NULL DEFAULT 0,
|
|
50
|
+
package_manager TEXT,
|
|
51
|
+
dev_server_running INTEGER NOT NULL DEFAULT 0,
|
|
52
|
+
build_server_running INTEGER NOT NULL DEFAULT 0,
|
|
53
|
+
ram_total_gb REAL,
|
|
54
|
+
ram_available_gb REAL,
|
|
55
|
+
ram_effective_limit_gb REAL,
|
|
56
|
+
low_ram_warning INTEGER NOT NULL DEFAULT 0,
|
|
57
|
+
build_allowed INTEGER NOT NULL DEFAULT -1,
|
|
51
58
|
detected_at INTEGER NOT NULL,
|
|
52
59
|
updated_at INTEGER NOT NULL
|
|
53
60
|
)
|
|
@@ -56,40 +63,181 @@ function initDatabase(): DatabaseSync {
|
|
|
56
63
|
}
|
|
57
64
|
|
|
58
65
|
/**
|
|
59
|
-
*
|
|
66
|
+
* Workspace mode with explicit states
|
|
67
|
+
*/
|
|
68
|
+
export type WorkspaceMode = "development" | "production" | "test" | "unknown";
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Evidence source for mode detection
|
|
72
|
+
*/
|
|
73
|
+
export interface EnvEvidence {
|
|
74
|
+
source: "process.env" | ".env file" | "package.json" | "command" | "detection";
|
|
75
|
+
key: string;
|
|
76
|
+
value: string;
|
|
77
|
+
timestamp: number;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Workspace environment result
|
|
60
82
|
*/
|
|
61
|
-
|
|
83
|
+
export interface WorkspaceEnvResult {
|
|
84
|
+
workspacePath: string;
|
|
85
|
+
mode: WorkspaceMode;
|
|
86
|
+
evidence: EnvEvidence[];
|
|
87
|
+
hasDevScript: boolean;
|
|
88
|
+
hasBuildScript: boolean;
|
|
89
|
+
packageManager: string | null;
|
|
90
|
+
devServerRunning: boolean;
|
|
91
|
+
buildServerRunning: boolean;
|
|
92
|
+
ramTotalGb: number | null;
|
|
93
|
+
ramAvailableGb: number | null;
|
|
94
|
+
ramEffectiveLimitGb: number | null;
|
|
95
|
+
lowRamWarning: boolean;
|
|
96
|
+
buildAllowed: boolean;
|
|
97
|
+
detectedAt: number;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Parse dotenv file without loading secrets
|
|
102
|
+
* Returns only NODE_ENV value, ignoring comments and other vars
|
|
103
|
+
*/
|
|
104
|
+
function parseNodeEnvFromDotenv(content: string): string | null {
|
|
105
|
+
const lines = content.split("\n");
|
|
106
|
+
for (const line of lines) {
|
|
107
|
+
// Skip empty lines and comments
|
|
108
|
+
const trimmed = line.trim();
|
|
109
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
110
|
+
|
|
111
|
+
// Match NODE_ENV= followed by value (handle quotes, spaces)
|
|
112
|
+
// Allow: NODE_ENV=development, NODE_ENV="production", NODE_ENV: development
|
|
113
|
+
const match = trimmed.match(/^NODE_ENV\s*[=:]\s*['"]?([^\s'";]+)['"]?/);
|
|
114
|
+
if (match) {
|
|
115
|
+
return match[1];
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Detect RAM information
|
|
123
|
+
*/
|
|
124
|
+
function detectRamInfo(): {
|
|
125
|
+
total: number | null;
|
|
126
|
+
available: number | null;
|
|
127
|
+
lowWarning: boolean;
|
|
128
|
+
} {
|
|
62
129
|
try {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
130
|
+
const totalBytes = totalmem();
|
|
131
|
+
const freeBytes = freemem();
|
|
132
|
+
const totalGb = Math.round((totalBytes / (1024 ** 3)) * 10) / 10;
|
|
133
|
+
const availableGb = Math.round((freeBytes / (1024 ** 3)) * 10) / 10;
|
|
134
|
+
const LOW_RAM_GB = 16;
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
total: totalGb,
|
|
138
|
+
available: availableGb,
|
|
139
|
+
lowWarning: totalGb < LOW_RAM_GB || availableGb < 4,
|
|
140
|
+
};
|
|
141
|
+
} catch {
|
|
142
|
+
return { total: null, available: null, lowWarning: false };
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Check if dev/build server is running
|
|
148
|
+
*/
|
|
149
|
+
function checkProcessRunning(pattern: string): boolean {
|
|
150
|
+
try {
|
|
151
|
+
if (platform() === "linux") {
|
|
152
|
+
const { execSync } = require("child_process");
|
|
153
|
+
const out = execSync(`pgrep -f "${pattern}" 2>/dev/null || true`, {
|
|
154
|
+
encoding: "utf8",
|
|
155
|
+
timeout: 2000,
|
|
156
|
+
});
|
|
157
|
+
return out.trim().split("\n").filter(Boolean).length > 0;
|
|
158
|
+
} else if (platform() === "darwin") {
|
|
159
|
+
const { execSync } = require("child_process");
|
|
160
|
+
const out = execSync(`pgrep -f "${pattern}" 2>/dev/null || true`, {
|
|
161
|
+
encoding: "utf8",
|
|
162
|
+
timeout: 2000,
|
|
163
|
+
});
|
|
164
|
+
return out.trim().split("\n").filter(Boolean).length > 0;
|
|
75
165
|
}
|
|
76
166
|
} catch {
|
|
77
|
-
//
|
|
167
|
+
// Process check failed - assume unknown
|
|
78
168
|
}
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Detect package manager from lock file
|
|
174
|
+
*/
|
|
175
|
+
function detectPackageManager(workspacePath: string): string | null {
|
|
176
|
+
if (existsSync(join(workspacePath, "pnpm-lock.yaml"))) return "pnpm";
|
|
177
|
+
if (existsSync(join(workspacePath, "yarn.lock"))) return "yarn";
|
|
178
|
+
if (existsSync(join(workspacePath, "package-lock.json"))) return "npm";
|
|
79
179
|
return null;
|
|
80
180
|
}
|
|
81
181
|
|
|
82
182
|
/**
|
|
83
|
-
*
|
|
183
|
+
* Determine workspace mode with proper precedence
|
|
184
|
+
* Never assume production - unknown requires investigation
|
|
84
185
|
*/
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
186
|
+
function determineMode(
|
|
187
|
+
processEnv: string | undefined,
|
|
188
|
+
dotenvNodeEnv: string | null,
|
|
189
|
+
evidence: EnvEvidence[]
|
|
190
|
+
): WorkspaceMode {
|
|
191
|
+
// Explicit process.env takes highest priority (for build processes)
|
|
192
|
+
if (processEnv) {
|
|
193
|
+
const env = processEnv.toLowerCase();
|
|
194
|
+
if (env === "production") {
|
|
195
|
+
evidence.push({
|
|
196
|
+
source: "process.env",
|
|
197
|
+
key: "NODE_ENV",
|
|
198
|
+
value: env,
|
|
199
|
+
timestamp: Date.now(),
|
|
200
|
+
});
|
|
201
|
+
return "production";
|
|
202
|
+
}
|
|
203
|
+
if (env === "development" || env === "dev") {
|
|
204
|
+
evidence.push({
|
|
205
|
+
source: "process.env",
|
|
206
|
+
key: "NODE_ENV",
|
|
207
|
+
value: env,
|
|
208
|
+
timestamp: Date.now(),
|
|
209
|
+
});
|
|
210
|
+
return "development";
|
|
211
|
+
}
|
|
212
|
+
if (env === "test") {
|
|
213
|
+
evidence.push({
|
|
214
|
+
source: "process.env",
|
|
215
|
+
key: "NODE_ENV",
|
|
216
|
+
value: env,
|
|
217
|
+
timestamp: Date.now(),
|
|
218
|
+
});
|
|
219
|
+
return "test";
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Check dotenv file if present
|
|
224
|
+
if (dotenvNodeEnv) {
|
|
225
|
+
const env = dotenvNodeEnv.toLowerCase();
|
|
226
|
+
evidence.push({
|
|
227
|
+
source: ".env file",
|
|
228
|
+
key: "NODE_ENV",
|
|
229
|
+
value: env,
|
|
230
|
+
timestamp: Date.now(),
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
if (env === "production") return "production";
|
|
234
|
+
if (env === "development" || env === "dev") return "development";
|
|
235
|
+
if (env === "test") return "test";
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Note: presence of dev script is a capability, not proof of mode
|
|
239
|
+
// Mode detection requires explicit NODE_ENV
|
|
240
|
+
return "unknown";
|
|
93
241
|
}
|
|
94
242
|
|
|
95
243
|
/**
|
|
@@ -97,93 +245,208 @@ export interface WorkspaceEnvResult {
|
|
|
97
245
|
*/
|
|
98
246
|
export function detectWorkspaceEnv(workspacePath: string): WorkspaceEnvResult {
|
|
99
247
|
const now = Date.now();
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
//
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
//
|
|
109
|
-
let
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
248
|
+
const evidence: EnvEvidence[] = [];
|
|
249
|
+
|
|
250
|
+
// Get process.env.NODE_ENV (for build process context)
|
|
251
|
+
const processNodeEnv = process.env.NODE_ENV;
|
|
252
|
+
|
|
253
|
+
// Detect RAM info
|
|
254
|
+
const ramInfo = detectRamInfo();
|
|
255
|
+
|
|
256
|
+
// Check for .env files and parse NODE_ENV
|
|
257
|
+
let dotenvNodeEnv: string | null = null;
|
|
258
|
+
const dotenvFiles = [
|
|
259
|
+
join(workspacePath, ".env"),
|
|
260
|
+
join(workspacePath, ".env.development"),
|
|
261
|
+
join(workspacePath, ".env.dev"),
|
|
262
|
+
];
|
|
263
|
+
for (const envFile of dotenvFiles) {
|
|
264
|
+
if (existsSync(envFile)) {
|
|
265
|
+
try {
|
|
266
|
+
const content = readFileSync(envFile, "utf8");
|
|
267
|
+
dotenvNodeEnv = parseNodeEnvFromDotenv(content);
|
|
268
|
+
if (dotenvNodeEnv) {
|
|
269
|
+
evidence.push({
|
|
270
|
+
source: ".env file",
|
|
271
|
+
key: "NODE_ENV",
|
|
272
|
+
value: dotenvNodeEnv,
|
|
273
|
+
timestamp: now,
|
|
274
|
+
});
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
277
|
+
} catch {
|
|
278
|
+
// Ignore read errors
|
|
117
279
|
}
|
|
118
|
-
} catch {
|
|
119
|
-
// Ignore read errors
|
|
120
280
|
}
|
|
121
281
|
}
|
|
122
282
|
|
|
123
|
-
// Check for dev
|
|
283
|
+
// Check for dev/build scripts in package.json
|
|
124
284
|
let hasDevScript = false;
|
|
285
|
+
let hasBuildScript = false;
|
|
125
286
|
const pkgJsonPath = join(workspacePath, "package.json");
|
|
126
287
|
if (existsSync(pkgJsonPath)) {
|
|
127
288
|
try {
|
|
128
289
|
const pkg = JSON.parse(readFileSync(pkgJsonPath, "utf8"));
|
|
129
|
-
hasDevScript = !!(pkg.scripts && (pkg.scripts.dev || pkg.scripts.development));
|
|
290
|
+
hasDevScript = !!(pkg.scripts && (pkg.scripts.dev || pkg.scripts.development || pkg.scripts["dev:next"]));
|
|
291
|
+
hasBuildScript = !!(pkg.scripts && (pkg.scripts.build || pkg.scripts["build:next"]));
|
|
292
|
+
if (hasDevScript) {
|
|
293
|
+
evidence.push({
|
|
294
|
+
source: "package.json",
|
|
295
|
+
key: "scripts.dev",
|
|
296
|
+
value: "present",
|
|
297
|
+
timestamp: now,
|
|
298
|
+
});
|
|
299
|
+
}
|
|
130
300
|
} catch {
|
|
131
301
|
// Ignore parse errors
|
|
132
302
|
}
|
|
133
303
|
}
|
|
134
304
|
|
|
135
|
-
//
|
|
136
|
-
|
|
137
|
-
|
|
305
|
+
// Detect package manager
|
|
306
|
+
const packageManager = detectPackageManager(workspacePath);
|
|
307
|
+
|
|
308
|
+
// Check for running dev/build processes
|
|
309
|
+
const devServerRunning = checkProcessRunning("(next dev|nuxt| vite |webpack|remix dev)");
|
|
310
|
+
const buildServerRunning = checkProcessRunning("(next build|nuxt build|webpack build)");
|
|
311
|
+
|
|
312
|
+
// Determine mode - NEVER assume production
|
|
313
|
+
const mode = determineMode(processNodeEnv, dotenvNodeEnv, evidence);
|
|
138
314
|
|
|
139
|
-
//
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
315
|
+
// Determine build permission
|
|
316
|
+
let buildAllowed = false;
|
|
317
|
+
if (mode === "production") {
|
|
318
|
+
buildAllowed = true; // Production build is allowed
|
|
319
|
+
} else if (mode === "development") {
|
|
320
|
+
buildAllowed = !ramInfo.lowWarning; // Allow if not low RAM
|
|
321
|
+
} else {
|
|
322
|
+
buildAllowed = false; // Unknown requires investigation
|
|
323
|
+
}
|
|
146
324
|
|
|
147
|
-
|
|
325
|
+
// Determine low RAM warning
|
|
326
|
+
const lowRamWarning = ramInfo.lowWarning;
|
|
148
327
|
|
|
149
|
-
|
|
328
|
+
// Build result
|
|
329
|
+
const result: WorkspaceEnvResult = {
|
|
150
330
|
workspacePath,
|
|
151
|
-
|
|
331
|
+
mode,
|
|
332
|
+
evidence,
|
|
152
333
|
hasDevScript,
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
334
|
+
hasBuildScript,
|
|
335
|
+
packageManager,
|
|
336
|
+
devServerRunning,
|
|
337
|
+
buildServerRunning,
|
|
338
|
+
ramTotalGb: ramInfo.total,
|
|
339
|
+
ramAvailableGb: ramInfo.available,
|
|
340
|
+
ramEffectiveLimitGb: ramInfo.total, // Could be enhanced with cgroup detection
|
|
341
|
+
lowRamWarning,
|
|
342
|
+
buildAllowed,
|
|
156
343
|
detectedAt: now,
|
|
157
344
|
};
|
|
345
|
+
|
|
346
|
+
// Store in SQLite (synchronously, exception-safe)
|
|
347
|
+
let db: DatabaseSync | null = null;
|
|
348
|
+
try {
|
|
349
|
+
db = initDatabase();
|
|
350
|
+
db.prepare(`
|
|
351
|
+
INSERT OR REPLACE INTO workspace_env
|
|
352
|
+
(workspace_path, mode, evidence, has_dev_script, has_build_script, package_manager,
|
|
353
|
+
dev_server_running, build_server_running, ram_total_gb, ram_available_gb,
|
|
354
|
+
ram_effective_limit_gb, low_ram_warning, build_allowed, detected_at, updated_at)
|
|
355
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
356
|
+
`).run(
|
|
357
|
+
workspacePath,
|
|
358
|
+
mode,
|
|
359
|
+
JSON.stringify(evidence),
|
|
360
|
+
hasDevScript ? 1 : 0,
|
|
361
|
+
hasBuildScript ? 1 : 0,
|
|
362
|
+
packageManager,
|
|
363
|
+
devServerRunning ? 1 : 0,
|
|
364
|
+
buildServerRunning ? 1 : 0,
|
|
365
|
+
ramInfo.total,
|
|
366
|
+
ramInfo.available,
|
|
367
|
+
ramInfo.total,
|
|
368
|
+
lowRamWarning ? 1 : 0,
|
|
369
|
+
buildAllowed ? 1 : 0,
|
|
370
|
+
now,
|
|
371
|
+
now
|
|
372
|
+
);
|
|
373
|
+
} catch (err) {
|
|
374
|
+
// SQLite failure - continue with in-memory result
|
|
375
|
+
console.error("[WorkspaceEnv] SQLite persistence failed:", err);
|
|
376
|
+
} finally {
|
|
377
|
+
if (db) {
|
|
378
|
+
try {
|
|
379
|
+
db.close();
|
|
380
|
+
} catch {
|
|
381
|
+
// Ignore close errors
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
return result;
|
|
158
387
|
}
|
|
159
388
|
|
|
160
389
|
/**
|
|
161
390
|
* Load cached workspace environment
|
|
162
391
|
*/
|
|
163
392
|
export function loadWorkspaceEnv(workspacePath: string): WorkspaceEnvResult | null {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
393
|
+
let db: DatabaseSync | null = null;
|
|
394
|
+
try {
|
|
395
|
+
db = initDatabase();
|
|
396
|
+
const row = db.prepare("SELECT * FROM workspace_env WHERE workspace_path = ?").get(workspacePath) as {
|
|
397
|
+
workspace_path: string;
|
|
398
|
+
mode: string;
|
|
399
|
+
evidence: string;
|
|
400
|
+
has_dev_script: number;
|
|
401
|
+
has_build_script: number;
|
|
402
|
+
package_manager: string | null;
|
|
403
|
+
dev_server_running: number;
|
|
404
|
+
build_server_running: number;
|
|
405
|
+
ram_total_gb: number | null;
|
|
406
|
+
ram_available_gb: number | null;
|
|
407
|
+
ram_effective_limit_gb: number | null;
|
|
408
|
+
low_ram_warning: number;
|
|
409
|
+
build_allowed: number;
|
|
410
|
+
detected_at: number;
|
|
411
|
+
} | undefined;
|
|
412
|
+
|
|
413
|
+
if (!row) return null;
|
|
414
|
+
|
|
415
|
+
// Check cache freshness (5 minutes for dev workspaces)
|
|
416
|
+
const ageMs = Date.now() - row.detected_at;
|
|
417
|
+
const MAX_CACHE_AGE_MS = 5 * 60 * 1000; // 5 minutes
|
|
418
|
+
|
|
419
|
+
if (ageMs > MAX_CACHE_AGE_MS) {
|
|
420
|
+
return null; // Cache stale
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
return {
|
|
424
|
+
workspacePath: row.workspace_path,
|
|
425
|
+
mode: row.mode as WorkspaceMode,
|
|
426
|
+
evidence: JSON.parse(row.evidence || "[]"),
|
|
427
|
+
hasDevScript: row.has_dev_script === 1,
|
|
428
|
+
hasBuildScript: row.has_build_script === 1,
|
|
429
|
+
packageManager: row.package_manager,
|
|
430
|
+
devServerRunning: row.dev_server_running === 1,
|
|
431
|
+
buildServerRunning: row.build_server_running === 1,
|
|
432
|
+
ramTotalGb: row.ram_total_gb,
|
|
433
|
+
ramAvailableGb: row.ram_available_gb,
|
|
434
|
+
ramEffectiveLimitGb: row.ram_effective_limit_gb,
|
|
435
|
+
lowRamWarning: row.low_ram_warning === 1,
|
|
436
|
+
buildAllowed: row.build_allowed === 1,
|
|
437
|
+
detectedAt: row.detected_at,
|
|
438
|
+
};
|
|
439
|
+
} catch {
|
|
440
|
+
return null;
|
|
441
|
+
} finally {
|
|
442
|
+
if (db) {
|
|
443
|
+
try {
|
|
444
|
+
db.close();
|
|
445
|
+
} catch {
|
|
446
|
+
// Ignore close errors
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
187
450
|
}
|
|
188
451
|
|
|
189
452
|
/**
|
|
@@ -193,86 +456,77 @@ export function getWorkspaceEnv(workspacePath: string): WorkspaceEnvResult {
|
|
|
193
456
|
// Try to load cached result first
|
|
194
457
|
const cached = loadWorkspaceEnv(workspacePath);
|
|
195
458
|
if (cached) {
|
|
196
|
-
|
|
197
|
-
const ageMs = Date.now() - cached.detectedAt;
|
|
198
|
-
if (ageMs < 60 * 60 * 1000) {
|
|
199
|
-
return cached;
|
|
200
|
-
}
|
|
459
|
+
return cached;
|
|
201
460
|
}
|
|
202
461
|
// Detect fresh
|
|
203
462
|
return detectWorkspaceEnv(workspacePath);
|
|
204
463
|
}
|
|
205
464
|
|
|
206
465
|
/**
|
|
207
|
-
*
|
|
466
|
+
* Revalidate workspace environment before impactful commands
|
|
208
467
|
*/
|
|
209
|
-
export function
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
468
|
+
export function revalidateWorkspaceEnv(workspacePath: string): WorkspaceEnvResult {
|
|
469
|
+
// Force fresh detection for command decisions
|
|
470
|
+
return detectWorkspaceEnv(workspacePath);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Format workspace env as agent-readable message
|
|
475
|
+
*/
|
|
476
|
+
export function formatEnvForAgent(workspacePath: string): string {
|
|
214
477
|
const env = getWorkspaceEnv(workspacePath);
|
|
215
478
|
|
|
216
|
-
|
|
217
|
-
|
|
479
|
+
let message = `[Workspace Environment]\n`;
|
|
480
|
+
message += `Mode: ${env.mode.toUpperCase()}\n`;
|
|
218
481
|
|
|
219
|
-
if (
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
reason: "Not detected as dev environment",
|
|
223
|
-
ramGb: env.ramGb,
|
|
224
|
-
};
|
|
482
|
+
if (env.evidence.length > 0) {
|
|
483
|
+
const sources = env.evidence.map((e) => `${e.source}:${e.value}`).join(", ");
|
|
484
|
+
message += `Evidence: ${sources}\n`;
|
|
225
485
|
}
|
|
226
486
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
487
|
+
message += `Dev script: ${env.hasDevScript ? "yes" : "no"}\n`;
|
|
488
|
+
message += `Build script: ${env.hasBuildScript ? "yes" : "no"}\n`;
|
|
489
|
+
|
|
490
|
+
if (env.devServerRunning) {
|
|
491
|
+
message += `Dev server: running\n`;
|
|
492
|
+
}
|
|
493
|
+
if (env.buildServerRunning) {
|
|
494
|
+
message += `Build server: running\n`;
|
|
233
495
|
}
|
|
234
496
|
|
|
235
|
-
if (env.
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
497
|
+
if (env.ramTotalGb !== null) {
|
|
498
|
+
const ramStr = env.ramAvailableGb !== null
|
|
499
|
+
? `${env.ramAvailableGb}/${env.ramTotalGb}GB available`
|
|
500
|
+
: `${env.ramTotalGb}GB total`;
|
|
501
|
+
message += `RAM: ${ramStr}\n`;
|
|
502
|
+
if (env.lowRamWarning) {
|
|
503
|
+
message += `WARNING: Low RAM - avoid parallel build + dev\n`;
|
|
504
|
+
}
|
|
241
505
|
}
|
|
242
506
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
507
|
+
message += `\n`;
|
|
508
|
+
message += `Build allowed: ${env.buildAllowed ? "yes" : "no - requires explicit request"}\n`;
|
|
509
|
+
|
|
510
|
+
if (env.mode === "unknown") {
|
|
511
|
+
message += `\nNOTE: Environment unknown. Explicitly ask user before build.\n`;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
return message;
|
|
248
515
|
}
|
|
249
516
|
|
|
250
517
|
/**
|
|
251
|
-
* Format workspace env as
|
|
518
|
+
* Format workspace env as compact status line for UI
|
|
252
519
|
*/
|
|
253
|
-
export function
|
|
254
|
-
const
|
|
255
|
-
const
|
|
520
|
+
export function formatEnvStatusLine(env: WorkspaceEnvResult): string {
|
|
521
|
+
const mode = env.mode.toUpperCase();
|
|
522
|
+
const devStatus = env.devServerRunning ? "running" : (env.hasDevScript ? "available" : "none");
|
|
523
|
+
const buildStatus = env.buildServerRunning ? "running" : (env.hasBuildScript ? "explicit request" : "none");
|
|
524
|
+
const ramStr = env.ramAvailableGb !== null ? `${env.ramAvailableGb}/${env.ramTotalGb}` : "";
|
|
256
525
|
|
|
257
|
-
let
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
message += `- Has dev script: ${details.hasDevScript ? "Yes" : "No"}\n`;
|
|
261
|
-
message += `- NODE_ENV: ${details.nodeEnv || "not set"}\n`;
|
|
262
|
-
message += `- Has .env file: ${details.hasEnvFile ? "Yes" : "No"}\n`;
|
|
263
|
-
message += `- RAM: ${details.ramGb ? `${details.ramGb}GB` : "unknown"}\n`;
|
|
264
|
-
message += `\n`;
|
|
265
|
-
|
|
266
|
-
if (env.preferDev) {
|
|
267
|
-
message += `RECOMMENDATION: Use \`dev\` mode (no build needed)\n`;
|
|
268
|
-
message += `Reason: ${env.reason}\n`;
|
|
269
|
-
message += `→ Run: \`bun run dev\` (or \`npm run dev\`)\n`;
|
|
270
|
-
message += `→ DON'T: Run \`build\` unless explicitly requested\n`;
|
|
271
|
-
} else {
|
|
272
|
-
message += `RECOMMENDATION: Build may be needed\n`;
|
|
273
|
-
message += `Reason: ${env.reason}\n`;
|
|
274
|
-
message += `→ Use: \`bun run build\` before running\n`;
|
|
526
|
+
let status = `Workspace: ${mode} | dev server: ${devStatus} | build: ${buildStatus}`;
|
|
527
|
+
if (ramStr) {
|
|
528
|
+
status += ` | RAM: ${ramStr}`;
|
|
275
529
|
}
|
|
276
530
|
|
|
277
|
-
return
|
|
531
|
+
return status;
|
|
278
532
|
}
|