agent-relay 3.1.15 → 3.1.16
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/index.cjs +125 -10
- package/dist/src/cli/commands/core.d.ts +2 -0
- package/dist/src/cli/commands/core.d.ts.map +1 -1
- package/dist/src/cli/commands/core.js +1 -0
- package/dist/src/cli/commands/core.js.map +1 -1
- package/dist/src/cli/lib/broker-lifecycle.d.ts.map +1 -1
- package/dist/src/cli/lib/broker-lifecycle.js +19 -2
- package/dist/src/cli/lib/broker-lifecycle.js.map +1 -1
- package/package.json +8 -8
- package/packages/acp-bridge/package.json +2 -2
- package/packages/config/package.json +1 -1
- package/packages/hooks/package.json +4 -4
- package/packages/memory/package.json +2 -2
- package/packages/openclaw/README.md +4 -7
- package/packages/openclaw/dist/setup.d.ts.map +1 -1
- package/packages/openclaw/dist/setup.js +10 -3
- package/packages/openclaw/dist/setup.js.map +1 -1
- package/packages/openclaw/package.json +2 -2
- package/packages/openclaw/skill/SKILL.md +27 -5
- package/packages/openclaw/src/setup.ts +10 -3
- package/packages/policy/package.json +2 -2
- package/packages/sdk/dist/client.d.ts +1 -0
- package/packages/sdk/dist/client.d.ts.map +1 -1
- package/packages/sdk/dist/client.js +3 -0
- package/packages/sdk/dist/client.js.map +1 -1
- package/packages/sdk/dist/workflows/runner.d.ts +20 -0
- package/packages/sdk/dist/workflows/runner.d.ts.map +1 -1
- package/packages/sdk/dist/workflows/runner.js +130 -11
- package/packages/sdk/dist/workflows/runner.js.map +1 -1
- package/packages/sdk/dist/workflows/types.d.ts +26 -0
- package/packages/sdk/dist/workflows/types.d.ts.map +1 -1
- package/packages/sdk/dist/workflows/types.js.map +1 -1
- package/packages/sdk/package.json +2 -2
- package/packages/sdk/src/client.ts +4 -0
- package/packages/sdk/src/workflows/runner.ts +158 -11
- package/packages/sdk/src/workflows/schema.json +219 -40
- package/packages/sdk/src/workflows/types.ts +29 -0
- package/packages/sdk-py/pyproject.toml +1 -1
- package/packages/sdk-py/src/agent_relay/__init__.py +2 -0
- package/packages/sdk-py/src/agent_relay/types.py +33 -0
- package/packages/telemetry/package.json +1 -1
- package/packages/trajectory/package.json +2 -2
- package/packages/user-directory/package.json +2 -2
- package/packages/utils/package.json +2 -2
- package/bin/agent-relay-broker-linux-arm64 +0 -0
|
@@ -86,6 +86,8 @@ export class WorkflowRunner {
|
|
|
86
86
|
lastIdleLog = new Map();
|
|
87
87
|
/** Tracks last logged activity type per agent to avoid duplicate status lines. */
|
|
88
88
|
lastActivity = new Map();
|
|
89
|
+
/** Resolved named paths from the top-level `paths` config, keyed by name → absolute directory. */
|
|
90
|
+
resolvedPaths = new Map();
|
|
89
91
|
constructor(options = {}) {
|
|
90
92
|
this.db = options.db ?? new InMemoryWorkflowDb();
|
|
91
93
|
this.workspaceId = options.workspaceId ?? 'local';
|
|
@@ -95,6 +97,76 @@ export class WorkflowRunner {
|
|
|
95
97
|
this.workersPath = path.join(this.cwd, '.agent-relay', 'team', 'workers.json');
|
|
96
98
|
this.executor = options.executor;
|
|
97
99
|
}
|
|
100
|
+
// ── Path resolution ─────────────────────────────────────────────────────
|
|
101
|
+
/** Expand environment variables like $HOME or $VAR in a path string. */
|
|
102
|
+
static resolveEnvVars(p) {
|
|
103
|
+
return p.replace(/\$([A-Za-z_][A-Za-z0-9_]*)/g, (_match, varName) => {
|
|
104
|
+
return process.env[varName] ?? _match;
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Resolve and validate the top-level `paths` definitions from the config.
|
|
109
|
+
* Returns a map of name → absolute directory path.
|
|
110
|
+
* Throws if a required path does not exist.
|
|
111
|
+
*/
|
|
112
|
+
resolvePathDefinitions(pathDefs, baseCwd) {
|
|
113
|
+
const resolved = new Map();
|
|
114
|
+
const errors = [];
|
|
115
|
+
const warnings = [];
|
|
116
|
+
if (!pathDefs || pathDefs.length === 0)
|
|
117
|
+
return { resolved, errors, warnings };
|
|
118
|
+
const seenNames = new Set();
|
|
119
|
+
for (const pd of pathDefs) {
|
|
120
|
+
if (seenNames.has(pd.name)) {
|
|
121
|
+
errors.push(`Duplicate path name "${pd.name}"`);
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
seenNames.add(pd.name);
|
|
125
|
+
const expanded = WorkflowRunner.resolveEnvVars(pd.path);
|
|
126
|
+
const abs = path.resolve(baseCwd, expanded);
|
|
127
|
+
resolved.set(pd.name, abs);
|
|
128
|
+
const isRequired = pd.required !== false; // default true
|
|
129
|
+
if (!existsSync(abs)) {
|
|
130
|
+
if (isRequired) {
|
|
131
|
+
errors.push(`Path "${pd.name}" resolves to "${abs}" which does not exist (required)`);
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
warnings.push(`Path "${pd.name}" resolves to "${abs}" which does not exist (optional)`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return { resolved, errors, warnings };
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Resolve an agent's effective working directory, considering `workdir` (named path reference)
|
|
142
|
+
* and `cwd` (explicit path). `workdir` takes precedence when both are set.
|
|
143
|
+
*/
|
|
144
|
+
resolveAgentCwd(agent) {
|
|
145
|
+
if (agent.workdir) {
|
|
146
|
+
const resolved = this.resolvedPaths.get(agent.workdir);
|
|
147
|
+
if (!resolved) {
|
|
148
|
+
throw new Error(`Agent "${agent.name}" references workdir "${agent.workdir}" which is not defined in paths`);
|
|
149
|
+
}
|
|
150
|
+
return resolved;
|
|
151
|
+
}
|
|
152
|
+
if (agent.cwd) {
|
|
153
|
+
return path.resolve(this.cwd, agent.cwd);
|
|
154
|
+
}
|
|
155
|
+
return this.cwd;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Resolve a step's working directory from its `workdir` field (named path reference).
|
|
159
|
+
* Returns undefined if no workdir is set.
|
|
160
|
+
*/
|
|
161
|
+
resolveStepWorkdir(step) {
|
|
162
|
+
if (!step.workdir)
|
|
163
|
+
return undefined;
|
|
164
|
+
const resolved = this.resolvedPaths.get(step.workdir);
|
|
165
|
+
if (!resolved) {
|
|
166
|
+
throw new Error(`Step "${step.name}" references workdir "${step.workdir}" which is not defined in paths`);
|
|
167
|
+
}
|
|
168
|
+
return resolved;
|
|
169
|
+
}
|
|
98
170
|
// ── Progress logging ────────────────────────────────────────────────────
|
|
99
171
|
/** Log a progress message with elapsed time since run start. */
|
|
100
172
|
log(msg) {
|
|
@@ -346,6 +418,17 @@ export class WorkflowRunner {
|
|
|
346
418
|
estimatedWaves: 0,
|
|
347
419
|
};
|
|
348
420
|
}
|
|
421
|
+
// 1b. Resolve and validate named paths
|
|
422
|
+
const pathResult = this.resolvePathDefinitions(resolved.paths, this.cwd);
|
|
423
|
+
errors.push(...pathResult.errors);
|
|
424
|
+
warnings.push(...pathResult.warnings);
|
|
425
|
+
const dryRunPaths = pathResult.resolved;
|
|
426
|
+
// Validate workdir references on agents
|
|
427
|
+
for (const agent of resolved.agents) {
|
|
428
|
+
if (agent.workdir && !dryRunPaths.has(agent.workdir)) {
|
|
429
|
+
errors.push(`Agent "${agent.name}" references workdir "${agent.workdir}" which is not defined in paths`);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
349
432
|
// 2. Find target workflow
|
|
350
433
|
const workflows = resolved.workflows ?? [];
|
|
351
434
|
const workflow = workflowName ? workflows.find((w) => w.name === workflowName) : workflows[0];
|
|
@@ -411,6 +494,12 @@ export class WorkflowRunner {
|
|
|
411
494
|
stepAgentCounts.set(step.agent, (stepAgentCounts.get(step.agent) ?? 0) + 1);
|
|
412
495
|
}
|
|
413
496
|
}
|
|
497
|
+
// Validate workdir references on steps
|
|
498
|
+
for (const step of resolvedSteps) {
|
|
499
|
+
if (step.workdir && !dryRunPaths.has(step.workdir)) {
|
|
500
|
+
errors.push(`Step "${step.name}" references workdir "${step.workdir}" which is not defined in paths`);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
414
503
|
// Validate cwd paths
|
|
415
504
|
for (const agent of resolved.agents) {
|
|
416
505
|
if (agent.cwd) {
|
|
@@ -499,7 +588,7 @@ export class WorkflowRunner {
|
|
|
499
588
|
name: a.name,
|
|
500
589
|
cli: a.cli,
|
|
501
590
|
role: a.role,
|
|
502
|
-
cwd: a.cwd,
|
|
591
|
+
cwd: a.workdir ? dryRunPaths.get(a.workdir) : a.cwd,
|
|
503
592
|
stepCount: stepAgentCounts.get(a.name) ?? 0,
|
|
504
593
|
}));
|
|
505
594
|
// 5. Simulate execution waves
|
|
@@ -775,6 +864,17 @@ export class WorkflowRunner {
|
|
|
775
864
|
/** Execute a named workflow from a validated config. */
|
|
776
865
|
async execute(config, workflowName, vars) {
|
|
777
866
|
const resolved = vars ? this.resolveVariables(config, vars) : config;
|
|
867
|
+
// Resolve and validate named paths from the top-level `paths` config
|
|
868
|
+
const pathResult = this.resolvePathDefinitions(resolved.paths, this.cwd);
|
|
869
|
+
if (pathResult.errors.length > 0) {
|
|
870
|
+
throw new Error(`Path validation failed:\n ${pathResult.errors.join('\n ')}`);
|
|
871
|
+
}
|
|
872
|
+
this.resolvedPaths = pathResult.resolved;
|
|
873
|
+
if (this.resolvedPaths.size > 0) {
|
|
874
|
+
for (const [name, abs] of this.resolvedPaths) {
|
|
875
|
+
console.log(`[workflow] path "${name}" → ${abs}`);
|
|
876
|
+
}
|
|
877
|
+
}
|
|
778
878
|
const workflows = resolved.workflows ?? [];
|
|
779
879
|
const workflow = workflowName ? workflows.find((w) => w.name === workflowName) : workflows[0];
|
|
780
880
|
if (!workflow) {
|
|
@@ -841,6 +941,12 @@ export class WorkflowRunner {
|
|
|
841
941
|
throw new Error(`Run "${runId}" is in status "${run.status}" and cannot be resumed`);
|
|
842
942
|
}
|
|
843
943
|
const config = vars ? this.resolveVariables(run.config, vars) : run.config;
|
|
944
|
+
// Resolve path definitions (same as execute()) so workdir lookups work on resume
|
|
945
|
+
const pathResult = this.resolvePathDefinitions(config.paths, this.cwd);
|
|
946
|
+
if (pathResult.errors.length > 0) {
|
|
947
|
+
throw new Error(`Path validation failed:\n ${pathResult.errors.join('\n ')}`);
|
|
948
|
+
}
|
|
949
|
+
this.resolvedPaths = pathResult.resolved;
|
|
844
950
|
const workflows = config.workflows ?? [];
|
|
845
951
|
const workflow = workflows.find((w) => w.name === run.workflowName);
|
|
846
952
|
if (!workflow) {
|
|
@@ -1386,10 +1492,12 @@ export class WorkflowRunner {
|
|
|
1386
1492
|
const value = this.resolveDotPath(key, stepOutputContext);
|
|
1387
1493
|
return value !== undefined ? String(value) : _match;
|
|
1388
1494
|
});
|
|
1495
|
+
// Resolve step workdir (named path reference) for deterministic steps
|
|
1496
|
+
const stepCwd = this.resolveStepWorkdir(step) ?? this.cwd;
|
|
1389
1497
|
try {
|
|
1390
1498
|
// Delegate to executor if present
|
|
1391
1499
|
if (this.executor?.executeDeterministicStep) {
|
|
1392
|
-
const result = await this.executor.executeDeterministicStep(step, resolvedCommand,
|
|
1500
|
+
const result = await this.executor.executeDeterministicStep(step, resolvedCommand, stepCwd);
|
|
1393
1501
|
const failOnError = step.failOnError !== false;
|
|
1394
1502
|
if (failOnError && result.exitCode !== 0) {
|
|
1395
1503
|
throw new Error(`Command failed with exit code ${result.exitCode}: ${result.output.slice(0, 500)}`);
|
|
@@ -1412,7 +1520,7 @@ export class WorkflowRunner {
|
|
|
1412
1520
|
const output = await new Promise((resolve, reject) => {
|
|
1413
1521
|
const child = cpSpawn('sh', ['-c', resolvedCommand], {
|
|
1414
1522
|
stdio: 'pipe',
|
|
1415
|
-
cwd:
|
|
1523
|
+
cwd: stepCwd,
|
|
1416
1524
|
env: { ...process.env },
|
|
1417
1525
|
});
|
|
1418
1526
|
const stdoutChunks = [];
|
|
@@ -1527,6 +1635,8 @@ export class WorkflowRunner {
|
|
|
1527
1635
|
? this.interpolateStepTask(step.path, stepOutputContext)
|
|
1528
1636
|
: path.join('.worktrees', step.name);
|
|
1529
1637
|
const createBranch = step.createBranch !== false;
|
|
1638
|
+
// Resolve workdir for worktree steps (same as deterministic/agent steps)
|
|
1639
|
+
const stepCwd = this.resolveStepWorkdir(step) ?? this.cwd;
|
|
1530
1640
|
if (!branch) {
|
|
1531
1641
|
const errorMsg = 'Worktree step missing required "branch" field';
|
|
1532
1642
|
await this.markStepFailed(state, errorMsg, runId);
|
|
@@ -1535,14 +1645,14 @@ export class WorkflowRunner {
|
|
|
1535
1645
|
try {
|
|
1536
1646
|
// Build the git worktree command
|
|
1537
1647
|
// If createBranch is true and branch doesn't exist, use -b flag
|
|
1538
|
-
const absoluteWorktreePath = path.resolve(
|
|
1648
|
+
const absoluteWorktreePath = path.resolve(stepCwd, worktreePath);
|
|
1539
1649
|
// First, check if the branch already exists
|
|
1540
1650
|
const checkBranchCmd = `git rev-parse --verify --quiet ${branch} 2>/dev/null`;
|
|
1541
1651
|
let branchExists = false;
|
|
1542
1652
|
await new Promise((resolve) => {
|
|
1543
1653
|
const checkChild = cpSpawn('sh', ['-c', checkBranchCmd], {
|
|
1544
1654
|
stdio: 'pipe',
|
|
1545
|
-
cwd:
|
|
1655
|
+
cwd: stepCwd,
|
|
1546
1656
|
env: { ...process.env },
|
|
1547
1657
|
});
|
|
1548
1658
|
checkChild.on('close', (code) => {
|
|
@@ -1570,7 +1680,7 @@ export class WorkflowRunner {
|
|
|
1570
1680
|
const output = await new Promise((resolve, reject) => {
|
|
1571
1681
|
const child = cpSpawn('sh', ['-c', worktreeCmd], {
|
|
1572
1682
|
stdio: 'pipe',
|
|
1573
|
-
cwd:
|
|
1683
|
+
cwd: stepCwd,
|
|
1574
1684
|
env: { ...process.env },
|
|
1575
1685
|
});
|
|
1576
1686
|
const stdoutChunks = [];
|
|
@@ -1710,12 +1820,20 @@ export class WorkflowRunner {
|
|
|
1710
1820
|
resolvedTask += nonInteractiveInfo;
|
|
1711
1821
|
}
|
|
1712
1822
|
}
|
|
1823
|
+
// Apply step-level workdir override to agent definition if present
|
|
1824
|
+
let effectiveAgentDef = agentDef;
|
|
1825
|
+
if (step.workdir) {
|
|
1826
|
+
const stepWorkdir = this.resolveStepWorkdir(step);
|
|
1827
|
+
if (stepWorkdir) {
|
|
1828
|
+
effectiveAgentDef = { ...agentDef, cwd: stepWorkdir, workdir: undefined };
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1713
1831
|
// Spawn agent via AgentRelay
|
|
1714
|
-
this.log(`[${step.name}] Spawning agent "${
|
|
1832
|
+
this.log(`[${step.name}] Spawning agent "${effectiveAgentDef.name}" (cli: ${effectiveAgentDef.cli})${step.workdir ? ` [workdir: ${step.workdir}]` : ''}`);
|
|
1715
1833
|
const resolvedStep = { ...step, task: resolvedTask };
|
|
1716
1834
|
const output = this.executor
|
|
1717
|
-
? await this.executor.executeAgentStep(resolvedStep,
|
|
1718
|
-
: await this.spawnAndWait(
|
|
1835
|
+
? await this.executor.executeAgentStep(resolvedStep, effectiveAgentDef, resolvedTask, timeoutMs)
|
|
1836
|
+
: await this.spawnAndWait(effectiveAgentDef, resolvedStep, timeoutMs);
|
|
1719
1837
|
this.log(`[${step.name}] Agent "${agentDef.name}" exited`);
|
|
1720
1838
|
// Run verification if configured
|
|
1721
1839
|
if (step.verification) {
|
|
@@ -1882,7 +2000,7 @@ export class WorkflowRunner {
|
|
|
1882
2000
|
const output = await new Promise((resolve, reject) => {
|
|
1883
2001
|
const child = cpSpawn(cmd, args, {
|
|
1884
2002
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
1885
|
-
cwd:
|
|
2003
|
+
cwd: this.resolveAgentCwd(agentDef),
|
|
1886
2004
|
env: this.getRelayEnv() ?? { ...process.env },
|
|
1887
2005
|
});
|
|
1888
2006
|
// Update workers.json with PID now that we have it
|
|
@@ -2028,6 +2146,7 @@ export class WorkflowRunner {
|
|
|
2028
2146
|
let stopHeartbeat;
|
|
2029
2147
|
let ptyChunks = [];
|
|
2030
2148
|
try {
|
|
2149
|
+
const agentCwd = this.resolveAgentCwd(agentDef);
|
|
2031
2150
|
agent = await this.relay.spawnPty({
|
|
2032
2151
|
name: agentName,
|
|
2033
2152
|
cli: agentDef.cli,
|
|
@@ -2036,7 +2155,7 @@ export class WorkflowRunner {
|
|
|
2036
2155
|
channels: agentChannels,
|
|
2037
2156
|
task: taskWithExit,
|
|
2038
2157
|
idleThresholdSecs: agentDef.constraints?.idleThresholdSecs,
|
|
2039
|
-
cwd:
|
|
2158
|
+
cwd: agentCwd !== this.cwd ? agentCwd : undefined,
|
|
2040
2159
|
});
|
|
2041
2160
|
// Re-key PTY maps if broker assigned a different name than requested
|
|
2042
2161
|
if (agent.name !== agentName) {
|