axstack 0.30.1 → 0.31.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
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "axstack",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.31.0",
|
|
4
4
|
"description": "Axstack installer and setup CLI: installs owned chat skills and role data, configures supported harness settings, and checks T3 Code capabilities.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"claude-code",
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
|
|
3
|
+
const flags = ['--role', '--roles', '--capabilities', '--picker', '--kind', '--verify'];
|
|
4
|
+
const nonempty = (value) => typeof value === 'string' && value.trim().length > 0;
|
|
5
|
+
const help = `Usage: bun dispatch-plan.js --role <id> --roles <roles.json>
|
|
6
|
+
--capabilities <saved.json> --picker <picker.json> --kind task|launch
|
|
7
|
+
[--verify <configuration.json>]
|
|
8
|
+
|
|
9
|
+
--role Role ID from the supplied snapshot
|
|
10
|
+
--roles Installed-style roles.json (version 1, roles array)
|
|
11
|
+
--capabilities Saved orchestrator_capabilities JSON; reuse the run's saved snapshot
|
|
12
|
+
--picker Saved picker JSON; uses chosenInstanceId
|
|
13
|
+
--kind task emits target; launch emits modelSelection
|
|
14
|
+
--verify Compare t3_thread_configuration JSON; print MATCH or each mismatch
|
|
15
|
+
--help Show all flags and exit codes
|
|
16
|
+
|
|
17
|
+
Read-only: reads supplied JSON, spawns adjacent resolve-models.js, writes stdout/stderr.
|
|
18
|
+
Never dispatches or changes snapshots. Reuse recorded exact model pins as given.
|
|
19
|
+
Exit codes: 0 ok, 1 error, 2 hold (including incomplete read-back).
|
|
20
|
+
`;
|
|
21
|
+
|
|
22
|
+
function hold(message) {
|
|
23
|
+
throw Object.assign(new Error(message), { exitCode: 2 });
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function argumentsFrom(args) {
|
|
27
|
+
const options = {};
|
|
28
|
+
for (let i = 0; i < args.length; i += 2) {
|
|
29
|
+
const flag = args[i];
|
|
30
|
+
if (!flags.includes(flag)) throw new Error(`unexpected argument ${flag}`);
|
|
31
|
+
const value = args[i + 1];
|
|
32
|
+
if (!nonempty(value) || value.startsWith('--')) throw new Error(`expected value after ${flag}`);
|
|
33
|
+
if (flag in options) throw new Error(`duplicate argument ${flag}`);
|
|
34
|
+
options[flag] = value;
|
|
35
|
+
}
|
|
36
|
+
for (const flag of flags.filter((flag) => flag !== '--verify')) {
|
|
37
|
+
if (!options[flag]) throw new Error(`expected ${flag}`);
|
|
38
|
+
}
|
|
39
|
+
if (!['task', 'launch'].includes(options['--kind'])) throw new Error('expected --kind task|launch');
|
|
40
|
+
return options;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const readJSON = (path) => JSON.parse(readFileSync(path, 'utf8'));
|
|
44
|
+
|
|
45
|
+
function normalizedOptions(options) {
|
|
46
|
+
const entries = Array.isArray(options) ? options.map((option) => [option?.id, option?.value])
|
|
47
|
+
: options && typeof options === 'object' ? Object.entries(options) : null;
|
|
48
|
+
if (!entries || entries.some(([id, value]) => typeof id !== 'string' || !id.trim()
|
|
49
|
+
|| !['string', 'boolean'].includes(typeof value))
|
|
50
|
+
|| new Set(entries.map(([id]) => id)).size !== entries.length) return null;
|
|
51
|
+
return JSON.stringify(entries.sort(([left], [right]) => left.localeCompare(right)));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function verify(configuration, instanceId, provider, model, options) {
|
|
55
|
+
const mismatches = [];
|
|
56
|
+
const selection = configuration?.modelSelection;
|
|
57
|
+
const compare = (field, expected, observed, equal = expected === observed) => {
|
|
58
|
+
if (!equal) mismatches.push(`${field}: ${observed == null ? 'unknown' : 'fail'}; `
|
|
59
|
+
+ `expected ${JSON.stringify(expected)}; observed ${JSON.stringify(observed) ?? 'missing'}`);
|
|
60
|
+
};
|
|
61
|
+
// T3 may omit the legacy provider field: the exact instance ID is its binding.
|
|
62
|
+
if (selection?.provider != null) {
|
|
63
|
+
compare('provider', provider, selection.provider,
|
|
64
|
+
(selection.provider === 'claude' ? 'claudeAgent' : selection.provider) === provider);
|
|
65
|
+
}
|
|
66
|
+
compare('instanceId', instanceId, selection?.instanceId);
|
|
67
|
+
compare('model', model, selection?.model);
|
|
68
|
+
compare('options', options, selection?.options,
|
|
69
|
+
normalizedOptions(options) === normalizedOptions(selection?.options));
|
|
70
|
+
compare('runtimeMode', 'full-access', configuration?.runtimeMode);
|
|
71
|
+
console.log(mismatches.length ? mismatches.join('\n') : 'MATCH');
|
|
72
|
+
return mismatches.length ? 2 : 0;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function main() {
|
|
76
|
+
if (process.argv.slice(2).includes('--help')) {
|
|
77
|
+
process.stdout.write(help);
|
|
78
|
+
return 0;
|
|
79
|
+
}
|
|
80
|
+
const args = argumentsFrom(process.argv.slice(2));
|
|
81
|
+
const snapshot = readJSON(args['--roles']);
|
|
82
|
+
if (snapshot?.version !== 1 || !Array.isArray(snapshot.roles)) {
|
|
83
|
+
throw new Error('roles snapshot must have version 1 and a roles array');
|
|
84
|
+
}
|
|
85
|
+
const roles = snapshot.roles.filter((role) => role?.id === args['--role']);
|
|
86
|
+
if (roles.length !== 1) hold(`role ${args['--role']} unavailable or ambiguous`);
|
|
87
|
+
const role = roles[0];
|
|
88
|
+
if (!nonempty(role.provider)) hold(`provider unavailable for role ${role.id}`);
|
|
89
|
+
if (role.modeId !== 'full-access' && !(role.provider === 'claude' && role.modeId === 'bypassPermissions')) {
|
|
90
|
+
hold(`mode ${role.modeId ?? 'missing'} unavailable for role ${role.id}`);
|
|
91
|
+
}
|
|
92
|
+
if (!nonempty(role.thinkingOptionId)) hold(`effort unavailable for role ${role.id}`);
|
|
93
|
+
if (!nonempty(role.model) && !nonempty(role.modelClass) && role.model !== null) {
|
|
94
|
+
hold(`model unavailable for role ${role.id}`);
|
|
95
|
+
}
|
|
96
|
+
const picker = readJSON(args['--picker']);
|
|
97
|
+
if (!nonempty(picker?.chosenInstanceId)) hold('picker chosen instance unavailable');
|
|
98
|
+
const modelArgs = typeof role.model === 'string' ? ['--model', role.model]
|
|
99
|
+
: role.modelClass ? ['--class', role.modelClass] : ['--model', String(role.model)];
|
|
100
|
+
const resolved = Bun.spawnSync([process.execPath, `${import.meta.dir}/resolve-models.js`,
|
|
101
|
+
'--provider', role.provider, '--capabilities', args['--capabilities'],
|
|
102
|
+
'--effort', role.thinkingOptionId, ...modelArgs], { stdout: 'pipe', stderr: 'pipe' });
|
|
103
|
+
if (resolved.stderr.length) process.stderr.write(resolved.stderr);
|
|
104
|
+
if (resolved.exitCode !== 0) return resolved.exitCode === 1 ? 2 : 1;
|
|
105
|
+
const { model, effortOption, providerInstanceId } = JSON.parse(resolved.stdout.toString());
|
|
106
|
+
const chosen = readJSON(args['--capabilities']).providers
|
|
107
|
+
.filter((provider) => provider.providerInstanceId === picker.chosenInstanceId);
|
|
108
|
+
if (chosen.length !== 1 || chosen[0].driverKind !== providerInstanceId
|
|
109
|
+
|| (['grok', 'antigravity'].includes(role.provider) && picker.chosenInstanceId !== providerInstanceId)) {
|
|
110
|
+
hold(`picker instance ${picker.chosenInstanceId} does not bind provider ${role.provider}`);
|
|
111
|
+
}
|
|
112
|
+
const selectedModel = chosen[0].models?.find((entry) => entry.id === model);
|
|
113
|
+
if (!selectedModel) hold(`model ${model} unavailable on instance ${picker.chosenInstanceId}`);
|
|
114
|
+
if (effortOption && !selectedModel.options?.some((option) => option.id === effortOption.id
|
|
115
|
+
&& option.options?.some((value) => value.id === effortOption.value))) {
|
|
116
|
+
hold(`effort ${effortOption.value} unavailable on instance ${picker.chosenInstanceId}`);
|
|
117
|
+
}
|
|
118
|
+
const options = effortOption ? [effortOption] : [];
|
|
119
|
+
if (args['--verify']) {
|
|
120
|
+
return verify(readJSON(args['--verify']), picker.chosenInstanceId, providerInstanceId, model, options);
|
|
121
|
+
}
|
|
122
|
+
const selection = { [args['--kind'] === 'launch' ? 'instanceId' : 'providerInstanceId']: picker.chosenInstanceId,
|
|
123
|
+
model, options };
|
|
124
|
+
console.log(JSON.stringify({ [args['--kind'] === 'launch' ? 'modelSelection' : 'target']: selection,
|
|
125
|
+
runtimeMode: 'full-access' }));
|
|
126
|
+
return 0;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
try {
|
|
130
|
+
process.exitCode = main();
|
|
131
|
+
} catch (error) {
|
|
132
|
+
console.error(`dispatch-plan ${error.exitCode === 2 ? 'hold' : 'error'}: ${error.message}`);
|
|
133
|
+
process.exitCode = error.exitCode ?? 1;
|
|
134
|
+
}
|