axstack 0.54.0 → 0.55.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/package.json +1 -1
- package/profiles/presets/claude-only.json +9 -0
- package/profiles/presets/codex-only.json +9 -0
- package/profiles/presets/mixed.json +9 -0
- package/skills/axstack/references/fleet-route-schema.md +314 -0
- package/skills/axstack/references/role-roster.md +5 -0
- package/skills/axstack/references/routing.md +2 -0
- package/skills/axstack/references/t3-runtime.md +1 -1
- package/skills/axstack/references/ui-verification.md +67 -4
- package/skills/axstack/scripts/dispatch-plan.js +34 -7
- package/skills/axstack/scripts/fleet-config.js +78 -0
- package/skills/axstack/scripts/fleet-dispatch.js +41 -0
- package/skills/axstack/scripts/fleet-facts.js +150 -0
- package/skills/axstack/scripts/fleet-proc.js +31 -0
- package/skills/axstack/scripts/fleet-route-cli.js +78 -0
- package/skills/axstack/scripts/fleet-route.js +85 -0
- package/skills/axstack/scripts/fleet-sample-cli.js +81 -0
- package/skills/axstack/scripts/fleet-sample-io.js +59 -0
- package/skills/axstack/scripts/fleet-sample-manifest.js +34 -0
- package/skills/axstack/scripts/fleet-sample.js +48 -0
- package/skills/axstack/scripts/resolve-models.js +8 -4
- package/skills/axstack-review/references/review-candidate.md +1 -1
- package/skills/axstack-verify/SKILL.md +17 -6
- package/src/roles.js +2 -2
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { parseProc, pressure } from './fleet-proc.js';
|
|
2
|
+
import { validateManifest, sampleOptions } from './fleet-sample-manifest.js';
|
|
3
|
+
import { readProc, wait } from './fleet-sample-io.js';
|
|
4
|
+
export { parseProc, pressure, validateManifest };
|
|
5
|
+
|
|
6
|
+
// Read functions and clocks are injectable for deterministic offline verification.
|
|
7
|
+
export async function sampleFleet(manifest, hosts, options, dependencies = {}) {
|
|
8
|
+
validateManifest(manifest, hosts);
|
|
9
|
+
const { intervalMs, timeoutMs } = sampleOptions(options);
|
|
10
|
+
const { clock = Date.now, monotonic = () => performance.now(), read = readProc, wait: pause = wait,
|
|
11
|
+
signal } = dependencies;
|
|
12
|
+
const telemetry = await Promise.all(manifest.hosts.map(async (host) => {
|
|
13
|
+
const controller = new AbortController();
|
|
14
|
+
const cancel = () => controller.abort(Error('cancelled'));
|
|
15
|
+
signal?.addEventListener('abort', cancel, { once: true });
|
|
16
|
+
if (signal?.aborted) cancel();
|
|
17
|
+
const timer = setTimeout(() => controller.abort(Error('timeout')), timeoutMs);
|
|
18
|
+
const windowStartedAt = clock(); const started = monotonic();
|
|
19
|
+
const source = host.transport === 'local' ? 'linux-proc-local' : 'linux-proc-ssh';
|
|
20
|
+
const provenance = { hostId: host.hostId, environmentId: host.environmentId, scope: 'whole-host', source,
|
|
21
|
+
intervalMs, timeoutMs, windowStartedAt, windowKind: 'collector-bracket' };
|
|
22
|
+
const check = () => { if (controller.signal.aborted) throw controller.signal.reason; };
|
|
23
|
+
try {
|
|
24
|
+
check();
|
|
25
|
+
const first = parseProc(await read(host, controller.signal));
|
|
26
|
+
check();
|
|
27
|
+
await pause(intervalMs, controller.signal);
|
|
28
|
+
check();
|
|
29
|
+
const last = parseProc(await read(host, controller.signal));
|
|
30
|
+
check();
|
|
31
|
+
const windowEndedAt = clock(); const durationMs = Math.floor(monotonic() - started);
|
|
32
|
+
if (durationMs >= timeoutMs) throw Error('timeout');
|
|
33
|
+
if (durationMs < intervalMs || windowEndedAt < windowStartedAt
|
|
34
|
+
|| windowEndedAt - windowStartedAt < durationMs
|
|
35
|
+
|| windowEndedAt - windowStartedAt > durationMs + 1000) throw Error('collector-clock');
|
|
36
|
+
return { ...provenance, status: 'ok', ...pressure(first, last), durationMs,
|
|
37
|
+
windowEndedAt, receivedAt: windowEndedAt };
|
|
38
|
+
} catch (error) {
|
|
39
|
+
const reason = controller.signal.aborted ? controller.signal.reason?.message : error.message;
|
|
40
|
+
return { ...provenance, status: ['timeout', 'cancelled', 'malformed', 'inconsistent-counters', 'collector-clock']
|
|
41
|
+
.includes(reason) ? reason : 'failed', durationMs: Math.max(0, Math.floor(monotonic() - started)), receivedAt: clock() };
|
|
42
|
+
} finally {
|
|
43
|
+
clearTimeout(timer);
|
|
44
|
+
signal?.removeEventListener('abort', cancel);
|
|
45
|
+
}
|
|
46
|
+
}));
|
|
47
|
+
return { schemaVersion: 1, telemetry };
|
|
48
|
+
}
|
|
@@ -3,6 +3,7 @@ import { readFileSync } from 'node:fs';
|
|
|
3
3
|
const bindings = { codex: 'codex', claude: 'claudeAgent', grok: 'grok' };
|
|
4
4
|
const effortIds = { codex: 'reasoningEffort', claude: 'effort', grok: 'reasoningEffort' };
|
|
5
5
|
const nonempty = (value) => typeof value === 'string' && value.trim().length > 0;
|
|
6
|
+
const optionValues = (option) => option.values ?? option.options?.map((value) => value.id);
|
|
6
7
|
|
|
7
8
|
function argumentsFrom(args) {
|
|
8
9
|
const options = { excluded: [] };
|
|
@@ -37,9 +38,11 @@ function providerFrom(catalog, provider) {
|
|
|
37
38
|
}
|
|
38
39
|
const instance = matches[0];
|
|
39
40
|
if (!Array.isArray(instance.models) || instance.models.some((model) =>
|
|
40
|
-
!model || !nonempty(model.id) || !Array.isArray(model.options) || model.options.some((option) =>
|
|
41
|
+
!model || !nonempty(model.id) || !Array.isArray(model.options ?? []) || (model.options ?? []).some((option) =>
|
|
41
42
|
!option || !nonempty(option.id) || (option.options !== undefined &&
|
|
42
|
-
(!Array.isArray(option.options) || option.options.some((value) => !value || !nonempty(value.id))))
|
|
43
|
+
(!Array.isArray(option.options) || option.options.some((value) => !value || !nonempty(value.id))))
|
|
44
|
+
|| (option.values !== undefined && (!Array.isArray(option.values)
|
|
45
|
+
|| option.values.some((value) => typeof value !== 'boolean' && !nonempty(value))))))) {
|
|
43
46
|
throw new Error('malformed capabilities models or options');
|
|
44
47
|
}
|
|
45
48
|
return instance;
|
|
@@ -58,6 +61,7 @@ function modelFrom(models, { provider, model: pin, class: modelClass, excluded }
|
|
|
58
61
|
}
|
|
59
62
|
const model = models[0];
|
|
60
63
|
if (!model || excluded.includes(model.id)) throw new Error('missing first listed model');
|
|
64
|
+
if (!model.id.startsWith('grok-')) throw new Error(`first listed model ${model.id} is not grok-family`);
|
|
61
65
|
return model;
|
|
62
66
|
}
|
|
63
67
|
if (!['codex', 'claude'].includes(provider) || !/^[a-z]+$/.test(modelClass)) {
|
|
@@ -90,8 +94,8 @@ try {
|
|
|
90
94
|
if ((options.class === 'terra' || /^gpt-\d+(?:\.\d+)*-terra$/.test(model.id)) && effort !== 'high') {
|
|
91
95
|
throw new Error('Terra requires exactly high effort');
|
|
92
96
|
}
|
|
93
|
-
const effortOptions = model.options.filter((option) => option.id === effortIds[provider]);
|
|
94
|
-
if (effortOptions.length !== 1 || !effortOptions[0]
|
|
97
|
+
const effortOptions = (model.options ?? []).filter((option) => option.id === effortIds[provider]);
|
|
98
|
+
if (effortOptions.length !== 1 || !optionValues(effortOptions[0])?.includes(effort)
|
|
95
99
|
|| (provider === 'grok' && effort === 'max')) {
|
|
96
100
|
throw new Error(`unsupported effort ${effort} for ${provider}/${model.id}`);
|
|
97
101
|
}
|
|
@@ -137,7 +137,7 @@ This section applies to peer and authored PR modes.
|
|
|
137
137
|
For an `axstack-verify create` PR, the reviewer drives the candidate's recipe because the base has none.
|
|
138
138
|
The author never performs this review drive.
|
|
139
139
|
The reviewer drives each affected feature and records that drive as the `Final-head check`.
|
|
140
|
-
Delegate browser drives to `axstack-ui-verifier` with the feature file path.
|
|
140
|
+
Delegate browser drives to `axstack-ui-verifier` with the feature file path; device drives follow [UI verification](../../axstack/references/ui-verification.md#android-device-and-webpwa-dual-run)'s dual-run trigger.
|
|
141
141
|
Running the applicable surface counts as spin-up; a CLI run qualifies.
|
|
142
142
|
Use loopback and isolated instances under [axstack-verify](../../axstack-verify/SKILL.md#isolate-every-attempt).
|
|
143
143
|
|
|
@@ -49,6 +49,13 @@ Name the app, surface and invocation conditions in the description.
|
|
|
49
49
|
- Cleanup: stop only processes started by this run and remove owned scratch.
|
|
50
50
|
- Helpers: explain each helper's purpose and working directory.
|
|
51
51
|
|
|
52
|
+
For a device surface, require a Device section in generated `verify-<app>` recipes.
|
|
53
|
+
The Device section must use device tools to install and launch the pinned build,
|
|
54
|
+
drive features, capture evidence and shutdown owned devices.
|
|
55
|
+
The Device section must follow [UI verification](../axstack/references/ui-verification.md#android-device-and-webpwa-dual-run)
|
|
56
|
+
for dual-run independence and reconciliation, fleet routing and desktop safety.
|
|
57
|
+
Record unavailable device surfaces as unverified.
|
|
58
|
+
|
|
52
59
|
Write `.agents/skills/verify-<app>/features/README.md` as an index of the top 3-5 user features.
|
|
53
60
|
Write per-feature files in `.agents/skills/verify-<app>/features/` and link them from the index.
|
|
54
61
|
Give each feature file its entry points, prerequisites, drive steps and observable proof.
|
|
@@ -79,10 +86,12 @@ Report an unavailable candidate run as unverified.
|
|
|
79
86
|
|
|
80
87
|
Execute launch, doctor, drive one mapped feature, capture evidence, clean up and confirm evidence survives.
|
|
81
88
|
Run doctor before each drive.
|
|
82
|
-
The author drives only non-browser surfaces.
|
|
83
|
-
Delegate
|
|
84
|
-
|
|
89
|
+
The author drives only non-browser, non-device surfaces.
|
|
90
|
+
Delegate a recipe's device checks and explicitly requested app/PWA verification
|
|
91
|
+
to the UI verifier roles for a dual run through
|
|
92
|
+
[UI verification](../axstack/references/ui-verification.md#android-device-and-webpwa-dual-run)
|
|
85
93
|
with the feature file path in the brief.
|
|
94
|
+
Delegate other recipe browser drives to one `axstack-ui-verifier` pass.
|
|
86
95
|
Give the verifier the pinned candidate, isolated instance and private evidence path.
|
|
87
96
|
Clean up after every failed attempt.
|
|
88
97
|
Retry once after a drift fix.
|
|
@@ -124,10 +133,12 @@ Classify each gap as doc drift, harness gap or product regression:
|
|
|
124
133
|
Drive every mapped feature live even when source looks clean.
|
|
125
134
|
Use the skill's Launch recipe for an isolated instance.
|
|
126
135
|
Run doctor before each drive.
|
|
127
|
-
The author drives only non-browser surfaces.
|
|
128
|
-
Delegate
|
|
129
|
-
|
|
136
|
+
The author drives only non-browser, non-device surfaces.
|
|
137
|
+
Delegate a recipe's device checks and explicitly requested app/PWA verification
|
|
138
|
+
to the UI verifier roles for a dual run through
|
|
139
|
+
[UI verification](../axstack/references/ui-verification.md#android-device-and-webpwa-dual-run)
|
|
130
140
|
with the feature file path in the brief.
|
|
141
|
+
Delegate other recipe browser drives to one `axstack-ui-verifier` pass.
|
|
131
142
|
Capture actions, resulting states and side effects under its proof standards.
|
|
132
143
|
Report unreachable prerequisites with attempted routes separately from passes.
|
|
133
144
|
Fix a missing prerequisite in the map as doc drift.
|
package/src/roles.js
CHANGED
|
@@ -92,8 +92,8 @@ export function assessRoleReadiness(roles, preset) {
|
|
|
92
92
|
const isIntentionalAbsence = (role) => role.model === null && (
|
|
93
93
|
(preset === 'mixed' && role.id === 'axstack-research-x' && role.provider === 'grok') ||
|
|
94
94
|
(preset === 'mixed' && role.id === 'axstack-arena-candidate-grok' && role.provider === 'grok') ||
|
|
95
|
-
(preset === 'codex-only' && ['axstack-advisor-opus', 'axstack-escalation-fable', 'axstack-arena-judge-opus', 'axstack-research-x', 'axstack-arena-candidate-grok', 'axstack-research-web-sol'].includes(role.id)) ||
|
|
96
|
-
(preset === 'claude-only' && ['axstack-advisor-astra', 'axstack-arena-judge-astra', 'axstack-research-x', 'axstack-arena-candidate-grok', 'axstack-auditor-sol', 'axstack-research-code-sol', 'axstack-explore-execution-sol', 'axstack-explore-codebase-luna', 'axstack-research-web-sol'].includes(role.id))
|
|
95
|
+
(preset === 'codex-only' && ['axstack-advisor-opus', 'axstack-escalation-fable', 'axstack-arena-judge-opus', 'axstack-research-x', 'axstack-arena-candidate-grok', 'axstack-research-web-sol', 'axstack-ui-verifier-sonnet'].includes(role.id)) ||
|
|
96
|
+
(preset === 'claude-only' && ['axstack-advisor-astra', 'axstack-arena-judge-astra', 'axstack-research-x', 'axstack-arena-candidate-grok', 'axstack-auditor-sol', 'axstack-research-code-sol', 'axstack-explore-execution-sol', 'axstack-explore-codebase-luna', 'axstack-research-web-sol', 'axstack-ui-verifier-sonnet'].includes(role.id))
|
|
97
97
|
);
|
|
98
98
|
for (const role of roles) {
|
|
99
99
|
if (!bounds.has(role.provider)) {
|