agent-relay 3.1.6 → 3.1.8

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 (50) hide show
  1. package/bin/agent-relay-broker-darwin-arm64 +0 -0
  2. package/bin/agent-relay-broker-darwin-x64 +0 -0
  3. package/bin/agent-relay-broker-linux-arm64 +0 -0
  4. package/bin/agent-relay-broker-linux-x64 +0 -0
  5. package/dist/index.cjs +130 -107
  6. package/package.json +8 -8
  7. package/packages/acp-bridge/package.json +2 -2
  8. package/packages/config/package.json +1 -1
  9. package/packages/hooks/package.json +4 -4
  10. package/packages/memory/package.json +2 -2
  11. package/packages/openclaw/dist/cli.js +8 -1
  12. package/packages/openclaw/dist/cli.js.map +1 -1
  13. package/packages/openclaw/dist/config.d.ts +6 -2
  14. package/packages/openclaw/dist/config.d.ts.map +1 -1
  15. package/packages/openclaw/dist/config.js +95 -7
  16. package/packages/openclaw/dist/config.js.map +1 -1
  17. package/packages/openclaw/dist/gateway.d.ts +3 -0
  18. package/packages/openclaw/dist/gateway.d.ts.map +1 -1
  19. package/packages/openclaw/dist/gateway.js +114 -16
  20. package/packages/openclaw/dist/gateway.js.map +1 -1
  21. package/packages/openclaw/dist/setup.d.ts.map +1 -1
  22. package/packages/openclaw/dist/setup.js +111 -15
  23. package/packages/openclaw/dist/setup.js.map +1 -1
  24. package/packages/openclaw/package.json +2 -2
  25. package/packages/openclaw/skill/SKILL.md +190 -31
  26. package/packages/openclaw/src/cli.ts +7 -1
  27. package/packages/openclaw/src/config.ts +94 -8
  28. package/packages/openclaw/src/gateway.ts +153 -19
  29. package/packages/openclaw/src/setup.ts +113 -13
  30. package/packages/policy/package.json +2 -2
  31. package/packages/sdk/dist/client.js +6 -8
  32. package/packages/sdk/dist/client.js.map +1 -1
  33. package/packages/sdk/dist/workflows/builder.d.ts +3 -1
  34. package/packages/sdk/dist/workflows/builder.d.ts.map +1 -1
  35. package/packages/sdk/dist/workflows/builder.js +1 -0
  36. package/packages/sdk/dist/workflows/builder.js.map +1 -1
  37. package/packages/sdk/dist/workflows/runner.d.ts +15 -1
  38. package/packages/sdk/dist/workflows/runner.d.ts.map +1 -1
  39. package/packages/sdk/dist/workflows/runner.js +146 -117
  40. package/packages/sdk/dist/workflows/runner.js.map +1 -1
  41. package/packages/sdk/package.json +2 -2
  42. package/packages/sdk/scripts/bundle-agent-relay.mjs +11 -1
  43. package/packages/sdk/src/client.ts +6 -8
  44. package/packages/sdk/src/workflows/builder.ts +4 -1
  45. package/packages/sdk/src/workflows/runner.ts +173 -119
  46. package/packages/sdk-py/pyproject.toml +1 -1
  47. package/packages/telemetry/package.json +1 -1
  48. package/packages/trajectory/package.json +2 -2
  49. package/packages/user-directory/package.json +2 -2
  50. package/packages/utils/package.json +2 -2
@@ -88,6 +88,29 @@ export interface WorkflowRunnerOptions {
88
88
  relay?: AgentRelayOptions;
89
89
  cwd?: string;
90
90
  summaryDir?: string;
91
+ executor?: StepExecutor;
92
+ }
93
+
94
+ // ── Step executor interface ──────────────────────────────────────────────────
95
+
96
+ /**
97
+ * Extension point for delegating step execution to an external backend
98
+ * (e.g. Daytona sandboxes) while keeping the runner's DAG/retry/verification
99
+ * machinery intact.
100
+ */
101
+ export interface StepExecutor {
102
+ executeAgentStep(
103
+ step: WorkflowStep,
104
+ agentDef: AgentDefinition,
105
+ resolvedTask: string,
106
+ timeoutMs?: number
107
+ ): Promise<string>;
108
+
109
+ executeDeterministicStep?(
110
+ step: WorkflowStep,
111
+ resolvedCommand: string,
112
+ cwd: string
113
+ ): Promise<{ output: string; exitCode: number }>;
91
114
  }
