zigrix 0.1.1 → 0.2.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/dist/config/defaults.d.ts +8 -0
- package/dist/config/defaults.js +8 -1
- package/dist/config/schema.d.ts +112 -0
- package/dist/config/schema.js +186 -12
- package/dist/dashboard/.next/BUILD_ID +1 -1
- package/dist/dashboard/.next/app-build-manifest.json +10 -10
- package/dist/dashboard/.next/app-path-routes-manifest.json +2 -2
- package/dist/dashboard/.next/build-manifest.json +2 -2
- package/dist/dashboard/.next/prerender-manifest.json +6 -6
- package/dist/dashboard/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/dist/dashboard/.next/server/app/_not-found.html +1 -1
- package/dist/dashboard/.next/server/app/_not-found.rsc +1 -1
- package/dist/dashboard/.next/server/app/api/auth/login/route_client-reference-manifest.js +1 -1
- package/dist/dashboard/.next/server/app/api/auth/logout/route_client-reference-manifest.js +1 -1
- package/dist/dashboard/.next/server/app/api/auth/session/route_client-reference-manifest.js +1 -1
- package/dist/dashboard/.next/server/app/api/auth/setup/route_client-reference-manifest.js +1 -1
- package/dist/dashboard/.next/server/app/api/overview/route_client-reference-manifest.js +1 -1
- package/dist/dashboard/.next/server/app/api/stream/route_client-reference-manifest.js +1 -1
- package/dist/dashboard/.next/server/app/api/tasks/[taskId]/cancel/route_client-reference-manifest.js +1 -1
- package/dist/dashboard/.next/server/app/api/tasks/[taskId]/conversation/route_client-reference-manifest.js +1 -1
- package/dist/dashboard/.next/server/app/api/tasks/[taskId]/route_client-reference-manifest.js +1 -1
- package/dist/dashboard/.next/server/app/login/page_client-reference-manifest.js +1 -1
- package/dist/dashboard/.next/server/app/login.html +1 -1
- package/dist/dashboard/.next/server/app/login.rsc +1 -1
- package/dist/dashboard/.next/server/app/page_client-reference-manifest.js +1 -1
- package/dist/dashboard/.next/server/app/setup/page_client-reference-manifest.js +1 -1
- package/dist/dashboard/.next/server/app/setup.html +1 -1
- package/dist/dashboard/.next/server/app/setup.rsc +1 -1
- package/dist/dashboard/.next/server/app-paths-manifest.json +2 -2
- package/dist/dashboard/.next/server/functions-config-manifest.json +2 -2
- package/dist/dashboard/.next/server/pages/404.html +1 -1
- package/dist/dashboard/.next/server/pages/500.html +1 -1
- package/dist/doctor.d.ts +3 -0
- package/dist/doctor.js +233 -60
- package/dist/index.js +262 -32
- package/dist/migrate/import-orchestration.d.ts +31 -0
- package/dist/migrate/import-orchestration.js +638 -0
- package/dist/onboard.js +130 -35
- package/dist/orchestration/evidence.d.ts +7 -0
- package/dist/orchestration/evidence.js +79 -4
- package/dist/orchestration/pipeline.d.ts +1 -0
- package/dist/orchestration/pipeline.js +26 -1
- package/dist/state/tasks.d.ts +37 -2
- package/dist/state/tasks.js +242 -10
- package/dist/state/verify.js +89 -11
- package/package.json +1 -1
- /package/dist/dashboard/.next/static/{iKGx5hWe1zbwJZWchF9kg → EZjkAnODdTglaMXuBw76E}/_buildManifest.js +0 -0
- /package/dist/dashboard/.next/static/{iKGx5hWe1zbwJZWchF9kg → EZjkAnODdTglaMXuBw76E}/_ssgManifest.js +0 -0
package/dist/onboard.js
CHANGED
|
@@ -4,7 +4,7 @@ import os from 'node:os';
|
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
6
|
import { addAgent } from './agents/registry.js';
|
|
7
|
-
import { inferStandardAgentRole, STANDARD_AGENT_ROLES } from './agents/roles.js';
|
|
7
|
+
import { inferStandardAgentRole, STANDARD_AGENT_ROLES, } from './agents/roles.js';
|
|
8
8
|
import { LEGACY_DEFAULT_GATEWAY_URL, resolveAbsolutePath } from './config/defaults.js';
|
|
9
9
|
import { loadConfig, writeConfigFile, writeDefaultConfig } from './config/load.js';
|
|
10
10
|
import { ensureBaseState, resolvePaths } from './state/paths.js';
|
|
@@ -40,7 +40,11 @@ function normalizeGatewayUrl(input) {
|
|
|
40
40
|
return parsed.toString().replace(/\/+$/, '');
|
|
41
41
|
}
|
|
42
42
|
function coerceGatewayPort(value) {
|
|
43
|
-
const n = typeof value === 'string' && value.trim().length > 0
|
|
43
|
+
const n = typeof value === 'string' && value.trim().length > 0
|
|
44
|
+
? Number(value)
|
|
45
|
+
: typeof value === 'number'
|
|
46
|
+
? value
|
|
47
|
+
: NaN;
|
|
44
48
|
if (!Number.isFinite(n) || n <= 0 || n > 65535)
|
|
45
49
|
return null;
|
|
46
50
|
return Math.trunc(n);
|
|
@@ -62,6 +66,41 @@ export function resolveOpenClawGatewayToken(openclawConfig) {
|
|
|
62
66
|
const token = openclawConfig?.gateway?.auth?.token;
|
|
63
67
|
return typeof token === 'string' && token.trim().length > 0 ? token : null;
|
|
64
68
|
}
|
|
69
|
+
function resolveExistingExecutablePath(candidate) {
|
|
70
|
+
try {
|
|
71
|
+
const stat = fs.lstatSync(candidate);
|
|
72
|
+
if (stat.isSymbolicLink()) {
|
|
73
|
+
return path.resolve(fs.realpathSync(candidate));
|
|
74
|
+
}
|
|
75
|
+
return fs.existsSync(candidate) ? path.resolve(candidate) : null;
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function resolveGlobalPackageBin(params) {
|
|
82
|
+
try {
|
|
83
|
+
const globalRoot = execSync('npm root -g', { encoding: 'utf8', timeout: 3000 }).trim();
|
|
84
|
+
if (!globalRoot)
|
|
85
|
+
return null;
|
|
86
|
+
const packageRoot = path.join(globalRoot, params.packageName);
|
|
87
|
+
const packageJsonPath = path.join(packageRoot, 'package.json');
|
|
88
|
+
if (!fs.existsSync(packageJsonPath))
|
|
89
|
+
return null;
|
|
90
|
+
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
91
|
+
const binEntry = typeof pkg.bin === 'string'
|
|
92
|
+
? pkg.bin
|
|
93
|
+
: pkg.bin && typeof pkg.bin === 'object'
|
|
94
|
+
? pkg.bin[params.binName]
|
|
95
|
+
: null;
|
|
96
|
+
if (typeof binEntry !== 'string' || binEntry.trim().length === 0)
|
|
97
|
+
return null;
|
|
98
|
+
return resolveExistingExecutablePath(path.resolve(packageRoot, binEntry));
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
65
104
|
function shouldReplaceStoredGatewayUrl(stored, detected) {
|
|
66
105
|
const trimmed = stored.trim();
|
|
67
106
|
if (!trimmed)
|
|
@@ -81,11 +120,17 @@ function shouldReplaceStoredGatewayUrl(stored, detected) {
|
|
|
81
120
|
* Returns the resolved absolute path or null.
|
|
82
121
|
*/
|
|
83
122
|
export function resolveOpenClawBin(openclawHome) {
|
|
123
|
+
const pickCandidate = (candidate) => {
|
|
124
|
+
if (!candidate)
|
|
125
|
+
return null;
|
|
126
|
+
return resolveExistingExecutablePath(candidate);
|
|
127
|
+
};
|
|
84
128
|
// Strategy 1: which/where
|
|
85
129
|
try {
|
|
86
130
|
const result = execSync('which openclaw', { encoding: 'utf8', timeout: 3000 }).trim();
|
|
87
|
-
|
|
88
|
-
|
|
131
|
+
const resolved = pickCandidate(result);
|
|
132
|
+
if (resolved)
|
|
133
|
+
return resolved;
|
|
89
134
|
}
|
|
90
135
|
catch {
|
|
91
136
|
// not in PATH
|
|
@@ -95,44 +140,58 @@ export function resolveOpenClawBin(openclawHome) {
|
|
|
95
140
|
const nodeVersion = process.versions.node;
|
|
96
141
|
const majorMinorPatch = nodeVersion; // e.g., "25.5.0"
|
|
97
142
|
const nvmCandidate = path.join(home, '.nvm', 'versions', 'node', `v${majorMinorPatch}`, 'bin', 'openclaw');
|
|
98
|
-
|
|
99
|
-
|
|
143
|
+
const nvmResolved = pickCandidate(nvmCandidate);
|
|
144
|
+
if (nvmResolved)
|
|
145
|
+
return nvmResolved;
|
|
100
146
|
// Also try nvm current symlink
|
|
101
147
|
const nvmCurrent = path.join(home, '.nvm', 'current', 'bin', 'openclaw');
|
|
102
|
-
|
|
103
|
-
|
|
148
|
+
const nvmCurrentResolved = pickCandidate(nvmCurrent);
|
|
149
|
+
if (nvmCurrentResolved)
|
|
150
|
+
return nvmCurrentResolved;
|
|
104
151
|
// Volta
|
|
105
152
|
const voltaCandidate = path.join(home, '.volta', 'bin', 'openclaw');
|
|
106
|
-
|
|
107
|
-
|
|
153
|
+
const voltaResolved = pickCandidate(voltaCandidate);
|
|
154
|
+
if (voltaResolved)
|
|
155
|
+
return voltaResolved;
|
|
108
156
|
// fnm
|
|
109
157
|
const fnmCandidate = path.join(home, '.fnm', 'node-versions', `v${majorMinorPatch}`, 'installation', 'bin', 'openclaw');
|
|
110
|
-
|
|
111
|
-
|
|
158
|
+
const fnmResolved = pickCandidate(fnmCandidate);
|
|
159
|
+
if (fnmResolved)
|
|
160
|
+
return fnmResolved;
|
|
112
161
|
// Strategy 3: well-known system paths
|
|
113
162
|
for (const dir of ['/usr/local/bin', path.join(home, '.local', 'bin')]) {
|
|
114
163
|
const candidate = path.join(dir, 'openclaw');
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
164
|
+
const resolved = pickCandidate(candidate);
|
|
165
|
+
if (resolved)
|
|
166
|
+
return resolved;
|
|
167
|
+
}
|
|
168
|
+
// Strategy 4: resolve the actual global package bin target (not the wrapper path)
|
|
169
|
+
const globalPackageBin = resolveGlobalPackageBin({
|
|
170
|
+
packageName: 'openclaw',
|
|
171
|
+
binName: 'openclaw',
|
|
172
|
+
});
|
|
173
|
+
if (globalPackageBin)
|
|
174
|
+
return globalPackageBin;
|
|
175
|
+
// Strategy 5: node global bin wrapper path (npm root -g)
|
|
119
176
|
try {
|
|
120
177
|
const globalRoot = execSync('npm root -g', { encoding: 'utf8', timeout: 3000 }).trim();
|
|
121
178
|
if (globalRoot) {
|
|
122
179
|
const candidate = path.join(path.dirname(globalRoot), 'bin', 'openclaw');
|
|
123
|
-
|
|
124
|
-
|
|
180
|
+
const resolved = pickCandidate(candidate);
|
|
181
|
+
if (resolved)
|
|
182
|
+
return resolved;
|
|
125
183
|
}
|
|
126
184
|
}
|
|
127
185
|
catch {
|
|
128
186
|
// npm not available or timed out
|
|
129
187
|
}
|
|
130
|
-
// Strategy
|
|
188
|
+
// Strategy 6: from openclaw home
|
|
131
189
|
if (openclawHome) {
|
|
132
190
|
// Some installations place a bin reference inside the home dir
|
|
133
191
|
const homeBin = path.join(openclawHome, 'bin', 'openclaw');
|
|
134
|
-
|
|
135
|
-
|
|
192
|
+
const resolved = pickCandidate(homeBin);
|
|
193
|
+
if (resolved)
|
|
194
|
+
return resolved;
|
|
136
195
|
}
|
|
137
196
|
return null;
|
|
138
197
|
}
|
|
@@ -151,10 +210,11 @@ export function registerAgents(config, agents, roleAssignments) {
|
|
|
151
210
|
skipped.push(agent.id);
|
|
152
211
|
continue;
|
|
153
212
|
}
|
|
154
|
-
const role = roleAssignments?.[agent.id] ??
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
213
|
+
const role = roleAssignments?.[agent.id] ??
|
|
214
|
+
inferStandardAgentRole({
|
|
215
|
+
agentId: agent.id,
|
|
216
|
+
theme: agent.identity?.theme ?? null,
|
|
217
|
+
});
|
|
158
218
|
const result = addAgent(current, {
|
|
159
219
|
id: agent.id,
|
|
160
220
|
role,
|
|
@@ -326,9 +386,7 @@ export function findSystemBinDir() {
|
|
|
326
386
|
*/
|
|
327
387
|
export function findUserBinDir() {
|
|
328
388
|
const home = process.env.HOME ?? '~';
|
|
329
|
-
const candidates = [
|
|
330
|
-
path.join(home, '.local', 'bin'),
|
|
331
|
-
];
|
|
389
|
+
const candidates = [path.join(home, '.local', 'bin')];
|
|
332
390
|
// Also check PATH dirs that are user-writable
|
|
333
391
|
const pathEnv = process.env.PATH ?? '';
|
|
334
392
|
for (const dir of pathEnv.split(path.delimiter).filter(Boolean)) {
|
|
@@ -346,6 +404,9 @@ export function findUserBinDir() {
|
|
|
346
404
|
}
|
|
347
405
|
return candidates[0];
|
|
348
406
|
}
|
|
407
|
+
function pathTargetsSameLocation(sourcePath, targetPath) {
|
|
408
|
+
return path.resolve(sourcePath) === path.resolve(targetPath);
|
|
409
|
+
}
|
|
349
410
|
/**
|
|
350
411
|
* Ensure zigrix is reachable from PATH.
|
|
351
412
|
* Priority:
|
|
@@ -374,6 +435,9 @@ export function ensureZigrixInPath(opts) {
|
|
|
374
435
|
: findSystemBinDir();
|
|
375
436
|
if (systemBinDir) {
|
|
376
437
|
const symlinkPath = path.join(systemBinDir, 'zigrix');
|
|
438
|
+
if (pathTargetsSameLocation(binEntry, symlinkPath)) {
|
|
439
|
+
return { alreadyInPath: true, symlinkCreated: false, symlinkPath: null, warning: null };
|
|
440
|
+
}
|
|
377
441
|
try {
|
|
378
442
|
// Remove stale entry if present
|
|
379
443
|
try {
|
|
@@ -414,6 +478,9 @@ export function ensureZigrixInPath(opts) {
|
|
|
414
478
|
};
|
|
415
479
|
}
|
|
416
480
|
const symlinkPath = path.join(userBinDir, 'zigrix');
|
|
481
|
+
if (pathTargetsSameLocation(binEntry, symlinkPath)) {
|
|
482
|
+
return { alreadyInPath: true, symlinkCreated: false, symlinkPath: null, warning: null };
|
|
483
|
+
}
|
|
417
484
|
try {
|
|
418
485
|
// Remove existing if present (stale symlink or old wrapper)
|
|
419
486
|
if (fs.existsSync(symlinkPath) || fs.lstatSync(symlinkPath).isSymbolicLink()) {
|
|
@@ -443,7 +510,9 @@ export function ensureZigrixInPath(opts) {
|
|
|
443
510
|
}
|
|
444
511
|
// Check if userBinDir is actually in PATH
|
|
445
512
|
const pathEnv = process.env.PATH ?? '';
|
|
446
|
-
const inPath = pathEnv
|
|
513
|
+
const inPath = pathEnv
|
|
514
|
+
.split(path.delimiter)
|
|
515
|
+
.some((d) => path.resolve(d) === path.resolve(userBinDir));
|
|
447
516
|
const warning = inPath
|
|
448
517
|
? null
|
|
449
518
|
: `Created zigrix at ${symlinkPath}, but ${userBinDir} is not in your PATH. Add it:\n export PATH="${userBinDir}:$PATH"`;
|
|
@@ -495,6 +564,9 @@ export function ensureOpenClawInPath(opts) {
|
|
|
495
564
|
: findSystemBinDir();
|
|
496
565
|
if (systemBinDir) {
|
|
497
566
|
const symlinkPath = path.join(systemBinDir, 'openclaw');
|
|
567
|
+
if (pathTargetsSameLocation(binPath, symlinkPath)) {
|
|
568
|
+
return { alreadyInPath: true, symlinkCreated: false, symlinkPath: null, warning: null };
|
|
569
|
+
}
|
|
498
570
|
try {
|
|
499
571
|
// Remove stale entry if present
|
|
500
572
|
try {
|
|
@@ -535,6 +607,9 @@ export function ensureOpenClawInPath(opts) {
|
|
|
535
607
|
};
|
|
536
608
|
}
|
|
537
609
|
const symlinkPath = path.join(userBinDir, 'openclaw');
|
|
610
|
+
if (pathTargetsSameLocation(binPath, symlinkPath)) {
|
|
611
|
+
return { alreadyInPath: true, symlinkCreated: false, symlinkPath: null, warning: null };
|
|
612
|
+
}
|
|
538
613
|
try {
|
|
539
614
|
// Remove existing if present (stale symlink)
|
|
540
615
|
if (fs.existsSync(symlinkPath) || fs.lstatSync(symlinkPath).isSymbolicLink()) {
|
|
@@ -558,7 +633,9 @@ export function ensureOpenClawInPath(opts) {
|
|
|
558
633
|
}
|
|
559
634
|
// Check if userBinDir is actually in PATH
|
|
560
635
|
const pathEnv = process.env.PATH ?? '';
|
|
561
|
-
const inPath = pathEnv
|
|
636
|
+
const inPath = pathEnv
|
|
637
|
+
.split(path.delimiter)
|
|
638
|
+
.some((d) => path.resolve(d) === path.resolve(userBinDir));
|
|
562
639
|
const warning = inPath
|
|
563
640
|
? null
|
|
564
641
|
: `Created openclaw at ${symlinkPath}, but ${userBinDir} is not in your PATH. Add it:\n export PATH="${userBinDir}:$PATH"`;
|
|
@@ -605,7 +682,13 @@ export function registerSkills(openclawHome) {
|
|
|
605
682
|
fs.mkdirSync(openclawSkillsDir, { recursive: true });
|
|
606
683
|
}
|
|
607
684
|
catch (e) {
|
|
608
|
-
return {
|
|
685
|
+
return {
|
|
686
|
+
registered,
|
|
687
|
+
skipped,
|
|
688
|
+
failed: [
|
|
689
|
+
`Could not create ${openclawSkillsDir}: ${e instanceof Error ? e.message : String(e)}`,
|
|
690
|
+
],
|
|
691
|
+
};
|
|
609
692
|
}
|
|
610
693
|
let skillDirs;
|
|
611
694
|
try {
|
|
@@ -681,7 +764,10 @@ export async function promptAgentSelection(agents) {
|
|
|
681
764
|
}
|
|
682
765
|
}
|
|
683
766
|
export async function promptAgentRoleAssignments(agents) {
|
|
684
|
-
const defaults = Object.fromEntries(agents.map((agent) => [
|
|
767
|
+
const defaults = Object.fromEntries(agents.map((agent) => [
|
|
768
|
+
agent.id,
|
|
769
|
+
inferStandardAgentRole({ agentId: agent.id, theme: agent.identity?.theme ?? null }),
|
|
770
|
+
]));
|
|
685
771
|
if (agents.length === 0)
|
|
686
772
|
return defaults;
|
|
687
773
|
try {
|
|
@@ -825,7 +911,8 @@ export async function runOnboard(options) {
|
|
|
825
911
|
const { configPath } = ensureConfig();
|
|
826
912
|
const loaded = loadConfig({ configPath });
|
|
827
913
|
let nextConfig = structuredClone(loaded.config);
|
|
828
|
-
const configuredWorkspace = nextConfig.workspace.projectsBaseDir?.trim() ||
|
|
914
|
+
const configuredWorkspace = nextConfig.workspace.projectsBaseDir?.trim() ||
|
|
915
|
+
path.join(nextConfig.paths.baseDir, 'workspace');
|
|
829
916
|
const desiredWorkspace = options.projectsBaseDir
|
|
830
917
|
? resolveAbsolutePath(options.projectsBaseDir)
|
|
831
918
|
: options.yes
|
|
@@ -888,7 +975,10 @@ export async function runOnboard(options) {
|
|
|
888
975
|
}
|
|
889
976
|
if (selectedAgents.length > 0) {
|
|
890
977
|
const roleAssignments = options.yes
|
|
891
|
-
? Object.fromEntries(selectedAgents.map((agent) => [
|
|
978
|
+
? Object.fromEntries(selectedAgents.map((agent) => [
|
|
979
|
+
agent.id,
|
|
980
|
+
inferStandardAgentRole({ agentId: agent.id, theme: agent.identity?.theme ?? null }),
|
|
981
|
+
]))
|
|
892
982
|
: await promptAgentRoleAssignments(selectedAgents);
|
|
893
983
|
const result = registerAgents(nextConfig, selectedAgents, roleAssignments);
|
|
894
984
|
nextConfig = result.config;
|
|
@@ -996,7 +1086,12 @@ export async function runOnboard(options) {
|
|
|
996
1086
|
}
|
|
997
1087
|
}
|
|
998
1088
|
// 7. Stabilize PATH — ensure openclaw is reachable from non-login shells
|
|
999
|
-
let openclawPathResult = {
|
|
1089
|
+
let openclawPathResult = {
|
|
1090
|
+
alreadyInPath: false,
|
|
1091
|
+
symlinkCreated: false,
|
|
1092
|
+
symlinkPath: null,
|
|
1093
|
+
warning: null,
|
|
1094
|
+
};
|
|
1000
1095
|
if (openclawBinPath) {
|
|
1001
1096
|
openclawPathResult = ensureOpenClawInPath();
|
|
1002
1097
|
if (openclawPathResult.symlinkCreated) {
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { type ZigrixPaths } from '../state/paths.js';
|
|
2
|
+
export type VerificationMapping = {
|
|
3
|
+
dod: string;
|
|
4
|
+
test: string;
|
|
5
|
+
};
|
|
2
6
|
export declare function collectEvidence(paths: ZigrixPaths, params: {
|
|
3
7
|
taskId: string;
|
|
4
8
|
agentId: string;
|
|
@@ -10,6 +14,9 @@ export declare function collectEvidence(paths: ZigrixPaths, params: {
|
|
|
10
14
|
summary?: string;
|
|
11
15
|
toolResults?: string[];
|
|
12
16
|
notes?: string;
|
|
17
|
+
dodItems?: string[];
|
|
18
|
+
testCases?: string[];
|
|
19
|
+
verificationMappings?: VerificationMapping[];
|
|
13
20
|
limit?: number;
|
|
14
21
|
}): Record<string, unknown> | null;
|
|
15
22
|
export declare function mergeEvidence(paths: ZigrixPaths, params: {
|
|
@@ -57,6 +57,29 @@ function resolveQaAgentId(task) {
|
|
|
57
57
|
const fallback = required.find((agentId) => String(agentId).trim().length > 0 && String(agentId).trim() !== orchestratorId);
|
|
58
58
|
return fallback ? String(fallback).trim() : null;
|
|
59
59
|
}
|
|
60
|
+
function sanitizeVerificationMapping(mapping) {
|
|
61
|
+
const dod = mapping.dod.trim();
|
|
62
|
+
const test = mapping.test.trim();
|
|
63
|
+
if (!dod || !test)
|
|
64
|
+
return null;
|
|
65
|
+
return { dod, test };
|
|
66
|
+
}
|
|
67
|
+
function collectVerificationMappings(evidence) {
|
|
68
|
+
const verification = evidence.verification;
|
|
69
|
+
if (!verification || typeof verification !== 'object' || Array.isArray(verification))
|
|
70
|
+
return [];
|
|
71
|
+
const rawMappings = verification.mappings;
|
|
72
|
+
if (!Array.isArray(rawMappings))
|
|
73
|
+
return [];
|
|
74
|
+
return rawMappings.flatMap((item) => {
|
|
75
|
+
if (!item || typeof item !== 'object' || Array.isArray(item))
|
|
76
|
+
return [];
|
|
77
|
+
const dod = typeof item.dod === 'string' ? String(item.dod) : '';
|
|
78
|
+
const test = typeof item.test === 'string' ? String(item.test) : '';
|
|
79
|
+
const sanitized = sanitizeVerificationMapping({ dod, test });
|
|
80
|
+
return sanitized ? [sanitized] : [];
|
|
81
|
+
});
|
|
82
|
+
}
|
|
60
83
|
export function collectEvidence(paths, params) {
|
|
61
84
|
ensureBaseState(paths);
|
|
62
85
|
const task = loadTask(paths, params.taskId);
|
|
@@ -72,6 +95,18 @@ export function collectEvidence(paths, params) {
|
|
|
72
95
|
extracted.toolResults = [...params.toolResults];
|
|
73
96
|
if (params.notes)
|
|
74
97
|
extracted.notes = params.notes;
|
|
98
|
+
const dodItems = (params.dodItems ?? []).map((item) => item.trim()).filter(Boolean);
|
|
99
|
+
const testCases = (params.testCases ?? []).map((item) => item.trim()).filter(Boolean);
|
|
100
|
+
const verificationMappings = (params.verificationMappings ?? [])
|
|
101
|
+
.map((mapping) => sanitizeVerificationMapping(mapping))
|
|
102
|
+
.filter((mapping) => Boolean(mapping));
|
|
103
|
+
if (dodItems.length || testCases.length || verificationMappings.length) {
|
|
104
|
+
extracted.verification = {
|
|
105
|
+
dodItems,
|
|
106
|
+
testCases,
|
|
107
|
+
mappings: verificationMappings,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
75
110
|
const outDir = path.join(paths.evidenceDir, params.taskId);
|
|
76
111
|
fs.mkdirSync(outDir, { recursive: true });
|
|
77
112
|
const outPath = path.join(outDir, `${params.agentId}.json`);
|
|
@@ -111,14 +146,54 @@ export function mergeEvidence(paths, params) {
|
|
|
111
146
|
const qaAgentId = resolveQaAgentId(task);
|
|
112
147
|
const qaPresent = qaAgentId ? presentAgents.includes(qaAgentId) : false;
|
|
113
148
|
const qaRequiredSatisfied = !(params.requireQa ?? false) || (qaAgentId !== null && qaPresent);
|
|
114
|
-
const
|
|
115
|
-
|
|
149
|
+
const qaVerificationMappings = qaAgentId
|
|
150
|
+
? items
|
|
151
|
+
.filter((item) => String(item.agentId) === qaAgentId)
|
|
152
|
+
.flatMap((item) => collectVerificationMappings(item.evidence))
|
|
153
|
+
: [];
|
|
154
|
+
const qaVerification = {
|
|
155
|
+
required: Boolean(params.requireQa),
|
|
156
|
+
mappingCount: qaVerificationMappings.length,
|
|
157
|
+
mappings: qaVerificationMappings,
|
|
158
|
+
complete: !(params.requireQa ?? false) || !qaPresent || qaVerificationMappings.length > 0,
|
|
159
|
+
};
|
|
160
|
+
const complete = missingAgents.length === 0 && qaRequiredSatisfied && qaVerification.complete;
|
|
161
|
+
const merged = {
|
|
162
|
+
ts: nowIso(),
|
|
163
|
+
taskId: params.taskId,
|
|
164
|
+
requiredAgents,
|
|
165
|
+
presentAgents,
|
|
166
|
+
missingAgents,
|
|
167
|
+
qaAgentId,
|
|
168
|
+
qaPresent,
|
|
169
|
+
qaVerification,
|
|
170
|
+
complete,
|
|
171
|
+
items,
|
|
172
|
+
};
|
|
116
173
|
const outPath = path.join(taskDir, '_merged.json');
|
|
117
174
|
fs.writeFileSync(outPath, `${JSON.stringify(merged, null, 2)}\n`, 'utf8');
|
|
118
175
|
appendEvent(paths.eventsFile, {
|
|
119
176
|
event: 'evidence_merged', taskId: params.taskId, phase: 'verification', actor: 'zigrix', status: complete ? 'DONE_PENDING_REPORT' : 'IN_PROGRESS',
|
|
120
|
-
payload: {
|
|
177
|
+
payload: {
|
|
178
|
+
requiredAgents,
|
|
179
|
+
missingAgents,
|
|
180
|
+
complete,
|
|
181
|
+
mergedPath: outPath,
|
|
182
|
+
qaPresent,
|
|
183
|
+
qaVerificationComplete: qaVerification.complete,
|
|
184
|
+
qaVerificationMappingCount: qaVerification.mappingCount,
|
|
185
|
+
},
|
|
121
186
|
});
|
|
122
187
|
rebuildIndex(paths);
|
|
123
|
-
return {
|
|
188
|
+
return {
|
|
189
|
+
ok: true,
|
|
190
|
+
taskId: params.taskId,
|
|
191
|
+
complete,
|
|
192
|
+
missingAgents,
|
|
193
|
+
qaAgentId,
|
|
194
|
+
qaPresent,
|
|
195
|
+
qaVerificationComplete: qaVerification.complete,
|
|
196
|
+
qaVerificationMappingCount: qaVerification.mappingCount,
|
|
197
|
+
mergedPath: outPath,
|
|
198
|
+
};
|
|
124
199
|
}
|
|
@@ -7,11 +7,36 @@ export function runPipeline(paths, params) {
|
|
|
7
7
|
const taskId = task.taskId;
|
|
8
8
|
steps.push({ step: 'task_create', result: task });
|
|
9
9
|
steps.push({ step: 'task_start', result: updateTaskStatus(paths, taskId, 'IN_PROGRESS') });
|
|
10
|
+
const verificationMappings = new Map();
|
|
11
|
+
for (const raw of params.verificationMappings ?? []) {
|
|
12
|
+
if (!raw.includes('='))
|
|
13
|
+
throw new Error(`invalid verification mapping format: ${raw} (expected agentId=dod=test)`);
|
|
14
|
+
const [agentId, rest] = raw.split(/=(.*)/s, 2);
|
|
15
|
+
const trimmedAgentId = agentId.trim();
|
|
16
|
+
const [dod, test] = (rest ?? '').split(/=(.*)/s, 2);
|
|
17
|
+
if (!trimmedAgentId || !dod?.trim() || !test?.trim()) {
|
|
18
|
+
throw new Error(`invalid verification mapping format: ${raw} (expected agentId=dod=test)`);
|
|
19
|
+
}
|
|
20
|
+
verificationMappings.set(trimmedAgentId, [
|
|
21
|
+
...(verificationMappings.get(trimmedAgentId) ?? []),
|
|
22
|
+
{ dod: dod.trim(), test: test.trim() },
|
|
23
|
+
]);
|
|
24
|
+
}
|
|
10
25
|
for (const raw of params.evidenceSummaries ?? []) {
|
|
11
26
|
if (!raw.includes('='))
|
|
12
27
|
throw new Error(`invalid evidence summary format: ${raw} (expected agentId=summary)`);
|
|
13
28
|
const [agentId, summary] = raw.split(/=(.*)/s, 2);
|
|
14
|
-
|
|
29
|
+
const trimmedAgentId = agentId.trim();
|
|
30
|
+
steps.push({
|
|
31
|
+
step: 'evidence_collect',
|
|
32
|
+
agentId: trimmedAgentId,
|
|
33
|
+
result: collectEvidence(paths, {
|
|
34
|
+
taskId,
|
|
35
|
+
agentId: trimmedAgentId,
|
|
36
|
+
summary: summary.trim(),
|
|
37
|
+
verificationMappings: verificationMappings.get(trimmedAgentId) ?? [],
|
|
38
|
+
}),
|
|
39
|
+
});
|
|
15
40
|
}
|
|
16
41
|
const merged = mergeEvidence(paths, { taskId, requiredAgents: params.requiredAgents, requireQa: params.requireQa });
|
|
17
42
|
steps.push({ step: 'evidence_merge', result: merged });
|
package/dist/state/tasks.d.ts
CHANGED
|
@@ -40,6 +40,36 @@ export type ZigrixTask = {
|
|
|
40
40
|
qaAgentId?: string;
|
|
41
41
|
orchestratorSessionKey?: string;
|
|
42
42
|
orchestratorSessionId?: string;
|
|
43
|
+
nextAction?: string;
|
|
44
|
+
resumeHint?: string;
|
|
45
|
+
staleReason?: string;
|
|
46
|
+
staleReasons?: string[];
|
|
47
|
+
};
|
|
48
|
+
export type StaleReasonCode = 'stale_timeout' | 'session_dead' | 'missing_session_mapping';
|
|
49
|
+
export type SessionDiagnosis = {
|
|
50
|
+
scope: 'orchestrator' | 'worker';
|
|
51
|
+
agentId: string;
|
|
52
|
+
sessionKey: string;
|
|
53
|
+
sessionId: string | null;
|
|
54
|
+
mappingSource: 'explicit' | 'parsed' | 'sessions_json' | 'none';
|
|
55
|
+
state: 'active' | 'deleted' | 'missing';
|
|
56
|
+
reason: 'session_dead' | 'missing_session_mapping' | null;
|
|
57
|
+
activePath: string | null;
|
|
58
|
+
deletedPath: string | null;
|
|
59
|
+
};
|
|
60
|
+
export type StaleTaskSummary = {
|
|
61
|
+
taskId: string;
|
|
62
|
+
title: string;
|
|
63
|
+
updatedAt: string;
|
|
64
|
+
hoursThreshold: number;
|
|
65
|
+
timedOut: boolean;
|
|
66
|
+
reason: string;
|
|
67
|
+
reasonCode: StaleReasonCode;
|
|
68
|
+
reasons: StaleReasonCode[];
|
|
69
|
+
nextAction: string;
|
|
70
|
+
resumeHint: string;
|
|
71
|
+
reportLine: string;
|
|
72
|
+
sessions: SessionDiagnosis[];
|
|
43
73
|
};
|
|
44
74
|
export declare function resolveTaskPaths(paths: ZigrixPaths, taskId: string): {
|
|
45
75
|
specPath: string;
|
|
@@ -68,6 +98,11 @@ export declare function recordTaskProgress(paths: ZigrixPaths, params: {
|
|
|
68
98
|
unitId?: string;
|
|
69
99
|
workPackage?: string;
|
|
70
100
|
}): Record<string, unknown> | null;
|
|
71
|
-
|
|
72
|
-
|
|
101
|
+
type StalePolicyOptions = {
|
|
102
|
+
agentsStateDir?: string | null;
|
|
103
|
+
fallbackReason?: string;
|
|
104
|
+
};
|
|
105
|
+
export declare function findStaleTasks(paths: ZigrixPaths, hours?: number, options?: StalePolicyOptions): StaleTaskSummary[];
|
|
106
|
+
export declare function applyStalePolicy(paths: ZigrixPaths, hours?: number, reason?: string, options?: StalePolicyOptions): Record<string, unknown>;
|
|
73
107
|
export declare function rebuildIndex(paths: ZigrixPaths): Record<string, unknown>;
|
|
108
|
+
export {};
|