gsd-unsupervised 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/cli.js +21 -0
- package/dist/config.d.ts +8 -2
- package/dist/config.js +11 -4
- package/dist/daemon.js +18 -0
- package/dist/orchestrator.js +1 -0
- package/dist/resource-governor.d.ts +29 -3
- package/dist/resource-governor.js +50 -13
- package/dist/status-server.d.ts +1 -0
- package/dist/status-server.js +20 -2
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -244,7 +244,7 @@ Resume uses this to re-run `execute-plan` for phase 2 plan 1 only, then continue
|
|
|
244
244
|
|
|
245
245
|
**Parallel goal pool:** With `--parallel`, a worker pool of size `--max-concurrent` is used; a per-workspace mutex keeps one goal running at a time for a single workspace (phase-level parallel inside execute-phase still applies).
|
|
246
246
|
|
|
247
|
-
**SMS (Twilio):** Optional. Set `TWILIO_ACCOUNT_SID`, `TWILIO_AUTH_TOKEN`, `TWILIO_FROM`, and `TWILIO_TO` to receive SMS on goal complete, goal failed, and daemon paused (after 3 retries). If any are unset, SMS is skipped and the daemon runs normally.
|
|
247
|
+
**SMS (Twilio):** Optional. Set `TWILIO_ACCOUNT_SID`, `TWILIO_AUTH_TOKEN`, `TWILIO_FROM`, and `TWILIO_TO` to receive SMS on goal complete, goal failed, and daemon paused (after 3 retries). If any are unset, SMS is skipped and the daemon runs normally. To verify delivery, run `npx gsd-unsupervised test-sms` from the project root (after `npm run build`).
|
|
248
248
|
|
|
249
249
|
**State and heartbeat:** When started via `./run` or `gsd-unsupervised run --state .gsd/state.json`, the daemon writes to `.gsd/state.json` (PID, current goal, progress, `lastHeartbeat`). You can use `lastHeartbeat` in an external cron or script to send a periodic "alive" SMS (e.g. every 30 min) or alert if the heartbeat is stale (e.g. >10 min).
|
|
250
250
|
|
package/dist/cli.js
CHANGED
|
@@ -9,6 +9,7 @@ import { loadConfig } from './config.js';
|
|
|
9
9
|
import { loadGoals, getPendingGoals } from './goals.js';
|
|
10
10
|
import { runDaemon, registerShutdownHandlers } from './daemon.js';
|
|
11
11
|
import { validateCursorApiKey } from './cursor-agent.js';
|
|
12
|
+
import { sendSms } from './notifier.js';
|
|
12
13
|
import { applyWslBootstrap } from './bootstrap/wsl-bootstrap.js';
|
|
13
14
|
import { readGsdStateFromPath } from './gsd-state.js';
|
|
14
15
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -166,6 +167,26 @@ program
|
|
|
166
167
|
const { runInit } = await import('./init-wizard.js');
|
|
167
168
|
await runInit();
|
|
168
169
|
});
|
|
170
|
+
/** Send a test SMS to verify Twilio config (TWILIO_* in .env or env). */
|
|
171
|
+
program
|
|
172
|
+
.command('test-sms')
|
|
173
|
+
.description('Send a test SMS to verify Twilio credentials and delivery')
|
|
174
|
+
.option('--message <text>', 'Custom message (default: GSD Autopilot test message)', undefined)
|
|
175
|
+
.action(async (opts) => {
|
|
176
|
+
const message = opts.message?.trim() ||
|
|
177
|
+
'GSD Autopilot test SMS. If you received this, notifications are working.';
|
|
178
|
+
try {
|
|
179
|
+
await sendSms(message);
|
|
180
|
+
console.log('Test SMS sent successfully. Check your phone (TWILIO_TO).');
|
|
181
|
+
}
|
|
182
|
+
catch (err) {
|
|
183
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
184
|
+
console.error('Failed to send test SMS:', msg);
|
|
185
|
+
console.error('');
|
|
186
|
+
console.error('Check: TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN, TWILIO_FROM, TWILIO_TO in .env or environment.');
|
|
187
|
+
process.exit(1);
|
|
188
|
+
}
|
|
189
|
+
});
|
|
169
190
|
export function main() {
|
|
170
191
|
program.parse();
|
|
171
192
|
}
|
package/dist/config.d.ts
CHANGED
|
@@ -6,14 +6,18 @@ export declare const AutopilotConfigSchema: z.ZodObject<{
|
|
|
6
6
|
maxConcurrent: z.ZodDefault<z.ZodNumber>;
|
|
7
7
|
/**
|
|
8
8
|
* Upper bound on allowed CPU usage before new agent work waits.
|
|
9
|
-
* Expressed as a fraction of total CPU capacity (1.0 = 100%
|
|
9
|
+
* Expressed as a fraction of total CPU capacity (1.0 = 100%). 0.8 = 80% recommended for parallel.
|
|
10
10
|
*/
|
|
11
11
|
maxCpuFraction: z.ZodDefault<z.ZodNumber>;
|
|
12
12
|
/**
|
|
13
13
|
* Upper bound on allowed memory usage before new agent work waits.
|
|
14
|
-
* Expressed as a fraction of total system memory (1.0 = 100%
|
|
14
|
+
* Expressed as a fraction of total system memory (1.0 = 100%). 0.8 = 80% recommended for parallel.
|
|
15
15
|
*/
|
|
16
16
|
maxMemoryFraction: z.ZodDefault<z.ZodNumber>;
|
|
17
|
+
/**
|
|
18
|
+
* Upper bound on GPU utilization (0–1) when nvidia-smi is available. 0.8 = 80% recommended for parallel.
|
|
19
|
+
*/
|
|
20
|
+
maxGpuFraction: z.ZodOptional<z.ZodNumber>;
|
|
17
21
|
verbose: z.ZodDefault<z.ZodBoolean>;
|
|
18
22
|
logLevel: z.ZodDefault<z.ZodEnum<["debug", "info", "warn", "error"]>>;
|
|
19
23
|
workspaceRoot: z.ZodDefault<z.ZodString>;
|
|
@@ -50,6 +54,7 @@ export declare const AutopilotConfigSchema: z.ZodObject<{
|
|
|
50
54
|
requireCleanGitBeforePlan: boolean;
|
|
51
55
|
autoCheckpoint: boolean;
|
|
52
56
|
ngrok: boolean;
|
|
57
|
+
maxGpuFraction?: number | undefined;
|
|
53
58
|
statusServerPort?: number | undefined;
|
|
54
59
|
statePath?: string | undefined;
|
|
55
60
|
}, {
|
|
@@ -58,6 +63,7 @@ export declare const AutopilotConfigSchema: z.ZodObject<{
|
|
|
58
63
|
maxConcurrent?: number | undefined;
|
|
59
64
|
maxCpuFraction?: number | undefined;
|
|
60
65
|
maxMemoryFraction?: number | undefined;
|
|
66
|
+
maxGpuFraction?: number | undefined;
|
|
61
67
|
verbose?: boolean | undefined;
|
|
62
68
|
logLevel?: "error" | "warn" | "info" | "debug" | undefined;
|
|
63
69
|
workspaceRoot?: string | undefined;
|
package/dist/config.js
CHANGED
|
@@ -9,14 +9,18 @@ export const AutopilotConfigSchema = z.object({
|
|
|
9
9
|
maxConcurrent: z.number().int().min(1).max(10).default(3),
|
|
10
10
|
/**
|
|
11
11
|
* Upper bound on allowed CPU usage before new agent work waits.
|
|
12
|
-
* Expressed as a fraction of total CPU capacity (1.0 = 100%
|
|
12
|
+
* Expressed as a fraction of total CPU capacity (1.0 = 100%). 0.8 = 80% recommended for parallel.
|
|
13
13
|
*/
|
|
14
|
-
maxCpuFraction: z.number().min(0.1).max(1).default(0.
|
|
14
|
+
maxCpuFraction: z.number().min(0.1).max(1).default(0.8),
|
|
15
15
|
/**
|
|
16
16
|
* Upper bound on allowed memory usage before new agent work waits.
|
|
17
|
-
* Expressed as a fraction of total system memory (1.0 = 100%
|
|
17
|
+
* Expressed as a fraction of total system memory (1.0 = 100%). 0.8 = 80% recommended for parallel.
|
|
18
18
|
*/
|
|
19
|
-
maxMemoryFraction: z.number().min(0.5).max(1).default(0.
|
|
19
|
+
maxMemoryFraction: z.number().min(0.5).max(1).default(0.8),
|
|
20
|
+
/**
|
|
21
|
+
* Upper bound on GPU utilization (0–1) when nvidia-smi is available. 0.8 = 80% recommended for parallel.
|
|
22
|
+
*/
|
|
23
|
+
maxGpuFraction: z.number().min(0.1).max(1).optional(),
|
|
20
24
|
verbose: z.boolean().default(false),
|
|
21
25
|
logLevel: z.enum(['debug', 'info', 'warn', 'error']).default('info'),
|
|
22
26
|
workspaceRoot: z.string().default(process.cwd()),
|
|
@@ -87,6 +91,9 @@ function readPlanningOverrides(workspaceRoot) {
|
|
|
87
91
|
if (typeof parsed.maxMemoryFraction === 'number') {
|
|
88
92
|
overrides.maxMemoryFraction = parsed.maxMemoryFraction;
|
|
89
93
|
}
|
|
94
|
+
if (typeof parsed.maxGpuFraction === 'number') {
|
|
95
|
+
overrides.maxGpuFraction = parsed.maxGpuFraction;
|
|
96
|
+
}
|
|
90
97
|
return overrides;
|
|
91
98
|
}
|
|
92
99
|
catch {
|
package/dist/daemon.js
CHANGED
|
@@ -169,6 +169,12 @@ export async function runDaemon(config, logger) {
|
|
|
169
169
|
status: 'crashed',
|
|
170
170
|
error: `Heartbeat timeout (>${heartbeatTimeoutMs / 1000}s)`,
|
|
171
171
|
});
|
|
172
|
+
try {
|
|
173
|
+
await sendSms(`GSD goal crashed (heartbeat timeout).\nGoal: ${crashed.goalTitle}`);
|
|
174
|
+
}
|
|
175
|
+
catch (e) {
|
|
176
|
+
logger.debug({ err: e }, 'SMS (goal crashed) skipped or failed');
|
|
177
|
+
}
|
|
172
178
|
}
|
|
173
179
|
}
|
|
174
180
|
catch {
|
|
@@ -178,6 +184,12 @@ export async function runDaemon(config, logger) {
|
|
|
178
184
|
status: 'crashed',
|
|
179
185
|
error: 'Heartbeat timeout (missing)',
|
|
180
186
|
});
|
|
187
|
+
try {
|
|
188
|
+
await sendSms(`GSD goal crashed (heartbeat timeout).\nGoal: ${crashed.goalTitle}`);
|
|
189
|
+
}
|
|
190
|
+
catch (e) {
|
|
191
|
+
logger.debug({ err: e }, 'SMS (goal crashed) skipped or failed');
|
|
192
|
+
}
|
|
181
193
|
}
|
|
182
194
|
}
|
|
183
195
|
resumeFrom = await computeResumePointer({
|
|
@@ -327,6 +339,12 @@ export async function runDaemon(config, logger) {
|
|
|
327
339
|
break;
|
|
328
340
|
}
|
|
329
341
|
logger.info({ goal: goal.title }, `Processing goal: ${goal.title}`);
|
|
342
|
+
try {
|
|
343
|
+
await sendSms(`GSD goal started.\nGoal: ${goal.title}`);
|
|
344
|
+
}
|
|
345
|
+
catch (e) {
|
|
346
|
+
logger.debug({ err: e }, 'SMS (goal started) skipped or failed');
|
|
347
|
+
}
|
|
330
348
|
const isFirst = completedCount === 1 && resumeFrom !== null;
|
|
331
349
|
await workspaceMutex.run(() => runOneGoal(goal, isFirst ? resumeFrom : null));
|
|
332
350
|
running.delete(goal.title);
|
package/dist/orchestrator.js
CHANGED
|
@@ -16,18 +16,28 @@ export interface LoadInfo {
|
|
|
16
16
|
memoryFraction: number;
|
|
17
17
|
totalMemBytes: number;
|
|
18
18
|
freeMemBytes: number;
|
|
19
|
+
/**
|
|
20
|
+
* Best-effort GPU utilization fraction (0–1). Only set when nvidia-smi is
|
|
21
|
+
* available and returns utilization; otherwise undefined.
|
|
22
|
+
*/
|
|
23
|
+
gpuFraction?: number;
|
|
19
24
|
}
|
|
20
25
|
export interface WaitForHeadroomOptions {
|
|
21
26
|
/**
|
|
22
27
|
* Maximum allowed CPU fraction before new agent work is allowed to start.
|
|
23
|
-
* 1.0 means 100% of all logical CPUs; 0.
|
|
28
|
+
* 1.0 means 100% of all logical CPUs; 0.8 recommended for parallel work.
|
|
24
29
|
*/
|
|
25
30
|
maxCpuFraction: number;
|
|
26
31
|
/**
|
|
27
32
|
* Maximum allowed memory fraction before new agent work is allowed to start.
|
|
28
|
-
* 1.0 means 100% of total RAM; 0.
|
|
33
|
+
* 1.0 means 100% of total RAM; 0.8 recommended for parallel work.
|
|
29
34
|
*/
|
|
30
35
|
maxMemoryFraction?: number;
|
|
36
|
+
/**
|
|
37
|
+
* Maximum allowed GPU utilization fraction (0–1). Only checked when
|
|
38
|
+
* nvidia-smi is available. 0.8 recommended for parallel work.
|
|
39
|
+
*/
|
|
40
|
+
maxGpuFraction?: number;
|
|
31
41
|
/**
|
|
32
42
|
* Minimum delay between load checks while waiting for headroom.
|
|
33
43
|
* Defaults to 2s to avoid busy-waiting.
|
|
@@ -47,8 +57,24 @@ export interface WaitForHeadroomOptions {
|
|
|
47
57
|
warn: (obj: unknown, msg?: string) => void;
|
|
48
58
|
};
|
|
49
59
|
}
|
|
50
|
-
|
|
60
|
+
/**
|
|
61
|
+
* Best-effort GPU utilization fraction (0–1) via nvidia-smi. Returns undefined
|
|
62
|
+
* if nvidia-smi is not available or parsing fails.
|
|
63
|
+
*/
|
|
64
|
+
export declare function getGpuFraction(): Promise<number | undefined>;
|
|
65
|
+
export declare function currentLoadInfo(maxCpuFraction?: number, maxMemoryFraction?: number, gpuFraction?: number): LoadInfo & {
|
|
51
66
|
maxCpuFraction?: number;
|
|
52
67
|
maxMemoryFraction?: number;
|
|
68
|
+
maxGpuFraction?: number;
|
|
53
69
|
};
|
|
70
|
+
/** Like currentLoadInfo but async; fetches GPU utilization when maxGpuFraction is set. */
|
|
71
|
+
export declare function currentLoadInfoAsync(options: {
|
|
72
|
+
maxCpuFraction?: number;
|
|
73
|
+
maxMemoryFraction?: number;
|
|
74
|
+
maxGpuFraction?: number;
|
|
75
|
+
}): Promise<LoadInfo & {
|
|
76
|
+
maxCpuFraction?: number;
|
|
77
|
+
maxMemoryFraction?: number;
|
|
78
|
+
maxGpuFraction?: number;
|
|
79
|
+
}>;
|
|
54
80
|
export declare function waitForHeadroom(options: WaitForHeadroomOptions): Promise<void>;
|
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
import os from 'node:os';
|
|
2
|
-
|
|
2
|
+
import { execFile } from 'node:child_process';
|
|
3
|
+
import { promisify } from 'node:util';
|
|
4
|
+
const execFileP = promisify(execFile);
|
|
5
|
+
/**
|
|
6
|
+
* Best-effort GPU utilization fraction (0–1) via nvidia-smi. Returns undefined
|
|
7
|
+
* if nvidia-smi is not available or parsing fails.
|
|
8
|
+
*/
|
|
9
|
+
export async function getGpuFraction() {
|
|
10
|
+
try {
|
|
11
|
+
const { stdout } = await execFileP('nvidia-smi', ['--query-gpu=utilization.gpu', '--format=csv,noheader,nounits'], {
|
|
12
|
+
timeout: 5000,
|
|
13
|
+
encoding: 'utf-8',
|
|
14
|
+
});
|
|
15
|
+
const line = stdout.trim().split('\n')[0];
|
|
16
|
+
const pct = line ? parseInt(line.trim(), 10) : NaN;
|
|
17
|
+
if (Number.isFinite(pct) && pct >= 0 && pct <= 100)
|
|
18
|
+
return pct / 100;
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
// nvidia-smi not installed or failed
|
|
22
|
+
}
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
export function currentLoadInfo(maxCpuFraction, maxMemoryFraction, gpuFraction) {
|
|
3
26
|
const [load1, load5, load15] = os.loadavg();
|
|
4
27
|
const cpuCount = Math.max(os.cpus()?.length ?? 1, 1);
|
|
5
28
|
const cpuFraction = cpuCount > 0 ? load1 / cpuCount : 0;
|
|
@@ -15,34 +38,48 @@ export function currentLoadInfo(maxCpuFraction, maxMemoryFraction) {
|
|
|
15
38
|
memoryFraction,
|
|
16
39
|
totalMemBytes,
|
|
17
40
|
freeMemBytes,
|
|
41
|
+
gpuFraction,
|
|
18
42
|
maxCpuFraction,
|
|
19
43
|
maxMemoryFraction,
|
|
20
44
|
};
|
|
21
45
|
}
|
|
46
|
+
/** Like currentLoadInfo but async; fetches GPU utilization when maxGpuFraction is set. */
|
|
47
|
+
export async function currentLoadInfoAsync(options) {
|
|
48
|
+
const gpuFraction = options.maxGpuFraction != null ? await getGpuFraction() : undefined;
|
|
49
|
+
const info = currentLoadInfo(options.maxCpuFraction, options.maxMemoryFraction, gpuFraction);
|
|
50
|
+
return { ...info, maxGpuFraction: options.maxGpuFraction };
|
|
51
|
+
}
|
|
22
52
|
export async function waitForHeadroom(options) {
|
|
23
|
-
const { maxCpuFraction, maxMemoryFraction, pollIntervalMs = 2000, maxWaitMs = 120_000, logger, } = options;
|
|
53
|
+
const { maxCpuFraction, maxMemoryFraction, maxGpuFraction, pollIntervalMs = 2000, maxWaitMs = 120_000, logger, } = options;
|
|
24
54
|
// Treat thresholds >= 1 as "no gating" for that resource to keep tests
|
|
25
55
|
// and opt-out configurations fast.
|
|
26
56
|
const validCpu = maxCpuFraction !== undefined && maxCpuFraction > 0 && maxCpuFraction < 1;
|
|
27
57
|
const validMem = maxMemoryFraction !== undefined && maxMemoryFraction > 0 && maxMemoryFraction < 1;
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
logger?.warn({ maxCpuFraction, maxMemoryFraction }, 'resource-governor: invalid thresholds, skipping headroom check');
|
|
58
|
+
const validGpu = maxGpuFraction !== undefined && maxGpuFraction > 0 && maxGpuFraction < 1;
|
|
59
|
+
if (!validCpu && !validMem && !validGpu) {
|
|
60
|
+
logger?.warn({ maxCpuFraction, maxMemoryFraction, maxGpuFraction }, 'resource-governor: invalid thresholds, skipping headroom check');
|
|
31
61
|
return;
|
|
32
62
|
}
|
|
33
63
|
const start = Date.now();
|
|
34
|
-
|
|
35
|
-
|
|
64
|
+
async function getInfo() {
|
|
65
|
+
const gpuFraction = validGpu ? await getGpuFraction() : undefined;
|
|
66
|
+
const info = currentLoadInfo(maxCpuFraction, maxMemoryFraction, gpuFraction);
|
|
67
|
+
return { ...info, maxGpuFraction };
|
|
68
|
+
}
|
|
69
|
+
let info = await getInfo();
|
|
36
70
|
const withinCpu = !validCpu || info.cpuFraction <= maxCpuFraction;
|
|
37
71
|
const withinMem = !validMem || info.memoryFraction <= maxMemoryFraction;
|
|
38
|
-
|
|
39
|
-
|
|
72
|
+
const withinGpu = !validGpu ||
|
|
73
|
+
info.gpuFraction == null ||
|
|
74
|
+
info.gpuFraction <= maxGpuFraction;
|
|
75
|
+
if (withinCpu && withinMem && withinGpu) {
|
|
76
|
+
logger?.debug({ load: info }, 'resource-governor: sufficient headroom, proceeding immediately');
|
|
40
77
|
return;
|
|
41
78
|
}
|
|
42
79
|
logger?.warn({ load: info }, 'resource-governor: high system load detected, waiting for headroom');
|
|
43
|
-
// Slow-path: periodically poll until below threshold or timeout expires.
|
|
44
80
|
while ((!validCpu || info.cpuFraction > maxCpuFraction) ||
|
|
45
|
-
(!validMem || info.memoryFraction > maxMemoryFraction)
|
|
81
|
+
(!validMem || info.memoryFraction > maxMemoryFraction) ||
|
|
82
|
+
(validGpu && info.gpuFraction != null && info.gpuFraction > maxGpuFraction)) {
|
|
46
83
|
const elapsed = Date.now() - start;
|
|
47
84
|
if (elapsed >= maxWaitMs) {
|
|
48
85
|
logger?.warn({ load: info, elapsedMs: elapsed, maxWaitMs }, 'resource-governor: max wait exceeded, proceeding despite high load');
|
|
@@ -51,7 +88,7 @@ export async function waitForHeadroom(options) {
|
|
|
51
88
|
const remainingMs = maxWaitMs - elapsed;
|
|
52
89
|
const delayMs = Math.min(pollIntervalMs, remainingMs);
|
|
53
90
|
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
54
|
-
info =
|
|
91
|
+
info = await getInfo();
|
|
55
92
|
}
|
|
56
|
-
logger?.debug({ load: info, waitedMs: Date.now() - start }, 'resource-governor:
|
|
93
|
+
logger?.debug({ load: info, waitedMs: Date.now() - start }, 'resource-governor: headroom restored, resuming work');
|
|
57
94
|
}
|
package/dist/status-server.d.ts
CHANGED
|
@@ -72,6 +72,7 @@ export interface DashboardStatusPayload {
|
|
|
72
72
|
systemLoad?: import('./resource-governor.js').LoadInfo & {
|
|
73
73
|
maxCpuFraction?: number;
|
|
74
74
|
maxMemoryFraction?: number;
|
|
75
|
+
maxGpuFraction?: number;
|
|
75
76
|
};
|
|
76
77
|
}
|
|
77
78
|
/** Optional webhook: add goals/todos via API or Twilio inbound. */
|
package/dist/status-server.js
CHANGED
|
@@ -12,7 +12,7 @@ function escapeTwiML(s) {
|
|
|
12
12
|
import { readStateMd } from './state-parser.js';
|
|
13
13
|
import { readSessionLog } from './session-log.js';
|
|
14
14
|
import { getRecentCommits } from './git.js';
|
|
15
|
-
import { currentLoadInfo } from './resource-governor.js';
|
|
15
|
+
import { currentLoadInfo, currentLoadInfoAsync } from './resource-governor.js';
|
|
16
16
|
const DEFAULT_PLANNING_CONFIG = {
|
|
17
17
|
mode: 'interactive',
|
|
18
18
|
depth: 'standard',
|
|
@@ -341,11 +341,29 @@ export function createStatusServer(port, getStatus, options) {
|
|
|
341
341
|
/** Dashboard API: rich payload. */
|
|
342
342
|
app.get('/api/status', async (_req, res) => {
|
|
343
343
|
const legacy = getStatus();
|
|
344
|
+
let systemLoad = currentLoadInfo();
|
|
345
|
+
if (options?.planningConfigPath) {
|
|
346
|
+
try {
|
|
347
|
+
const raw = await readFile(options.planningConfigPath, 'utf-8');
|
|
348
|
+
const planning = JSON.parse(raw);
|
|
349
|
+
const maxCpu = typeof planning.maxCpuFraction === 'number' ? planning.maxCpuFraction : 0.8;
|
|
350
|
+
const maxMem = typeof planning.maxMemoryFraction === 'number' ? planning.maxMemoryFraction : 0.8;
|
|
351
|
+
const maxGpu = typeof planning.maxGpuFraction === 'number' ? planning.maxGpuFraction : undefined;
|
|
352
|
+
systemLoad = await currentLoadInfoAsync({
|
|
353
|
+
maxCpuFraction: maxCpu,
|
|
354
|
+
maxMemoryFraction: maxMem,
|
|
355
|
+
maxGpuFraction: maxGpu,
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
catch {
|
|
359
|
+
// keep sync load info
|
|
360
|
+
}
|
|
361
|
+
}
|
|
344
362
|
const payload = {
|
|
345
363
|
...legacy,
|
|
346
364
|
tokens: {},
|
|
347
365
|
cost: {},
|
|
348
|
-
systemLoad
|
|
366
|
+
systemLoad,
|
|
349
367
|
};
|
|
350
368
|
if (options) {
|
|
351
369
|
const [stateSnapshot, sessionLogEntries, gitFeed] = await Promise.all([
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gsd-unsupervised",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Autonomous orchestrator for Cursor agent + GSD framework",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/cli.js",
|
|
@@ -28,7 +28,8 @@
|
|
|
28
28
|
"start": "node dist/cli.js",
|
|
29
29
|
"dev": "tsc --watch",
|
|
30
30
|
"test": "vitest run --reporter verbose",
|
|
31
|
-
"test:integration": "vitest run tests/"
|
|
31
|
+
"test:integration": "vitest run tests/",
|
|
32
|
+
"test:sms": "node dist/cli.js test-sms"
|
|
32
33
|
},
|
|
33
34
|
"engines": {
|
|
34
35
|
"node": ">=18"
|