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.
Files changed (48) hide show
  1. package/dist/config/defaults.d.ts +8 -0
  2. package/dist/config/defaults.js +8 -1
  3. package/dist/config/schema.d.ts +112 -0
  4. package/dist/config/schema.js +186 -12
  5. package/dist/dashboard/.next/BUILD_ID +1 -1
  6. package/dist/dashboard/.next/app-build-manifest.json +10 -10
  7. package/dist/dashboard/.next/app-path-routes-manifest.json +2 -2
  8. package/dist/dashboard/.next/build-manifest.json +2 -2
  9. package/dist/dashboard/.next/prerender-manifest.json +6 -6
  10. package/dist/dashboard/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  11. package/dist/dashboard/.next/server/app/_not-found.html +1 -1
  12. package/dist/dashboard/.next/server/app/_not-found.rsc +1 -1
  13. package/dist/dashboard/.next/server/app/api/auth/login/route_client-reference-manifest.js +1 -1
  14. package/dist/dashboard/.next/server/app/api/auth/logout/route_client-reference-manifest.js +1 -1
  15. package/dist/dashboard/.next/server/app/api/auth/session/route_client-reference-manifest.js +1 -1
  16. package/dist/dashboard/.next/server/app/api/auth/setup/route_client-reference-manifest.js +1 -1
  17. package/dist/dashboard/.next/server/app/api/overview/route_client-reference-manifest.js +1 -1
  18. package/dist/dashboard/.next/server/app/api/stream/route_client-reference-manifest.js +1 -1
  19. package/dist/dashboard/.next/server/app/api/tasks/[taskId]/cancel/route_client-reference-manifest.js +1 -1
  20. package/dist/dashboard/.next/server/app/api/tasks/[taskId]/conversation/route_client-reference-manifest.js +1 -1
  21. package/dist/dashboard/.next/server/app/api/tasks/[taskId]/route_client-reference-manifest.js +1 -1
  22. package/dist/dashboard/.next/server/app/login/page_client-reference-manifest.js +1 -1
  23. package/dist/dashboard/.next/server/app/login.html +1 -1
  24. package/dist/dashboard/.next/server/app/login.rsc +1 -1
  25. package/dist/dashboard/.next/server/app/page_client-reference-manifest.js +1 -1
  26. package/dist/dashboard/.next/server/app/setup/page_client-reference-manifest.js +1 -1
  27. package/dist/dashboard/.next/server/app/setup.html +1 -1
  28. package/dist/dashboard/.next/server/app/setup.rsc +1 -1
  29. package/dist/dashboard/.next/server/app-paths-manifest.json +2 -2
  30. package/dist/dashboard/.next/server/functions-config-manifest.json +2 -2
  31. package/dist/dashboard/.next/server/pages/404.html +1 -1
  32. package/dist/dashboard/.next/server/pages/500.html +1 -1
  33. package/dist/doctor.d.ts +3 -0
  34. package/dist/doctor.js +233 -60
  35. package/dist/index.js +262 -32
  36. package/dist/migrate/import-orchestration.d.ts +31 -0
  37. package/dist/migrate/import-orchestration.js +638 -0
  38. package/dist/onboard.js +130 -35
  39. package/dist/orchestration/evidence.d.ts +7 -0
  40. package/dist/orchestration/evidence.js +79 -4
  41. package/dist/orchestration/pipeline.d.ts +1 -0
  42. package/dist/orchestration/pipeline.js +26 -1
  43. package/dist/state/tasks.d.ts +37 -2
  44. package/dist/state/tasks.js +242 -10
  45. package/dist/state/verify.js +89 -11
  46. package/package.json +1 -1
  47. /package/dist/dashboard/.next/static/{iKGx5hWe1zbwJZWchF9kg → EZjkAnODdTglaMXuBw76E}/_buildManifest.js +0 -0
  48. /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 ? Number(value) : typeof value === 'number' ? value : NaN;
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
- if (result && fs.existsSync(result))
88
- return path.resolve(result);
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
- if (fs.existsSync(nvmCandidate))
99
- return path.resolve(nvmCandidate);
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
- if (fs.existsSync(nvmCurrent))
103
- return path.resolve(nvmCurrent);
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
- if (fs.existsSync(voltaCandidate))
107
- return path.resolve(voltaCandidate);
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
- if (fs.existsSync(fnmCandidate))
111
- return path.resolve(fnmCandidate);
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
- if (fs.existsSync(candidate))
116
- return path.resolve(candidate);
117
- }
118
- // Strategy 4: node global lib path (npm root -g)
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
- if (fs.existsSync(candidate))
124
- return path.resolve(candidate);
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 5: from openclaw home
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
- if (fs.existsSync(homeBin))
135
- return path.resolve(homeBin);
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] ?? inferStandardAgentRole({
155
- agentId: agent.id,
156
- theme: agent.identity?.theme ?? null,
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.split(path.delimiter).some((d) => path.resolve(d) === path.resolve(userBinDir));
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.split(path.delimiter).some((d) => path.resolve(d) === path.resolve(userBinDir));
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 { registered, skipped, failed: [`Could not create ${openclawSkillsDir}: ${e instanceof Error ? e.message : String(e)}`] };
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) => [agent.id, inferStandardAgentRole({ agentId: agent.id, theme: agent.identity?.theme ?? null })]));
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() || path.join(nextConfig.paths.baseDir, 'workspace');
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) => [agent.id, inferStandardAgentRole({ agentId: agent.id, theme: agent.identity?.theme ?? null })]))
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 = { alreadyInPath: false, symlinkCreated: false, symlinkPath: null, warning: null };
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 complete = missingAgents.length === 0 && qaRequiredSatisfied;
115
- const merged = { ts: nowIso(), taskId: params.taskId, requiredAgents, presentAgents, missingAgents, qaAgentId, qaPresent, complete, items };
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: { requiredAgents, missingAgents, complete, mergedPath: outPath, qaPresent },
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 { ok: true, taskId: params.taskId, complete, missingAgents, qaAgentId, qaPresent, mergedPath: outPath };
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
  }
@@ -5,6 +5,7 @@ export declare function runPipeline(paths: ZigrixPaths, params: {
5
5
  scale?: string;
6
6
  requiredAgents?: string[];
7
7
  evidenceSummaries?: string[];
8
+ verificationMappings?: string[];
8
9
  requireQa?: boolean;
9
10
  autoReport?: boolean;
10
11
  recordFeedback?: boolean;
@@ -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
- steps.push({ step: 'evidence_collect', agentId: agentId.trim(), result: collectEvidence(paths, { taskId, agentId: agentId.trim(), summary: summary.trim() }) });
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 });
@@ -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
- export declare function findStaleTasks(paths: ZigrixPaths, hours?: number): ZigrixTask[];
72
- export declare function applyStalePolicy(paths: ZigrixPaths, hours?: number, reason?: string): Record<string, unknown>;
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 {};