@grantx/fleet-core 0.1.2 → 0.1.4
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/package.json +1 -1
- package/src/conductor-loop.js +30 -0
- package/src/config.js +20 -3
- package/src/index.js +2 -2
- package/src/platform.js +26 -2
package/package.json
CHANGED
package/src/conductor-loop.js
CHANGED
|
@@ -8,6 +8,7 @@ import { SmartDispatcher } from './smart-dispatcher.js';
|
|
|
8
8
|
import { ConductorRunner, parseDispatchBlock } from './conductor-runner.js';
|
|
9
9
|
import { fetchFleetState, executeDecisions } from './fleet-utils.js';
|
|
10
10
|
import { buildConductorEventPrompt } from './dispatch-prompts.js';
|
|
11
|
+
import { checkCredentialHealth } from './platform.js';
|
|
11
12
|
|
|
12
13
|
export class ConductorLoop {
|
|
13
14
|
/**
|
|
@@ -35,6 +36,8 @@ export class ConductorLoop {
|
|
|
35
36
|
// Cron trackers
|
|
36
37
|
this._lastHealthCheck = Date.now();
|
|
37
38
|
this._lastSynthesis = Date.now();
|
|
39
|
+
this._lastCredCheck = Date.now();
|
|
40
|
+
this._credHealthy = true;
|
|
38
41
|
|
|
39
42
|
// Stats
|
|
40
43
|
this.stats = {
|
|
@@ -307,6 +310,33 @@ export class ConductorLoop {
|
|
|
307
310
|
this.pushEvent({ type: 'synthesis', timestamp: new Date().toISOString() });
|
|
308
311
|
log.info('conductor-loop', 'Cron: synthesis');
|
|
309
312
|
}
|
|
313
|
+
|
|
314
|
+
// Credential health (every 30 min)
|
|
315
|
+
const credIntervalMs = (this.cronConfig.credCheckMinutes || 30) * 60 * 1000;
|
|
316
|
+
if (now - this._lastCredCheck >= credIntervalMs) {
|
|
317
|
+
this._lastCredCheck = now;
|
|
318
|
+
this._checkCredentials();
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
async _checkCredentials() {
|
|
323
|
+
try {
|
|
324
|
+
const result = await checkCredentialHealth();
|
|
325
|
+
if (!result.ok && this._credHealthy) {
|
|
326
|
+
this._credHealthy = false;
|
|
327
|
+
log.error('conductor-loop', `Credential check FAILED: ${result.reason}`);
|
|
328
|
+
this.pushEvent({
|
|
329
|
+
type: 'credential_expired',
|
|
330
|
+
reason: result.reason,
|
|
331
|
+
timestamp: new Date().toISOString(),
|
|
332
|
+
});
|
|
333
|
+
} else if (result.ok && !this._credHealthy) {
|
|
334
|
+
this._credHealthy = true;
|
|
335
|
+
log.info('conductor-loop', 'Credentials restored');
|
|
336
|
+
}
|
|
337
|
+
} catch (err) {
|
|
338
|
+
log.warn('conductor-loop', `Credential check error: ${err.message}`);
|
|
339
|
+
}
|
|
310
340
|
}
|
|
311
341
|
|
|
312
342
|
// ── Dedup helpers ─────────────────────────────────────────────────
|
package/src/config.js
CHANGED
|
@@ -50,11 +50,18 @@ function findConfigFile(dir) {
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
/**
|
|
53
|
-
*
|
|
53
|
+
* Valid team IDs. Used by init.js and validated here.
|
|
54
|
+
*/
|
|
55
|
+
export const VALID_TEAM_IDS = ['grantx-ml', 'grantx-data', 'grantx-fullstack', 'grantx-skunkworks'];
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Validate config schema. Supports version 1 and 2.
|
|
59
|
+
* v1: original schema (auto-generated agents)
|
|
60
|
+
* v2: rich agents with identity, domains, frozenDecisions, repos, slack
|
|
54
61
|
*/
|
|
55
62
|
function validate(config, filePath) {
|
|
56
|
-
if (config.version !== 1) {
|
|
57
|
-
throw new Error(`${filePath}: unsupported version ${config.version} (expected 1)`);
|
|
63
|
+
if (config.version !== 1 && config.version !== 2) {
|
|
64
|
+
throw new Error(`${filePath}: unsupported version ${config.version} (expected 1 or 2)`);
|
|
58
65
|
}
|
|
59
66
|
if (!config.teamId || typeof config.teamId !== 'string') {
|
|
60
67
|
throw new Error(`${filePath}: teamId is required (string)`);
|
|
@@ -79,6 +86,16 @@ function validate(config, filePath) {
|
|
|
79
86
|
throw new Error(`${filePath}: agent "${agent.name}" must have a role (string)`);
|
|
80
87
|
}
|
|
81
88
|
}
|
|
89
|
+
|
|
90
|
+
// v2-specific: repos and slack are optional arrays/objects — validate shape if present
|
|
91
|
+
if (config.repos && !Array.isArray(config.repos)) {
|
|
92
|
+
throw new Error(`${filePath}: repos must be an array`);
|
|
93
|
+
}
|
|
94
|
+
if (config.slack) {
|
|
95
|
+
if (config.slack.botToken && typeof config.slack.botToken !== 'string') {
|
|
96
|
+
throw new Error(`${filePath}: slack.botToken must be a string`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
82
99
|
}
|
|
83
100
|
|
|
84
101
|
/**
|
package/src/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// @grantx/fleet-core — shared engine for the fleet plugin
|
|
2
2
|
// Cross-platform, config-driven, no hardcoded Grantx references.
|
|
3
3
|
|
|
4
|
-
export { loadConfig, getAgentRoster, getConductor, getWorkerAgents } from './config.js';
|
|
4
|
+
export { loadConfig, getAgentRoster, getConductor, getWorkerAgents, VALID_TEAM_IDS } from './config.js';
|
|
5
5
|
export { AgentPool } from './agent-runner.js';
|
|
6
6
|
export { ConductorRunner, parseDispatchBlock } from './conductor-runner.js';
|
|
7
7
|
export { SmartDispatcher } from './smart-dispatcher.js';
|
|
@@ -9,4 +9,4 @@ export { ConductorLoop } from './conductor-loop.js';
|
|
|
9
9
|
export { SupabaseClient } from './supabase-client.js';
|
|
10
10
|
export { fetchFleetState, executeDecisions, dispatchAgent, recordTaskFailure } from './fleet-utils.js';
|
|
11
11
|
export { log } from './logger.js';
|
|
12
|
-
export
|
|
12
|
+
export { isWindows, isMac, isLinux, lockDir, ensureLockDir, buildAgentEnv, setupCredentials, refreshCredentials, fleetDir, agentHome, checkCredentialHealth } from './platform.js';
|
package/src/platform.js
CHANGED
|
@@ -23,14 +23,15 @@ export function ensureLockDir() {
|
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
25
|
* Build environment variables for a spawned agent process.
|
|
26
|
-
* Inherits the
|
|
26
|
+
* Inherits the full parent environment (TMPDIR, USER, SHELL, LANG, etc.
|
|
27
|
+
* are required by claude CLI). Overrides only isolation vars.
|
|
27
28
|
*/
|
|
28
29
|
export function buildAgentEnv(agentName, agentHome, teamId, extra = {}) {
|
|
29
30
|
const env = {
|
|
31
|
+
...process.env,
|
|
30
32
|
HOME: agentHome,
|
|
31
33
|
FLEET_AGENT_ID: agentName,
|
|
32
34
|
FLEET_TEAM_ID: teamId,
|
|
33
|
-
PATH: process.env.PATH,
|
|
34
35
|
CLOUDSDK_CONFIG: process.env.CLOUDSDK_CONFIG || path.join(os.homedir(), '.config', 'gcloud'),
|
|
35
36
|
...extra,
|
|
36
37
|
};
|
|
@@ -100,3 +101,26 @@ export function fleetDir(projectRoot) {
|
|
|
100
101
|
export function agentHome(projectRoot, agentName) {
|
|
101
102
|
return path.join(projectRoot, '.fleet', 'agents', agentName);
|
|
102
103
|
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Check if Claude credentials are valid by spawning a quick test.
|
|
107
|
+
* Returns { ok: true } or { ok: false, reason: string }.
|
|
108
|
+
*/
|
|
109
|
+
export async function checkCredentialHealth() {
|
|
110
|
+
const { execFile } = await import('node:child_process');
|
|
111
|
+
return new Promise((resolve) => {
|
|
112
|
+
const child = execFile('claude', ['-p', 'respond PONG', '--max-turns', '1'], {
|
|
113
|
+
timeout: 30000,
|
|
114
|
+
env: process.env,
|
|
115
|
+
}, (err, stdout, stderr) => {
|
|
116
|
+
if (err) {
|
|
117
|
+
const reason = stderr?.includes('OAuth') || stderr?.includes('expired')
|
|
118
|
+
? 'OAuth token expired — run `claude login` on host'
|
|
119
|
+
: err.message;
|
|
120
|
+
resolve({ ok: false, reason });
|
|
121
|
+
} else {
|
|
122
|
+
resolve({ ok: true });
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
}
|