agent-relay 1.3.1 → 1.3.3
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/.trajectories/active/traj_3yx9dy148mge.json +42 -0
- package/.trajectories/completed/2026-01/traj_1g7yx6qtg4ai.json +49 -0
- package/.trajectories/completed/2026-01/traj_1g7yx6qtg4ai.md +31 -0
- package/.trajectories/completed/2026-01/traj_4qwd4zmhfwp4.json +49 -0
- package/.trajectories/completed/2026-01/traj_4qwd4zmhfwp4.md +31 -0
- package/.trajectories/completed/2026-01/traj_6unwwmgyj5sq.json +109 -0
- package/.trajectories/completed/2026-01/traj_a0tqx8biw9c4.json +49 -0
- package/.trajectories/completed/2026-01/traj_a0tqx8biw9c4.md +31 -0
- package/.trajectories/completed/2026-01/traj_ax8uungxz2qh.json +66 -0
- package/.trajectories/completed/2026-01/traj_ax8uungxz2qh.md +36 -0
- package/.trajectories/completed/2026-01/traj_c9izbh2snpzf.json +49 -0
- package/.trajectories/completed/2026-01/traj_c9izbh2snpzf.md +31 -0
- package/.trajectories/completed/2026-01/traj_cpn70dw066nt.json +65 -0
- package/.trajectories/completed/2026-01/traj_cpn70dw066nt.md +37 -0
- package/.trajectories/completed/2026-01/traj_erglv2f8t9eh.json +36 -0
- package/.trajectories/completed/2026-01/traj_erglv2f8t9eh.md +21 -0
- package/.trajectories/completed/2026-01/traj_he75f24d1xfm.json +101 -0
- package/.trajectories/completed/2026-01/traj_he75f24d1xfm.md +52 -0
- package/.trajectories/completed/2026-01/traj_lgtodco7dp1n.json +61 -0
- package/.trajectories/completed/2026-01/traj_lgtodco7dp1n.md +36 -0
- package/.trajectories/completed/2026-01/traj_oszg9flv74pk.json +73 -0
- package/.trajectories/completed/2026-01/traj_oszg9flv74pk.md +41 -0
- package/.trajectories/completed/2026-01/traj_pulomd3y8cvj.json +77 -0
- package/.trajectories/completed/2026-01/traj_pulomd3y8cvj.md +42 -0
- package/.trajectories/completed/2026-01/traj_rsavt0jipi3c.json +109 -0
- package/.trajectories/completed/2026-01/traj_rsavt0jipi3c.md +56 -0
- package/.trajectories/completed/2026-01/traj_x721m1j9rzup.json +113 -0
- package/.trajectories/completed/2026-01/traj_x721m1j9rzup.md +57 -0
- package/.trajectories/completed/2026-01/traj_xjqvmep5ed3h.json +61 -0
- package/.trajectories/completed/2026-01/traj_xjqvmep5ed3h.md +36 -0
- package/.trajectories/completed/2026-01/traj_y7n6hfbf7dmg.json +49 -0
- package/.trajectories/completed/2026-01/traj_y7n6hfbf7dmg.md +31 -0
- package/.trajectories/completed/2026-01/traj_yvfkwnkdiso2.json +49 -0
- package/.trajectories/completed/2026-01/traj_yvfkwnkdiso2.md +31 -0
- package/.trajectories/index.json +140 -1
- package/README.md +23 -9
- package/TRAIL_GIT_AUTH_FIX.md +113 -0
- package/deploy/workspace/codex.config.toml +1 -1
- package/deploy/workspace/entrypoint.sh +20 -79
- package/deploy/workspace/gh-relay +156 -0
- package/deploy/workspace/git-credential-relay +5 -1
- package/dist/bridge/multi-project-client.js +13 -10
- package/dist/bridge/spawner.d.ts +2 -0
- package/dist/bridge/spawner.js +58 -76
- package/dist/bridge/types.d.ts +2 -0
- package/dist/cli/index.d.ts +8 -6
- package/dist/cli/index.js +297 -30
- package/dist/cloud/api/admin.js +16 -3
- package/dist/cloud/api/codex-auth-helper.js +28 -8
- package/dist/cloud/api/consensus.d.ts +13 -0
- package/dist/cloud/api/consensus.js +259 -0
- package/dist/cloud/api/daemons.js +205 -1
- package/dist/cloud/api/git.js +37 -7
- package/dist/cloud/api/onboarding.js +4 -1
- package/dist/cloud/api/provider-env.d.ts +5 -0
- package/dist/cloud/api/provider-env.js +27 -0
- package/dist/cloud/api/providers.js +2 -0
- package/dist/cloud/api/test-helpers.js +130 -0
- package/dist/cloud/api/workspaces.js +38 -3
- package/dist/cloud/db/bulk-ingest.d.ts +88 -0
- package/dist/cloud/db/bulk-ingest.js +268 -0
- package/dist/cloud/db/drizzle.d.ts +33 -0
- package/dist/cloud/db/drizzle.js +174 -2
- package/dist/cloud/db/index.d.ts +24 -5
- package/dist/cloud/db/index.js +19 -4
- package/dist/cloud/db/schema.d.ts +397 -3
- package/dist/cloud/db/schema.js +75 -1
- package/dist/cloud/provisioner/index.d.ts +8 -0
- package/dist/cloud/provisioner/index.js +256 -50
- package/dist/cloud/server.js +47 -3
- package/dist/cloud/services/index.d.ts +1 -0
- package/dist/cloud/services/index.js +2 -0
- package/dist/cloud/services/nango.d.ts +3 -4
- package/dist/cloud/services/nango.js +11 -33
- package/dist/cloud/services/workspace-keepalive.d.ts +76 -0
- package/dist/cloud/services/workspace-keepalive.js +234 -0
- package/dist/config/relay-config.d.ts +23 -0
- package/dist/config/relay-config.js +23 -0
- package/dist/daemon/agent-manager.d.ts +20 -1
- package/dist/daemon/agent-manager.js +51 -0
- package/dist/daemon/agent-registry.js +4 -4
- package/dist/daemon/agent-signing.d.ts +158 -0
- package/dist/daemon/agent-signing.js +523 -0
- package/dist/daemon/api.js +18 -1
- package/dist/daemon/cli-auth.d.ts +4 -1
- package/dist/daemon/cli-auth.js +55 -11
- package/dist/daemon/cloud-sync.d.ts +47 -1
- package/dist/daemon/cloud-sync.js +152 -3
- package/dist/daemon/connection.d.ts +28 -0
- package/dist/daemon/connection.js +113 -22
- package/dist/daemon/consensus-integration.d.ts +167 -0
- package/dist/daemon/consensus-integration.js +371 -0
- package/dist/daemon/consensus.d.ts +271 -0
- package/dist/daemon/consensus.js +632 -0
- package/dist/daemon/delivery-tracker.d.ts +34 -0
- package/dist/daemon/delivery-tracker.js +104 -0
- package/dist/daemon/enhanced-features.d.ts +118 -0
- package/dist/daemon/enhanced-features.js +178 -0
- package/dist/daemon/index.d.ts +4 -0
- package/dist/daemon/index.js +5 -0
- package/dist/daemon/rate-limiter.d.ts +68 -0
- package/dist/daemon/rate-limiter.js +130 -0
- package/dist/daemon/router.d.ts +18 -11
- package/dist/daemon/router.js +57 -113
- package/dist/daemon/server.d.ts +13 -1
- package/dist/daemon/server.js +71 -9
- package/dist/daemon/sync-queue.d.ts +116 -0
- package/dist/daemon/sync-queue.js +361 -0
- package/dist/dashboard/out/404.html +1 -1
- package/dist/dashboard/out/_next/static/chunks/116-de2a4ac06e5000dc.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/847-f1f467060f32afff.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/919-87d604a5d76c1fbd.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/app/{page-c617745b81344f4f.js → page-7f64824ae7d06707.js} +1 -1
- package/dist/dashboard/out/_next/static/chunks/app/cloud/link/page-3f559d393902aad2.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/login/page-16d1715ddaa874ee.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/{page-dc786c183425c2ac.js → page-814efc4d77b4191d.js} +1 -1
- package/dist/dashboard/out/_next/static/chunks/{main-2ee6beb2ae96d210.js → main-5a40a5ae29646e1b.js} +1 -1
- package/dist/dashboard/out/_next/static/css/44d2b52637b511bc.css +1 -0
- package/dist/dashboard/out/app/onboarding.html +1 -1
- package/dist/dashboard/out/app/onboarding.txt +1 -1
- package/dist/dashboard/out/app.html +1 -1
- package/dist/dashboard/out/app.txt +2 -2
- package/dist/dashboard/out/cloud/link.html +1 -0
- package/dist/dashboard/out/cloud/link.txt +7 -0
- package/dist/dashboard/out/connect-repos.html +1 -1
- package/dist/dashboard/out/connect-repos.txt +1 -1
- package/dist/dashboard/out/history.html +1 -1
- package/dist/dashboard/out/history.txt +2 -2
- package/dist/dashboard/out/index.html +1 -1
- package/dist/dashboard/out/index.txt +2 -2
- package/dist/dashboard/out/login.html +2 -3
- package/dist/dashboard/out/login.txt +2 -2
- package/dist/dashboard/out/metrics.html +1 -1
- package/dist/dashboard/out/metrics.txt +2 -2
- package/dist/dashboard/out/pricing.html +2 -2
- package/dist/dashboard/out/pricing.txt +1 -1
- package/dist/dashboard/out/providers/setup/claude.html +1 -1
- package/dist/dashboard/out/providers/setup/claude.txt +1 -1
- package/dist/dashboard/out/providers/setup/codex.html +1 -1
- package/dist/dashboard/out/providers/setup/codex.txt +1 -1
- package/dist/dashboard/out/providers.html +1 -1
- package/dist/dashboard/out/providers.txt +1 -1
- package/dist/dashboard/out/signup.html +2 -2
- package/dist/dashboard/out/signup.txt +1 -1
- package/dist/dashboard-server/server.js +244 -28
- package/dist/health-worker-manager.d.ts +62 -0
- package/dist/health-worker-manager.js +144 -0
- package/dist/health-worker.d.ts +9 -0
- package/dist/health-worker.js +79 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +5 -1
- package/dist/memory/context-compaction.d.ts +156 -0
- package/dist/memory/context-compaction.js +453 -0
- package/dist/memory/index.d.ts +1 -0
- package/dist/memory/index.js +1 -0
- package/dist/protocol/channels.js +4 -4
- package/dist/protocol/framing.d.ts +72 -10
- package/dist/protocol/framing.js +194 -25
- package/dist/storage/adapter.d.ts +8 -1
- package/dist/storage/adapter.js +11 -0
- package/dist/storage/batched-sqlite-adapter.d.ts +71 -0
- package/dist/storage/batched-sqlite-adapter.js +183 -0
- package/dist/storage/dead-letter-queue.d.ts +196 -0
- package/dist/storage/dead-letter-queue.js +427 -0
- package/dist/storage/dlq-adapter.d.ts +195 -0
- package/dist/storage/dlq-adapter.js +664 -0
- package/dist/trajectory/config.d.ts +32 -14
- package/dist/trajectory/config.js +38 -16
- package/dist/trajectory/integration.js +217 -64
- package/dist/utils/git-remote.d.ts +47 -0
- package/dist/utils/git-remote.js +125 -0
- package/dist/utils/id-generator.d.ts +35 -0
- package/dist/utils/id-generator.js +60 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/dist/utils/precompiled-patterns.d.ts +110 -0
- package/dist/utils/precompiled-patterns.js +322 -0
- package/dist/wrapper/auth-detection.js +1 -1
- package/dist/wrapper/base-wrapper.d.ts +40 -0
- package/dist/wrapper/base-wrapper.js +60 -6
- package/dist/wrapper/client.d.ts +14 -4
- package/dist/wrapper/client.js +89 -31
- package/dist/wrapper/idle-detector.d.ts +102 -0
- package/dist/wrapper/idle-detector.js +279 -0
- package/dist/wrapper/parser.d.ts +4 -0
- package/dist/wrapper/parser.js +19 -1
- package/dist/wrapper/pty-wrapper.d.ts +14 -2
- package/dist/wrapper/pty-wrapper.js +132 -32
- package/dist/wrapper/shared.d.ts +1 -1
- package/dist/wrapper/shared.js +1 -1
- package/dist/wrapper/tmux-wrapper.d.ts +20 -2
- package/dist/wrapper/tmux-wrapper.js +163 -40
- package/package.json +3 -1
- package/scripts/run-migrations.js +43 -0
- package/scripts/verify-schema.js +134 -0
- package/tests/benchmarks/protocol.bench.ts +310 -0
- package/dist/dashboard/out/_next/static/chunks/116-2502180def231162.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/899-fc02ed79e3de4302.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/login/page-c22d080201cbd9fb.js +0 -1
- package/dist/dashboard/out/_next/static/css/48a8fbe3e659080e.css +0 -1
- /package/dist/dashboard/out/_next/static/{sDcbGRTYLcpPvyTs_rsNb → R-uQOUcOLINtsp6ACeZa9}/_buildManifest.js +0 -0
- /package/dist/dashboard/out/_next/static/{sDcbGRTYLcpPvyTs_rsNb → R-uQOUcOLINtsp6ACeZa9}/_ssgManifest.js +0 -0
|
@@ -184,6 +184,11 @@ export class PtyWrapper extends BaseWrapper {
|
|
|
184
184
|
}
|
|
185
185
|
this.running = true;
|
|
186
186
|
this.sessionStartTime = Date.now();
|
|
187
|
+
// Set PID for idle detector (enables process state inspection on Linux)
|
|
188
|
+
if (this.ptyProcess.pid) {
|
|
189
|
+
this.setIdleDetectorPid(this.ptyProcess.pid);
|
|
190
|
+
console.log(`[pty:${this.config.name}] Idle detector initialized with PID ${this.ptyProcess.pid}`);
|
|
191
|
+
}
|
|
187
192
|
// Skip hooks and continuity in interactive mode - user handles all prompts directly
|
|
188
193
|
if (!this.config.interactive) {
|
|
189
194
|
// Dispatch session start hook (handles trajectory initialization)
|
|
@@ -208,18 +213,40 @@ export class PtyWrapper extends BaseWrapper {
|
|
|
208
213
|
this.config.onExit?.(exitCode);
|
|
209
214
|
this.client.destroy();
|
|
210
215
|
});
|
|
211
|
-
//
|
|
212
|
-
//
|
|
213
|
-
|
|
216
|
+
// Wait for agent to be idle before injecting instructions
|
|
217
|
+
// This replaces the fixed 2-second delay with actual readiness detection
|
|
218
|
+
this.waitForAgentReady().then(() => {
|
|
214
219
|
if (!this.config.interactive) {
|
|
215
220
|
this.injectInstructions();
|
|
216
221
|
}
|
|
217
222
|
this.readyForMessages = true;
|
|
223
|
+
console.log(`[pty:${this.config.name}] Agent ready for messages (queueLen=${this.messageQueue.length}, interactive=${this.config.interactive})`);
|
|
218
224
|
// Process any messages that arrived while waiting (skip in interactive mode)
|
|
219
225
|
if (!this.config.interactive) {
|
|
220
226
|
this.processMessageQueue();
|
|
221
227
|
}
|
|
222
|
-
}
|
|
228
|
+
}).catch(err => {
|
|
229
|
+
console.error(`[pty:${this.config.name}] Failed to wait for agent ready:`, err);
|
|
230
|
+
// Fall back to marking ready anyway to avoid blocking forever
|
|
231
|
+
this.readyForMessages = true;
|
|
232
|
+
console.log(`[pty:${this.config.name}] Agent ready for messages (fallback, queueLen=${this.messageQueue.length})`);
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Wait for the agent to be ready for input.
|
|
237
|
+
* Uses idle detection instead of a fixed delay.
|
|
238
|
+
*/
|
|
239
|
+
async waitForAgentReady() {
|
|
240
|
+
// Minimum wait to ensure the CLI process has started
|
|
241
|
+
await sleep(500);
|
|
242
|
+
// Wait for agent to become idle (CLI fully initialized)
|
|
243
|
+
const result = await this.waitForIdleState(10000, 200);
|
|
244
|
+
if (result.isIdle) {
|
|
245
|
+
console.log(`[pty:${this.config.name}] Agent ready (confidence: ${(result.confidence * 100).toFixed(0)}%)`);
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
console.warn(`[pty:${this.config.name}] Agent readiness timeout, proceeding anyway`);
|
|
249
|
+
}
|
|
223
250
|
}
|
|
224
251
|
// Note: initializeAgentId() and getAgentId() are inherited from BaseWrapper
|
|
225
252
|
/**
|
|
@@ -322,6 +349,8 @@ export class PtyWrapper extends BaseWrapper {
|
|
|
322
349
|
handleOutput(data) {
|
|
323
350
|
// Track output timing for stability checks
|
|
324
351
|
this.lastOutputTime = Date.now();
|
|
352
|
+
// Feed output to idle detector for robust idle detection
|
|
353
|
+
this.feedIdleDetectorOutput(data);
|
|
325
354
|
// Append to raw buffer
|
|
326
355
|
this.rawBuffer += data;
|
|
327
356
|
// Write to log file if available
|
|
@@ -581,6 +610,14 @@ export class PtyWrapper extends BaseWrapper {
|
|
|
581
610
|
// 500 chars is enough to capture most relay message headers
|
|
582
611
|
const lookbackStart = Math.max(0, this.lastParsedLength - 500);
|
|
583
612
|
const contentToParse = cleanContent.substring(lookbackStart);
|
|
613
|
+
// Debug: Check if content contains relay pattern
|
|
614
|
+
if (contentToParse.includes('->relay:')) {
|
|
615
|
+
const relayLines = contentToParse.split('\n').filter(l => l.includes('->relay:'));
|
|
616
|
+
console.log(`[pty:${this.config.name}] [RELAY-DEBUG] Found ${relayLines.length} lines with ->relay: pattern`);
|
|
617
|
+
relayLines.slice(0, 3).forEach((line, i) => {
|
|
618
|
+
console.log(`[pty:${this.config.name}] [RELAY-DEBUG] Line ${i}: "${line.substring(0, 80)}..."`);
|
|
619
|
+
});
|
|
620
|
+
}
|
|
584
621
|
// First, try to find fenced multi-line messages: ->relay:Target <<<\n...\n>>>
|
|
585
622
|
this.parseFencedMessages(contentToParse);
|
|
586
623
|
// Then parse single-line messages
|
|
@@ -610,6 +647,7 @@ export class PtyWrapper extends BaseWrapper {
|
|
|
610
647
|
}
|
|
611
648
|
// Skip placeholder targets (documentation examples like "AgentName", "Lead", etc.)
|
|
612
649
|
if (isPlaceholderTarget(target)) {
|
|
650
|
+
console.error(`[pty-wrapper] Filtered fenced message - placeholder target: ${target}`);
|
|
613
651
|
continue;
|
|
614
652
|
}
|
|
615
653
|
// Find the closing >>>
|
|
@@ -630,6 +668,7 @@ export class PtyWrapper extends BaseWrapper {
|
|
|
630
668
|
}
|
|
631
669
|
// Skip placeholder targets after parsing cross-project syntax
|
|
632
670
|
if (isPlaceholderTarget(to)) {
|
|
671
|
+
console.error(`[pty-wrapper] Filtered fenced message - placeholder target after cross-project parse: ${to}`);
|
|
633
672
|
continue;
|
|
634
673
|
}
|
|
635
674
|
this.sendRelayCommand({
|
|
@@ -676,8 +715,10 @@ export class PtyWrapper extends BaseWrapper {
|
|
|
676
715
|
if (!body)
|
|
677
716
|
continue;
|
|
678
717
|
// Skip placeholder targets (documentation examples)
|
|
679
|
-
if (isPlaceholderTarget(target))
|
|
718
|
+
if (isPlaceholderTarget(target)) {
|
|
719
|
+
console.error(`[pty-wrapper] Filtered single-line message - placeholder target: ${target}`);
|
|
680
720
|
continue;
|
|
721
|
+
}
|
|
681
722
|
// Parse target for cross-project syntax
|
|
682
723
|
const colonIdx = target.indexOf(':');
|
|
683
724
|
let to = target;
|
|
@@ -687,8 +728,10 @@ export class PtyWrapper extends BaseWrapper {
|
|
|
687
728
|
to = target.substring(colonIdx + 1);
|
|
688
729
|
}
|
|
689
730
|
// Skip placeholder targets after parsing cross-project syntax
|
|
690
|
-
if (isPlaceholderTarget(to))
|
|
731
|
+
if (isPlaceholderTarget(to)) {
|
|
732
|
+
console.error(`[pty-wrapper] Filtered single-line message - placeholder target after cross-project parse: ${to}`);
|
|
691
733
|
continue;
|
|
734
|
+
}
|
|
692
735
|
this.sendRelayCommand({
|
|
693
736
|
to,
|
|
694
737
|
kind: 'message',
|
|
@@ -702,8 +745,10 @@ export class PtyWrapper extends BaseWrapper {
|
|
|
702
745
|
if (!body)
|
|
703
746
|
continue;
|
|
704
747
|
// Skip placeholder targets (documentation examples)
|
|
705
|
-
if (isPlaceholderTarget(target))
|
|
748
|
+
if (isPlaceholderTarget(target)) {
|
|
749
|
+
console.error(`[pty-wrapper] Filtered single-line message - placeholder target: ${target}`);
|
|
706
750
|
continue;
|
|
751
|
+
}
|
|
707
752
|
// Parse target for cross-project syntax
|
|
708
753
|
const colonIdx = target.indexOf(':');
|
|
709
754
|
let to = target;
|
|
@@ -713,8 +758,10 @@ export class PtyWrapper extends BaseWrapper {
|
|
|
713
758
|
to = target.substring(colonIdx + 1);
|
|
714
759
|
}
|
|
715
760
|
// Skip placeholder targets after parsing cross-project syntax
|
|
716
|
-
if (isPlaceholderTarget(to))
|
|
761
|
+
if (isPlaceholderTarget(to)) {
|
|
762
|
+
console.error(`[pty-wrapper] Filtered single-line message - placeholder target after cross-project parse: ${to}`);
|
|
717
763
|
continue;
|
|
764
|
+
}
|
|
718
765
|
this.sendRelayCommand({
|
|
719
766
|
to,
|
|
720
767
|
kind: 'message',
|
|
@@ -948,22 +995,24 @@ export class PtyWrapper extends BaseWrapper {
|
|
|
948
995
|
}
|
|
949
996
|
/**
|
|
950
997
|
* Execute spawn via API or callback.
|
|
951
|
-
*
|
|
998
|
+
* After spawning, waits for the agent to come online and sends the task via relay.
|
|
952
999
|
*/
|
|
953
1000
|
async executeSpawn(name, cli, task) {
|
|
954
1001
|
console.log(`[pty:${this.config.name}] [SPAWN-DEBUG] executeSpawn called: name=${name}, cli=${cli}, task="${task.substring(0, 50)}..."`);
|
|
955
1002
|
console.log(`[pty:${this.config.name}] [SPAWN-DEBUG] dashboardPort=${this.config.dashboardPort}, hasOnSpawn=${!!this.config.onSpawn}`);
|
|
1003
|
+
let spawned = false;
|
|
956
1004
|
if (this.config.dashboardPort) {
|
|
957
1005
|
// Use dashboard API for spawning (works from spawned agents)
|
|
958
1006
|
try {
|
|
959
1007
|
const response = await fetch(`http://localhost:${this.config.dashboardPort}/api/spawn`, {
|
|
960
1008
|
method: 'POST',
|
|
961
1009
|
headers: { 'Content-Type': 'application/json' },
|
|
962
|
-
body: JSON.stringify({ name, cli, task
|
|
1010
|
+
body: JSON.stringify({ name, cli }), // No task - we send it after agent is online
|
|
963
1011
|
});
|
|
964
1012
|
const result = await response.json();
|
|
965
1013
|
if (result.success) {
|
|
966
1014
|
console.log(`[pty:${this.config.name}] Spawned ${name} via API`);
|
|
1015
|
+
spawned = true;
|
|
967
1016
|
}
|
|
968
1017
|
else {
|
|
969
1018
|
console.error(`[pty:${this.config.name}] Spawn failed: ${result.error}`);
|
|
@@ -977,11 +1026,57 @@ export class PtyWrapper extends BaseWrapper {
|
|
|
977
1026
|
// Fall back to callback
|
|
978
1027
|
try {
|
|
979
1028
|
await this.config.onSpawn(name, cli, task);
|
|
1029
|
+
spawned = true;
|
|
980
1030
|
}
|
|
981
1031
|
catch (err) {
|
|
982
1032
|
console.error(`[pty:${this.config.name}] Spawn failed: ${err.message}`);
|
|
983
1033
|
}
|
|
984
1034
|
}
|
|
1035
|
+
// If spawn succeeded and we have a task, wait for agent to come online and send it
|
|
1036
|
+
if (spawned && task && task.trim() && this.config.dashboardPort) {
|
|
1037
|
+
await this.waitAndSendTask(name, task);
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
/**
|
|
1041
|
+
* Wait for a spawned agent to come online, then send the task via relay.
|
|
1042
|
+
* Uses the wrapper's own relay client so the message comes "from" this agent,
|
|
1043
|
+
* not from the dashboard's relay client.
|
|
1044
|
+
*/
|
|
1045
|
+
async waitAndSendTask(agentName, task) {
|
|
1046
|
+
const maxWaitMs = 30000;
|
|
1047
|
+
const pollIntervalMs = 500;
|
|
1048
|
+
const startTime = Date.now();
|
|
1049
|
+
console.log(`[pty:${this.config.name}] Waiting for ${agentName} to come online...`);
|
|
1050
|
+
// Poll for agent to be online using dedicated status endpoint
|
|
1051
|
+
while (Date.now() - startTime < maxWaitMs) {
|
|
1052
|
+
try {
|
|
1053
|
+
const response = await fetch(`http://localhost:${this.config.dashboardPort}/api/agents/${encodeURIComponent(agentName)}/online`);
|
|
1054
|
+
const data = await response.json();
|
|
1055
|
+
if (data.online) {
|
|
1056
|
+
console.log(`[pty:${this.config.name}] ${agentName} is online, sending task...`);
|
|
1057
|
+
// Send task directly via our relay client (not dashboard API)
|
|
1058
|
+
// This ensures the message comes "from" this agent, not from _DashboardUI
|
|
1059
|
+
if (this.client.state === 'READY') {
|
|
1060
|
+
const sent = this.client.sendMessage(agentName, task, 'message');
|
|
1061
|
+
if (sent) {
|
|
1062
|
+
console.log(`[pty:${this.config.name}] Task sent to ${agentName}`);
|
|
1063
|
+
}
|
|
1064
|
+
else {
|
|
1065
|
+
console.error(`[pty:${this.config.name}] Failed to send task to ${agentName}: sendMessage returned false`);
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
else {
|
|
1069
|
+
console.error(`[pty:${this.config.name}] Failed to send task to ${agentName}: relay client not ready (state: ${this.client.state})`);
|
|
1070
|
+
}
|
|
1071
|
+
return;
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
catch (err) {
|
|
1075
|
+
// Ignore poll errors, keep trying
|
|
1076
|
+
}
|
|
1077
|
+
await sleep(pollIntervalMs);
|
|
1078
|
+
}
|
|
1079
|
+
console.error(`[pty:${this.config.name}] Timeout waiting for ${agentName} to come online`);
|
|
985
1080
|
}
|
|
986
1081
|
/**
|
|
987
1082
|
* Execute release via API or callback.
|
|
@@ -1021,6 +1116,8 @@ export class PtyWrapper extends BaseWrapper {
|
|
|
1021
1116
|
* Extends BaseWrapper to add PTY-specific behavior.
|
|
1022
1117
|
*/
|
|
1023
1118
|
handleIncomingMessage(from, payload, messageId, meta, originalTo) {
|
|
1119
|
+
const bodyPreview = payload.body.substring(0, 50).replace(/\n/g, '\\n');
|
|
1120
|
+
console.log(`[pty:${this.config.name}] Message received from ${from}: "${bodyPreview}..." (readyForMessages=${this.readyForMessages}, queueLen=${this.messageQueue.length})`);
|
|
1024
1121
|
// Call base class to handle deduplication and queuing
|
|
1025
1122
|
super.handleIncomingMessage(from, payload, messageId, meta, originalTo);
|
|
1026
1123
|
// PTY-specific: Process the message queue immediately
|
|
@@ -1032,27 +1129,14 @@ export class PtyWrapper extends BaseWrapper {
|
|
|
1032
1129
|
}
|
|
1033
1130
|
/**
|
|
1034
1131
|
* Wait for output to stabilize before injection.
|
|
1035
|
-
*
|
|
1132
|
+
* Uses UniversalIdleDetector (from BaseWrapper) for robust cross-CLI idle detection.
|
|
1133
|
+
* Returns true if agent is idle and ready for input.
|
|
1036
1134
|
*/
|
|
1037
1135
|
async waitForOutputStable() {
|
|
1038
|
-
const
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
await sleep(INJECTION_CONSTANTS.STABILITY_POLL_MS);
|
|
1043
|
-
const timeSinceOutput = Date.now() - this.lastOutputTime;
|
|
1044
|
-
const bufferUnchanged = this.rawBuffer.length === lastBufferLength;
|
|
1045
|
-
// Consider stable if no output for at least one poll interval
|
|
1046
|
-
if (timeSinceOutput >= INJECTION_CONSTANTS.STABILITY_POLL_MS && bufferUnchanged) {
|
|
1047
|
-
stablePolls++;
|
|
1048
|
-
if (stablePolls >= INJECTION_CONSTANTS.REQUIRED_STABLE_POLLS) {
|
|
1049
|
-
return true;
|
|
1050
|
-
}
|
|
1051
|
-
}
|
|
1052
|
-
else {
|
|
1053
|
-
stablePolls = 0;
|
|
1054
|
-
lastBufferLength = this.rawBuffer.length;
|
|
1055
|
-
}
|
|
1136
|
+
const result = await this.waitForIdleState(INJECTION_CONSTANTS.STABILITY_TIMEOUT_MS, INJECTION_CONSTANTS.STABILITY_POLL_MS);
|
|
1137
|
+
if (result.isIdle) {
|
|
1138
|
+
console.log(`[pty:${this.config.name}] Idle detected (confidence: ${(result.confidence * 100).toFixed(0)}%)`);
|
|
1139
|
+
return true;
|
|
1056
1140
|
}
|
|
1057
1141
|
// Timeout - return true anyway to avoid blocking forever
|
|
1058
1142
|
console.warn(`[pty:${this.config.name}] Stability timeout, proceeding with injection`);
|
|
@@ -1090,6 +1174,8 @@ export class PtyWrapper extends BaseWrapper {
|
|
|
1090
1174
|
this.isInjecting = false;
|
|
1091
1175
|
return;
|
|
1092
1176
|
}
|
|
1177
|
+
const bodyPreview = msg.body.substring(0, 50).replace(/\n/g, '\\n');
|
|
1178
|
+
console.log(`[pty:${this.config.name}] Processing message from ${msg.from}: "${bodyPreview}..." (remaining=${this.messageQueue.length})`);
|
|
1093
1179
|
try {
|
|
1094
1180
|
// Wait for output to stabilize before injecting
|
|
1095
1181
|
await this.waitForOutputStable();
|
|
@@ -1128,9 +1214,21 @@ export class PtyWrapper extends BaseWrapper {
|
|
|
1128
1214
|
if (!this.ptyProcess || !this.running) {
|
|
1129
1215
|
throw new Error('PTY process not running');
|
|
1130
1216
|
}
|
|
1131
|
-
//
|
|
1132
|
-
|
|
1133
|
-
|
|
1217
|
+
// Use bracketed paste mode for CLIs that support it (claude, codex, gemini)
|
|
1218
|
+
// This prevents interleaving with CLI output and ensures clean input
|
|
1219
|
+
const useBracketedPaste = this.cliType === 'claude' || this.cliType === 'codex' || this.cliType === 'gemini';
|
|
1220
|
+
if (useBracketedPaste) {
|
|
1221
|
+
// Bracketed paste: \x1b[200~ starts paste, \x1b[201~ ends paste
|
|
1222
|
+
this.ptyProcess.write('\x1b[200~' + inj + '\x1b[201~');
|
|
1223
|
+
}
|
|
1224
|
+
else {
|
|
1225
|
+
this.ptyProcess.write(inj);
|
|
1226
|
+
}
|
|
1227
|
+
// Wait longer for CLI to process the pasted content before sending Enter.
|
|
1228
|
+
// The standard 50ms delay is too short for CLIs like Claude that need time
|
|
1229
|
+
// to process bracketed paste content before accepting Enter.
|
|
1230
|
+
await sleep(200);
|
|
1231
|
+
// Send Enter key - use \r for PTY (carriage return)
|
|
1134
1232
|
this.ptyProcess.write('\r');
|
|
1135
1233
|
},
|
|
1136
1234
|
log: (message) => console.log(`[pty:${this.config.name}] ${message}`),
|
|
@@ -1175,6 +1273,8 @@ export class PtyWrapper extends BaseWrapper {
|
|
|
1175
1273
|
injectInstructions() {
|
|
1176
1274
|
if (!this.running)
|
|
1177
1275
|
return;
|
|
1276
|
+
if (this.config.skipInstructions)
|
|
1277
|
+
return;
|
|
1178
1278
|
// Guard: Only inject once per session
|
|
1179
1279
|
if (this.instructionsInjected) {
|
|
1180
1280
|
console.log(`[pty:${this.config.name}] Init instructions already injected, skipping`);
|
package/dist/wrapper/shared.d.ts
CHANGED
|
@@ -53,7 +53,7 @@ export declare const INJECTION_CONSTANTS: {
|
|
|
53
53
|
/** Timeout for injection verification (ms) */
|
|
54
54
|
readonly VERIFICATION_TIMEOUT_MS: 2000;
|
|
55
55
|
/** Delay between message and Enter key (ms) */
|
|
56
|
-
readonly ENTER_DELAY_MS:
|
|
56
|
+
readonly ENTER_DELAY_MS: 100;
|
|
57
57
|
/** Backoff multiplier for retries (ms per attempt) */
|
|
58
58
|
readonly RETRY_BACKOFF_MS: 300;
|
|
59
59
|
/** Delay between processing queued messages (ms) */
|
package/dist/wrapper/shared.js
CHANGED
|
@@ -19,7 +19,7 @@ export const INJECTION_CONSTANTS = {
|
|
|
19
19
|
/** Timeout for injection verification (ms) */
|
|
20
20
|
VERIFICATION_TIMEOUT_MS: 2000,
|
|
21
21
|
/** Delay between message and Enter key (ms) */
|
|
22
|
-
ENTER_DELAY_MS:
|
|
22
|
+
ENTER_DELAY_MS: 100,
|
|
23
23
|
/** Backoff multiplier for retries (ms per attempt) */
|
|
24
24
|
RETRY_BACKOFF_MS: 300,
|
|
25
25
|
/** Delay between processing queued messages (ms) */
|
|
@@ -127,6 +127,16 @@ export declare class TmuxWrapper extends BaseWrapper {
|
|
|
127
127
|
* Initialize agent ID for continuity/resume functionality (uses logStderr for tmux)
|
|
128
128
|
*/
|
|
129
129
|
protected initializeAgentId(): Promise<void>;
|
|
130
|
+
/**
|
|
131
|
+
* Initialize the idle detector with the tmux pane PID.
|
|
132
|
+
* This enables process state inspection on Linux for more reliable idle detection.
|
|
133
|
+
*/
|
|
134
|
+
private initializeIdleDetectorPid;
|
|
135
|
+
/**
|
|
136
|
+
* Wait for the agent to be ready for input.
|
|
137
|
+
* Uses idle detection instead of a fixed delay.
|
|
138
|
+
*/
|
|
139
|
+
private waitForAgentReady;
|
|
130
140
|
/**
|
|
131
141
|
* Inject usage instructions for the agent including persistence protocol
|
|
132
142
|
*/
|
|
@@ -222,9 +232,16 @@ export declare class TmuxWrapper extends BaseWrapper {
|
|
|
222
232
|
*/
|
|
223
233
|
private parseSessionEndAndClose;
|
|
224
234
|
/**
|
|
225
|
-
* Execute spawn via API (if dashboardPort set) or callback
|
|
235
|
+
* Execute spawn via API (if dashboardPort set) or callback.
|
|
236
|
+
* After spawning, waits for the agent to come online and sends the task via relay.
|
|
226
237
|
*/
|
|
227
238
|
protected executeSpawn(name: string, cli: string, task: string): Promise<void>;
|
|
239
|
+
/**
|
|
240
|
+
* Wait for a spawned agent to come online, then send the task via relay.
|
|
241
|
+
* Uses the wrapper's own relay client so the message comes "from" this agent,
|
|
242
|
+
* not from the dashboard's relay client.
|
|
243
|
+
*/
|
|
244
|
+
private waitAndSendTask;
|
|
228
245
|
/**
|
|
229
246
|
* Execute release via API (if dashboardPort set) or callback
|
|
230
247
|
*/
|
|
@@ -246,7 +263,8 @@ export declare class TmuxWrapper extends BaseWrapper {
|
|
|
246
263
|
*/
|
|
247
264
|
protected handleIncomingMessage(from: string, payload: SendPayload, messageId: string, meta?: SendMeta, originalTo?: string): void;
|
|
248
265
|
/**
|
|
249
|
-
* Check if we should inject a message
|
|
266
|
+
* Check if we should inject a message.
|
|
267
|
+
* Uses UniversalIdleDetector (from BaseWrapper) for robust cross-CLI idle detection.
|
|
250
268
|
*/
|
|
251
269
|
private checkForInjectionOpportunity;
|
|
252
270
|
/**
|