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
|
@@ -90,6 +90,43 @@ export function isLauncherCommand(cmdline: string): boolean {
|
|
|
90
90
|
return /^(npm|bun|yarn|pnpm)\b.*\b(run\s+)?(dev|start)\b/.test(cmdline.trim());
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
+
/**
|
|
94
|
+
* The RAM rule behind the "low memory" warning.
|
|
95
|
+
*
|
|
96
|
+
* The warning exists to avoid running a build AND a dev server in parallel,
|
|
97
|
+
* so it is about the headroom that is actually available - not about the size
|
|
98
|
+
* of the machine. A 15 GB laptop with 6.5 GB free can build; a 32 GB server
|
|
99
|
+
* with 3 GB free cannot.
|
|
100
|
+
*
|
|
101
|
+
* - Below 4 GB free: always low.
|
|
102
|
+
* - Below 16 GB total: a small machine needs real headroom (6 GB) before a
|
|
103
|
+
* parallel build and dev server are safe.
|
|
104
|
+
*/
|
|
105
|
+
export function isLowRamWarning(totalGb: number | null, availableGb: number | null): boolean {
|
|
106
|
+
if (availableGb === null) return false;
|
|
107
|
+
if (availableGb < 4) return true;
|
|
108
|
+
if (totalGb === null) return false;
|
|
109
|
+
return totalGb < 16 && availableGb < 6;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* The headroom a build needs before it is allowed in development mode.
|
|
114
|
+
*/
|
|
115
|
+
export const MIN_BUILD_HEADROOM_GB = 2;
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* The reason the build permission is (not) given.
|
|
119
|
+
*
|
|
120
|
+
* The agent has to know WHY a build is not allowed: an unknown environment
|
|
121
|
+
* needs an explicit request, a memory shortage needs a lighter build.
|
|
122
|
+
*/
|
|
123
|
+
export function buildAllowanceReason(env: WorkspaceEnvResult): string {
|
|
124
|
+
if (env.buildAllowed) return "yes";
|
|
125
|
+
if (env.mode === "unknown") return "no - requires explicit request";
|
|
126
|
+
if (env.lowRamWarning) return "no - low memory";
|
|
127
|
+
return "no - requires explicit request";
|
|
128
|
+
}
|
|
129
|
+
|
|
93
130
|
export type ServerState = "running" | "not-detected" | "not-checked";
|
|
94
131
|
|
|
95
132
|
/**
|
|
@@ -231,6 +268,51 @@ export function isEnvShapedKey(key: string): boolean {
|
|
|
231
268
|
return /(^|_)(ENV|ENVIRONMENT|MODE|STAGE)$/i.test(key);
|
|
232
269
|
}
|
|
233
270
|
|
|
271
|
+
/**
|
|
272
|
+
* The namespaces of the desktop session variables.
|
|
273
|
+
*
|
|
274
|
+
* A session variable names the desktop session ("GNOME_SHELL_SESSION_MODE",
|
|
275
|
+
* "XDG_SESSION_TYPE"), never the environment of a workspace. Some of them
|
|
276
|
+
* end in "_MODE" or "_ENV", so without this list they look like an
|
|
277
|
+
* environment selector and decide the mode of a workspace that has no
|
|
278
|
+
* environment at all.
|
|
279
|
+
*/
|
|
280
|
+
const SESSION_VARIABLE_PREFIXES = [
|
|
281
|
+
"GNOME_",
|
|
282
|
+
"XDG_",
|
|
283
|
+
"DESKTOP_",
|
|
284
|
+
"DBUS_",
|
|
285
|
+
"GDM_",
|
|
286
|
+
"SDDM_",
|
|
287
|
+
"LIGHTDM_",
|
|
288
|
+
"GTK_",
|
|
289
|
+
"QT_",
|
|
290
|
+
"MATE_",
|
|
291
|
+
"KDE_",
|
|
292
|
+
"PLASMA_",
|
|
293
|
+
"UNITY_",
|
|
294
|
+
"XFCE_",
|
|
295
|
+
"CINNAMON_",
|
|
296
|
+
"WAYLAND_",
|
|
297
|
+
"MIR_",
|
|
298
|
+
"I3_",
|
|
299
|
+
"TERM_",
|
|
300
|
+
"SSH_",
|
|
301
|
+
"TMUX_",
|
|
302
|
+
];
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* True for a key that belongs to the desktop session, not to the workspace.
|
|
306
|
+
*
|
|
307
|
+
* The rule is a namespace list, not a value check: the value of a session
|
|
308
|
+
* variable ("ubuntu", "wayland", "user") says nothing about the environment
|
|
309
|
+
* of a workspace, so such a variable must never name the mode.
|
|
310
|
+
*/
|
|
311
|
+
export function isSessionVariableKey(key: string): boolean {
|
|
312
|
+
const upper = key.toUpperCase();
|
|
313
|
+
return SESSION_VARIABLE_PREFIXES.some((prefix) => upper.startsWith(prefix));
|
|
314
|
+
}
|
|
315
|
+
|
|
234
316
|
/**
|
|
235
317
|
* True for a key that may hold a secret.
|
|
236
318
|
*/
|
|
@@ -282,6 +364,9 @@ export function detectModeHint(
|
|
|
282
364
|
): DotenvVar | null {
|
|
283
365
|
const nodeEnv = vars.find((v) => v.key === "NODE_ENV");
|
|
284
366
|
if (nodeEnv) return nodeEnv;
|
|
367
|
+
// A session variable is not an environment selector: it must never
|
|
368
|
+
// become the hint that names the mode.
|
|
369
|
+
const usableVars = vars.filter((v) => !isSessionVariableKey(v.key));
|
|
285
370
|
// The value match is opt-in: a caller that passes the process env
|
|
286
371
|
// variables must not let an unrelated variable that happens to hold the
|
|
287
372
|
// word "development" decide the mode.
|
|
@@ -289,7 +374,7 @@ export function detectModeHint(
|
|
|
289
374
|
// An environment shaped key whose value names a known mode wins: an
|
|
290
375
|
// earlier "APP_MODE=staging" or an empty "APP_ENV=" of the same file
|
|
291
376
|
// must not shadow the "AMOS_ENV=development" of the same file.
|
|
292
|
-
const usable =
|
|
377
|
+
const usable = usableVars.find(
|
|
293
378
|
(v) =>
|
|
294
379
|
v.key !== "NODE_ENV" &&
|
|
295
380
|
isEnvShapedKey(v.key) &&
|
|
@@ -299,7 +384,9 @@ export function detectModeHint(
|
|
|
299
384
|
}
|
|
300
385
|
// No usable key: fall back to the first environment shaped key so that
|
|
301
386
|
// the label can name it (it names no known mode).
|
|
302
|
-
return
|
|
387
|
+
return (
|
|
388
|
+
usableVars.find((v) => v.key !== "NODE_ENV" && isEnvShapedKey(v.key)) ?? null
|
|
389
|
+
);
|
|
303
390
|
}
|
|
304
391
|
|
|
305
392
|
/**
|
|
@@ -363,6 +450,9 @@ export function collectProcessEnvVars(env: NodeJS.ProcessEnv = process.env): Dot
|
|
|
363
450
|
for (const key of Object.keys(env)) {
|
|
364
451
|
const value = env[key];
|
|
365
452
|
if (value === undefined) continue;
|
|
453
|
+
// A session variable of the desktop is never an environment
|
|
454
|
+
// selector, so it is not even collected as a candidate.
|
|
455
|
+
if (isSessionVariableKey(key)) continue;
|
|
366
456
|
if (key === "NODE_ENV" || isEnvShapedKey(key)) {
|
|
367
457
|
vars.push({ key, value });
|
|
368
458
|
}
|
|
@@ -379,6 +469,98 @@ export interface ModeResolution {
|
|
|
379
469
|
source: "process.env" | ".env file" | null;
|
|
380
470
|
}
|
|
381
471
|
|
|
472
|
+
/**
|
|
473
|
+
* The deployment configuration of a repository.
|
|
474
|
+
*
|
|
475
|
+
* A repository that carries one of these files is deployed somewhere: it
|
|
476
|
+
* has a runtime environment of its own, so the shape of the repository
|
|
477
|
+
* cannot prove that the workspace is a development checkout.
|
|
478
|
+
*/
|
|
479
|
+
export const DEPLOYMENT_MARKER_FILES = [
|
|
480
|
+
"Dockerfile",
|
|
481
|
+
"Containerfile",
|
|
482
|
+
"docker-compose.yml",
|
|
483
|
+
"docker-compose.yaml",
|
|
484
|
+
"compose.yml",
|
|
485
|
+
"compose.yaml",
|
|
486
|
+
"Procfile",
|
|
487
|
+
"fly.toml",
|
|
488
|
+
"render.yaml",
|
|
489
|
+
"railway.json",
|
|
490
|
+
"vercel.json",
|
|
491
|
+
"netlify.toml",
|
|
492
|
+
"serverless.yml",
|
|
493
|
+
"serverless.yaml",
|
|
494
|
+
"app.yaml",
|
|
495
|
+
"ecosystem.config.cjs",
|
|
496
|
+
"ecosystem.config.js",
|
|
497
|
+
] as const;
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* The facts about the repository the workspace belongs to.
|
|
501
|
+
*
|
|
502
|
+
* They are read from the file system (and from git), never from a variable:
|
|
503
|
+
* a published package that is deployed nowhere is source code that the
|
|
504
|
+
* user runs on their own machine.
|
|
505
|
+
*/
|
|
506
|
+
export interface RepositoryFacts {
|
|
507
|
+
/** The workspace is a git checkout. */
|
|
508
|
+
isGitRepository: boolean;
|
|
509
|
+
/** The current branch ("" when it cannot be read). */
|
|
510
|
+
branch: string;
|
|
511
|
+
/** The package.json describes a package that is published to a registry. */
|
|
512
|
+
isPublishedPackage: boolean;
|
|
513
|
+
/** The repository carries a deployment configuration. */
|
|
514
|
+
hasDeploymentMarker: boolean;
|
|
515
|
+
/** A dev or build server runs in this workspace. */
|
|
516
|
+
hasRunningServer: boolean;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/**
|
|
520
|
+
* A mode that the facts about the repository named.
|
|
521
|
+
*/
|
|
522
|
+
export interface RepositoryModeDetection {
|
|
523
|
+
mode: WorkspaceMode;
|
|
524
|
+
/**
|
|
525
|
+
* The phrase that names the detection.
|
|
526
|
+
*
|
|
527
|
+
* It is used as the key of the evidence ("source-only repository",
|
|
528
|
+
* "git branch develop"), so the label can name it the same way it
|
|
529
|
+
* names a variable.
|
|
530
|
+
*/
|
|
531
|
+
key: string;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
/**
|
|
535
|
+
* The branches that name a development line of work.
|
|
536
|
+
*/
|
|
537
|
+
const DEVELOPMENT_BRANCHES = new Set(["develop", "development"]);
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* Name the mode of a workspace from the facts about its repository.
|
|
541
|
+
*
|
|
542
|
+
* This is the LAST tier of the mode detection: it runs only when no
|
|
543
|
+
* variable names the environment at all. It never claims production - a
|
|
544
|
+
* checkout of a production branch is not a production environment, and the
|
|
545
|
+
* deployment marker of the repository keeps the answer honest.
|
|
546
|
+
*
|
|
547
|
+
* Two sets of facts name a development workspace:
|
|
548
|
+
* 1. a published package without a deployment configuration and without a
|
|
549
|
+
* running server: source code that the user runs on their own machine
|
|
550
|
+
* 2. a branch that names a development line of work ("develop")
|
|
551
|
+
*/
|
|
552
|
+
export function modeFromRepositoryFacts(facts: RepositoryFacts): RepositoryModeDetection | null {
|
|
553
|
+
if (!facts.isGitRepository) return null;
|
|
554
|
+
if (facts.isPublishedPackage && !facts.hasDeploymentMarker && !facts.hasRunningServer) {
|
|
555
|
+
return { mode: "development", key: "source-only repository" };
|
|
556
|
+
}
|
|
557
|
+
const branch = facts.branch.trim().toLowerCase();
|
|
558
|
+
if (DEVELOPMENT_BRANCHES.has(branch)) {
|
|
559
|
+
return { mode: "development", key: `git branch ${branch}` };
|
|
560
|
+
}
|
|
561
|
+
return null;
|
|
562
|
+
}
|
|
563
|
+
|
|
382
564
|
/**
|
|
383
565
|
* Resolve the workspace mode from the variables that were found.
|
|
384
566
|
*
|
|
@@ -436,6 +618,17 @@ function findModeEvidence(env: WorkspaceEnvResult): EnvEvidence | undefined {
|
|
|
436
618
|
return candidates.find((e) => modeFromValue(e.value) !== null) ?? candidates[0];
|
|
437
619
|
}
|
|
438
620
|
|
|
621
|
+
/**
|
|
622
|
+
* The evidence entry that names the mode through the repository (if any).
|
|
623
|
+
*
|
|
624
|
+
* A detection is weaker than a variable: it is the shape of the checkout,
|
|
625
|
+
* not a declaration of the environment. It is therefore only used when no
|
|
626
|
+
* variable names the environment at all.
|
|
627
|
+
*/
|
|
628
|
+
function findDetectionEvidence(env: WorkspaceEnvResult): EnvEvidence | undefined {
|
|
629
|
+
return env.evidence.find((e) => e.source === "detection");
|
|
630
|
+
}
|
|
631
|
+
|
|
439
632
|
/**
|
|
440
633
|
* Human readable mode label.
|
|
441
634
|
*
|
|
@@ -451,9 +644,12 @@ export function describeWorkspaceMode(env: WorkspaceEnvResult): string {
|
|
|
451
644
|
const hint = findModeEvidence(env);
|
|
452
645
|
const where = hint?.file ? ` in ${hint.file}` : "";
|
|
453
646
|
if (env.mode !== "unknown") {
|
|
454
|
-
return hint
|
|
455
|
-
|
|
456
|
-
|
|
647
|
+
if (hint) return `${env.mode.toUpperCase()} (from ${hint.key}${where})`;
|
|
648
|
+
// The mode came from the repository, not from a variable: the label
|
|
649
|
+
// names the reason ("DEVELOPMENT (source-only repository)").
|
|
650
|
+
const detection = findDetectionEvidence(env);
|
|
651
|
+
if (detection) return `${env.mode.toUpperCase()} (${detection.key})`;
|
|
652
|
+
return env.mode.toUpperCase();
|
|
457
653
|
}
|
|
458
654
|
if (hint) {
|
|
459
655
|
// The stored mode and the evidence can disagree (a stale cache row):
|
|
@@ -470,6 +666,13 @@ export function describeWorkspaceMode(env: WorkspaceEnvResult): string {
|
|
|
470
666
|
}
|
|
471
667
|
return `UNKNOWN (${hint.key}=${hint.value} is not a known mode${where})`;
|
|
472
668
|
}
|
|
669
|
+
// A stale cache row can carry a detection next to an unknown mode: the
|
|
670
|
+
// detection names a mode, so the mode is shown.
|
|
671
|
+
const detection = findDetectionEvidence(env);
|
|
672
|
+
if (detection) {
|
|
673
|
+
const named = modeFromValue(detection.value);
|
|
674
|
+
if (named) return `${named.toUpperCase()} (${detection.key})`;
|
|
675
|
+
}
|
|
473
676
|
return "UNKNOWN (no NODE_ENV or environment variable found)";
|
|
474
677
|
}
|
|
475
678
|
|
|
@@ -590,7 +793,7 @@ export function formatEnvForAgentResult(env: WorkspaceEnvResult): string {
|
|
|
590
793
|
}
|
|
591
794
|
|
|
592
795
|
message += `\n`;
|
|
593
|
-
message += `Build allowed: ${env
|
|
796
|
+
message += `Build allowed: ${buildAllowanceReason(env)}\n`;
|
|
594
797
|
|
|
595
798
|
if (env.mode === "unknown") {
|
|
596
799
|
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`;
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
import { DatabaseSync } from "node:sqlite";
|
|
16
16
|
import { execSync } from "child_process";
|
|
17
|
-
import { existsSync, readFileSync, mkdirSync, readlinkSync, realpathSync } from "fs";
|
|
17
|
+
import { existsSync, readFileSync, readdirSync, mkdirSync, readlinkSync, realpathSync } from "fs";
|
|
18
18
|
import { join } from "path";
|
|
19
19
|
import { platform, totalmem, freemem } from "os";
|
|
20
20
|
import {
|
|
@@ -26,12 +26,16 @@ import {
|
|
|
26
26
|
describeServerState,
|
|
27
27
|
describeWorkspaceMode,
|
|
28
28
|
detectModeHint,
|
|
29
|
+
DEPLOYMENT_MARKER_FILES,
|
|
29
30
|
filterOwnPids,
|
|
30
31
|
formatEnvForAgentResult,
|
|
31
32
|
formatEnvStatusLine as formatStatusLine,
|
|
32
33
|
formatWorkspacePath,
|
|
33
34
|
isLauncherCommand,
|
|
35
|
+
isLowRamWarning,
|
|
34
36
|
MASKED_MODE_VALUE,
|
|
37
|
+
MIN_BUILD_HEADROOM_GB,
|
|
38
|
+
modeFromRepositoryFacts,
|
|
35
39
|
modeFromValue,
|
|
36
40
|
orderDotenvFileNames,
|
|
37
41
|
parseDotenvVars,
|
|
@@ -41,6 +45,7 @@ import { readDotenvFiles } from "./workspace-dotenv.js";
|
|
|
41
45
|
import type {
|
|
42
46
|
DotenvVar,
|
|
43
47
|
EnvEvidence,
|
|
48
|
+
RepositoryFacts,
|
|
44
49
|
ServerState,
|
|
45
50
|
WorkspaceEnvResult,
|
|
46
51
|
WorkspaceMode,
|
|
@@ -145,12 +150,11 @@ function detectRamInfo(): {
|
|
|
145
150
|
const freeBytes = freemem();
|
|
146
151
|
const totalGb = Math.round((totalBytes / (1024 ** 3)) * 10) / 10;
|
|
147
152
|
const availableGb = Math.round((freeBytes / (1024 ** 3)) * 10) / 10;
|
|
148
|
-
const LOW_RAM_GB = 16;
|
|
149
153
|
|
|
150
154
|
return {
|
|
151
155
|
total: totalGb,
|
|
152
156
|
available: availableGb,
|
|
153
|
-
lowWarning: totalGb
|
|
157
|
+
lowWarning: isLowRamWarning(totalGb, availableGb),
|
|
154
158
|
};
|
|
155
159
|
} catch {
|
|
156
160
|
return { total: null, available: null, lowWarning: false };
|
|
@@ -565,6 +569,99 @@ function detectPackageManager(workspacePath: string): string | null {
|
|
|
565
569
|
return null;
|
|
566
570
|
}
|
|
567
571
|
|
|
572
|
+
/**
|
|
573
|
+
* The git repository of a workspace.
|
|
574
|
+
*
|
|
575
|
+
* The root of the repository is the unit of the detection: a package
|
|
576
|
+
* published from a monorepo is described by the root, not by the folder
|
|
577
|
+
* the harness happens to run in.
|
|
578
|
+
*/
|
|
579
|
+
function detectGitFacts(workspacePath: string): { root: string; branch: string } {
|
|
580
|
+
try {
|
|
581
|
+
const out = execSync(
|
|
582
|
+
"git rev-parse --show-toplevel 2>/dev/null; git rev-parse --abbrev-ref HEAD 2>/dev/null",
|
|
583
|
+
{ cwd: workspacePath, encoding: "utf8", timeout: 1500 },
|
|
584
|
+
);
|
|
585
|
+
const lines = out.split("\n").map((line) => line.trim());
|
|
586
|
+
const root = lines[0] ?? "";
|
|
587
|
+
const branch = lines[1] ?? "";
|
|
588
|
+
// A detached HEAD reports "HEAD" - that is not a branch name.
|
|
589
|
+
return { root, branch: branch === "HEAD" ? "" : branch };
|
|
590
|
+
} catch {
|
|
591
|
+
// No git, no repository, or a timeout: nothing is known.
|
|
592
|
+
return { root: "", branch: "" };
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
/**
|
|
597
|
+
* True when the package is published to a registry.
|
|
598
|
+
*
|
|
599
|
+
* A package that is published names a version and declares what it ships
|
|
600
|
+
* ("files", "bin" or a "publishConfig"). A repository that only holds an
|
|
601
|
+
* application usually declares none of them - so this stays a careful
|
|
602
|
+
* signal instead of "every package.json is published".
|
|
603
|
+
*/
|
|
604
|
+
function isPublishedPackage(packageDir: string): boolean {
|
|
605
|
+
const pkgJsonPath = join(packageDir, "package.json");
|
|
606
|
+
if (!existsSync(pkgJsonPath)) return false;
|
|
607
|
+
try {
|
|
608
|
+
const pkg = JSON.parse(readFileSync(pkgJsonPath, "utf8"));
|
|
609
|
+
if (!pkg || typeof pkg !== "object") return false;
|
|
610
|
+
if (pkg.private === true) return false;
|
|
611
|
+
if (typeof pkg.name !== "string" || !pkg.name) return false;
|
|
612
|
+
if (typeof pkg.version !== "string" || !pkg.version) return false;
|
|
613
|
+
return (
|
|
614
|
+
Array.isArray(pkg.files) ||
|
|
615
|
+
(typeof pkg.bin === "object" && pkg.bin !== null) ||
|
|
616
|
+
(typeof pkg.publishConfig === "object" && pkg.publishConfig !== null)
|
|
617
|
+
);
|
|
618
|
+
} catch {
|
|
619
|
+
return false;
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
/**
|
|
624
|
+
* True when the repository carries a deployment configuration.
|
|
625
|
+
*
|
|
626
|
+
* A deployment file or a deploy workflow means the repository has a
|
|
627
|
+
* runtime environment of its own - the shape of the repository can then no
|
|
628
|
+
* longer say that the workspace is a development checkout.
|
|
629
|
+
*/
|
|
630
|
+
function hasDeploymentConfiguration(repoRoot: string): boolean {
|
|
631
|
+
for (const marker of DEPLOYMENT_MARKER_FILES) {
|
|
632
|
+
if (existsSync(join(repoRoot, marker))) return true;
|
|
633
|
+
}
|
|
634
|
+
const workflowDir = join(repoRoot, ".github", "workflows");
|
|
635
|
+
if (existsSync(workflowDir)) {
|
|
636
|
+
try {
|
|
637
|
+
for (const name of readdirSync(workflowDir)) {
|
|
638
|
+
if (/deploy/i.test(name)) return true;
|
|
639
|
+
}
|
|
640
|
+
} catch {
|
|
641
|
+
// An unreadable directory proves nothing.
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
return false;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
/**
|
|
648
|
+
* Read the facts about the repository the workspace belongs to.
|
|
649
|
+
*/
|
|
650
|
+
function detectRepositoryFacts(
|
|
651
|
+
workspacePath: string,
|
|
652
|
+
hasRunningServer: boolean,
|
|
653
|
+
): RepositoryFacts {
|
|
654
|
+
const { root, branch } = detectGitFacts(workspacePath);
|
|
655
|
+
const repoRoot = root || workspacePath;
|
|
656
|
+
return {
|
|
657
|
+
isGitRepository: root !== "",
|
|
658
|
+
branch,
|
|
659
|
+
isPublishedPackage: isPublishedPackage(repoRoot),
|
|
660
|
+
hasDeploymentMarker: hasDeploymentConfiguration(repoRoot),
|
|
661
|
+
hasRunningServer,
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
|
|
568
665
|
/**
|
|
569
666
|
* Determine workspace mode with proper precedence
|
|
570
667
|
* Never assume production - unknown requires investigation
|
|
@@ -576,11 +673,15 @@ function detectPackageManager(workspacePath: string): string | null {
|
|
|
576
673
|
* key whose value names a known mode, then any other environment shaped
|
|
577
674
|
* key
|
|
578
675
|
* 3. an environment shaped key of the process env (the files are silent)
|
|
676
|
+
* 4. the facts about the repository (only when NO variable names the
|
|
677
|
+
* environment at all) - a published source repository without a runtime
|
|
678
|
+
* of its own is a development checkout
|
|
579
679
|
*/
|
|
580
680
|
function determineMode(
|
|
581
681
|
processVars: DotenvVar[],
|
|
582
682
|
dotenvVars: DotenvVar[],
|
|
583
|
-
evidence: EnvEvidence[]
|
|
683
|
+
evidence: EnvEvidence[],
|
|
684
|
+
repositoryFacts: RepositoryFacts,
|
|
584
685
|
): WorkspaceMode {
|
|
585
686
|
const resolved = resolveWorkspaceMode(processVars, dotenvVars);
|
|
586
687
|
if (resolved.hint && resolved.source) {
|
|
@@ -599,8 +700,20 @@ function determineMode(
|
|
|
599
700
|
timestamp: Date.now(),
|
|
600
701
|
file: resolved.hint.file,
|
|
601
702
|
});
|
|
703
|
+
// An explicit variable decides - even when it names no known mode
|
|
704
|
+
// ("staging"). The mode stays unknown and the label names the value.
|
|
705
|
+
return resolved.mode;
|
|
602
706
|
}
|
|
603
|
-
|
|
707
|
+
// No variable names the environment: the repository may still name it.
|
|
708
|
+
const detection = modeFromRepositoryFacts(repositoryFacts);
|
|
709
|
+
if (!detection) return resolved.mode;
|
|
710
|
+
evidence.push({
|
|
711
|
+
source: "detection",
|
|
712
|
+
key: detection.key,
|
|
713
|
+
value: detection.mode,
|
|
714
|
+
timestamp: Date.now(),
|
|
715
|
+
});
|
|
716
|
+
return detection.mode;
|
|
604
717
|
}
|
|
605
718
|
|
|
606
719
|
/**
|
|
@@ -655,14 +768,22 @@ export function detectWorkspaceEnv(workspacePath: string): WorkspaceEnvResult {
|
|
|
655
768
|
detectServerStates(workspacePath);
|
|
656
769
|
|
|
657
770
|
// Determine mode - NEVER assume production
|
|
658
|
-
const
|
|
771
|
+
const repositoryFacts = detectRepositoryFacts(
|
|
772
|
+
workspacePath,
|
|
773
|
+
devServerState === "running" || buildServerState === "running",
|
|
774
|
+
);
|
|
775
|
+
const mode = determineMode(processVars, dotenvVars, evidence, repositoryFacts);
|
|
659
776
|
|
|
660
777
|
// Determine build permission
|
|
661
778
|
let buildAllowed = false;
|
|
662
779
|
if (mode === "production") {
|
|
663
780
|
buildAllowed = true; // Production build is allowed
|
|
664
781
|
} else if (mode === "development") {
|
|
665
|
-
|
|
782
|
+
// A build needs headroom, not a big machine. The low-memory warning is
|
|
783
|
+
// about running a build AND a dev server in parallel; a build on its
|
|
784
|
+
// own only needs enough free memory to finish.
|
|
785
|
+
buildAllowed =
|
|
786
|
+
ramInfo.available === null ? false : ramInfo.available >= MIN_BUILD_HEADROOM_GB;
|
|
666
787
|
} else {
|
|
667
788
|
buildAllowed = false; // Unknown requires investigation
|
|
668
789
|
}
|
|
@@ -3,6 +3,7 @@ import { mkdtempSync, readFileSync, rmSync, symlinkSync, writeFileSync } from "n
|
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import {
|
|
6
|
+
buildAllowanceReason,
|
|
6
7
|
canShowModeValue,
|
|
7
8
|
collectDotenvVars,
|
|
8
9
|
collectProcessEnvVars,
|
|
@@ -16,7 +17,10 @@ import {
|
|
|
16
17
|
formatEnvStatusLine,
|
|
17
18
|
formatWorkspacePath,
|
|
18
19
|
isLauncherCommand,
|
|
20
|
+
isLowRamWarning,
|
|
21
|
+
isSessionVariableKey,
|
|
19
22
|
MASKED_MODE_VALUE,
|
|
23
|
+
modeFromRepositoryFacts,
|
|
20
24
|
modeFromValue,
|
|
21
25
|
orderDotenvFileNames,
|
|
22
26
|
parseDotenvVars,
|
|
@@ -623,3 +627,176 @@ describe("the template files of a workspace", () => {
|
|
|
623
627
|
expect(isTemplateDotenvName(".env.production")).toBe(false);
|
|
624
628
|
});
|
|
625
629
|
});
|
|
630
|
+
|
|
631
|
+
describe("the session variables of a desktop", () => {
|
|
632
|
+
test("a session variable is not an environment selector", () => {
|
|
633
|
+
expect(isSessionVariableKey("GNOME_SHELL_SESSION_MODE")).toBe(true);
|
|
634
|
+
expect(isSessionVariableKey("XDG_SESSION_TYPE")).toBe(true);
|
|
635
|
+
expect(isSessionVariableKey("DESKTOP_SESSION")).toBe(true);
|
|
636
|
+
expect(isSessionVariableKey("DBUS_SESSION_BUS_ADDRESS")).toBe(true);
|
|
637
|
+
});
|
|
638
|
+
|
|
639
|
+
test("a real environment selector is not a session variable", () => {
|
|
640
|
+
expect(isSessionVariableKey("NODE_ENV")).toBe(false);
|
|
641
|
+
expect(isSessionVariableKey("AMOS_ENV")).toBe(false);
|
|
642
|
+
expect(isSessionVariableKey("APP_MODE")).toBe(false);
|
|
643
|
+
expect(isSessionVariableKey("RAILS_ENV")).toBe(false);
|
|
644
|
+
});
|
|
645
|
+
|
|
646
|
+
test("a session variable never decides the mode of a workspace", () => {
|
|
647
|
+
// The scenario of the user: GNOME_SHELL_SESSION_MODE=ubuntu is set in
|
|
648
|
+
// every GNOME shell and must not make a source repository unknown.
|
|
649
|
+
const processVars = collectProcessEnvVars({
|
|
650
|
+
GNOME_SHELL_SESSION_MODE: "ubuntu",
|
|
651
|
+
});
|
|
652
|
+
|
|
653
|
+
expect(processVars).toEqual([]);
|
|
654
|
+
expect(resolveWorkspaceMode(processVars, []).mode).toBe("unknown");
|
|
655
|
+
expect(resolveWorkspaceMode(processVars, []).hint).toBeNull();
|
|
656
|
+
});
|
|
657
|
+
|
|
658
|
+
test("a session variable in a dotenv file does not decide the mode either", () => {
|
|
659
|
+
const vars = collectDotenvVars([
|
|
660
|
+
{ name: ".env", content: "XDG_SESSION_TYPE=x11\n" },
|
|
661
|
+
]);
|
|
662
|
+
|
|
663
|
+
expect(resolveWorkspaceMode([], vars).hint).toBeNull();
|
|
664
|
+
});
|
|
665
|
+
|
|
666
|
+
test("a session variable does not shadow a real environment selector", () => {
|
|
667
|
+
const vars = collectDotenvVars([
|
|
668
|
+
{ name: ".env", content: "GNOME_SHELL_SESSION_MODE=ubuntu\nAMOS_ENV=production\n" },
|
|
669
|
+
]);
|
|
670
|
+
const resolved = resolveWorkspaceMode([], vars);
|
|
671
|
+
|
|
672
|
+
expect(resolved.mode).toBe("production");
|
|
673
|
+
expect(resolved.hint?.key).toBe("AMOS_ENV");
|
|
674
|
+
});
|
|
675
|
+
});
|
|
676
|
+
|
|
677
|
+
describe("the mode detection of a source repository", () => {
|
|
678
|
+
const published = {
|
|
679
|
+
isGitRepository: true,
|
|
680
|
+
branch: "main",
|
|
681
|
+
isPublishedPackage: true,
|
|
682
|
+
hasDeploymentMarker: false,
|
|
683
|
+
hasRunningServer: false,
|
|
684
|
+
};
|
|
685
|
+
|
|
686
|
+
test("a published package without a runtime is a development checkout", () => {
|
|
687
|
+
expect(modeFromRepositoryFacts(published)).toEqual({
|
|
688
|
+
mode: "development",
|
|
689
|
+
key: "source-only repository",
|
|
690
|
+
});
|
|
691
|
+
});
|
|
692
|
+
|
|
693
|
+
test("a deployment configuration keeps the mode unknown", () => {
|
|
694
|
+
const detection = modeFromRepositoryFacts({
|
|
695
|
+
...published,
|
|
696
|
+
hasDeploymentMarker: true,
|
|
697
|
+
});
|
|
698
|
+
|
|
699
|
+
expect(detection).toBeNull();
|
|
700
|
+
});
|
|
701
|
+
|
|
702
|
+
test("a development branch is the fallback", () => {
|
|
703
|
+
expect(
|
|
704
|
+
modeFromRepositoryFacts({
|
|
705
|
+
...published,
|
|
706
|
+
branch: "develop",
|
|
707
|
+
isPublishedPackage: false,
|
|
708
|
+
}),
|
|
709
|
+
).toEqual({ mode: "development", key: "git branch develop" });
|
|
710
|
+
expect(
|
|
711
|
+
modeFromRepositoryFacts({
|
|
712
|
+
...published,
|
|
713
|
+
branch: "development",
|
|
714
|
+
isPublishedPackage: false,
|
|
715
|
+
}),
|
|
716
|
+
).toEqual({ mode: "development", key: "git branch development" });
|
|
717
|
+
});
|
|
718
|
+
|
|
719
|
+
test("a production branch is never a development checkout", () => {
|
|
720
|
+
expect(
|
|
721
|
+
modeFromRepositoryFacts({
|
|
722
|
+
...published,
|
|
723
|
+
branch: "main",
|
|
724
|
+
isPublishedPackage: false,
|
|
725
|
+
}),
|
|
726
|
+
).toBeNull();
|
|
727
|
+
expect(
|
|
728
|
+
modeFromRepositoryFacts({
|
|
729
|
+
...published,
|
|
730
|
+
branch: "production",
|
|
731
|
+
isPublishedPackage: false,
|
|
732
|
+
}),
|
|
733
|
+
).toBeNull();
|
|
734
|
+
});
|
|
735
|
+
|
|
736
|
+
test("a directory that is not a repository names no mode", () => {
|
|
737
|
+
expect(
|
|
738
|
+
modeFromRepositoryFacts({
|
|
739
|
+
...published,
|
|
740
|
+
isGitRepository: false,
|
|
741
|
+
}),
|
|
742
|
+
).toBeNull();
|
|
743
|
+
});
|
|
744
|
+
|
|
745
|
+
test("the label names the reason of the detection", () => {
|
|
746
|
+
const env = makeEnv({
|
|
747
|
+
mode: "development",
|
|
748
|
+
buildAllowed: true,
|
|
749
|
+
evidence: [
|
|
750
|
+
{ source: "detection", key: "source-only repository", value: "development", timestamp: 0 },
|
|
751
|
+
],
|
|
752
|
+
});
|
|
753
|
+
|
|
754
|
+
expect(describeWorkspaceMode(env)).toBe("DEVELOPMENT (source-only repository)");
|
|
755
|
+
});
|
|
756
|
+
});
|
|
757
|
+
|
|
758
|
+
describe("the low memory rule", () => {
|
|
759
|
+
test("flags a machine with almost no free memory", () => {
|
|
760
|
+
expect(isLowRamWarning(64, 2)).toBe(true);
|
|
761
|
+
expect(isLowRamWarning(16, 3.9)).toBe(true);
|
|
762
|
+
});
|
|
763
|
+
|
|
764
|
+
test("does not flag a small machine with real headroom", () => {
|
|
765
|
+
expect(isLowRamWarning(15, 6.6)).toBe(false);
|
|
766
|
+
expect(isLowRamWarning(8, 8)).toBe(false);
|
|
767
|
+
});
|
|
768
|
+
|
|
769
|
+
test("flags a big machine that ran out of memory", () => {
|
|
770
|
+
expect(isLowRamWarning(64, 3)).toBe(true);
|
|
771
|
+
});
|
|
772
|
+
|
|
773
|
+
test("does not flag an unknown memory state", () => {
|
|
774
|
+
expect(isLowRamWarning(null, null)).toBe(false);
|
|
775
|
+
expect(isLowRamWarning(null, 12)).toBe(false);
|
|
776
|
+
});
|
|
777
|
+
});
|
|
778
|
+
|
|
779
|
+
describe("the build permission", () => {
|
|
780
|
+
test("allows a build when the environment allows it", () => {
|
|
781
|
+
expect(buildAllowanceReason(makeEnv({ mode: "development", buildAllowed: true }))).toBe("yes");
|
|
782
|
+
});
|
|
783
|
+
|
|
784
|
+
test("asks for an explicit request when the mode is unknown", () => {
|
|
785
|
+
expect(
|
|
786
|
+
buildAllowanceReason(makeEnv({ mode: "unknown", buildAllowed: false, lowRamWarning: false })),
|
|
787
|
+
).toBe("no - requires explicit request");
|
|
788
|
+
});
|
|
789
|
+
|
|
790
|
+
test("names the memory shortage when the memory is the reason", () => {
|
|
791
|
+
expect(
|
|
792
|
+
buildAllowanceReason(makeEnv({ mode: "development", buildAllowed: false, lowRamWarning: true })),
|
|
793
|
+
).toBe("no - low memory");
|
|
794
|
+
});
|
|
795
|
+
|
|
796
|
+
test("appears in the agent context", () => {
|
|
797
|
+
const message = formatEnvForAgentResult(
|
|
798
|
+
makeEnv({ mode: "development", buildAllowed: false, lowRamWarning: true }),
|
|
799
|
+
);
|
|
800
|
+
expect(message).toContain("Build allowed: no - low memory");
|
|
801
|
+
});
|
|
802
|
+
});
|