pi-harness-runtime 1.1.394 → 1.1.396
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 +135 -4
- package/harness/glm-quota-logger.ts +17 -0
- package/harness/langchain/planner-logger.ts +15 -0
- package/harness/service-diagnostics.ts +32 -1
- package/index.ts +92 -3
- package/package.json +1 -1
- package/packages/a2a-adapter/package.json +1 -1
- package/packages/agent-policy/package.json +1 -1
- 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-format.d.ts +87 -0
- package/packages/contracts/dist/workspace-env-format.js +174 -6
- package/packages/contracts/dist/workspace-env.js +120 -9
- package/packages/contracts/package.json +1 -1
- package/packages/contracts/src/workspace-env-format.ts +209 -6
- package/packages/contracts/src/workspace-env.ts +128 -7
- package/packages/contracts/test/workspace-env.test.ts +177 -0
- 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/dist/continuation-controller.d.ts +47 -0
- package/packages/jev-judge/dist/continuation-controller.d.ts.map +1 -1
- package/packages/jev-judge/dist/continuation-controller.js +142 -16
- package/packages/jev-judge/dist/continuation-controller.js.map +1 -1
- package/packages/jev-judge/dist/index.d.ts +6 -0
- package/packages/jev-judge/dist/index.d.ts.map +1 -1
- package/packages/jev-judge/dist/index.js +6 -0
- package/packages/jev-judge/dist/index.js.map +1 -1
- package/packages/jev-judge/dist/validation-ledger.d.ts +129 -0
- package/packages/jev-judge/dist/validation-ledger.d.ts.map +1 -0
- package/packages/jev-judge/dist/validation-ledger.js +234 -0
- package/packages/jev-judge/dist/validation-ledger.js.map +1 -0
- package/packages/jev-judge/dist/validation-plan.d.ts +176 -0
- package/packages/jev-judge/dist/validation-plan.d.ts.map +1 -0
- package/packages/jev-judge/dist/validation-plan.js +293 -0
- package/packages/jev-judge/dist/validation-plan.js.map +1 -0
- package/packages/jev-judge/dist/validation-tie-breaker.d.ts +48 -0
- package/packages/jev-judge/dist/validation-tie-breaker.d.ts.map +1 -0
- package/packages/jev-judge/dist/validation-tie-breaker.js +83 -0
- package/packages/jev-judge/dist/validation-tie-breaker.js.map +1 -0
- package/packages/jev-judge/package.json +1 -1
- package/packages/jev-judge/src/continuation-controller.ts +179 -20
- package/packages/jev-judge/src/index.ts +41 -0
- package/packages/jev-judge/src/validation-ledger.ts +290 -0
- package/packages/jev-judge/src/validation-plan.ts +406 -0
- package/packages/jev-judge/src/validation-tie-breaker.ts +123 -0
- package/packages/jev-judge/tests/continuation-controller.test.ts +167 -0
- package/packages/jev-judge/tests/validation-ledger.test.ts +181 -0
- package/packages/jev-judge/tests/validation-plan.test.ts +170 -0
- package/packages/jev-judge/tests/validation-tie-breaker.test.ts +88 -0
- 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/logging/README.md +37 -0
- package/packages/logging/dist/buffer.d.ts +33 -0
- package/packages/logging/dist/buffer.d.ts.map +1 -0
- package/packages/logging/dist/buffer.js +60 -0
- package/packages/logging/dist/buffer.js.map +1 -0
- package/packages/logging/dist/config.d.ts +40 -0
- package/packages/logging/dist/config.d.ts.map +1 -0
- package/packages/logging/dist/config.js +114 -0
- package/packages/logging/dist/config.js.map +1 -0
- package/packages/logging/dist/index.d.ts +32 -0
- package/packages/logging/dist/index.d.ts.map +1 -0
- package/packages/logging/dist/index.js +31 -0
- package/packages/logging/dist/index.js.map +1 -0
- package/packages/logging/dist/redact.d.ts +43 -0
- package/packages/logging/dist/redact.d.ts.map +1 -0
- package/packages/logging/dist/redact.js +220 -0
- package/packages/logging/dist/redact.js.map +1 -0
- package/packages/logging/dist/sampler.d.ts +37 -0
- package/packages/logging/dist/sampler.d.ts.map +1 -0
- package/packages/logging/dist/sampler.js +78 -0
- package/packages/logging/dist/sampler.js.map +1 -0
- package/packages/logging/dist/shipper.d.ts +82 -0
- package/packages/logging/dist/shipper.d.ts.map +1 -0
- package/packages/logging/dist/shipper.js +238 -0
- package/packages/logging/dist/shipper.js.map +1 -0
- package/packages/logging/dist/singleton.d.ts +38 -0
- package/packages/logging/dist/singleton.d.ts.map +1 -0
- package/packages/logging/dist/singleton.js +95 -0
- package/packages/logging/dist/singleton.js.map +1 -0
- package/packages/logging/dist/spool.d.ts +39 -0
- package/packages/logging/dist/spool.d.ts.map +1 -0
- package/packages/logging/dist/spool.js +110 -0
- package/packages/logging/dist/spool.js.map +1 -0
- package/packages/logging/dist/transports/r2.d.ts +31 -0
- package/packages/logging/dist/transports/r2.d.ts.map +1 -0
- package/packages/logging/dist/transports/r2.js +60 -0
- package/packages/logging/dist/transports/r2.js.map +1 -0
- package/packages/logging/dist/transports/webhook.d.ts +22 -0
- package/packages/logging/dist/transports/webhook.d.ts.map +1 -0
- package/packages/logging/dist/transports/webhook.js +37 -0
- package/packages/logging/dist/transports/webhook.js.map +1 -0
- package/packages/logging/dist/types.d.ts +73 -0
- package/packages/logging/dist/types.d.ts.map +1 -0
- package/packages/logging/dist/types.js +19 -0
- package/packages/logging/dist/types.js.map +1 -0
- package/packages/logging/package.json +37 -0
- package/packages/logging/src/buffer.ts +77 -0
- package/packages/logging/src/config.ts +163 -0
- package/packages/logging/src/index.ts +87 -0
- package/packages/logging/src/redact.ts +271 -0
- package/packages/logging/src/sampler.ts +97 -0
- package/packages/logging/src/shipper.ts +281 -0
- package/packages/logging/src/singleton.ts +142 -0
- package/packages/logging/src/spool.ts +139 -0
- package/packages/logging/src/transports/r2.ts +86 -0
- package/packages/logging/src/transports/webhook.ts +54 -0
- package/packages/logging/src/types.ts +85 -0
- package/packages/logging/test/buffer.test.ts +44 -0
- package/packages/logging/test/config.test.ts +97 -0
- package/packages/logging/test/integration.test.ts +135 -0
- package/packages/logging/test/redact.test.ts +109 -0
- package/packages/logging/test/sampler.test.ts +57 -0
- package/packages/logging/test/shipper.test.ts +152 -0
- package/packages/logging/test/spool.test.ts +93 -0
- 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
- package/scripts/unified-webhook-server.ts +102 -0
- package/scripts/validation-run.ts +191 -0
|
@@ -82,6 +82,30 @@ export interface WorkspaceEnvResult {
|
|
|
82
82
|
* without the SQLite cache.
|
|
83
83
|
*/
|
|
84
84
|
export declare function isLauncherCommand(cmdline: string): boolean;
|
|
85
|
+
/**
|
|
86
|
+
* The RAM rule behind the "low memory" warning.
|
|
87
|
+
*
|
|
88
|
+
* The warning exists to avoid running a build AND a dev server in parallel,
|
|
89
|
+
* so it is about the headroom that is actually available - not about the size
|
|
90
|
+
* of the machine. A 15 GB laptop with 6.5 GB free can build; a 32 GB server
|
|
91
|
+
* with 3 GB free cannot.
|
|
92
|
+
*
|
|
93
|
+
* - Below 4 GB free: always low.
|
|
94
|
+
* - Below 16 GB total: a small machine needs real headroom (6 GB) before a
|
|
95
|
+
* parallel build and dev server are safe.
|
|
96
|
+
*/
|
|
97
|
+
export declare function isLowRamWarning(totalGb: number | null, availableGb: number | null): boolean;
|
|
98
|
+
/**
|
|
99
|
+
* The headroom a build needs before it is allowed in development mode.
|
|
100
|
+
*/
|
|
101
|
+
export declare const MIN_BUILD_HEADROOM_GB = 2;
|
|
102
|
+
/**
|
|
103
|
+
* The reason the build permission is (not) given.
|
|
104
|
+
*
|
|
105
|
+
* The agent has to know WHY a build is not allowed: an unknown environment
|
|
106
|
+
* needs an explicit request, a memory shortage needs a lighter build.
|
|
107
|
+
*/
|
|
108
|
+
export declare function buildAllowanceReason(env: WorkspaceEnvResult): string;
|
|
85
109
|
export type ServerState = "running" | "not-detected" | "not-checked";
|
|
86
110
|
/**
|
|
87
111
|
* The name of the operating system the harness runs on.
|
|
@@ -135,6 +159,14 @@ export declare const MASKED_MODE_VALUE = "<set>";
|
|
|
135
159
|
* the name of an environment before its value may decide the mode.
|
|
136
160
|
*/
|
|
137
161
|
export declare function isEnvShapedKey(key: string): boolean;
|
|
162
|
+
/**
|
|
163
|
+
* True for a key that belongs to the desktop session, not to the workspace.
|
|
164
|
+
*
|
|
165
|
+
* The rule is a namespace list, not a value check: the value of a session
|
|
166
|
+
* variable ("ubuntu", "wayland", "user") says nothing about the environment
|
|
167
|
+
* of a workspace, so such a variable must never name the mode.
|
|
168
|
+
*/
|
|
169
|
+
export declare function isSessionVariableKey(key: string): boolean;
|
|
138
170
|
/**
|
|
139
171
|
* True for a key that may hold a secret.
|
|
140
172
|
*/
|
|
@@ -202,6 +234,61 @@ export interface ModeResolution {
|
|
|
202
234
|
hint: DotenvVar | null;
|
|
203
235
|
source: "process.env" | ".env file" | null;
|
|
204
236
|
}
|
|
237
|
+
/**
|
|
238
|
+
* The deployment configuration of a repository.
|
|
239
|
+
*
|
|
240
|
+
* A repository that carries one of these files is deployed somewhere: it
|
|
241
|
+
* has a runtime environment of its own, so the shape of the repository
|
|
242
|
+
* cannot prove that the workspace is a development checkout.
|
|
243
|
+
*/
|
|
244
|
+
export declare const DEPLOYMENT_MARKER_FILES: readonly ["Dockerfile", "Containerfile", "docker-compose.yml", "docker-compose.yaml", "compose.yml", "compose.yaml", "Procfile", "fly.toml", "render.yaml", "railway.json", "vercel.json", "netlify.toml", "serverless.yml", "serverless.yaml", "app.yaml", "ecosystem.config.cjs", "ecosystem.config.js"];
|
|
245
|
+
/**
|
|
246
|
+
* The facts about the repository the workspace belongs to.
|
|
247
|
+
*
|
|
248
|
+
* They are read from the file system (and from git), never from a variable:
|
|
249
|
+
* a published package that is deployed nowhere is source code that the
|
|
250
|
+
* user runs on their own machine.
|
|
251
|
+
*/
|
|
252
|
+
export interface RepositoryFacts {
|
|
253
|
+
/** The workspace is a git checkout. */
|
|
254
|
+
isGitRepository: boolean;
|
|
255
|
+
/** The current branch ("" when it cannot be read). */
|
|
256
|
+
branch: string;
|
|
257
|
+
/** The package.json describes a package that is published to a registry. */
|
|
258
|
+
isPublishedPackage: boolean;
|
|
259
|
+
/** The repository carries a deployment configuration. */
|
|
260
|
+
hasDeploymentMarker: boolean;
|
|
261
|
+
/** A dev or build server runs in this workspace. */
|
|
262
|
+
hasRunningServer: boolean;
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* A mode that the facts about the repository named.
|
|
266
|
+
*/
|
|
267
|
+
export interface RepositoryModeDetection {
|
|
268
|
+
mode: WorkspaceMode;
|
|
269
|
+
/**
|
|
270
|
+
* The phrase that names the detection.
|
|
271
|
+
*
|
|
272
|
+
* It is used as the key of the evidence ("source-only repository",
|
|
273
|
+
* "git branch develop"), so the label can name it the same way it
|
|
274
|
+
* names a variable.
|
|
275
|
+
*/
|
|
276
|
+
key: string;
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Name the mode of a workspace from the facts about its repository.
|
|
280
|
+
*
|
|
281
|
+
* This is the LAST tier of the mode detection: it runs only when no
|
|
282
|
+
* variable names the environment at all. It never claims production - a
|
|
283
|
+
* checkout of a production branch is not a production environment, and the
|
|
284
|
+
* deployment marker of the repository keeps the answer honest.
|
|
285
|
+
*
|
|
286
|
+
* Two sets of facts name a development workspace:
|
|
287
|
+
* 1. a published package without a deployment configuration and without a
|
|
288
|
+
* running server: source code that the user runs on their own machine
|
|
289
|
+
* 2. a branch that names a development line of work ("develop")
|
|
290
|
+
*/
|
|
291
|
+
export declare function modeFromRepositoryFacts(facts: RepositoryFacts): RepositoryModeDetection | null;
|
|
205
292
|
/**
|
|
206
293
|
* Resolve the workspace mode from the variables that were found.
|
|
207
294
|
*
|
|
@@ -31,6 +31,46 @@
|
|
|
31
31
|
export function isLauncherCommand(cmdline) {
|
|
32
32
|
return /^(npm|bun|yarn|pnpm)\b.*\b(run\s+)?(dev|start)\b/.test(cmdline.trim());
|
|
33
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* The RAM rule behind the "low memory" warning.
|
|
36
|
+
*
|
|
37
|
+
* The warning exists to avoid running a build AND a dev server in parallel,
|
|
38
|
+
* so it is about the headroom that is actually available - not about the size
|
|
39
|
+
* of the machine. A 15 GB laptop with 6.5 GB free can build; a 32 GB server
|
|
40
|
+
* with 3 GB free cannot.
|
|
41
|
+
*
|
|
42
|
+
* - Below 4 GB free: always low.
|
|
43
|
+
* - Below 16 GB total: a small machine needs real headroom (6 GB) before a
|
|
44
|
+
* parallel build and dev server are safe.
|
|
45
|
+
*/
|
|
46
|
+
export function isLowRamWarning(totalGb, availableGb) {
|
|
47
|
+
if (availableGb === null)
|
|
48
|
+
return false;
|
|
49
|
+
if (availableGb < 4)
|
|
50
|
+
return true;
|
|
51
|
+
if (totalGb === null)
|
|
52
|
+
return false;
|
|
53
|
+
return totalGb < 16 && availableGb < 6;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* The headroom a build needs before it is allowed in development mode.
|
|
57
|
+
*/
|
|
58
|
+
export const MIN_BUILD_HEADROOM_GB = 2;
|
|
59
|
+
/**
|
|
60
|
+
* The reason the build permission is (not) given.
|
|
61
|
+
*
|
|
62
|
+
* The agent has to know WHY a build is not allowed: an unknown environment
|
|
63
|
+
* needs an explicit request, a memory shortage needs a lighter build.
|
|
64
|
+
*/
|
|
65
|
+
export function buildAllowanceReason(env) {
|
|
66
|
+
if (env.buildAllowed)
|
|
67
|
+
return "yes";
|
|
68
|
+
if (env.mode === "unknown")
|
|
69
|
+
return "no - requires explicit request";
|
|
70
|
+
if (env.lowRamWarning)
|
|
71
|
+
return "no - low memory";
|
|
72
|
+
return "no - requires explicit request";
|
|
73
|
+
}
|
|
34
74
|
/**
|
|
35
75
|
* The name of the operating system the harness runs on.
|
|
36
76
|
*
|
|
@@ -167,6 +207,49 @@ export const MASKED_MODE_VALUE = "<set>";
|
|
|
167
207
|
export function isEnvShapedKey(key) {
|
|
168
208
|
return /(^|_)(ENV|ENVIRONMENT|MODE|STAGE)$/i.test(key);
|
|
169
209
|
}
|
|
210
|
+
/**
|
|
211
|
+
* The namespaces of the desktop session variables.
|
|
212
|
+
*
|
|
213
|
+
* A session variable names the desktop session ("GNOME_SHELL_SESSION_MODE",
|
|
214
|
+
* "XDG_SESSION_TYPE"), never the environment of a workspace. Some of them
|
|
215
|
+
* end in "_MODE" or "_ENV", so without this list they look like an
|
|
216
|
+
* environment selector and decide the mode of a workspace that has no
|
|
217
|
+
* environment at all.
|
|
218
|
+
*/
|
|
219
|
+
const SESSION_VARIABLE_PREFIXES = [
|
|
220
|
+
"GNOME_",
|
|
221
|
+
"XDG_",
|
|
222
|
+
"DESKTOP_",
|
|
223
|
+
"DBUS_",
|
|
224
|
+
"GDM_",
|
|
225
|
+
"SDDM_",
|
|
226
|
+
"LIGHTDM_",
|
|
227
|
+
"GTK_",
|
|
228
|
+
"QT_",
|
|
229
|
+
"MATE_",
|
|
230
|
+
"KDE_",
|
|
231
|
+
"PLASMA_",
|
|
232
|
+
"UNITY_",
|
|
233
|
+
"XFCE_",
|
|
234
|
+
"CINNAMON_",
|
|
235
|
+
"WAYLAND_",
|
|
236
|
+
"MIR_",
|
|
237
|
+
"I3_",
|
|
238
|
+
"TERM_",
|
|
239
|
+
"SSH_",
|
|
240
|
+
"TMUX_",
|
|
241
|
+
];
|
|
242
|
+
/**
|
|
243
|
+
* True for a key that belongs to the desktop session, not to the workspace.
|
|
244
|
+
*
|
|
245
|
+
* The rule is a namespace list, not a value check: the value of a session
|
|
246
|
+
* variable ("ubuntu", "wayland", "user") says nothing about the environment
|
|
247
|
+
* of a workspace, so such a variable must never name the mode.
|
|
248
|
+
*/
|
|
249
|
+
export function isSessionVariableKey(key) {
|
|
250
|
+
const upper = key.toUpperCase();
|
|
251
|
+
return SESSION_VARIABLE_PREFIXES.some((prefix) => upper.startsWith(prefix));
|
|
252
|
+
}
|
|
170
253
|
/**
|
|
171
254
|
* True for a key that may hold a secret.
|
|
172
255
|
*/
|
|
@@ -212,6 +295,9 @@ export function detectModeHint(vars, options = {}) {
|
|
|
212
295
|
const nodeEnv = vars.find((v) => v.key === "NODE_ENV");
|
|
213
296
|
if (nodeEnv)
|
|
214
297
|
return nodeEnv;
|
|
298
|
+
// A session variable is not an environment selector: it must never
|
|
299
|
+
// become the hint that names the mode.
|
|
300
|
+
const usableVars = vars.filter((v) => !isSessionVariableKey(v.key));
|
|
215
301
|
// The value match is opt-in: a caller that passes the process env
|
|
216
302
|
// variables must not let an unrelated variable that happens to hold the
|
|
217
303
|
// word "development" decide the mode.
|
|
@@ -219,7 +305,7 @@ export function detectModeHint(vars, options = {}) {
|
|
|
219
305
|
// An environment shaped key whose value names a known mode wins: an
|
|
220
306
|
// earlier "APP_MODE=staging" or an empty "APP_ENV=" of the same file
|
|
221
307
|
// must not shadow the "AMOS_ENV=development" of the same file.
|
|
222
|
-
const usable =
|
|
308
|
+
const usable = usableVars.find((v) => v.key !== "NODE_ENV" &&
|
|
223
309
|
isEnvShapedKey(v.key) &&
|
|
224
310
|
modeFromValue(v.value) !== null);
|
|
225
311
|
if (usable)
|
|
@@ -227,7 +313,7 @@ export function detectModeHint(vars, options = {}) {
|
|
|
227
313
|
}
|
|
228
314
|
// No usable key: fall back to the first environment shaped key so that
|
|
229
315
|
// the label can name it (it names no known mode).
|
|
230
|
-
return
|
|
316
|
+
return (usableVars.find((v) => v.key !== "NODE_ENV" && isEnvShapedKey(v.key)) ?? null);
|
|
231
317
|
}
|
|
232
318
|
/**
|
|
233
319
|
* The dotenv files of a workspace, in the order they are read.
|
|
@@ -289,12 +375,71 @@ export function collectProcessEnvVars(env = process.env) {
|
|
|
289
375
|
const value = env[key];
|
|
290
376
|
if (value === undefined)
|
|
291
377
|
continue;
|
|
378
|
+
// A session variable of the desktop is never an environment
|
|
379
|
+
// selector, so it is not even collected as a candidate.
|
|
380
|
+
if (isSessionVariableKey(key))
|
|
381
|
+
continue;
|
|
292
382
|
if (key === "NODE_ENV" || isEnvShapedKey(key)) {
|
|
293
383
|
vars.push({ key, value });
|
|
294
384
|
}
|
|
295
385
|
}
|
|
296
386
|
return vars;
|
|
297
387
|
}
|
|
388
|
+
/**
|
|
389
|
+
* The deployment configuration of a repository.
|
|
390
|
+
*
|
|
391
|
+
* A repository that carries one of these files is deployed somewhere: it
|
|
392
|
+
* has a runtime environment of its own, so the shape of the repository
|
|
393
|
+
* cannot prove that the workspace is a development checkout.
|
|
394
|
+
*/
|
|
395
|
+
export const DEPLOYMENT_MARKER_FILES = [
|
|
396
|
+
"Dockerfile",
|
|
397
|
+
"Containerfile",
|
|
398
|
+
"docker-compose.yml",
|
|
399
|
+
"docker-compose.yaml",
|
|
400
|
+
"compose.yml",
|
|
401
|
+
"compose.yaml",
|
|
402
|
+
"Procfile",
|
|
403
|
+
"fly.toml",
|
|
404
|
+
"render.yaml",
|
|
405
|
+
"railway.json",
|
|
406
|
+
"vercel.json",
|
|
407
|
+
"netlify.toml",
|
|
408
|
+
"serverless.yml",
|
|
409
|
+
"serverless.yaml",
|
|
410
|
+
"app.yaml",
|
|
411
|
+
"ecosystem.config.cjs",
|
|
412
|
+
"ecosystem.config.js",
|
|
413
|
+
];
|
|
414
|
+
/**
|
|
415
|
+
* The branches that name a development line of work.
|
|
416
|
+
*/
|
|
417
|
+
const DEVELOPMENT_BRANCHES = new Set(["develop", "development"]);
|
|
418
|
+
/**
|
|
419
|
+
* Name the mode of a workspace from the facts about its repository.
|
|
420
|
+
*
|
|
421
|
+
* This is the LAST tier of the mode detection: it runs only when no
|
|
422
|
+
* variable names the environment at all. It never claims production - a
|
|
423
|
+
* checkout of a production branch is not a production environment, and the
|
|
424
|
+
* deployment marker of the repository keeps the answer honest.
|
|
425
|
+
*
|
|
426
|
+
* Two sets of facts name a development workspace:
|
|
427
|
+
* 1. a published package without a deployment configuration and without a
|
|
428
|
+
* running server: source code that the user runs on their own machine
|
|
429
|
+
* 2. a branch that names a development line of work ("develop")
|
|
430
|
+
*/
|
|
431
|
+
export function modeFromRepositoryFacts(facts) {
|
|
432
|
+
if (!facts.isGitRepository)
|
|
433
|
+
return null;
|
|
434
|
+
if (facts.isPublishedPackage && !facts.hasDeploymentMarker && !facts.hasRunningServer) {
|
|
435
|
+
return { mode: "development", key: "source-only repository" };
|
|
436
|
+
}
|
|
437
|
+
const branch = facts.branch.trim().toLowerCase();
|
|
438
|
+
if (DEVELOPMENT_BRANCHES.has(branch)) {
|
|
439
|
+
return { mode: "development", key: `git branch ${branch}` };
|
|
440
|
+
}
|
|
441
|
+
return null;
|
|
442
|
+
}
|
|
298
443
|
/**
|
|
299
444
|
* Resolve the workspace mode from the variables that were found.
|
|
300
445
|
*
|
|
@@ -346,6 +491,16 @@ function findModeEvidence(env) {
|
|
|
346
491
|
// A value that names a known mode wins over a value that cannot be read.
|
|
347
492
|
return candidates.find((e) => modeFromValue(e.value) !== null) ?? candidates[0];
|
|
348
493
|
}
|
|
494
|
+
/**
|
|
495
|
+
* The evidence entry that names the mode through the repository (if any).
|
|
496
|
+
*
|
|
497
|
+
* A detection is weaker than a variable: it is the shape of the checkout,
|
|
498
|
+
* not a declaration of the environment. It is therefore only used when no
|
|
499
|
+
* variable names the environment at all.
|
|
500
|
+
*/
|
|
501
|
+
function findDetectionEvidence(env) {
|
|
502
|
+
return env.evidence.find((e) => e.source === "detection");
|
|
503
|
+
}
|
|
349
504
|
/**
|
|
350
505
|
* Human readable mode label.
|
|
351
506
|
*
|
|
@@ -361,9 +516,14 @@ export function describeWorkspaceMode(env) {
|
|
|
361
516
|
const hint = findModeEvidence(env);
|
|
362
517
|
const where = hint?.file ? ` in ${hint.file}` : "";
|
|
363
518
|
if (env.mode !== "unknown") {
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
519
|
+
if (hint)
|
|
520
|
+
return `${env.mode.toUpperCase()} (from ${hint.key}${where})`;
|
|
521
|
+
// The mode came from the repository, not from a variable: the label
|
|
522
|
+
// names the reason ("DEVELOPMENT (source-only repository)").
|
|
523
|
+
const detection = findDetectionEvidence(env);
|
|
524
|
+
if (detection)
|
|
525
|
+
return `${env.mode.toUpperCase()} (${detection.key})`;
|
|
526
|
+
return env.mode.toUpperCase();
|
|
367
527
|
}
|
|
368
528
|
if (hint) {
|
|
369
529
|
// The stored mode and the evidence can disagree (a stale cache row):
|
|
@@ -382,6 +542,14 @@ export function describeWorkspaceMode(env) {
|
|
|
382
542
|
}
|
|
383
543
|
return `UNKNOWN (${hint.key}=${hint.value} is not a known mode${where})`;
|
|
384
544
|
}
|
|
545
|
+
// A stale cache row can carry a detection next to an unknown mode: the
|
|
546
|
+
// detection names a mode, so the mode is shown.
|
|
547
|
+
const detection = findDetectionEvidence(env);
|
|
548
|
+
if (detection) {
|
|
549
|
+
const named = modeFromValue(detection.value);
|
|
550
|
+
if (named)
|
|
551
|
+
return `${named.toUpperCase()} (${detection.key})`;
|
|
552
|
+
}
|
|
385
553
|
return "UNKNOWN (no NODE_ENV or environment variable found)";
|
|
386
554
|
}
|
|
387
555
|
export function describeServerState(stateOrRunning, hasScript) {
|
|
@@ -481,7 +649,7 @@ export function formatEnvForAgentResult(env) {
|
|
|
481
649
|
}
|
|
482
650
|
}
|
|
483
651
|
message += `\n`;
|
|
484
|
-
message += `Build allowed: ${env
|
|
652
|
+
message += `Build allowed: ${buildAllowanceReason(env)}\n`;
|
|
485
653
|
if (env.mode === "unknown") {
|
|
486
654
|
message += `\nNOTE: Environment unknown - a dev/build script only means a server CAN be started, it does not establish the workspace mode. Explicitly ask the user before build.\n`;
|
|
487
655
|
}
|
|
@@ -13,10 +13,10 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import { DatabaseSync } from "node:sqlite";
|
|
15
15
|
import { execSync } from "child_process";
|
|
16
|
-
import { existsSync, readFileSync, mkdirSync, readlinkSync, realpathSync } from "fs";
|
|
16
|
+
import { existsSync, readFileSync, readdirSync, mkdirSync, readlinkSync, realpathSync } from "fs";
|
|
17
17
|
import { join } from "path";
|
|
18
18
|
import { platform, totalmem, freemem } from "os";
|
|
19
|
-
import { canShowModeValue, collectDotenvVars, collectProcessEnvVars, describeCwdCommand, describeOs, describeServerState, describeWorkspaceMode, detectModeHint, filterOwnPids, formatEnvForAgentResult, formatEnvStatusLine as formatStatusLine, formatWorkspacePath, isLauncherCommand, MASKED_MODE_VALUE, modeFromValue, orderDotenvFileNames, parseDotenvVars, resolveWorkspaceMode, } from "./workspace-env-format.js";
|
|
19
|
+
import { canShowModeValue, collectDotenvVars, collectProcessEnvVars, describeCwdCommand, describeOs, describeServerState, describeWorkspaceMode, detectModeHint, DEPLOYMENT_MARKER_FILES, filterOwnPids, formatEnvForAgentResult, formatEnvStatusLine as formatStatusLine, formatWorkspacePath, isLauncherCommand, isLowRamWarning, MASKED_MODE_VALUE, MIN_BUILD_HEADROOM_GB, modeFromRepositoryFacts, modeFromValue, orderDotenvFileNames, parseDotenvVars, resolveWorkspaceMode, } from "./workspace-env-format.js";
|
|
20
20
|
import { readDotenvFiles } from "./workspace-dotenv.js";
|
|
21
21
|
const STORE_DIR = join(process.env.HOME || "/home/frappe", ".pi-harness-runtime");
|
|
22
22
|
/**
|
|
@@ -95,11 +95,10 @@ function detectRamInfo() {
|
|
|
95
95
|
const freeBytes = freemem();
|
|
96
96
|
const totalGb = Math.round((totalBytes / (1024 ** 3)) * 10) / 10;
|
|
97
97
|
const availableGb = Math.round((freeBytes / (1024 ** 3)) * 10) / 10;
|
|
98
|
-
const LOW_RAM_GB = 16;
|
|
99
98
|
return {
|
|
100
99
|
total: totalGb,
|
|
101
100
|
available: availableGb,
|
|
102
|
-
lowWarning: totalGb
|
|
101
|
+
lowWarning: isLowRamWarning(totalGb, availableGb),
|
|
103
102
|
};
|
|
104
103
|
}
|
|
105
104
|
catch {
|
|
@@ -520,6 +519,97 @@ function detectPackageManager(workspacePath) {
|
|
|
520
519
|
return "npm";
|
|
521
520
|
return null;
|
|
522
521
|
}
|
|
522
|
+
/**
|
|
523
|
+
* The git repository of a workspace.
|
|
524
|
+
*
|
|
525
|
+
* The root of the repository is the unit of the detection: a package
|
|
526
|
+
* published from a monorepo is described by the root, not by the folder
|
|
527
|
+
* the harness happens to run in.
|
|
528
|
+
*/
|
|
529
|
+
function detectGitFacts(workspacePath) {
|
|
530
|
+
try {
|
|
531
|
+
const out = execSync("git rev-parse --show-toplevel 2>/dev/null; git rev-parse --abbrev-ref HEAD 2>/dev/null", { cwd: workspacePath, encoding: "utf8", timeout: 1500 });
|
|
532
|
+
const lines = out.split("\n").map((line) => line.trim());
|
|
533
|
+
const root = lines[0] ?? "";
|
|
534
|
+
const branch = lines[1] ?? "";
|
|
535
|
+
// A detached HEAD reports "HEAD" - that is not a branch name.
|
|
536
|
+
return { root, branch: branch === "HEAD" ? "" : branch };
|
|
537
|
+
}
|
|
538
|
+
catch {
|
|
539
|
+
// No git, no repository, or a timeout: nothing is known.
|
|
540
|
+
return { root: "", branch: "" };
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
/**
|
|
544
|
+
* True when the package is published to a registry.
|
|
545
|
+
*
|
|
546
|
+
* A package that is published names a version and declares what it ships
|
|
547
|
+
* ("files", "bin" or a "publishConfig"). A repository that only holds an
|
|
548
|
+
* application usually declares none of them - so this stays a careful
|
|
549
|
+
* signal instead of "every package.json is published".
|
|
550
|
+
*/
|
|
551
|
+
function isPublishedPackage(packageDir) {
|
|
552
|
+
const pkgJsonPath = join(packageDir, "package.json");
|
|
553
|
+
if (!existsSync(pkgJsonPath))
|
|
554
|
+
return false;
|
|
555
|
+
try {
|
|
556
|
+
const pkg = JSON.parse(readFileSync(pkgJsonPath, "utf8"));
|
|
557
|
+
if (!pkg || typeof pkg !== "object")
|
|
558
|
+
return false;
|
|
559
|
+
if (pkg.private === true)
|
|
560
|
+
return false;
|
|
561
|
+
if (typeof pkg.name !== "string" || !pkg.name)
|
|
562
|
+
return false;
|
|
563
|
+
if (typeof pkg.version !== "string" || !pkg.version)
|
|
564
|
+
return false;
|
|
565
|
+
return (Array.isArray(pkg.files) ||
|
|
566
|
+
(typeof pkg.bin === "object" && pkg.bin !== null) ||
|
|
567
|
+
(typeof pkg.publishConfig === "object" && pkg.publishConfig !== null));
|
|
568
|
+
}
|
|
569
|
+
catch {
|
|
570
|
+
return false;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
/**
|
|
574
|
+
* True when the repository carries a deployment configuration.
|
|
575
|
+
*
|
|
576
|
+
* A deployment file or a deploy workflow means the repository has a
|
|
577
|
+
* runtime environment of its own - the shape of the repository can then no
|
|
578
|
+
* longer say that the workspace is a development checkout.
|
|
579
|
+
*/
|
|
580
|
+
function hasDeploymentConfiguration(repoRoot) {
|
|
581
|
+
for (const marker of DEPLOYMENT_MARKER_FILES) {
|
|
582
|
+
if (existsSync(join(repoRoot, marker)))
|
|
583
|
+
return true;
|
|
584
|
+
}
|
|
585
|
+
const workflowDir = join(repoRoot, ".github", "workflows");
|
|
586
|
+
if (existsSync(workflowDir)) {
|
|
587
|
+
try {
|
|
588
|
+
for (const name of readdirSync(workflowDir)) {
|
|
589
|
+
if (/deploy/i.test(name))
|
|
590
|
+
return true;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
catch {
|
|
594
|
+
// An unreadable directory proves nothing.
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
return false;
|
|
598
|
+
}
|
|
599
|
+
/**
|
|
600
|
+
* Read the facts about the repository the workspace belongs to.
|
|
601
|
+
*/
|
|
602
|
+
function detectRepositoryFacts(workspacePath, hasRunningServer) {
|
|
603
|
+
const { root, branch } = detectGitFacts(workspacePath);
|
|
604
|
+
const repoRoot = root || workspacePath;
|
|
605
|
+
return {
|
|
606
|
+
isGitRepository: root !== "",
|
|
607
|
+
branch,
|
|
608
|
+
isPublishedPackage: isPublishedPackage(repoRoot),
|
|
609
|
+
hasDeploymentMarker: hasDeploymentConfiguration(repoRoot),
|
|
610
|
+
hasRunningServer,
|
|
611
|
+
};
|
|
612
|
+
}
|
|
523
613
|
/**
|
|
524
614
|
* Determine workspace mode with proper precedence
|
|
525
615
|
* Never assume production - unknown requires investigation
|
|
@@ -531,8 +621,11 @@ function detectPackageManager(workspacePath) {
|
|
|
531
621
|
* key whose value names a known mode, then any other environment shaped
|
|
532
622
|
* key
|
|
533
623
|
* 3. an environment shaped key of the process env (the files are silent)
|
|
624
|
+
* 4. the facts about the repository (only when NO variable names the
|
|
625
|
+
* environment at all) - a published source repository without a runtime
|
|
626
|
+
* of its own is a development checkout
|
|
534
627
|
*/
|
|
535
|
-
function determineMode(processVars, dotenvVars, evidence) {
|
|
628
|
+
function determineMode(processVars, dotenvVars, evidence, repositoryFacts) {
|
|
536
629
|
const resolved = resolveWorkspaceMode(processVars, dotenvVars);
|
|
537
630
|
if (resolved.hint && resolved.source) {
|
|
538
631
|
// The value is only kept when it is provably not a secret: the
|
|
@@ -546,8 +639,21 @@ function determineMode(processVars, dotenvVars, evidence) {
|
|
|
546
639
|
timestamp: Date.now(),
|
|
547
640
|
file: resolved.hint.file,
|
|
548
641
|
});
|
|
549
|
-
|
|
550
|
-
|
|
642
|
+
// An explicit variable decides - even when it names no known mode
|
|
643
|
+
// ("staging"). The mode stays unknown and the label names the value.
|
|
644
|
+
return resolved.mode;
|
|
645
|
+
}
|
|
646
|
+
// No variable names the environment: the repository may still name it.
|
|
647
|
+
const detection = modeFromRepositoryFacts(repositoryFacts);
|
|
648
|
+
if (!detection)
|
|
649
|
+
return resolved.mode;
|
|
650
|
+
evidence.push({
|
|
651
|
+
source: "detection",
|
|
652
|
+
key: detection.key,
|
|
653
|
+
value: detection.mode,
|
|
654
|
+
timestamp: Date.now(),
|
|
655
|
+
});
|
|
656
|
+
return detection.mode;
|
|
551
657
|
}
|
|
552
658
|
/**
|
|
553
659
|
* Detect and store workspace environment
|
|
@@ -594,14 +700,19 @@ export function detectWorkspaceEnv(workspacePath) {
|
|
|
594
700
|
// workspace (see detectServerStates).
|
|
595
701
|
const { dev: devServerState, build: buildServerState } = detectServerStates(workspacePath);
|
|
596
702
|
// Determine mode - NEVER assume production
|
|
597
|
-
const
|
|
703
|
+
const repositoryFacts = detectRepositoryFacts(workspacePath, devServerState === "running" || buildServerState === "running");
|
|
704
|
+
const mode = determineMode(processVars, dotenvVars, evidence, repositoryFacts);
|
|
598
705
|
// Determine build permission
|
|
599
706
|
let buildAllowed = false;
|
|
600
707
|
if (mode === "production") {
|
|
601
708
|
buildAllowed = true; // Production build is allowed
|
|
602
709
|
}
|
|
603
710
|
else if (mode === "development") {
|
|
604
|
-
|
|
711
|
+
// A build needs headroom, not a big machine. The low-memory warning is
|
|
712
|
+
// about running a build AND a dev server in parallel; a build on its
|
|
713
|
+
// own only needs enough free memory to finish.
|
|
714
|
+
buildAllowed =
|
|
715
|
+
ramInfo.available === null ? false : ramInfo.available >= MIN_BUILD_HEADROOM_GB;
|
|
605
716
|
}
|
|
606
717
|
else {
|
|
607
718
|
buildAllowed = false; // Unknown requires investigation
|