agent-relay 3.1.23 → 3.2.1
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/README.md +2 -0
- package/bin/agent-relay-broker-darwin-arm64 +0 -0
- package/bin/agent-relay-broker-darwin-x64 +0 -0
- package/bin/agent-relay-broker-linux-arm64 +0 -0
- package/bin/agent-relay-broker-linux-x64 +0 -0
- package/dist/index.cjs +4053 -16716
- package/dist/src/cli/commands/setup.js +1 -1
- package/dist/src/cli/commands/setup.js.map +1 -1
- package/dist/src/cli/lib/broker-lifecycle.d.ts.map +1 -1
- package/dist/src/cli/lib/broker-lifecycle.js +11 -0
- package/dist/src/cli/lib/broker-lifecycle.js.map +1 -1
- package/dist/src/cli/lib/relaycast-mcp-command.d.ts +5 -0
- package/dist/src/cli/lib/relaycast-mcp-command.d.ts.map +1 -0
- package/dist/src/cli/lib/relaycast-mcp-command.js +13 -0
- package/dist/src/cli/lib/relaycast-mcp-command.js.map +1 -0
- package/dist/src/cli/relaycast-mcp.d.ts +39 -0
- package/dist/src/cli/relaycast-mcp.d.ts.map +1 -0
- package/dist/src/cli/relaycast-mcp.js +432 -0
- package/dist/src/cli/relaycast-mcp.js.map +1 -0
- package/package.json +9 -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 +7 -7
- package/packages/openclaw/dist/identity/files.js +5 -5
- package/packages/openclaw/dist/identity/files.js.map +1 -1
- package/packages/openclaw/dist/setup.js +4 -4
- package/packages/openclaw/package.json +2 -2
- package/packages/openclaw/skill/SKILL.md +24 -24
- package/packages/openclaw/src/identity/files.ts +5 -5
- package/packages/openclaw/src/setup.ts +4 -4
- package/packages/openclaw/templates/SOUL.md.template +5 -5
- package/packages/policy/package.json +2 -2
- package/packages/sdk/dist/__tests__/completion-pipeline.test.d.ts +14 -0
- package/packages/sdk/dist/__tests__/completion-pipeline.test.d.ts.map +1 -0
- package/packages/sdk/dist/__tests__/completion-pipeline.test.js +1476 -0
- package/packages/sdk/dist/__tests__/completion-pipeline.test.js.map +1 -0
- package/packages/sdk/dist/__tests__/e2e-owner-review.test.js +2 -2
- package/packages/sdk/dist/__tests__/e2e-owner-review.test.js.map +1 -1
- package/packages/sdk/dist/__tests__/unit.test.js +8 -0
- package/packages/sdk/dist/__tests__/unit.test.js.map +1 -1
- package/packages/sdk/dist/client.js +2 -2
- package/packages/sdk/dist/client.js.map +1 -1
- package/packages/sdk/dist/examples/example.js +1 -1
- package/packages/sdk/dist/examples/example.js.map +1 -1
- package/packages/sdk/dist/examples/ralph-loop.js +6 -6
- package/packages/sdk/dist/examples/ralph-loop.js.map +1 -1
- package/packages/sdk/dist/relay-adapter.js +4 -4
- package/packages/sdk/dist/relay-adapter.js.map +1 -1
- package/packages/sdk/dist/relay.d.ts +1 -0
- package/packages/sdk/dist/relay.d.ts.map +1 -1
- package/packages/sdk/dist/relay.js +2 -0
- package/packages/sdk/dist/relay.js.map +1 -1
- package/packages/sdk/dist/workflows/runner.d.ts +53 -2
- package/packages/sdk/dist/workflows/runner.d.ts.map +1 -1
- package/packages/sdk/dist/workflows/runner.js +1277 -94
- package/packages/sdk/dist/workflows/runner.js.map +1 -1
- package/packages/sdk/dist/workflows/trajectory.d.ts +6 -2
- package/packages/sdk/dist/workflows/trajectory.d.ts.map +1 -1
- package/packages/sdk/dist/workflows/trajectory.js +37 -2
- package/packages/sdk/dist/workflows/trajectory.js.map +1 -1
- package/packages/sdk/dist/workflows/types.d.ts +88 -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/dist/workflows/validator.js +4 -4
- package/packages/sdk/dist/workflows/validator.js.map +1 -1
- package/packages/sdk/package.json +2 -2
- package/packages/sdk/src/__tests__/completion-pipeline.test.ts +1820 -0
- package/packages/sdk/src/__tests__/e2e-owner-review.test.ts +2 -2
- package/packages/sdk/src/__tests__/idle-nudge.test.ts +68 -0
- package/packages/sdk/src/__tests__/unit.test.ts +10 -0
- package/packages/sdk/src/__tests__/workflow-runner.test.ts +113 -4
- package/packages/sdk/src/client.ts +2 -2
- package/packages/sdk/src/examples/example.ts +1 -1
- package/packages/sdk/src/examples/ralph-loop.ts +6 -6
- package/packages/sdk/src/relay-adapter.ts +4 -4
- package/packages/sdk/src/relay.ts +2 -0
- package/packages/sdk/src/workflows/README.md +43 -11
- package/packages/sdk/src/workflows/runner.ts +1759 -102
- package/packages/sdk/src/workflows/schema.json +6 -0
- package/packages/sdk/src/workflows/trajectory.ts +52 -3
- package/packages/sdk/src/workflows/types.ts +149 -0
- package/packages/sdk/src/workflows/validator.ts +4 -4
- package/packages/sdk-py/pyproject.toml +1 -1
- package/packages/sdk-py/src/agent_relay/models.py +11 -0
- package/packages/sdk-py/src/agent_relay/relay.py +9 -6
- package/packages/sdk-py/tests/test_relay_lifecycle_hooks.py +23 -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/relay-snippets/agent-relay-protocol.md +4 -4
- package/relay-snippets/agent-relay-snippet.md +31 -43
|
@@ -126,6 +126,12 @@ Added workdir to WorktreeWorkflowStep
|
|
|
126
126
|
},
|
|
127
127
|
"idleNudge": {
|
|
128
128
|
"$ref": "#/definitions/IdleNudgeConfig"
|
|
129
|
+
},
|
|
130
|
+
"completionGracePeriodMs": {
|
|
131
|
+
"type": "integer",
|
|
132
|
+
"minimum": 0,
|
|
133
|
+
"default": 5000,
|
|
134
|
+
"description": "Grace period (ms) after an agent exits with code 0 but without posting the expected coordination signal. During this window the runner checks verification gates and evidence before failing. Set to 0 to disable."
|
|
129
135
|
}
|
|
130
136
|
}
|
|
131
137
|
},
|
|
@@ -16,7 +16,7 @@ import { existsSync } from 'node:fs';
|
|
|
16
16
|
import { mkdir, writeFile, rename } from 'node:fs/promises';
|
|
17
17
|
import path from 'node:path';
|
|
18
18
|
|
|
19
|
-
import type { TrajectoryConfig, WorkflowStep } from './types.js';
|
|
19
|
+
import type { StepCompletionDecision, TrajectoryConfig, WorkflowStep } from './types.js';
|
|
20
20
|
|
|
21
21
|
// ── Trajectory file format (compatible with trail CLI) ───────────────────────
|
|
22
22
|
|
|
@@ -84,6 +84,8 @@ export interface StepOutcome {
|
|
|
84
84
|
nonInteractive?: boolean;
|
|
85
85
|
/** Duration in ms. */
|
|
86
86
|
durationMs?: number;
|
|
87
|
+
/** How the step completion was determined. */
|
|
88
|
+
completionMode?: StepCompletionDecision['mode'];
|
|
87
89
|
}
|
|
88
90
|
|
|
89
91
|
// ── Failure root-cause categories ───────────────────────────────────────────
|
|
@@ -339,8 +341,35 @@ export class WorkflowTrajectory {
|
|
|
339
341
|
await this.flush();
|
|
340
342
|
}
|
|
341
343
|
|
|
344
|
+
async stepCompletionDecision(stepName: string, decision: StepCompletionDecision): Promise<void> {
|
|
345
|
+
if (!this.enabled || !this.trajectory) return;
|
|
346
|
+
|
|
347
|
+
const modeLabel = decision.mode === 'marker' ? 'marker-based' : `${decision.mode}-based`;
|
|
348
|
+
const reason = decision.reason ? ` — ${decision.reason}` : '';
|
|
349
|
+
const evidence = this.formatCompletionEvidenceSummary(decision.evidence);
|
|
350
|
+
const evidenceSuffix = evidence ? ` (${evidence})` : '';
|
|
351
|
+
|
|
352
|
+
this.addEvent(
|
|
353
|
+
decision.mode === 'marker' ? 'completion-marker' : 'completion-evidence',
|
|
354
|
+
`"${stepName}" ${modeLabel} completion${reason}${evidenceSuffix}`,
|
|
355
|
+
'medium',
|
|
356
|
+
{
|
|
357
|
+
stepName,
|
|
358
|
+
completionMode: decision.mode,
|
|
359
|
+
reason: decision.reason,
|
|
360
|
+
evidence: decision.evidence,
|
|
361
|
+
}
|
|
362
|
+
);
|
|
363
|
+
await this.flush();
|
|
364
|
+
}
|
|
365
|
+
|
|
342
366
|
/** Record step completed — captures what was accomplished. */
|
|
343
|
-
async stepCompleted(
|
|
367
|
+
async stepCompleted(
|
|
368
|
+
step: WorkflowStep,
|
|
369
|
+
output: string,
|
|
370
|
+
attempt: number,
|
|
371
|
+
decision?: StepCompletionDecision
|
|
372
|
+
): Promise<void> {
|
|
344
373
|
if (!this.enabled || !this.trajectory) return;
|
|
345
374
|
|
|
346
375
|
const suffix = attempt > 1 ? ` (after ${attempt} attempts)` : '';
|
|
@@ -357,7 +386,12 @@ export class WorkflowTrajectory {
|
|
|
357
386
|
? lastMeaningful
|
|
358
387
|
: output.trim().slice(0, 120) || '(no output)';
|
|
359
388
|
|
|
360
|
-
|
|
389
|
+
if (decision) {
|
|
390
|
+
await this.stepCompletionDecision(step.name, decision);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
const modeSuffix = decision ? ` [${decision.mode}]` : '';
|
|
394
|
+
this.addEvent('finding', `"${step.name}" completed${suffix}${modeSuffix} → ${completion}`, 'medium');
|
|
361
395
|
await this.flush();
|
|
362
396
|
}
|
|
363
397
|
|
|
@@ -697,6 +731,21 @@ export class WorkflowTrajectory {
|
|
|
697
731
|
chapter.events.push(event);
|
|
698
732
|
}
|
|
699
733
|
|
|
734
|
+
private formatCompletionEvidenceSummary(
|
|
735
|
+
evidence: StepCompletionDecision['evidence'] | undefined
|
|
736
|
+
): string | undefined {
|
|
737
|
+
if (!evidence) return undefined;
|
|
738
|
+
|
|
739
|
+
const parts: string[] = [];
|
|
740
|
+
if (evidence.summary) parts.push(evidence.summary);
|
|
741
|
+
if (evidence.signals?.length) parts.push(`signals=${evidence.signals.join(', ')}`);
|
|
742
|
+
if (evidence.channelPosts?.length) parts.push(`channel=${evidence.channelPosts.join(' | ')}`);
|
|
743
|
+
if (evidence.files?.length) parts.push(`files=${evidence.files.join(', ')}`);
|
|
744
|
+
if (evidence.exitCode !== undefined) parts.push(`exit=${evidence.exitCode}`);
|
|
745
|
+
|
|
746
|
+
return parts.length > 0 ? parts.join('; ') : undefined;
|
|
747
|
+
}
|
|
748
|
+
|
|
700
749
|
private async flush(): Promise<void> {
|
|
701
750
|
if (!this.trajectory) return;
|
|
702
751
|
|
|
@@ -76,6 +76,13 @@ export interface SwarmConfig {
|
|
|
76
76
|
channel?: string;
|
|
77
77
|
/** Idle agent detection and nudging configuration for interactive agents. */
|
|
78
78
|
idleNudge?: IdleNudgeConfig;
|
|
79
|
+
/**
|
|
80
|
+
* Grace period (ms) after an agent exits with code 0 but without posting
|
|
81
|
+
* the expected coordination signal. During this window the runner checks
|
|
82
|
+
* verification gates and evidence before failing the step.
|
|
83
|
+
* Default: 5000 (5 seconds). Set to 0 to disable.
|
|
84
|
+
*/
|
|
85
|
+
completionGracePeriodMs?: number;
|
|
79
86
|
}
|
|
80
87
|
|
|
81
88
|
export type SwarmPattern =
|
|
@@ -310,6 +317,128 @@ export interface VerificationCheck {
|
|
|
310
317
|
description?: string;
|
|
311
318
|
}
|
|
312
319
|
|
|
320
|
+
// ── Completion evidence ─────────────────────────────────────────────────────
|
|
321
|
+
|
|
322
|
+
export type CompletionEvidenceSignalSource =
|
|
323
|
+
| 'channel'
|
|
324
|
+
| 'stdout'
|
|
325
|
+
| 'stderr'
|
|
326
|
+
| 'process'
|
|
327
|
+
| 'filesystem'
|
|
328
|
+
| 'tool'
|
|
329
|
+
| 'verification';
|
|
330
|
+
|
|
331
|
+
export type CompletionEvidenceSignalKind =
|
|
332
|
+
| 'worker_done'
|
|
333
|
+
| 'lead_done'
|
|
334
|
+
| 'step_complete'
|
|
335
|
+
| 'owner_decision'
|
|
336
|
+
| 'review_decision'
|
|
337
|
+
| 'task_summary'
|
|
338
|
+
| 'verification_passed'
|
|
339
|
+
| 'verification_failed'
|
|
340
|
+
| 'process_exit'
|
|
341
|
+
| 'custom';
|
|
342
|
+
|
|
343
|
+
export interface CompletionEvidenceSignal {
|
|
344
|
+
kind: CompletionEvidenceSignalKind;
|
|
345
|
+
source: CompletionEvidenceSignalSource;
|
|
346
|
+
text: string;
|
|
347
|
+
observedAt: string;
|
|
348
|
+
sender?: string;
|
|
349
|
+
actor?: string;
|
|
350
|
+
role?: string;
|
|
351
|
+
value?: string;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
export type CompletionEvidenceChannelOrigin = 'runner_post' | 'forwarded_chunk' | 'relay_message';
|
|
355
|
+
|
|
356
|
+
export interface CompletionEvidenceChannelPost {
|
|
357
|
+
stepName: string;
|
|
358
|
+
text: string;
|
|
359
|
+
postedAt: string;
|
|
360
|
+
origin: CompletionEvidenceChannelOrigin;
|
|
361
|
+
completionRelevant: boolean;
|
|
362
|
+
sender?: string;
|
|
363
|
+
actor?: string;
|
|
364
|
+
role?: string;
|
|
365
|
+
target?: string;
|
|
366
|
+
signals: CompletionEvidenceSignal[];
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
export type CompletionEvidenceFileChangeKind = 'created' | 'modified' | 'deleted';
|
|
370
|
+
|
|
371
|
+
export interface CompletionEvidenceFileChange {
|
|
372
|
+
path: string;
|
|
373
|
+
kind: CompletionEvidenceFileChangeKind;
|
|
374
|
+
observedAt: string;
|
|
375
|
+
root?: string;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
export type CompletionEvidenceToolSideEffectType =
|
|
379
|
+
| 'persist_step_output'
|
|
380
|
+
| 'post_channel_message'
|
|
381
|
+
| 'verification_observed'
|
|
382
|
+
| 'worktree_created'
|
|
383
|
+
| 'owner_monitoring'
|
|
384
|
+
| 'review_started'
|
|
385
|
+
| 'review_completed'
|
|
386
|
+
| 'worker_exit'
|
|
387
|
+
| 'worker_error'
|
|
388
|
+
| 'retry'
|
|
389
|
+
| 'custom';
|
|
390
|
+
|
|
391
|
+
export interface CompletionEvidenceToolSideEffect {
|
|
392
|
+
type: CompletionEvidenceToolSideEffectType;
|
|
393
|
+
detail: string;
|
|
394
|
+
observedAt: string;
|
|
395
|
+
raw?: Record<string, unknown>;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
export interface StepCompletionEvidence {
|
|
399
|
+
stepName: string;
|
|
400
|
+
status?: WorkflowStepStatus;
|
|
401
|
+
startedAt?: string;
|
|
402
|
+
completedAt?: string;
|
|
403
|
+
lastUpdatedAt: string;
|
|
404
|
+
roots: string[];
|
|
405
|
+
output: {
|
|
406
|
+
stdout: string;
|
|
407
|
+
stderr: string;
|
|
408
|
+
combined: string;
|
|
409
|
+
};
|
|
410
|
+
channelPosts: CompletionEvidenceChannelPost[];
|
|
411
|
+
files: CompletionEvidenceFileChange[];
|
|
412
|
+
process: {
|
|
413
|
+
exitCode?: number;
|
|
414
|
+
exitSignal?: string;
|
|
415
|
+
};
|
|
416
|
+
toolSideEffects: CompletionEvidenceToolSideEffect[];
|
|
417
|
+
coordinationSignals: CompletionEvidenceSignal[];
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
export type StepCompletionMode =
|
|
421
|
+
| 'marker'
|
|
422
|
+
| 'evidence'
|
|
423
|
+
| 'verification'
|
|
424
|
+
| 'owner_decision'
|
|
425
|
+
| 'review'
|
|
426
|
+
| 'heuristic';
|
|
427
|
+
|
|
428
|
+
export interface StepCompletionDecisionEvidence {
|
|
429
|
+
summary?: string;
|
|
430
|
+
signals?: string[];
|
|
431
|
+
channelPosts?: string[];
|
|
432
|
+
files?: string[];
|
|
433
|
+
exitCode?: number;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
export interface StepCompletionDecision {
|
|
437
|
+
mode: StepCompletionMode;
|
|
438
|
+
reason?: string;
|
|
439
|
+
evidence?: StepCompletionDecisionEvidence;
|
|
440
|
+
}
|
|
441
|
+
|
|
313
442
|
// ── Coordination ────────────────────────────────────────────────────────────
|
|
314
443
|
|
|
315
444
|
/** Coordination settings for multi-agent synchronization. */
|
|
@@ -394,6 +523,25 @@ export interface WorkflowRunRow {
|
|
|
394
523
|
}
|
|
395
524
|
|
|
396
525
|
export type WorkflowStepStatus = 'pending' | 'running' | 'completed' | 'failed' | 'skipped';
|
|
526
|
+
export type WorkflowOwnerDecision =
|
|
527
|
+
| 'COMPLETE'
|
|
528
|
+
| 'INCOMPLETE_RETRY'
|
|
529
|
+
| 'INCOMPLETE_FAIL'
|
|
530
|
+
| 'NEEDS_CLARIFICATION';
|
|
531
|
+
/**
|
|
532
|
+
* Completion reasons are recorded for both successful and failed steps.
|
|
533
|
+
* `retry_requested_by_owner` is a retry-control signal, not a success state:
|
|
534
|
+
* the runner retries while budget remains and fails the step once retries are exhausted.
|
|
535
|
+
*/
|
|
536
|
+
export type WorkflowStepCompletionReason =
|
|
537
|
+
| 'completed_verified'
|
|
538
|
+
| 'completed_by_owner_decision'
|
|
539
|
+
| 'completed_by_evidence'
|
|
540
|
+
| 'completed_by_process_exit'
|
|
541
|
+
| 'retry_requested_by_owner'
|
|
542
|
+
| 'failed_verification'
|
|
543
|
+
| 'failed_owner_decision'
|
|
544
|
+
| 'failed_no_evidence';
|
|
397
545
|
|
|
398
546
|
/** Database row representing a single workflow step execution. */
|
|
399
547
|
export interface WorkflowStepRow {
|
|
@@ -410,6 +558,7 @@ export interface WorkflowStepRow {
|
|
|
410
558
|
dependsOn: string[];
|
|
411
559
|
output?: string;
|
|
412
560
|
error?: string;
|
|
561
|
+
completionReason?: WorkflowStepCompletionReason;
|
|
413
562
|
startedAt?: string;
|
|
414
563
|
completedAt?: string;
|
|
415
564
|
retryCount: number;
|
|
@@ -90,22 +90,22 @@ export function validateWorkflow(config: RelayYamlConfig): ValidationIssue[] {
|
|
|
90
90
|
task.length > 500 &&
|
|
91
91
|
!task.includes('do not') &&
|
|
92
92
|
!task.includes('Do NOT') &&
|
|
93
|
-
!task.includes('
|
|
93
|
+
!task.includes('mcp__relaycast__agent_add') &&
|
|
94
94
|
!task.includes('add_agent')
|
|
95
95
|
) {
|
|
96
96
|
issues.push({
|
|
97
97
|
severity: 'info',
|
|
98
98
|
code: 'CLAUDE_NO_SPAWN_GUARD',
|
|
99
99
|
message: `Step "${step.name}" uses interactive claude with a long task. Claude may spontaneously spawn sub-agents via relay MCP tools.`,
|
|
100
|
-
fix: `Add "Do NOT use
|
|
100
|
+
fix: `Add "Do NOT use mcp__relaycast__agent_add or add_agent to spawn sub-agents." to the task, or use \`interactive: false\`.`,
|
|
101
101
|
location: `step:${step.name}`,
|
|
102
102
|
});
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
-
// Check 4: non-interactive agent that references
|
|
105
|
+
// Check 4: non-interactive agent that references relay messaging tools in task
|
|
106
106
|
if (
|
|
107
107
|
def.interactive === false &&
|
|
108
|
-
(task.includes('
|
|
108
|
+
(task.includes('mcp__relaycast__dm_send') || task.includes('mcp__relaycast__message_post') || task.includes('mcp__relaycast__inbox_check'))
|
|
109
109
|
) {
|
|
110
110
|
issues.push({
|
|
111
111
|
severity: 'warning',
|
|
@@ -25,3 +25,14 @@ class Models:
|
|
|
25
25
|
GEMINI_2_5_PRO = "gemini-2.5-pro"
|
|
26
26
|
GEMINI_2_5_FLASH = "gemini-2.5-flash"
|
|
27
27
|
GEMINI_2_5_FLASH_LITE = "gemini-2.5-flash-lite"
|
|
28
|
+
|
|
29
|
+
class Opencode:
|
|
30
|
+
OPENCODE_BIG_PICKLE = "opencode/big-pickle"
|
|
31
|
+
OPENCODE_GPT_5_NANO = "opencode/gpt-5-nano"
|
|
32
|
+
OPENCODE_MIMO_V2_FLASH_FREE = "opencode/mimo-v2-flash-free"
|
|
33
|
+
OPENCODE_MINIMAX_M2_5_FREE = "opencode/minimax-m2.5-free"
|
|
34
|
+
OPENAI_CODEX_MINI_LATEST = "openai/codex-mini-latest"
|
|
35
|
+
OPENAI_GPT_5_2 = "openai/gpt-5.2"
|
|
36
|
+
OPENAI_O3_MINI = "openai/o3-mini"
|
|
37
|
+
OPENAI_O3_PRO = "openai/o3-pro"
|
|
38
|
+
OPENAI_O4_MINI = "openai/o4-mini"
|
|
@@ -291,10 +291,11 @@ class HumanHandle:
|
|
|
291
291
|
class AgentSpawner:
|
|
292
292
|
"""Shorthand spawner for a specific CLI (e.g., relay.claude.spawn(...))."""
|
|
293
293
|
|
|
294
|
-
def __init__(self, cli: str, default_name: str, relay: AgentRelay):
|
|
294
|
+
def __init__(self, cli: str, default_name: str, relay: AgentRelay, transport: str = "pty"):
|
|
295
295
|
self._cli = cli
|
|
296
296
|
self._default_name = default_name
|
|
297
297
|
self._relay = relay
|
|
298
|
+
self._transport = transport
|
|
298
299
|
|
|
299
300
|
async def spawn(
|
|
300
301
|
self,
|
|
@@ -326,9 +327,10 @@ class AgentSpawner:
|
|
|
326
327
|
)
|
|
327
328
|
|
|
328
329
|
try:
|
|
329
|
-
result = await client.
|
|
330
|
+
result = await client.spawn_provider(
|
|
330
331
|
name=agent_name,
|
|
331
|
-
|
|
332
|
+
provider=self._cli,
|
|
333
|
+
transport=self._transport,
|
|
332
334
|
args=args or [],
|
|
333
335
|
channels=agent_channels,
|
|
334
336
|
task=task,
|
|
@@ -434,9 +436,10 @@ class AgentRelay:
|
|
|
434
436
|
self._idle_resolvers: dict[str, list[asyncio.Future[str]]] = {}
|
|
435
437
|
|
|
436
438
|
# Shorthand spawners
|
|
437
|
-
self.codex = AgentSpawner("codex", "Codex", self)
|
|
438
|
-
self.claude = AgentSpawner("claude", "Claude", self)
|
|
439
|
-
self.gemini = AgentSpawner("gemini", "Gemini", self)
|
|
439
|
+
self.codex = AgentSpawner("codex", "Codex", self, transport="pty")
|
|
440
|
+
self.claude = AgentSpawner("claude", "Claude", self, transport="pty")
|
|
441
|
+
self.gemini = AgentSpawner("gemini", "Gemini", self, transport="pty")
|
|
442
|
+
self.opencode = AgentSpawner("opencode", "OpenCode", self, transport="headless")
|
|
440
443
|
|
|
441
444
|
@property
|
|
442
445
|
def workspace_key(self) -> Optional[str]:
|
|
@@ -23,6 +23,12 @@ class _FakeRelayClient:
|
|
|
23
23
|
raise self.spawn_error
|
|
24
24
|
return {"name": kwargs["name"], "runtime": "pty"}
|
|
25
25
|
|
|
26
|
+
async def spawn_provider(self, **kwargs):
|
|
27
|
+
self.spawn_calls.append(kwargs)
|
|
28
|
+
if self.spawn_error:
|
|
29
|
+
raise self.spawn_error
|
|
30
|
+
return {"name": kwargs["name"], "runtime": "pty"}
|
|
31
|
+
|
|
26
32
|
async def release(self, name: str, reason: str | None = None):
|
|
27
33
|
self.release_calls.append((name, reason))
|
|
28
34
|
if self.release_error:
|
|
@@ -142,6 +148,23 @@ async def test_shorthand_spawn_lifecycle_hooks_success():
|
|
|
142
148
|
|
|
143
149
|
assert agent.name == "ShorthandWorker"
|
|
144
150
|
assert events == ["start", "success"]
|
|
151
|
+
assert client.spawn_calls[-1]["transport"] == "pty"
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
@pytest.mark.asyncio
|
|
155
|
+
async def test_opencode_shorthand_spawn_success():
|
|
156
|
+
relay = AgentRelay()
|
|
157
|
+
client = _FakeRelayClient()
|
|
158
|
+
relay._ensure_started = AsyncMock(return_value=client)
|
|
159
|
+
|
|
160
|
+
agent = await relay.opencode.spawn(
|
|
161
|
+
name="OpencodeWorker",
|
|
162
|
+
channels=["general"],
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
assert agent.name == "OpencodeWorker"
|
|
166
|
+
assert client.spawn_calls[-1]["provider"] == "opencode"
|
|
167
|
+
assert client.spawn_calls[-1]["transport"] == "headless"
|
|
145
168
|
|
|
146
169
|
|
|
147
170
|
@pytest.mark.asyncio
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-relay/trajectory",
|
|
3
|
-
"version": "3.1
|
|
3
|
+
"version": "3.2.1",
|
|
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
|
|
25
|
+
"@agent-relay/config": "3.2.1"
|
|
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
|
|
3
|
+
"version": "3.2.1",
|
|
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
|
|
25
|
+
"@agent-relay/utils": "3.2.1"
|
|
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
|
|
3
|
+
"version": "3.2.1",
|
|
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
|
|
115
|
+
"@agent-relay/config": "3.2.1",
|
|
116
116
|
"compare-versions": "^6.1.1"
|
|
117
117
|
},
|
|
118
118
|
"publishConfig": {
|
|
@@ -4,10 +4,10 @@ Advanced features for session continuity and trajectory tracking.
|
|
|
4
4
|
|
|
5
5
|
## Session Continuity
|
|
6
6
|
|
|
7
|
-
Use `
|
|
7
|
+
Use `mcp__relaycast__send_dm` with a continuity message to save state for session recovery:
|
|
8
8
|
|
|
9
9
|
```
|
|
10
|
-
|
|
10
|
+
mcp__relaycast__send_dm(to: "system", text: "KIND: continuity\nACTION: save\n\nCurrent task: Implementing user authentication\nCompleted: User model, JWT utils\nIn progress: Login endpoint")
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
### When to Save
|
|
@@ -51,10 +51,10 @@ trail abandon --reason "Blocked by missing credentials"
|
|
|
51
51
|
|
|
52
52
|
## Cross-Project Messaging
|
|
53
53
|
|
|
54
|
-
In bridge mode, use `project:agent` format with `
|
|
54
|
+
In bridge mode, use `project:agent` format with `mcp__relaycast__send_dm`:
|
|
55
55
|
|
|
56
56
|
```
|
|
57
|
-
|
|
57
|
+
mcp__relaycast__send_dm(to: "frontend:Designer", text: "Please update the login UI.")
|
|
58
58
|
```
|
|
59
59
|
|
|
60
60
|
Special targets:
|
|
@@ -4,41 +4,30 @@ Real-time agent-to-agent messaging via MCP tools.
|
|
|
4
4
|
|
|
5
5
|
## MCP Tools
|
|
6
6
|
|
|
7
|
-
All agent communication uses MCP tools provided by the Relaycast MCP server
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
|
11
|
-
|
|
|
12
|
-
| `
|
|
13
|
-
| `
|
|
14
|
-
| `
|
|
15
|
-
| `
|
|
16
|
-
| `
|
|
7
|
+
All agent communication uses MCP tools provided by the Relaycast MCP server.
|
|
8
|
+
Tool names use dot-notation: Claude uses `mcp__relaycast__<category>_<action>`, other CLIs use `relaycast.<category>.<action>`.
|
|
9
|
+
|
|
10
|
+
| Tool | Description |
|
|
11
|
+
| --------------------------------- | ------------------------------------- |
|
|
12
|
+
| `dm_send(to, text)` | Send a DM to an agent |
|
|
13
|
+
| `message_post(channel, text)` | Post a message to a channel |
|
|
14
|
+
| `inbox_check()` | Check your inbox for new messages |
|
|
15
|
+
| `agent_list()` | List online agents |
|
|
16
|
+
| `agent_add(name, cli, task)` | Spawn a new worker agent |
|
|
17
|
+
| `agent_remove(name)` | Release/stop a worker agent |
|
|
17
18
|
|
|
18
19
|
## Sending Messages
|
|
19
20
|
|
|
20
|
-
Use the `relay_send` MCP tool:
|
|
21
|
-
|
|
22
|
-
```
|
|
23
|
-
relay_send(to: "AgentName", message: "Your message here")
|
|
24
|
-
```
|
|
25
|
-
|
|
26
21
|
### Direct Messages
|
|
27
22
|
|
|
28
23
|
```
|
|
29
|
-
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
### Broadcast to All
|
|
33
|
-
|
|
34
|
-
```
|
|
35
|
-
relay_send(to: "*", message: "I've finished the auth module")
|
|
24
|
+
mcp__relaycast__dm_send(to: "Bob", text: "Can you review my code changes?")
|
|
36
25
|
```
|
|
37
26
|
|
|
38
27
|
### Channel Messages
|
|
39
28
|
|
|
40
29
|
```
|
|
41
|
-
|
|
30
|
+
mcp__relaycast__message_post(channel: "general", text: "The API endpoints are ready")
|
|
42
31
|
```
|
|
43
32
|
|
|
44
33
|
## Spawning & Releasing Agents
|
|
@@ -46,23 +35,24 @@ relay_send(to: "#frontend", message: "The API endpoints are ready")
|
|
|
46
35
|
### Spawn a Worker
|
|
47
36
|
|
|
48
37
|
```
|
|
49
|
-
|
|
38
|
+
mcp__relaycast__agent_add(name: "WorkerName", cli: "claude", task: "Task description here")
|
|
50
39
|
```
|
|
51
40
|
|
|
52
41
|
### CLI Options
|
|
53
42
|
|
|
54
|
-
| CLI Value
|
|
55
|
-
|
|
|
56
|
-
| `claude`
|
|
57
|
-
| `codex`
|
|
58
|
-
| `gemini`
|
|
59
|
-
| `
|
|
60
|
-
| `
|
|
43
|
+
| CLI Value | Description |
|
|
44
|
+
| ----------- | ---------------------------- |
|
|
45
|
+
| `claude` | Claude Code (Anthropic) |
|
|
46
|
+
| `codex` | Codex CLI (OpenAI) |
|
|
47
|
+
| `gemini` | Gemini CLI (Google) |
|
|
48
|
+
| `opencode` | OpenCode CLI (multi-model) |
|
|
49
|
+
| `aider` | Aider coding assistant |
|
|
50
|
+
| `goose` | Goose AI assistant |
|
|
61
51
|
|
|
62
52
|
### Release a Worker
|
|
63
53
|
|
|
64
54
|
```
|
|
65
|
-
|
|
55
|
+
mcp__relaycast__agent_remove(name: "WorkerName")
|
|
66
56
|
```
|
|
67
57
|
|
|
68
58
|
## Receiving Messages
|
|
@@ -86,11 +76,11 @@ Reply to the channel shown, not the sender.
|
|
|
86
76
|
If you were spawned by another agent:
|
|
87
77
|
|
|
88
78
|
1. Your first message is your task from your spawner
|
|
89
|
-
2. Use `
|
|
79
|
+
2. Use `mcp__relaycast__dm_send` to reply to your spawner
|
|
90
80
|
3. Report status to your spawner (your lead), not broadcast
|
|
91
81
|
|
|
92
82
|
```
|
|
93
|
-
|
|
83
|
+
mcp__relaycast__dm_send(to: "Lead", text: "ACK: Starting on the task.")
|
|
94
84
|
```
|
|
95
85
|
|
|
96
86
|
## Protocol
|
|
@@ -103,10 +93,10 @@ relay_send(to: "Lead", message: "ACK: Starting on the task.")
|
|
|
103
93
|
|
|
104
94
|
**Local communication** uses plain agent names. The `project:` prefix is **ONLY** for cross-project bridge mode.
|
|
105
95
|
|
|
106
|
-
| Context | Correct
|
|
107
|
-
| ---------------------- |
|
|
108
|
-
| Local (same project) | `
|
|
109
|
-
| Bridge (cross-project) | `
|
|
96
|
+
| Context | Correct | Incorrect |
|
|
97
|
+
| ---------------------- | ----------------------------------------------------------- | ------------------------------------------------------ |
|
|
98
|
+
| Local (same project) | `mcp__relaycast__dm_send(to: "Lead", ...)` | `mcp__relaycast__dm_send(to: "project:lead", ...)` |
|
|
99
|
+
| Bridge (cross-project) | `mcp__relaycast__dm_send(to: "frontend:Designer", ...)` | N/A |
|
|
110
100
|
|
|
111
101
|
## Multi-Workspace
|
|
112
102
|
|
|
@@ -118,12 +108,10 @@ Relay message from Alice [my-team / abc123]: Hello!
|
|
|
118
108
|
|
|
119
109
|
- Messages are scoped to the originating workspace
|
|
120
110
|
- Reply within the same workspace context shown in the message header
|
|
121
|
-
- Use `relay_status()` to see which workspaces are connected
|
|
122
111
|
|
|
123
112
|
## Checking Status
|
|
124
113
|
|
|
125
114
|
```
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
relay_status() # Check connection status
|
|
115
|
+
mcp__relaycast__agent_list() # List online agents
|
|
116
|
+
mcp__relaycast__inbox_check() # Check for unread messages
|
|
129
117
|
```
|