@maestrofrontier/frontier 1.4.5 → 1.6.0
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/plugins/marketplace.json +21 -21
- package/.codex-plugin/plugin.json +29 -29
- package/.cursorrules +197 -194
- package/AGENTS.md +3 -3
- package/README.md +368 -368
- package/bin/maestro.cjs +75 -75
- package/commands/compress.md +36 -36
- package/commands/frontier.md +124 -124
- package/commands/terse.md +23 -23
- package/docs/codex.md +167 -167
- package/docs/orchestration.md +168 -168
- package/frontier/cli.cjs +279 -252
- package/frontier/config.cjs +468 -468
- package/frontier/dispatch.cjs +267 -255
- package/frontier/judge.cjs +92 -92
- package/frontier/progress.cjs +138 -0
- package/frontier/run.cjs +201 -180
- package/frontier/schema.cjs +112 -112
- package/frontier/semaphore.cjs +49 -49
- package/frontier/synthesize.cjs +79 -79
- package/hooks/frontier-autorun.cjs +135 -120
- package/hooks/hooks.json +103 -103
- package/hooks/maestro-doctrine-guard.cjs +81 -81
- package/hooks/maestro-gate-reminder.cjs +22 -7
- package/hooks/maestro-gate-telemetry.cjs +79 -77
- package/hooks/maestro-phase-scope.cjs +118 -118
- package/hooks/maestro-statusline-sync.cjs +152 -152
- package/hooks/maestro-subagent-guard.cjs +148 -148
- package/hooks/maestro-terse-mode.cjs +189 -189
- package/hooks/maestro-toolbudget-advisory.cjs +127 -127
- package/integrations/README.md +111 -111
- package/integrations/cline/skills/frontier/SKILL.md +75 -75
- package/integrations/codex/prompts/frontier.md +70 -70
- package/integrations/codex/prompts/update.md +39 -39
- package/integrations/codex/skills/maestro-frontier/SKILL.md +122 -122
- package/integrations/codex/skills/maestro-settings/SKILL.md +55 -55
- package/integrations/codex/skills/maestro-terse/SKILL.md +58 -58
- package/integrations/codex/skills/maestro-update/SKILL.md +31 -31
- package/integrations/cursor/commands/frontier.md +63 -63
- package/integrations/cursor/commands/update.md +34 -34
- package/integrations/gemini/commands/frontier.toml +76 -76
- package/integrations/windsurf/workflows/frontier.md +70 -70
- package/package.json +59 -58
- package/scripts/install.cjs +1014 -1014
- package/settings/cli.cjs +140 -140
- package/settings/config.cjs +309 -309
- package/skills/maestro-frontier/SKILL.md +122 -122
- package/skills/maestro-settings/SKILL.md +55 -55
- package/skills/maestro-terse/SKILL.md +58 -58
- package/skills/maestro-update/SKILL.md +31 -31
- package/skills/terse/SKILL.md +74 -74
package/frontier/semaphore.cjs
CHANGED
|
@@ -1,49 +1,49 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// Maestro Frontier — bounded-concurrency map utility.
|
|
3
|
-
// mapLimit(items, limit, asyncFn) -> Promise<settled[]>
|
|
4
|
-
// Each settled item is {ok:true,value} or {ok:false,error}.
|
|
5
|
-
// A rejected task releases its permit and does NOT starve the pool.
|
|
6
|
-
|
|
7
|
-
'use strict';
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* @template T, R
|
|
11
|
-
* @param {T[]} items
|
|
12
|
-
* @param {number} limit
|
|
13
|
-
* @param {(item: T, index: number) => Promise<R>} asyncFn
|
|
14
|
-
* @returns {Promise<({ok:true,value:R}|{ok:false,error:unknown})[]}
|
|
15
|
-
*/
|
|
16
|
-
function mapLimit(items, limit, asyncFn) {
|
|
17
|
-
return new Promise((resolve) => {
|
|
18
|
-
const n = items.length;
|
|
19
|
-
if (n === 0) { resolve([]); return; }
|
|
20
|
-
|
|
21
|
-
const results = new Array(n);
|
|
22
|
-
let nextIdx = 0; // index of next item to start
|
|
23
|
-
let inFlight = 0; // currently running tasks
|
|
24
|
-
let done = 0; // settled tasks
|
|
25
|
-
|
|
26
|
-
function run() {
|
|
27
|
-
while (inFlight < limit && nextIdx < n) {
|
|
28
|
-
const idx = nextIdx++;
|
|
29
|
-
inFlight++;
|
|
30
|
-
Promise.resolve()
|
|
31
|
-
.then(() => asyncFn(items[idx], idx))
|
|
32
|
-
.then(
|
|
33
|
-
(value) => { results[idx] = { ok: true, value }; },
|
|
34
|
-
(error) => { results[idx] = { ok: false, error }; }
|
|
35
|
-
)
|
|
36
|
-
.finally(() => {
|
|
37
|
-
inFlight--;
|
|
38
|
-
done++;
|
|
39
|
-
if (done === n) { resolve(results); return; }
|
|
40
|
-
run();
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
run();
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
module.exports = { mapLimit };
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Maestro Frontier — bounded-concurrency map utility.
|
|
3
|
+
// mapLimit(items, limit, asyncFn) -> Promise<settled[]>
|
|
4
|
+
// Each settled item is {ok:true,value} or {ok:false,error}.
|
|
5
|
+
// A rejected task releases its permit and does NOT starve the pool.
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @template T, R
|
|
11
|
+
* @param {T[]} items
|
|
12
|
+
* @param {number} limit
|
|
13
|
+
* @param {(item: T, index: number) => Promise<R>} asyncFn
|
|
14
|
+
* @returns {Promise<({ok:true,value:R}|{ok:false,error:unknown})[]}
|
|
15
|
+
*/
|
|
16
|
+
function mapLimit(items, limit, asyncFn) {
|
|
17
|
+
return new Promise((resolve) => {
|
|
18
|
+
const n = items.length;
|
|
19
|
+
if (n === 0) { resolve([]); return; }
|
|
20
|
+
|
|
21
|
+
const results = new Array(n);
|
|
22
|
+
let nextIdx = 0; // index of next item to start
|
|
23
|
+
let inFlight = 0; // currently running tasks
|
|
24
|
+
let done = 0; // settled tasks
|
|
25
|
+
|
|
26
|
+
function run() {
|
|
27
|
+
while (inFlight < limit && nextIdx < n) {
|
|
28
|
+
const idx = nextIdx++;
|
|
29
|
+
inFlight++;
|
|
30
|
+
Promise.resolve()
|
|
31
|
+
.then(() => asyncFn(items[idx], idx))
|
|
32
|
+
.then(
|
|
33
|
+
(value) => { results[idx] = { ok: true, value }; },
|
|
34
|
+
(error) => { results[idx] = { ok: false, error }; }
|
|
35
|
+
)
|
|
36
|
+
.finally(() => {
|
|
37
|
+
inFlight--;
|
|
38
|
+
done++;
|
|
39
|
+
if (done === n) { resolve(results); return; }
|
|
40
|
+
run();
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
run();
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
module.exports = { mapLimit };
|
package/frontier/synthesize.cjs
CHANGED
|
@@ -1,79 +1,79 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// Maestro Frontier — synthesis stage: build prompt + invoke Opus for final answer.
|
|
3
|
-
|
|
4
|
-
'use strict';
|
|
5
|
-
|
|
6
|
-
const dispatch = require('./dispatch.cjs');
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Build the synthesis prompt for Opus.
|
|
10
|
-
* @param {string} userPrompt
|
|
11
|
-
* @param {{ analysis?: import('./schema.cjs').Analysis, responses: import('./schema.cjs').PanelResponse[] }} bundle
|
|
12
|
-
* @param {object} cfg
|
|
13
|
-
* @returns {string}
|
|
14
|
-
*/
|
|
15
|
-
function buildSynthPrompt(userPrompt, bundle, cfg) {
|
|
16
|
-
const antiMajority =
|
|
17
|
-
'Do NOT majority-vote or pick the most common answer; weigh correctness and evidence — ' +
|
|
18
|
-
'a single correct minority response outweighs a popular wrong one.';
|
|
19
|
-
|
|
20
|
-
let groundingSection;
|
|
21
|
-
if (bundle.analysis) {
|
|
22
|
-
groundingSection =
|
|
23
|
-
`PANEL ANALYSIS (structured):
|
|
24
|
-
${JSON.stringify(bundle.analysis, null, 2)}
|
|
25
|
-
|
|
26
|
-
Ground your final answer in this analysis:
|
|
27
|
-
- Adopt the consensus points as established facts.
|
|
28
|
-
- RESOLVE contradictions by reasoning about which stance is most correct; do not dodge them.
|
|
29
|
-
- Preserve unique insights that add value.
|
|
30
|
-
- Address any blind spots the analysis identified.`;
|
|
31
|
-
} else {
|
|
32
|
-
const raw = bundle.responses.map(
|
|
33
|
-
r => `### Response from ${r.model}\n${r.content}`
|
|
34
|
-
).join('\n\n');
|
|
35
|
-
groundingSection =
|
|
36
|
-
`RAW PANEL RESPONSES:
|
|
37
|
-
${raw}
|
|
38
|
-
|
|
39
|
-
Ground your final answer in these responses.`;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
return `You are a SYNTHESIZER producing the definitive final answer to a user question.
|
|
43
|
-
|
|
44
|
-
USER QUESTION:
|
|
45
|
-
${userPrompt}
|
|
46
|
-
|
|
47
|
-
${groundingSection}
|
|
48
|
-
|
|
49
|
-
IMPORTANT: ${antiMajority}
|
|
50
|
-
|
|
51
|
-
Write the final answer as clear, direct prose. No JSON, no meta-commentary, no preamble about your process. Output the answer only.`;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Run the synthesis stage. Returns the final answer string or '' on failure (degrades gracefully).
|
|
56
|
-
* @param {string} userPrompt
|
|
57
|
-
* @param {{ analysis?: import('./schema.cjs').Analysis, responses: import('./schema.cjs').PanelResponse[] }} bundle
|
|
58
|
-
* @param {object} cfg
|
|
59
|
-
* @param {{ spawn?: Function }} [deps]
|
|
60
|
-
* @returns {Promise<string>}
|
|
61
|
-
*/
|
|
62
|
-
async function runSynth(userPrompt, bundle, cfg, deps) {
|
|
63
|
-
const spawn = (deps && deps.spawn) || dispatch.spawnOne;
|
|
64
|
-
let r;
|
|
65
|
-
try {
|
|
66
|
-
r = await spawn(
|
|
67
|
-
buildSynthPrompt(userPrompt, bundle, cfg),
|
|
68
|
-
cfg.adapters[cfg.synthModel],
|
|
69
|
-
{ timeoutMs: cfg.timeoutMs, fusionDepth: 1 }
|
|
70
|
-
);
|
|
71
|
-
} catch {
|
|
72
|
-
return '';
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
if (r && r.ok && r.content) return r.content;
|
|
76
|
-
return '';
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
module.exports = { buildSynthPrompt, runSynth };
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Maestro Frontier — synthesis stage: build prompt + invoke Opus for final answer.
|
|
3
|
+
|
|
4
|
+
'use strict';
|
|
5
|
+
|
|
6
|
+
const dispatch = require('./dispatch.cjs');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Build the synthesis prompt for Opus.
|
|
10
|
+
* @param {string} userPrompt
|
|
11
|
+
* @param {{ analysis?: import('./schema.cjs').Analysis, responses: import('./schema.cjs').PanelResponse[] }} bundle
|
|
12
|
+
* @param {object} cfg
|
|
13
|
+
* @returns {string}
|
|
14
|
+
*/
|
|
15
|
+
function buildSynthPrompt(userPrompt, bundle, cfg) {
|
|
16
|
+
const antiMajority =
|
|
17
|
+
'Do NOT majority-vote or pick the most common answer; weigh correctness and evidence — ' +
|
|
18
|
+
'a single correct minority response outweighs a popular wrong one.';
|
|
19
|
+
|
|
20
|
+
let groundingSection;
|
|
21
|
+
if (bundle.analysis) {
|
|
22
|
+
groundingSection =
|
|
23
|
+
`PANEL ANALYSIS (structured):
|
|
24
|
+
${JSON.stringify(bundle.analysis, null, 2)}
|
|
25
|
+
|
|
26
|
+
Ground your final answer in this analysis:
|
|
27
|
+
- Adopt the consensus points as established facts.
|
|
28
|
+
- RESOLVE contradictions by reasoning about which stance is most correct; do not dodge them.
|
|
29
|
+
- Preserve unique insights that add value.
|
|
30
|
+
- Address any blind spots the analysis identified.`;
|
|
31
|
+
} else {
|
|
32
|
+
const raw = bundle.responses.map(
|
|
33
|
+
r => `### Response from ${r.model}\n${r.content}`
|
|
34
|
+
).join('\n\n');
|
|
35
|
+
groundingSection =
|
|
36
|
+
`RAW PANEL RESPONSES:
|
|
37
|
+
${raw}
|
|
38
|
+
|
|
39
|
+
Ground your final answer in these responses.`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return `You are a SYNTHESIZER producing the definitive final answer to a user question.
|
|
43
|
+
|
|
44
|
+
USER QUESTION:
|
|
45
|
+
${userPrompt}
|
|
46
|
+
|
|
47
|
+
${groundingSection}
|
|
48
|
+
|
|
49
|
+
IMPORTANT: ${antiMajority}
|
|
50
|
+
|
|
51
|
+
Write the final answer as clear, direct prose. No JSON, no meta-commentary, no preamble about your process. Output the answer only.`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Run the synthesis stage. Returns the final answer string or '' on failure (degrades gracefully).
|
|
56
|
+
* @param {string} userPrompt
|
|
57
|
+
* @param {{ analysis?: import('./schema.cjs').Analysis, responses: import('./schema.cjs').PanelResponse[] }} bundle
|
|
58
|
+
* @param {object} cfg
|
|
59
|
+
* @param {{ spawn?: Function }} [deps]
|
|
60
|
+
* @returns {Promise<string>}
|
|
61
|
+
*/
|
|
62
|
+
async function runSynth(userPrompt, bundle, cfg, deps) {
|
|
63
|
+
const spawn = (deps && deps.spawn) || dispatch.spawnOne;
|
|
64
|
+
let r;
|
|
65
|
+
try {
|
|
66
|
+
r = await spawn(
|
|
67
|
+
buildSynthPrompt(userPrompt, bundle, cfg),
|
|
68
|
+
cfg.adapters[cfg.synthModel],
|
|
69
|
+
{ timeoutMs: cfg.timeoutMs, fusionDepth: 1 }
|
|
70
|
+
);
|
|
71
|
+
} catch {
|
|
72
|
+
return '';
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (r && r.ok && r.content) return r.content;
|
|
76
|
+
return '';
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
module.exports = { buildSynthPrompt, runSynth };
|
|
@@ -1,120 +1,135 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// Maestro Frontier autorun hook (UserPromptSubmit). When the engine is
|
|
3
|
-
// armed (frontier-state mode != 'off'), every user prompt is run through
|
|
4
|
-
// the configured preset/model via runFrontier, and the synthesized answer
|
|
5
|
-
// is injected back as additionalContext with a relay instruction + a
|
|
6
|
-
// one-line preset header, so the live session relays it. mode == 'off' ->
|
|
7
|
-
// zero overhead: no engine require, no spawn, no injected context.
|
|
8
|
-
//
|
|
9
|
-
// Recursion guard (load-bearing): the engine spawns child `claude -p`
|
|
10
|
-
// CLIs, and headless `claude -p` re-fires UserPromptSubmit hooks (verified
|
|
11
|
-
// against the Claude Code hooks contract). dispatch.cjs sets FUSION_DEPTH
|
|
12
|
-
// on every spawned child; this hook no-ops whenever FUSION_DEPTH is present,
|
|
13
|
-
// BEFORE any engine require or spawn. Without it the first armed prompt
|
|
14
|
-
// would fork the engine recursively.
|
|
15
|
-
//
|
|
16
|
-
// Degrade-to-normal: any engine error, empty answer, or thrown exception
|
|
17
|
-
// exits 0 with empty stdout (logging only to stderr), so a broken engine
|
|
18
|
-
// never blocks or corrupts a turn — the session just answers normally. A
|
|
19
|
-
// UserPromptSubmit hook can only ADD context; it cannot suppress the main
|
|
20
|
-
// turn, which is exactly the relay model.
|
|
21
|
-
//
|
|
22
|
-
// .cjs so Node treats it as CommonJS regardless of any parent "type":
|
|
23
|
-
// "module" package.json. configDir/state patterns reused from
|
|
24
|
-
// frontier/config.cjs; structure ported from maestro-terse-mode.cjs.
|
|
25
|
-
|
|
26
|
-
'use strict';
|
|
27
|
-
|
|
28
|
-
const fs = require('fs');
|
|
29
|
-
|
|
30
|
-
function noop() { process.exit(0); }
|
|
31
|
-
|
|
32
|
-
let data = {};
|
|
33
|
-
try { data = JSON.parse(fs.readFileSync(0, 'utf8')); } catch { noop(); }
|
|
34
|
-
|
|
35
|
-
if (data.hook_event_name !== 'UserPromptSubmit') noop();
|
|
36
|
-
|
|
37
|
-
// Recursion guard FIRST: never run the engine inside an engine-spawned CLI.
|
|
38
|
-
// The engine sets FUSION_DEPTH on every child (dispatch.cjs:108); depth >= 1
|
|
39
|
-
// means we are inside a spawned panel/judge/synth process. Mirror run.cjs's
|
|
40
|
-
// own parseInt check (run.cjs:27) so the two layers agree and a stray
|
|
41
|
-
// FUSION_DEPTH='0'/'' in the environment reads as "not a child".
|
|
42
|
-
const fusionDepth = parseInt(process.env.FUSION_DEPTH || '0', 10);
|
|
43
|
-
if (Number.isFinite(fusionDepth) && fusionDepth >= 1) noop();
|
|
44
|
-
|
|
45
|
-
let state;
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
//
|
|
62
|
-
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
//
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
process.
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
noop();
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Maestro Frontier autorun hook (UserPromptSubmit). When the engine is
|
|
3
|
+
// armed (frontier-state mode != 'off'), every user prompt is run through
|
|
4
|
+
// the configured preset/model via runFrontier, and the synthesized answer
|
|
5
|
+
// is injected back as additionalContext with a relay instruction + a
|
|
6
|
+
// one-line preset header, so the live session relays it. mode == 'off' ->
|
|
7
|
+
// zero overhead: no engine require, no spawn, no injected context.
|
|
8
|
+
//
|
|
9
|
+
// Recursion guard (load-bearing): the engine spawns child `claude -p`
|
|
10
|
+
// CLIs, and headless `claude -p` re-fires UserPromptSubmit hooks (verified
|
|
11
|
+
// against the Claude Code hooks contract). dispatch.cjs sets FUSION_DEPTH
|
|
12
|
+
// on every spawned child; this hook no-ops whenever FUSION_DEPTH is present,
|
|
13
|
+
// BEFORE any engine require or spawn. Without it the first armed prompt
|
|
14
|
+
// would fork the engine recursively.
|
|
15
|
+
//
|
|
16
|
+
// Degrade-to-normal: any engine error, empty answer, or thrown exception
|
|
17
|
+
// exits 0 with empty stdout (logging only to stderr), so a broken engine
|
|
18
|
+
// never blocks or corrupts a turn — the session just answers normally. A
|
|
19
|
+
// UserPromptSubmit hook can only ADD context; it cannot suppress the main
|
|
20
|
+
// turn, which is exactly the relay model.
|
|
21
|
+
//
|
|
22
|
+
// .cjs so Node treats it as CommonJS regardless of any parent "type":
|
|
23
|
+
// "module" package.json. configDir/state patterns reused from
|
|
24
|
+
// frontier/config.cjs; structure ported from maestro-terse-mode.cjs.
|
|
25
|
+
|
|
26
|
+
'use strict';
|
|
27
|
+
|
|
28
|
+
const fs = require('fs');
|
|
29
|
+
|
|
30
|
+
function noop() { process.exit(0); }
|
|
31
|
+
|
|
32
|
+
let data = {};
|
|
33
|
+
try { data = JSON.parse(fs.readFileSync(0, 'utf8')); } catch { noop(); }
|
|
34
|
+
|
|
35
|
+
if (data.hook_event_name !== 'UserPromptSubmit') noop();
|
|
36
|
+
|
|
37
|
+
// Recursion guard FIRST: never run the engine inside an engine-spawned CLI.
|
|
38
|
+
// The engine sets FUSION_DEPTH on every child (dispatch.cjs:108); depth >= 1
|
|
39
|
+
// means we are inside a spawned panel/judge/synth process. Mirror run.cjs's
|
|
40
|
+
// own parseInt check (run.cjs:27) so the two layers agree and a stray
|
|
41
|
+
// FUSION_DEPTH='0'/'' in the environment reads as "not a child".
|
|
42
|
+
const fusionDepth = parseInt(process.env.FUSION_DEPTH || '0', 10);
|
|
43
|
+
if (Number.isFinite(fusionDepth) && fusionDepth >= 1) noop();
|
|
44
|
+
|
|
45
|
+
let state;
|
|
46
|
+
let scope;
|
|
47
|
+
try {
|
|
48
|
+
const cfg = require('../frontier/config.cjs');
|
|
49
|
+
const cwd = data.cwd || process.env.CLAUDE_PROJECT_DIR || process.env.CODEX_PROJECT_DIR || process.cwd();
|
|
50
|
+
scope = cfg.resolveScope([], { cwd });
|
|
51
|
+
state = cfg.loadState(scope);
|
|
52
|
+
} catch {
|
|
53
|
+
noop();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Off -> zero overhead: no run.cjs require, no spawn, no injected context.
|
|
57
|
+
if (!state || state.mode === 'off') noop();
|
|
58
|
+
|
|
59
|
+
const prompt = String(data.prompt || '');
|
|
60
|
+
|
|
61
|
+
// Optional length gate (default 0 = every prompt). Skips trivially short
|
|
62
|
+
// prompts ("yes"/"ok") so they don't pay a full engine run.
|
|
63
|
+
const rawMin = Number(state.autorunMinChars);
|
|
64
|
+
const minChars = Number.isFinite(rawMin) && rawMin > 0 ? rawMin : 0;
|
|
65
|
+
if (prompt.trim().length < minChars) noop();
|
|
66
|
+
|
|
67
|
+
// Any unexpected throw after the await boundary degrades to a normal turn,
|
|
68
|
+
// never a non-zero exit / unhandled rejection.
|
|
69
|
+
run().catch((e) => {
|
|
70
|
+
process.stderr.write('frontier-autorun: ' + ((e && e.message) || e) + '\n');
|
|
71
|
+
process.exit(0);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
async function run() {
|
|
75
|
+
let result;
|
|
76
|
+
const runStart = Date.now();
|
|
77
|
+
// Live statusline progress: write the current stage as each one starts so
|
|
78
|
+
// the context-bar can show ƒ⠿ fanning / ƒ⚖ judging / ƒ✶ synth during the
|
|
79
|
+
// otherwise-silent blocking run. Cleared below on completion or error.
|
|
80
|
+
const progress = require('../frontier/progress.cjs');
|
|
81
|
+
const onProgress = progress.makeProgressWriter(scope);
|
|
82
|
+
try {
|
|
83
|
+
const { runFrontier } = require('../frontier/run.cjs');
|
|
84
|
+
result = await runFrontier({ prompt, state, deps: { onProgress } });
|
|
85
|
+
} catch (e) {
|
|
86
|
+
progress.clearProgress(scope);
|
|
87
|
+
process.stderr.write('frontier-autorun: ' + ((e && e.message) || e) + '\n');
|
|
88
|
+
noop();
|
|
89
|
+
}
|
|
90
|
+
const runMs = Date.now() - runStart;
|
|
91
|
+
progress.clearProgress(scope);
|
|
92
|
+
|
|
93
|
+
if (!result || result.status !== 'ok' || !result.final) {
|
|
94
|
+
if (result && result.status === 'error') {
|
|
95
|
+
process.stderr.write(
|
|
96
|
+
'frontier-autorun: engine error [' + result.failure_reason + ']: ' + result.error + '\n');
|
|
97
|
+
}
|
|
98
|
+
noop();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const modelCount = result.responses ? result.responses.length : 1;
|
|
102
|
+
const banner = '⚡ Frontier \xb7 ' + presetHeader(state) + ' \xb7 ' + modelCount + ' models \xb7 ' + Math.round(runMs / 1000) + 's';
|
|
103
|
+
|
|
104
|
+
const context =
|
|
105
|
+
'MAESTRO FRONTIER AUTORUN — ' + presetHeader(state) + '\n\n' +
|
|
106
|
+
'The Maestro Frontier engine already ran this prompt through the panel ' +
|
|
107
|
+
'above and produced the answer below. Begin your response with this exact ' +
|
|
108
|
+
'banner line (verbatim, on its own line): ' + banner + '\n\n' +
|
|
109
|
+
'Then relay the answer — you may reformat for clarity, but do not redo ' +
|
|
110
|
+
'the work or contradict it:\n\n' +
|
|
111
|
+
result.final;
|
|
112
|
+
|
|
113
|
+
// fs.writeSync (synchronous, unbuffered) guarantees the full payload reaches
|
|
114
|
+
// stdout before exit. process.exit() does NOT drain a pipe-backed
|
|
115
|
+
// process.stdout, which would truncate a large engine answer (multi-KB
|
|
116
|
+
// synthesis) into malformed JSON the hook consumer cannot parse.
|
|
117
|
+
fs.writeSync(1, JSON.stringify({
|
|
118
|
+
hookSpecificOutput: {
|
|
119
|
+
hookEventName: 'UserPromptSubmit',
|
|
120
|
+
additionalContext: context,
|
|
121
|
+
},
|
|
122
|
+
}));
|
|
123
|
+
process.exit(0);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function presetHeader(st) {
|
|
127
|
+
if (st.mode === 'single') return 'single · ' + st.model;
|
|
128
|
+
if (st.mode === 'fusion') {
|
|
129
|
+
const preset = st.preset === 'custom'
|
|
130
|
+
? 'custom (' + (Array.isArray(st.models) ? st.models.join(', ') : '') + ')'
|
|
131
|
+
: st.preset;
|
|
132
|
+
return 'fusion · ' + preset;
|
|
133
|
+
}
|
|
134
|
+
return String(st.mode);
|
|
135
|
+
}
|