92
115
 
93
116
  // ── Variable context for template resolution ────────────────────────────────
@@ -136,6 +159,7 @@ export class WorkflowRunner {
136
159
  private readonly relayOptions: AgentRelayOptions;
137
160
  private readonly cwd: string;
138
161
  private readonly summaryDir: string;
162
+ private readonly executor?: StepExecutor;
139
163
 
140
164
  /** @internal exposed for CLI signal-handler shutdown only */
141
165
  relay?: AgentRelay;
@@ -186,6 +210,7 @@ export class WorkflowRunner {
186
210
  this.cwd = options.cwd ?? process.cwd();
187
211
  this.summaryDir = options.summaryDir ?? path.join(this.cwd, '.relay', 'summaries');
188
212
  this.workersPath = path.join(this.cwd, '.agent-relay', 'team', 'workers.json');
213
+ this.executor = options.executor;
189
214
  }
190
215
 
191
216
  // ── Progress logging ────────────────────────────────────────────────────
@@ -1137,133 +1162,136 @@ export class WorkflowRunner {
1137
1162
  config.swarm.channel = channel;
1138
1163
  await this.db.updateRun(runId, { config });
1139
1164
  }
1140
- this.log('Resolving Relaycast API key...');
1141
- await this.ensureRelaycastApiKey(channel);
1142
- this.log('API key resolved');
1143
- if (this.relayApiKeyAutoCreated && this.relayApiKey) {
1144
- this.log(`Workspace created — follow this run in Relaycast:`);
1145
- this.log(` Observer: https://observer.relaycast.dev/?key=${this.relayApiKey}`);
1146
- this.log(` Channel: ${channel}`);
1147
- }
1148
-
1149
- this.log('Starting broker...');
1150
- // Include a short run ID suffix in the broker name so each workflow execution
1151
- // registers a unique identity in Relaycast. Without this, re-running in the same
1152
- // workspace hits a 409 conflict because the previous run's agent is still registered.
1153
- const brokerBaseName = path.basename(this.cwd) || 'workflow';
1154
- const brokerName = `${brokerBaseName}-${runId.slice(0, 8)}`;
1155
- this.relay = new AgentRelay({
1156
- ...this.relayOptions,
1157
- brokerName,
1158
- channels: [channel],
1159
- env: this.getRelayEnv(),
1160
- // Workflows spawn agents across multiple waves; each spawn requires a PTY +
1161
- // Relaycast registration. 60s is too tight when the broker is saturated with
1162
- // long-running PTY processes from earlier steps. 120s gives room to breathe.
1163
- requestTimeoutMs: this.relayOptions.requestTimeoutMs ?? 120_000,
1164
- });
1165
-
1166
- // Wire PTY output dispatcher — routes chunks to per-agent listeners + activity logging
1167
- this.relay.onWorkerOutput = ({ name, chunk }) => {
1168
- const listener = this.ptyListeners.get(name);
1169
- if (listener) listener(chunk);
1170
-
1171
- // Parse PTY output for high-signal activity
1172
- const stripped = WorkflowRunner.stripAnsi(chunk);
1173
- const shortName = name.replace(/-[a-f0-9]{6,}$/, '');
1174
- let activity: string | undefined;
1175
- if (/Read\(/.test(stripped)) {
1176
- // Extract filename — path may be truncated at chunk boundary so require
1177
- // at least a dir separator or 8+ chars to trust the basename.
1178
- const m = stripped.match(/Read\(\s*~?([^\s)"']{8,})/);
1179
- if (m) {
1180
- const base = path.basename(m[1]);
1181
- activity = base.length >= 3 ? `Reading ${base}` : 'Reading file...';
1182
- } else {
1183
- activity = 'Reading file...';
1184
- }
1185
- } else if (/Edit\(/.test(stripped)) {
1186
- const m = stripped.match(/Edit\(\s*~?([^\s)"']{8,})/);
1187
- if (m) {
1188
- const base = path.basename(m[1]);
1189
- activity = base.length >= 3 ? `Editing ${base}` : 'Editing file...';
1190
- } else {
1191
- activity = 'Editing file...';
1192
- }
1193
- } else if (/Bash\(/.test(stripped)) {
1194
- // Extract a short preview of the command
1195
- const m = stripped.match(/Bash\(\s*(.{1,40})/);
1196
- activity = m ? `Running: ${m[1].trim()}...` : 'Running command...';
1197
- } else if (/Explore\(/.test(stripped)) {
1198
- const m = stripped.match(/Explore\(\s*(.{1,50})/);
1199
- activity = m ? `Exploring: ${m[1].replace(/\).*/, '').trim()}` : 'Exploring codebase...';
1200
- } else if (/Task\(/.test(stripped)) {
1201
- activity = 'Running sub-agent...';
1202
- } else if (/Sublimating|Thinking|Coalescing|Cultivating/.test(stripped)) {
1203
- const m = stripped.match(/(\d+)s/);
1204
- activity = m ? `Thinking... (${m[1]}s)` : 'Thinking...';
1205
- }
1206
- if (activity && this.lastActivity.get(name) !== activity) {
1207
- this.lastActivity.set(name, activity);
1208
- this.log(`[${shortName}] ${activity}`);
1165
+ // Skip broker/relay init when an external executor handles agent spawning
1166
+ if (!this.executor) {
1167
+ this.log('Resolving Relaycast API key...');
1168
+ await this.ensureRelaycastApiKey(channel);
1169
+ this.log('API key resolved');
1170
+ if (this.relayApiKeyAutoCreated && this.relayApiKey) {
1171
+ this.log(`Workspace created — follow this run in Relaycast:`);
1172
+ this.log(` Observer: https://observer.relaycast.dev/?key=${this.relayApiKey}`);
1173
+ this.log(` Channel: ${channel}`);
1209
1174
  }
1210
- };
1211
-
1212
- // Wire relay event hooks for rich console logging
1213
- this.relay.onMessageReceived = (msg) => {
1214
- const body = msg.text.length > 120 ? msg.text.slice(0, 117) + '...' : msg.text;
1215
- const fromShort = msg.from.replace(/-[a-f0-9]{6,}$/, '');
1216
- const toShort = msg.to.replace(/-[a-f0-9]{6,}$/, '');
1217
- this.log(`[msg] ${fromShort} → ${toShort}: ${body}`);
1218
- };
1219
1175
 
1220
- this.relay.onAgentSpawned = (agent) => {
1221
- // Skip agents already managed by step execution
1222
- if (!this.activeAgentHandles.has(agent.name)) {
1223
- this.log(`[spawned] ${agent.name} (${agent.runtime})`);
1224
- }
1225
- };
1176
+ this.log('Starting broker...');
1177
+ // Include a short run ID suffix in the broker name so each workflow execution
1178
+ // registers a unique identity in Relaycast. Without this, re-running in the same
1179
+ // workspace hits a 409 conflict because the previous run's agent is still registered.
1180
+ const brokerBaseName = path.basename(this.cwd) || 'workflow';
1181
+ const brokerName = `${brokerBaseName}-${runId.slice(0, 8)}`;
1182
+ this.relay = new AgentRelay({
1183
+ ...this.relayOptions,
1184
+ brokerName,
1185
+ channels: [channel],
1186
+ env: this.getRelayEnv(),
1187
+ // Workflows spawn agents across multiple waves; each spawn requires a PTY +
1188
+ // Relaycast registration. 60s is too tight when the broker is saturated with
1189
+ // long-running PTY processes from earlier steps. 120s gives room to breathe.
1190
+ requestTimeoutMs: this.relayOptions.requestTimeoutMs ?? 120_000,
1191
+ });
1226
1192
 
1227
- this.relay.onAgentExited = (agent) => {
1228
- this.lastActivity.delete(agent.name);
1229
- this.lastIdleLog.delete(agent.name);
1230
- if (!this.activeAgentHandles.has(agent.name)) {
1231
- this.log(`[exited] ${agent.name} (code: ${agent.exitCode ?? '?'})`);
1232
- }
1233
- };
1193
+ // Wire PTY output dispatcher — routes chunks to per-agent listeners + activity logging
1194
+ this.relay.onWorkerOutput = ({ name, chunk }) => {
1195
+ const listener = this.ptyListeners.get(name);
1196
+ if (listener) listener(chunk);
1234
1197
 
1235
- this.relay.onAgentIdle = ({ name, idleSecs }) => {
1236
- // Only log at 30s multiples to avoid watchdog spam
1237
- const bucket = Math.floor(idleSecs / 30) * 30;
1238
- if (bucket >= 30 && this.lastIdleLog.get(name) !== bucket) {
1239
- this.lastIdleLog.set(name, bucket);
1198
+ // Parse PTY output for high-signal activity
1199
+ const stripped = WorkflowRunner.stripAnsi(chunk);
1240
1200
  const shortName = name.replace(/-[a-f0-9]{6,}$/, '');
1241
- this.log(`[idle] ${shortName} silent for ${bucket}s`);
1242
- }
1243
- };
1201
+ let activity: string | undefined;
1202
+ if (/Read\(/.test(stripped)) {
1203
+ // Extract filename — path may be truncated at chunk boundary so require
1204
+ // at least a dir separator or 8+ chars to trust the basename.
1205
+ const m = stripped.match(/Read\(\s*~?([^\s)"']{8,})/);
1206
+ if (m) {
1207
+ const base = path.basename(m[1]);
1208
+ activity = base.length >= 3 ? `Reading ${base}` : 'Reading file...';
1209
+ } else {
1210
+ activity = 'Reading file...';
1211
+ }
1212
+ } else if (/Edit\(/.test(stripped)) {
1213
+ const m = stripped.match(/Edit\(\s*~?([^\s)"']{8,})/);
1214
+ if (m) {
1215
+ const base = path.basename(m[1]);
1216
+ activity = base.length >= 3 ? `Editing ${base}` : 'Editing file...';
1217
+ } else {
1218
+ activity = 'Editing file...';
1219
+ }
1220
+ } else if (/Bash\(/.test(stripped)) {
1221
+ // Extract a short preview of the command
1222
+ const m = stripped.match(/Bash\(\s*(.{1,40})/);
1223
+ activity = m ? `Running: ${m[1].trim()}...` : 'Running command...';
1224
+ } else if (/Explore\(/.test(stripped)) {
1225
+ const m = stripped.match(/Explore\(\s*(.{1,50})/);
1226
+ activity = m ? `Exploring: ${m[1].replace(/\).*/, '').trim()}` : 'Exploring codebase...';
1227
+ } else if (/Task\(/.test(stripped)) {
1228
+ activity = 'Running sub-agent...';
1229
+ } else if (/Sublimating|Thinking|Coalescing|Cultivating/.test(stripped)) {
1230
+ const m = stripped.match(/(\d+)s/);
1231
+ activity = m ? `Thinking... (${m[1]}s)` : 'Thinking...';
1232
+ }
1233
+ if (activity && this.lastActivity.get(name) !== activity) {
1234
+ this.lastActivity.set(name, activity);
1235
+ this.log(`[${shortName}] ${activity}`);
1236
+ }
1237
+ };
1238
+
1239
+ // Wire relay event hooks for rich console logging
1240
+ this.relay.onMessageReceived = (msg) => {
1241
+ const body = msg.text.length > 120 ? msg.text.slice(0, 117) + '...' : msg.text;
1242
+ const fromShort = msg.from.replace(/-[a-f0-9]{6,}$/, '');
1243
+ const toShort = msg.to.replace(/-[a-f0-9]{6,}$/, '');
1244
+ this.log(`[msg] ${fromShort} → ${toShort}: ${body}`);
1245
+ };
1246
+
1247
+ this.relay.onAgentSpawned = (agent) => {
1248
+ // Skip agents already managed by step execution
1249
+ if (!this.activeAgentHandles.has(agent.name)) {
1250
+ this.log(`[spawned] ${agent.name} (${agent.runtime})`);
1251
+ }
1252
+ };
1244
1253
 
1245
- this.relaycast = undefined;
1246
- this.relaycastAgent = undefined;
1254
+ this.relay.onAgentExited = (agent) => {
1255
+ this.lastActivity.delete(agent.name);
1256
+ this.lastIdleLog.delete(agent.name);
1257
+ if (!this.activeAgentHandles.has(agent.name)) {
1258
+ this.log(`[exited] ${agent.name} (code: ${agent.exitCode ?? '?'})`);
1259
+ }
1260
+ };
1261
+
1262
+ this.relay.onAgentIdle = ({ name, idleSecs }) => {
1263
+ // Only log at 30s multiples to avoid watchdog spam
1264
+ const bucket = Math.floor(idleSecs / 30) * 30;
1265
+ if (bucket >= 30 && this.lastIdleLog.get(name) !== bucket) {
1266
+ this.lastIdleLog.set(name, bucket);
1267
+ const shortName = name.replace(/-[a-f0-9]{6,}$/, '');
1268
+ this.log(`[idle] ${shortName} silent for ${bucket}s`);
1269
+ }
1270
+ };
1247
1271
 
1248
- // Wire broker stderr to console for observability
1249
- this.unsubBrokerStderr = this.relay.onBrokerStderr((line: string) => {
1250
- console.log(`[broker] ${line}`);
1251
- });
1272
+ this.relaycast = undefined;
1273
+ this.relaycastAgent = undefined;
1252
1274
 
1253
- this.log(`Creating channel: ${channel}...`);
1254
- if (isResume) {
1255
- await this.createAndJoinRelaycastChannel(channel);
1256
- } else {
1257
- await this.createAndJoinRelaycastChannel(channel, workflow.description);
1258
- }
1259
- this.log('Channel ready');
1275
+ // Wire broker stderr to console for observability
1276
+ this.unsubBrokerStderr = this.relay.onBrokerStderr((line: string) => {
1277
+ console.log(`[broker] ${line}`);
1278
+ });
1260
1279
 
1261
- if (isResume) {
1262
- this.postToChannel(`Workflow **${workflow.name}** resumed — ${pendingCount} pending steps`);
1263
- } else {
1264
- this.postToChannel(
1265
- `Workflow **${workflow.name}** started — ${workflow.steps.length} steps, pattern: ${config.swarm.pattern}`
1266
- );
1280
+ this.log(`Creating channel: ${channel}...`);
1281
+ if (isResume) {
1282
+ await this.createAndJoinRelaycastChannel(channel);
1283
+ } else {
1284
+ await this.createAndJoinRelaycastChannel(channel, workflow.description);
1285
+ }
1286
+ this.log('Channel ready');
1287
+
1288
+ if (isResume) {
1289
+ this.postToChannel(`Workflow **${workflow.name}** resumed — ${pendingCount} pending steps`);
1290
+ } else {
1291
+ this.postToChannel(
1292
+ `Workflow **${workflow.name}** started — ${workflow.steps.length} steps, pattern: ${config.swarm.pattern}`
1293
+ );
1294
+ }
1267
1295
  }
1268
1296
 
1269
1297
  const agentMap = new Map<string, AgentDefinition>();
@@ -1703,6 +1731,30 @@ export class WorkflowRunner {
1703
1731
  });
1704
1732
 
1705
1733
  try {
1734
+ // Delegate to executor if present
1735
+ if (this.executor?.executeDeterministicStep) {
1736
+ const result = await this.executor.executeDeterministicStep(step, resolvedCommand, this.cwd);
1737
+ const failOnError = step.failOnError !== false;
1738
+ if (failOnError && result.exitCode !== 0) {
1739
+ throw new Error(`Command failed with exit code ${result.exitCode}: ${result.output.slice(0, 500)}`);
1740
+ }
1741
+ const output = step.captureOutput !== false ? result.output : `Command completed (exit code ${result.exitCode})`;
1742
+
1743
+ // Mark completed
1744
+ state.row.status = 'completed';
1745
+ state.row.output = output;
1746
+ state.row.completedAt = new Date().toISOString();
1747
+ await this.db.updateStep(state.row.id, {
1748
+ status: 'completed',
1749
+ output,
1750
+ completedAt: state.row.completedAt,
1751
+ updatedAt: new Date().toISOString(),
1752
+ });
1753
+ await this.persistStepOutput(runId, step.name, output);
1754
+ this.emit({ type: 'step:completed', runId, stepName: step.name, output });
1755
+ return;
1756
+ }
1757
+
1706
1758
  const output = await new Promise<string>((resolve, reject) => {
1707
1759
  const child = cpSpawn('sh', ['-c', resolvedCommand], {
1708
1760
  stdio: 'pipe',
@@ -2070,7 +2122,9 @@ export class WorkflowRunner {
2070
2122
  // Spawn agent via AgentRelay
2071
2123
  this.log(`[${step.name}] Spawning agent "${agentDef.name}" (cli: ${agentDef.cli})`);
2072
2124
  const resolvedStep = { ...step, task: resolvedTask };
2073
- const output = await this.spawnAndWait(agentDef, resolvedStep, timeoutMs);
2125
+ const output = this.executor
2126
+ ? await this.executor.executeAgentStep(resolvedStep, agentDef, resolvedTask, timeoutMs)
2127
+ : await this.spawnAndWait(agentDef, resolvedStep, timeoutMs);
2074
2128
  this.log(`[${step.name}] Agent "${agentDef.name}" exited`);
2075
2129
 
2076
2130
  // Run verification if configured
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "agent-relay-sdk"
7
- version = "3.1.6"
7
+ version = "3.1.8"
8
8
  description = "Python SDK for Agent Relay workflows"
9
9
  readme = "README.md"
10
10
  license = "Apache-2.0"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-relay/telemetry",
3
- "version": "3.1.6",
3
+ "version": "3.1.8",
4
4
  "description": "Anonymous telemetry for Agent Relay usage analytics",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-relay/trajectory",
3
- "version": "3.1.6",
3
+ "version": "3.1.8",
4
4
  "description": "Trajectory integration utilities (trail/PDERO) for Relay",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -22,7 +22,7 @@
22
22
  "test:watch": "vitest"
23
23
  },
24
24
  "dependencies": {
25
- "@agent-relay/config": "3.1.6"
25
+ "@agent-relay/config": "3.1.8"
26
26
  },
27
27
  "devDependencies": {
28
28
  "@types/node": "^22.19.3",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-relay/user-directory",
3
- "version": "3.1.6",
3
+ "version": "3.1.8",
4
4
  "description": "User directory service for agent-relay (per-user credential storage)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -22,7 +22,7 @@
22
22
  "test:watch": "vitest"
23
23
  },
24
24
  "dependencies": {
25
- "@agent-relay/utils": "3.1.6"
25
+ "@agent-relay/utils": "3.1.8"
26
26
  },
27
27
  "devDependencies": {
28
28
  "@types/node": "^22.19.3",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-relay/utils",
3
- "version": "3.1.6",
3
+ "version": "3.1.8",
4
4
  "description": "Shared utilities for agent-relay: logging, name generation, command resolution, update checking",
5
5
  "type": "module",
6
6
  "main": "dist/cjs/index.js",
@@ -112,7 +112,7 @@
112
112
  "vitest": "^3.2.4"
113
113
  },
114
114
  "dependencies": {
115
- "@agent-relay/config": "3.1.6",
115
+ "@agent-relay/config": "3.1.8",
116
116
  "compare-versions": "^6.1.1"
117
117
  },
118
118
  "publishConfig": {