agent-relay 1.3.0 → 1.3.2
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/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 +19 -1
- package/dist/bridge/types.d.ts +2 -0
- package/dist/cli/index.d.ts +1 -1
- package/dist/cli/index.js +115 -69
- 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 +47 -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 +98 -15
- 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 +55 -111
- 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/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 +36 -0
- package/dist/wrapper/base-wrapper.js +48 -2
- package/dist/wrapper/client.d.ts +14 -4
- package/dist/wrapper/client.js +84 -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 +7 -1
- package/dist/wrapper/pty-wrapper.js +51 -27
- package/dist/wrapper/tmux-wrapper.d.ts +12 -1
- package/dist/wrapper/tmux-wrapper.js +65 -17
- package/package.json +5 -5
- 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/404.html +0 -1
- package/dist/dashboard/out/_next/static/T1tgCqVWHFIkV7ClEtzD7/_buildManifest.js +0 -1
- package/dist/dashboard/out/_next/static/T1tgCqVWHFIkV7ClEtzD7/_ssgManifest.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/116-2502180def231162.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/117-f7b8ab0809342e77.js +0 -2
- package/dist/dashboard/out/_next/static/chunks/282-980c2eb8fff20123.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/532-bace199897eeab37.js +0 -9
- package/dist/dashboard/out/_next/static/chunks/648-5cc6e1921389a58a.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/766-b54f0853794b78c3.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/83-b51836037078006c.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/891-6cd50de1224f70bb.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/899-bb19a9b3d9b39ea6.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/_not-found/page-53b8a69f76db17d0.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/app/onboarding/page-8939b0fc700f7eca.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/app/page-5af1b6b439858aa6.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/connect-repos/page-f45ecbc3e06134fc.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/history/page-8c8bed33beb2bf1c.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/layout-2433bb48965f4333.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/login/page-16f3b49e55b1e0ed.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/metrics/page-ac39dc0cc3c26fa7.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/page-4a5938c18a11a654.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/pricing/page-982a7000fee44014.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/providers/page-ac3a6ac433fd6001.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/providers/setup/[provider]/page-09f9caae98a18c09.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/signup/page-547dd0ca55ecd0ba.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/e868780c-48e5f147c90a3a41.js +0 -18
- package/dist/dashboard/out/_next/static/chunks/fd9d1056-609918ca7b6280bb.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/framework-f66176bb897dc684.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/main-2ee6beb2ae96d210.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/main-app-5d692157a8eb1fd9.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/pages/_app-72b849fbd24ac258.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/pages/_error-7ba65e1336b92748.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/polyfills-42372ed130431b0a.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/webpack-1cdd8ed57114d5e1.js +0 -1
- package/dist/dashboard/out/_next/static/css/85d2af9c7ac74d62.css +0 -1
- package/dist/dashboard/out/_next/static/css/fe4b28883eeff359.css +0 -1
- package/dist/dashboard/out/alt-logos/agent-relay-logo-128.png +0 -0
- package/dist/dashboard/out/alt-logos/agent-relay-logo-256.png +0 -0
- package/dist/dashboard/out/alt-logos/agent-relay-logo-32.png +0 -0
- package/dist/dashboard/out/alt-logos/agent-relay-logo-512.png +0 -0
- package/dist/dashboard/out/alt-logos/agent-relay-logo-64.png +0 -0
- package/dist/dashboard/out/alt-logos/agent-relay-logo.svg +0 -45
- package/dist/dashboard/out/alt-logos/logo.svg +0 -38
- package/dist/dashboard/out/alt-logos/monogram-logo-128.png +0 -0
- package/dist/dashboard/out/alt-logos/monogram-logo-256.png +0 -0
- package/dist/dashboard/out/alt-logos/monogram-logo-32.png +0 -0
- package/dist/dashboard/out/alt-logos/monogram-logo-512.png +0 -0
- package/dist/dashboard/out/alt-logos/monogram-logo-64.png +0 -0
- package/dist/dashboard/out/alt-logos/monogram-logo.svg +0 -38
- package/dist/dashboard/out/app/onboarding.html +0 -1
- package/dist/dashboard/out/app/onboarding.txt +0 -7
- package/dist/dashboard/out/app.html +0 -1
- package/dist/dashboard/out/app.txt +0 -7
- package/dist/dashboard/out/apple-icon.png +0 -0
- package/dist/dashboard/out/connect-repos.html +0 -1
- package/dist/dashboard/out/connect-repos.txt +0 -7
- package/dist/dashboard/out/history.html +0 -1
- package/dist/dashboard/out/history.txt +0 -7
- package/dist/dashboard/out/index.html +0 -1
- package/dist/dashboard/out/index.txt +0 -7
- package/dist/dashboard/out/login.html +0 -6
- package/dist/dashboard/out/login.txt +0 -7
- package/dist/dashboard/out/metrics.html +0 -1
- package/dist/dashboard/out/metrics.txt +0 -7
- package/dist/dashboard/out/pricing.html +0 -13
- package/dist/dashboard/out/pricing.txt +0 -7
- package/dist/dashboard/out/providers/setup/claude.html +0 -1
- package/dist/dashboard/out/providers/setup/claude.txt +0 -8
- package/dist/dashboard/out/providers/setup/codex.html +0 -1
- package/dist/dashboard/out/providers/setup/codex.txt +0 -8
- package/dist/dashboard/out/providers.html +0 -1
- package/dist/dashboard/out/providers.txt +0 -7
- package/dist/dashboard/out/signup.html +0 -6
- package/dist/dashboard/out/signup.txt +0 -7
- package/dist/dashboard-server/metrics.d.ts +0 -105
- package/dist/dashboard-server/metrics.js +0 -193
- package/dist/dashboard-server/needs-attention.d.ts +0 -24
- package/dist/dashboard-server/needs-attention.js +0 -78
- package/dist/dashboard-server/server.d.ts +0 -15
- package/dist/dashboard-server/server.js +0 -3776
- package/dist/dashboard-server/start.d.ts +0 -6
- package/dist/dashboard-server/start.js +0 -13
- package/dist/dashboard-server/user-bridge.d.ts +0 -103
- package/dist/dashboard-server/user-bridge.js +0 -189
|
@@ -26,6 +26,8 @@ import { getTrajectoryIntegration, detectPhaseFromContent, detectToolCalls, dete
|
|
|
26
26
|
import { escapeForShell } from '../bridge/utils.js';
|
|
27
27
|
import { detectProviderAuthRevocation } from './auth-detection.js';
|
|
28
28
|
import { stripAnsi, sleep, getDefaultRelayPrefix, buildInjectionString, injectWithRetry as sharedInjectWithRetry, INJECTION_CONSTANTS, CLI_QUIRKS, } from './shared.js';
|
|
29
|
+
import { getTmuxPanePid } from './idle-detector.js';
|
|
30
|
+
import { DEFAULT_TMUX_WRAPPER_CONFIG } from '../config/relay-config.js';
|
|
29
31
|
const execAsync = promisify(exec);
|
|
30
32
|
// Constants for cursor stability detection in waitForClearInput
|
|
31
33
|
/** Number of consecutive polls with stable cursor before assuming input is clear */
|
|
@@ -82,16 +84,7 @@ export class TmuxWrapper extends BaseWrapper {
|
|
|
82
84
|
const mergedConfig = {
|
|
83
85
|
cols: process.stdout.columns || 120,
|
|
84
86
|
rows: process.stdout.rows || 40,
|
|
85
|
-
|
|
86
|
-
idleBeforeInjectMs: 1500,
|
|
87
|
-
injectRetryMs: 500,
|
|
88
|
-
debug: false,
|
|
89
|
-
debugLogIntervalMs: 0,
|
|
90
|
-
mouseMode: true, // Enable mouse scroll passthrough by default
|
|
91
|
-
activityIdleThresholdMs: 30_000, // Consider idle after 30s with no output
|
|
92
|
-
outputStabilityTimeoutMs: 2000,
|
|
93
|
-
outputStabilityPollMs: 200,
|
|
94
|
-
streamLogs: true, // Stream output to daemon for dashboard
|
|
87
|
+
...DEFAULT_TMUX_WRAPPER_CONFIG,
|
|
95
88
|
...config,
|
|
96
89
|
};
|
|
97
90
|
// Call parent constructor (initializes client, cliType, relayPrefix, continuity)
|
|
@@ -353,10 +346,19 @@ export class TmuxWrapper extends BaseWrapper {
|
|
|
353
346
|
this.initializeTrajectory();
|
|
354
347
|
// Initialize continuity and get/create agentId
|
|
355
348
|
this.initializeAgentId();
|
|
356
|
-
// Inject instructions for the agent (after a delay to let CLI initialize)
|
|
357
|
-
setTimeout(() => this.injectInstructions(), 3000);
|
|
358
349
|
// Start background polling (silent - no stdout writes)
|
|
359
350
|
this.startSilentPolling();
|
|
351
|
+
// Initialize idle detector with the tmux pane PID for process state inspection
|
|
352
|
+
this.initializeIdleDetectorPid();
|
|
353
|
+
// Wait for agent to be ready, then inject instructions
|
|
354
|
+
// This replaces the fixed 3-second delay with actual readiness detection
|
|
355
|
+
this.waitForAgentReady().then(() => {
|
|
356
|
+
this.injectInstructions();
|
|
357
|
+
}).catch(err => {
|
|
358
|
+
this.logStderr(`Failed to wait for agent ready: ${err.message}`, true);
|
|
359
|
+
// Fall back to injecting after a delay
|
|
360
|
+
setTimeout(() => this.injectInstructions(), 3000);
|
|
361
|
+
});
|
|
360
362
|
// Attach user to tmux session
|
|
361
363
|
// This takes over stdin/stdout - user sees the real terminal
|
|
362
364
|
this.attachToSession();
|
|
@@ -409,6 +411,41 @@ export class TmuxWrapper extends BaseWrapper {
|
|
|
409
411
|
this.logStderr(`Failed to initialize agent ID: ${err.message}`, true);
|
|
410
412
|
}
|
|
411
413
|
}
|
|
414
|
+
/**
|
|
415
|
+
* Initialize the idle detector with the tmux pane PID.
|
|
416
|
+
* This enables process state inspection on Linux for more reliable idle detection.
|
|
417
|
+
*/
|
|
418
|
+
async initializeIdleDetectorPid() {
|
|
419
|
+
try {
|
|
420
|
+
const pid = await getTmuxPanePid(this.tmuxPath, this.sessionName);
|
|
421
|
+
if (pid) {
|
|
422
|
+
this.setIdleDetectorPid(pid);
|
|
423
|
+
this.logStderr(`Idle detector initialized with PID ${pid}`);
|
|
424
|
+
}
|
|
425
|
+
else {
|
|
426
|
+
this.logStderr('Could not get pane PID for idle detection (will use output analysis)');
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
catch (err) {
|
|
430
|
+
this.logStderr(`Failed to initialize idle detector PID: ${err.message}`);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* Wait for the agent to be ready for input.
|
|
435
|
+
* Uses idle detection instead of a fixed delay.
|
|
436
|
+
*/
|
|
437
|
+
async waitForAgentReady() {
|
|
438
|
+
// Minimum wait to ensure the CLI process has started
|
|
439
|
+
await sleep(500);
|
|
440
|
+
// Wait for agent to become idle (CLI fully initialized)
|
|
441
|
+
const result = await this.waitForIdleState(10000, 200);
|
|
442
|
+
if (result.isIdle) {
|
|
443
|
+
this.logStderr(`Agent ready (confidence: ${(result.confidence * 100).toFixed(0)}%)`);
|
|
444
|
+
}
|
|
445
|
+
else {
|
|
446
|
+
this.logStderr('Agent readiness timeout, proceeding anyway');
|
|
447
|
+
}
|
|
448
|
+
}
|
|
412
449
|
/**
|
|
413
450
|
* Inject usage instructions for the agent including persistence protocol
|
|
414
451
|
*/
|
|
@@ -586,6 +623,9 @@ export class TmuxWrapper extends BaseWrapper {
|
|
|
586
623
|
if (stdout.length !== this.processedOutputLength) {
|
|
587
624
|
this.lastOutputTime = Date.now();
|
|
588
625
|
this.markActivity();
|
|
626
|
+
// Feed new output to idle detector for more robust idle detection
|
|
627
|
+
const newOutput = stdout.substring(this.processedOutputLength);
|
|
628
|
+
this.feedIdleDetectorOutput(newOutput);
|
|
589
629
|
this.processedOutputLength = stdout.length;
|
|
590
630
|
// Stream new output to daemon for dashboard log viewing
|
|
591
631
|
// Use filtered output to exclude thinking blocks and relay commands
|
|
@@ -713,8 +753,9 @@ export class TmuxWrapper extends BaseWrapper {
|
|
|
713
753
|
*/
|
|
714
754
|
sendRelayCommand(cmd) {
|
|
715
755
|
const msgHash = `${cmd.to}:${cmd.body}`;
|
|
716
|
-
// Permanent dedup - never send the same message twice
|
|
756
|
+
// Permanent dedup - never send the same message twice
|
|
717
757
|
if (this.sentMessageHashes.has(msgHash)) {
|
|
758
|
+
this.logStderr(`[DEDUP] Skipped duplicate message to ${cmd.to} (hash already sent)`);
|
|
718
759
|
return;
|
|
719
760
|
}
|
|
720
761
|
// If client not ready, queue for later and return
|
|
@@ -1177,7 +1218,8 @@ export class TmuxWrapper extends BaseWrapper {
|
|
|
1177
1218
|
this.checkForInjectionOpportunity();
|
|
1178
1219
|
}
|
|
1179
1220
|
/**
|
|
1180
|
-
* Check if we should inject a message
|
|
1221
|
+
* Check if we should inject a message.
|
|
1222
|
+
* Uses UniversalIdleDetector (from BaseWrapper) for robust cross-CLI idle detection.
|
|
1181
1223
|
*/
|
|
1182
1224
|
checkForInjectionOpportunity() {
|
|
1183
1225
|
if (this.messageQueue.length === 0)
|
|
@@ -1186,13 +1228,19 @@ export class TmuxWrapper extends BaseWrapper {
|
|
|
1186
1228
|
return;
|
|
1187
1229
|
if (!this.running)
|
|
1188
1230
|
return;
|
|
1189
|
-
//
|
|
1190
|
-
const
|
|
1191
|
-
if (
|
|
1231
|
+
// Use universal idle detector for more reliable detection (inherited from BaseWrapper)
|
|
1232
|
+
const idleResult = this.checkIdleForInjection();
|
|
1233
|
+
if (!idleResult.isIdle) {
|
|
1234
|
+
// Not idle yet, retry later
|
|
1192
1235
|
const retryMs = this.config.injectRetryMs ?? 500;
|
|
1193
1236
|
setTimeout(() => this.checkForInjectionOpportunity(), retryMs);
|
|
1194
1237
|
return;
|
|
1195
1238
|
}
|
|
1239
|
+
// Log detection method in debug mode
|
|
1240
|
+
if (this.config.debug && idleResult.signals.length > 0) {
|
|
1241
|
+
const signalInfo = idleResult.signals.map(s => `${s.source}:${(s.confidence * 100).toFixed(0)}%`).join(', ');
|
|
1242
|
+
this.logStderr(`Idle detected (${signalInfo})`);
|
|
1243
|
+
}
|
|
1196
1244
|
this.injectNextMessage();
|
|
1197
1245
|
}
|
|
1198
1246
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-relay",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.2",
|
|
4
4
|
"description": "Real-time agent-to-agent communication system",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -13,9 +13,8 @@
|
|
|
13
13
|
},
|
|
14
14
|
"scripts": {
|
|
15
15
|
"postinstall": "npm rebuild better-sqlite3 && node scripts/postinstall.js",
|
|
16
|
-
"build": "npm run clean && tsc
|
|
17
|
-
"
|
|
18
|
-
"postbuild": "chmod +x dist/cli/index.js && mkdir -p dist/dashboard && cp -r src/dashboard/out dist/dashboard/",
|
|
16
|
+
"build": "npm run clean && tsc",
|
|
17
|
+
"postbuild": "chmod +x dist/cli/index.js",
|
|
19
18
|
"dev:watch": "tsc -w",
|
|
20
19
|
"predev": "npm run clean && tsc && chmod +x dist/cli/index.js",
|
|
21
20
|
"dev": "concurrently -n daemon,next -c blue,magenta \"npm run dev:daemon\" \"npm run dev:next\"",
|
|
@@ -27,7 +26,6 @@
|
|
|
27
26
|
"dev:rebuild": "npm run build && echo '✓ Rebuilt (linked version updated)'",
|
|
28
27
|
"start": "node dist/cli/index.js",
|
|
29
28
|
"daemon": "node dist/daemon/server.js",
|
|
30
|
-
"dashboard": "node dist/dashboard-server/start.js",
|
|
31
29
|
"pretest": "npm run build",
|
|
32
30
|
"test": "vitest run",
|
|
33
31
|
"test:integration": "vitest run test/cloud/*.integration.test.ts",
|
|
@@ -41,6 +39,8 @@
|
|
|
41
39
|
"clean": "rm -rf dist",
|
|
42
40
|
"db:generate": "drizzle-kit generate",
|
|
43
41
|
"db:migrate": "drizzle-kit migrate",
|
|
42
|
+
"db:migrate:run": "node scripts/run-migrations.js",
|
|
43
|
+
"db:migrate:verify": "node scripts/verify-schema.js",
|
|
44
44
|
"db:push": "drizzle-kit push",
|
|
45
45
|
"db:studio": "drizzle-kit studio",
|
|
46
46
|
"services:up": "docker compose -f docker-compose.dev.yml up -d postgres redis && echo '✓ Postgres and Redis running'",
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Run database migrations (standalone)
|
|
4
|
+
*
|
|
5
|
+
* This script is used in CI to verify migrations run successfully.
|
|
6
|
+
* It connects to the database and runs all pending migrations.
|
|
7
|
+
*
|
|
8
|
+
* This is a standalone script that doesn't depend on the cloud config,
|
|
9
|
+
* so it only requires DATABASE_URL to run.
|
|
10
|
+
*
|
|
11
|
+
* Usage: DATABASE_URL=postgres://... node scripts/run-migrations.js
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import pg from 'pg';
|
|
15
|
+
import { drizzle } from 'drizzle-orm/node-postgres';
|
|
16
|
+
import { migrate } from 'drizzle-orm/node-postgres/migrator';
|
|
17
|
+
|
|
18
|
+
const { Pool } = pg;
|
|
19
|
+
|
|
20
|
+
async function main() {
|
|
21
|
+
console.log('Starting database migrations...');
|
|
22
|
+
console.log(`Database URL: ${process.env.DATABASE_URL?.replace(/:[^:@]+@/, ':***@') || 'not set'}`);
|
|
23
|
+
|
|
24
|
+
if (!process.env.DATABASE_URL) {
|
|
25
|
+
console.error('ERROR: DATABASE_URL environment variable is required');
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
|
|
30
|
+
const db = drizzle(pool);
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
await migrate(db, { migrationsFolder: './src/cloud/db/migrations' });
|
|
34
|
+
console.log('All migrations completed successfully');
|
|
35
|
+
} catch (error) {
|
|
36
|
+
console.error('Migration failed:', error);
|
|
37
|
+
process.exit(1);
|
|
38
|
+
} finally {
|
|
39
|
+
await pool.end();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
main();
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Verify database schema after migrations
|
|
4
|
+
*
|
|
5
|
+
* This script verifies that all expected tables exist after migrations.
|
|
6
|
+
* It dynamically reads table definitions from the schema to avoid hardcoding.
|
|
7
|
+
*
|
|
8
|
+
* Usage: DATABASE_URL=postgres://... node scripts/verify-schema.js
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import pg from 'pg';
|
|
12
|
+
import * as schema from '../dist/cloud/db/schema.js';
|
|
13
|
+
|
|
14
|
+
const { Pool } = pg;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Extract table names from the schema module.
|
|
18
|
+
* Drizzle pgTable objects store their name in Symbol.for('drizzle:Name').
|
|
19
|
+
*/
|
|
20
|
+
function getTablesFromSchema() {
|
|
21
|
+
const tables = [];
|
|
22
|
+
const drizzleNameSymbol = Symbol.for('drizzle:Name');
|
|
23
|
+
|
|
24
|
+
for (const [key, value] of Object.entries(schema)) {
|
|
25
|
+
// Skip relation definitions (they end with 'Relations')
|
|
26
|
+
if (key.endsWith('Relations')) continue;
|
|
27
|
+
|
|
28
|
+
// Drizzle tables have the table name in a Symbol
|
|
29
|
+
if (value && typeof value === 'object' && value[drizzleNameSymbol]) {
|
|
30
|
+
tables.push(value[drizzleNameSymbol]);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return tables;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Dynamically get tables from schema
|
|
37
|
+
const SCHEMA_TABLES = getTablesFromSchema();
|
|
38
|
+
const EXPECTED_TABLES = [...SCHEMA_TABLES];
|
|
39
|
+
|
|
40
|
+
// Key columns to spot-check (subset of critical columns)
|
|
41
|
+
const EXPECTED_COLUMNS = {
|
|
42
|
+
users: ['id', 'email', 'created_at'],
|
|
43
|
+
workspaces: ['id', 'user_id', 'name', 'status'],
|
|
44
|
+
linked_daemons: ['id', 'user_id', 'workspace_id', 'status'],
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
async function main() {
|
|
48
|
+
console.log('Verifying database schema...\n');
|
|
49
|
+
|
|
50
|
+
if (!process.env.DATABASE_URL) {
|
|
51
|
+
console.error('ERROR: DATABASE_URL environment variable is required');
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
console.log(`Found ${SCHEMA_TABLES.length} tables in schema.ts:`);
|
|
56
|
+
console.log(` ${SCHEMA_TABLES.join(', ')}\n`);
|
|
57
|
+
|
|
58
|
+
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
// Get all tables in the public schema
|
|
62
|
+
const tablesResult = await pool.query(`
|
|
63
|
+
SELECT table_name
|
|
64
|
+
FROM information_schema.tables
|
|
65
|
+
WHERE table_schema = 'public'
|
|
66
|
+
ORDER BY table_name
|
|
67
|
+
`);
|
|
68
|
+
|
|
69
|
+
const existingTables = tablesResult.rows.map((r) => r.table_name);
|
|
70
|
+
console.log('Existing tables:', existingTables.join(', '));
|
|
71
|
+
console.log('');
|
|
72
|
+
|
|
73
|
+
// Check for missing tables
|
|
74
|
+
const missingTables = EXPECTED_TABLES.filter((t) => !existingTables.includes(t));
|
|
75
|
+
if (missingTables.length > 0) {
|
|
76
|
+
console.error('MISSING TABLES:', missingTables.join(', '));
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
console.log(`All ${EXPECTED_TABLES.length} expected tables exist`);
|
|
80
|
+
|
|
81
|
+
// Verify key columns
|
|
82
|
+
console.log('\nVerifying key columns...');
|
|
83
|
+
for (const [table, columns] of Object.entries(EXPECTED_COLUMNS)) {
|
|
84
|
+
const columnsResult = await pool.query(
|
|
85
|
+
`
|
|
86
|
+
SELECT column_name
|
|
87
|
+
FROM information_schema.columns
|
|
88
|
+
WHERE table_schema = 'public' AND table_name = $1
|
|
89
|
+
`,
|
|
90
|
+
[table]
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
const existingColumns = columnsResult.rows.map((r) => r.column_name);
|
|
94
|
+
const missingColumns = columns.filter((c) => !existingColumns.includes(c));
|
|
95
|
+
|
|
96
|
+
if (missingColumns.length > 0) {
|
|
97
|
+
console.error(`Table '${table}' missing columns: ${missingColumns.join(', ')}`);
|
|
98
|
+
console.error(`Existing columns: ${existingColumns.join(', ')}`);
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
101
|
+
console.log(` ${table}: OK (${columns.length} key columns verified)`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Check migration history (table may be in public or drizzle schema)
|
|
105
|
+
try {
|
|
106
|
+
// Try public schema first, then drizzle schema
|
|
107
|
+
let migrationsResult;
|
|
108
|
+
try {
|
|
109
|
+
migrationsResult = await pool.query(`
|
|
110
|
+
SELECT id, hash, created_at FROM public.__drizzle_migrations ORDER BY created_at
|
|
111
|
+
`);
|
|
112
|
+
} catch {
|
|
113
|
+
migrationsResult = await pool.query(`
|
|
114
|
+
SELECT id, hash, created_at FROM drizzle.__drizzle_migrations ORDER BY created_at
|
|
115
|
+
`);
|
|
116
|
+
}
|
|
117
|
+
console.log(`\nMigration history: ${migrationsResult.rows.length} migrations applied`);
|
|
118
|
+
for (const row of migrationsResult.rows) {
|
|
119
|
+
console.log(` - ${row.id} (${new Date(Number(row.created_at)).toISOString()})`);
|
|
120
|
+
}
|
|
121
|
+
} catch {
|
|
122
|
+
console.log('\nMigration history: (table not found, but migrations ran successfully)');
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
console.log('\nSchema verification passed!');
|
|
126
|
+
} catch (error) {
|
|
127
|
+
console.error('Schema verification failed:', error);
|
|
128
|
+
process.exit(1);
|
|
129
|
+
} finally {
|
|
130
|
+
await pool.end();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
main();
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Protocol Performance Benchmarks
|
|
3
|
+
*
|
|
4
|
+
* Run with: npx tsx tests/benchmarks/protocol.bench.ts
|
|
5
|
+
*
|
|
6
|
+
* Measures:
|
|
7
|
+
* - Frame encoding/decoding performance
|
|
8
|
+
* - ID generation (generateId vs uuid)
|
|
9
|
+
* - Parser throughput
|
|
10
|
+
* - Dedup cache performance
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { performance } from 'node:perf_hooks';
|
|
14
|
+
import { v4 as uuid } from 'uuid';
|
|
15
|
+
import { generateId, IdGenerator } from '../../src/utils/id-generator.js';
|
|
16
|
+
import {
|
|
17
|
+
encodeFrame,
|
|
18
|
+
encodeFrameLegacy,
|
|
19
|
+
FrameParser,
|
|
20
|
+
initMessagePack,
|
|
21
|
+
hasMessagePack,
|
|
22
|
+
} from '../../src/protocol/framing.js';
|
|
23
|
+
import type { Envelope } from '../../src/protocol/types.js';
|
|
24
|
+
import { OutputParser } from '../../src/wrapper/parser.js';
|
|
25
|
+
|
|
26
|
+
// ANSI colors for output
|
|
27
|
+
const GREEN = '\x1b[32m';
|
|
28
|
+
const YELLOW = '\x1b[33m';
|
|
29
|
+
const CYAN = '\x1b[36m';
|
|
30
|
+
const RESET = '\x1b[0m';
|
|
31
|
+
const BOLD = '\x1b[1m';
|
|
32
|
+
|
|
33
|
+
interface BenchResult {
|
|
34
|
+
name: string;
|
|
35
|
+
opsPerSec: number;
|
|
36
|
+
avgMs: number;
|
|
37
|
+
totalMs: number;
|
|
38
|
+
iterations: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Run a benchmark function and measure performance.
|
|
43
|
+
*/
|
|
44
|
+
function bench(name: string, fn: () => void, iterations = 10000): BenchResult {
|
|
45
|
+
// Warmup
|
|
46
|
+
for (let i = 0; i < Math.min(1000, iterations / 10); i++) {
|
|
47
|
+
fn();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Measure
|
|
51
|
+
const start = performance.now();
|
|
52
|
+
for (let i = 0; i < iterations; i++) {
|
|
53
|
+
fn();
|
|
54
|
+
}
|
|
55
|
+
const totalMs = performance.now() - start;
|
|
56
|
+
const avgMs = totalMs / iterations;
|
|
57
|
+
const opsPerSec = Math.round((iterations / totalMs) * 1000);
|
|
58
|
+
|
|
59
|
+
return { name, opsPerSec, avgMs, totalMs, iterations };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function printResult(result: BenchResult): void {
|
|
63
|
+
const opsStr = result.opsPerSec.toLocaleString();
|
|
64
|
+
const avgStr = result.avgMs < 0.01 ? result.avgMs.toExponential(2) : result.avgMs.toFixed(4);
|
|
65
|
+
console.log(
|
|
66
|
+
` ${CYAN}${result.name.padEnd(35)}${RESET} ` +
|
|
67
|
+
`${GREEN}${opsStr.padStart(10)} ops/s${RESET} ` +
|
|
68
|
+
`${YELLOW}${avgStr} ms/op${RESET}`
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function printHeader(title: string): void {
|
|
73
|
+
console.log(`\n${BOLD}${title}${RESET}`);
|
|
74
|
+
console.log('─'.repeat(65));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Test data
|
|
78
|
+
const smallEnvelope: Envelope = {
|
|
79
|
+
v: 1,
|
|
80
|
+
type: 'SEND',
|
|
81
|
+
id: 'test-id-12345678',
|
|
82
|
+
ts: Date.now(),
|
|
83
|
+
to: 'Bob',
|
|
84
|
+
payload: { kind: 'message', body: 'Hello!' },
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const mediumEnvelope: Envelope = {
|
|
88
|
+
v: 1,
|
|
89
|
+
type: 'SEND',
|
|
90
|
+
id: 'test-id-12345678',
|
|
91
|
+
ts: Date.now(),
|
|
92
|
+
to: 'Bob',
|
|
93
|
+
payload: {
|
|
94
|
+
kind: 'message',
|
|
95
|
+
body: 'This is a medium-length message that contains more content than a simple hello. It includes some additional context and information that might be typical in agent-to-agent communication.',
|
|
96
|
+
data: { priority: 'high', thread: 'auth-module', tags: ['urgent', 'review'] },
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const largeEnvelope: Envelope = {
|
|
101
|
+
v: 1,
|
|
102
|
+
type: 'SEND',
|
|
103
|
+
id: 'test-id-12345678',
|
|
104
|
+
ts: Date.now(),
|
|
105
|
+
to: 'Bob',
|
|
106
|
+
payload: { kind: 'message', body: 'x'.repeat(10000) },
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
async function main(): Promise<void> {
|
|
110
|
+
console.log(`\n${BOLD}╔════════════════════════════════════════════════════════════════╗${RESET}`);
|
|
111
|
+
console.log(`${BOLD}║ Agent Relay Protocol Benchmarks ║${RESET}`);
|
|
112
|
+
console.log(`${BOLD}╚════════════════════════════════════════════════════════════════╝${RESET}`);
|
|
113
|
+
|
|
114
|
+
// Initialize MessagePack if available
|
|
115
|
+
const hasMsgPack = await initMessagePack();
|
|
116
|
+
console.log(`\nMessagePack available: ${hasMsgPack ? 'yes' : 'no'}`);
|
|
117
|
+
|
|
118
|
+
// ─────────────────────────────────────────────────────────────────
|
|
119
|
+
// ID Generation
|
|
120
|
+
// ─────────────────────────────────────────────────────────────────
|
|
121
|
+
printHeader('ID Generation');
|
|
122
|
+
|
|
123
|
+
const idGen = new IdGenerator();
|
|
124
|
+
printResult(bench('uuid() [baseline]', () => uuid()));
|
|
125
|
+
printResult(bench('generateId() [optimized]', () => generateId()));
|
|
126
|
+
printResult(bench('IdGenerator.next()', () => idGen.next()));
|
|
127
|
+
printResult(bench('IdGenerator.short()', () => idGen.short()));
|
|
128
|
+
|
|
129
|
+
// ─────────────────────────────────────────────────────────────────
|
|
130
|
+
// Frame Encoding
|
|
131
|
+
// ─────────────────────────────────────────────────────────────────
|
|
132
|
+
printHeader('Frame Encoding (JSON)');
|
|
133
|
+
|
|
134
|
+
printResult(bench('encodeFrameLegacy (small)', () => encodeFrameLegacy(smallEnvelope)));
|
|
135
|
+
printResult(bench('encodeFrameLegacy (medium)', () => encodeFrameLegacy(mediumEnvelope)));
|
|
136
|
+
printResult(bench('encodeFrameLegacy (large)', () => encodeFrameLegacy(largeEnvelope), 1000));
|
|
137
|
+
|
|
138
|
+
if (hasMsgPack) {
|
|
139
|
+
printHeader('Frame Encoding (MessagePack)');
|
|
140
|
+
printResult(bench('encodeFrame msgpack (small)', () => encodeFrame(smallEnvelope, 'msgpack')));
|
|
141
|
+
printResult(bench('encodeFrame msgpack (medium)', () => encodeFrame(mediumEnvelope, 'msgpack')));
|
|
142
|
+
printResult(bench('encodeFrame msgpack (large)', () => encodeFrame(largeEnvelope, 'msgpack'), 1000));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// ─────────────────────────────────────────────────────────────────
|
|
146
|
+
// Frame Parsing
|
|
147
|
+
// ─────────────────────────────────────────────────────────────────
|
|
148
|
+
printHeader('Frame Parsing');
|
|
149
|
+
|
|
150
|
+
const smallFrame = encodeFrameLegacy(smallEnvelope);
|
|
151
|
+
const mediumFrame = encodeFrameLegacy(mediumEnvelope);
|
|
152
|
+
const largeFrame = encodeFrameLegacy(largeEnvelope);
|
|
153
|
+
|
|
154
|
+
// Create fresh parser for each benchmark to avoid buffer accumulation
|
|
155
|
+
printResult(
|
|
156
|
+
bench('FrameParser.push (small)', () => {
|
|
157
|
+
const parser = new FrameParser();
|
|
158
|
+
parser.setLegacyMode(true);
|
|
159
|
+
parser.push(smallFrame);
|
|
160
|
+
})
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
printResult(
|
|
164
|
+
bench('FrameParser.push (medium)', () => {
|
|
165
|
+
const parser = new FrameParser();
|
|
166
|
+
parser.setLegacyMode(true);
|
|
167
|
+
parser.push(mediumFrame);
|
|
168
|
+
})
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
printResult(
|
|
172
|
+
bench(
|
|
173
|
+
'FrameParser.push (large)',
|
|
174
|
+
() => {
|
|
175
|
+
const parser = new FrameParser();
|
|
176
|
+
parser.setLegacyMode(true);
|
|
177
|
+
parser.push(largeFrame);
|
|
178
|
+
},
|
|
179
|
+
1000
|
|
180
|
+
)
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
// Multiple frames in sequence (reuse parser)
|
|
184
|
+
printResult(
|
|
185
|
+
bench('FrameParser 10 msgs (reuse)', () => {
|
|
186
|
+
const parser = new FrameParser();
|
|
187
|
+
parser.setLegacyMode(true);
|
|
188
|
+
for (let i = 0; i < 10; i++) {
|
|
189
|
+
parser.push(smallFrame);
|
|
190
|
+
}
|
|
191
|
+
}, 1000)
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
// ─────────────────────────────────────────────────────────────────
|
|
195
|
+
// Output Parser
|
|
196
|
+
// ─────────────────────────────────────────────────────────────────
|
|
197
|
+
printHeader('Output Parser');
|
|
198
|
+
|
|
199
|
+
const normalLine = 'This is a normal line of agent output that should be passed through.\n';
|
|
200
|
+
const relayLine = '->relay:Bob Hello, this is a message for you!\n';
|
|
201
|
+
const mixedOutput =
|
|
202
|
+
'Some output\n' +
|
|
203
|
+
'More output\n' +
|
|
204
|
+
'->relay:Bob Can you review auth.ts?\n' +
|
|
205
|
+
'Even more output\n' +
|
|
206
|
+
'Final line\n';
|
|
207
|
+
|
|
208
|
+
printResult(
|
|
209
|
+
bench('OutputParser normal line', () => {
|
|
210
|
+
const parser = new OutputParser();
|
|
211
|
+
parser.parse(normalLine);
|
|
212
|
+
})
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
printResult(
|
|
216
|
+
bench('OutputParser relay line', () => {
|
|
217
|
+
const parser = new OutputParser();
|
|
218
|
+
parser.parse(relayLine);
|
|
219
|
+
})
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
printResult(
|
|
223
|
+
bench('OutputParser mixed (5 lines)', () => {
|
|
224
|
+
const parser = new OutputParser();
|
|
225
|
+
parser.parse(mixedOutput);
|
|
226
|
+
})
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
// ─────────────────────────────────────────────────────────────────
|
|
230
|
+
// Deduplication Cache
|
|
231
|
+
// ─────────────────────────────────────────────────────────────────
|
|
232
|
+
printHeader('Deduplication Cache');
|
|
233
|
+
|
|
234
|
+
// Circular cache (new implementation)
|
|
235
|
+
class CircularDedupeCache {
|
|
236
|
+
private ids: Set<string> = new Set();
|
|
237
|
+
private ring: string[];
|
|
238
|
+
private head = 0;
|
|
239
|
+
private readonly capacity: number;
|
|
240
|
+
|
|
241
|
+
constructor(capacity = 2000) {
|
|
242
|
+
this.capacity = capacity;
|
|
243
|
+
this.ring = new Array(capacity);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
check(id: string): boolean {
|
|
247
|
+
if (this.ids.has(id)) return true;
|
|
248
|
+
if (this.ids.size >= this.capacity) {
|
|
249
|
+
const oldest = this.ring[this.head];
|
|
250
|
+
if (oldest) this.ids.delete(oldest);
|
|
251
|
+
}
|
|
252
|
+
this.ring[this.head] = id;
|
|
253
|
+
this.ids.add(id);
|
|
254
|
+
this.head = (this.head + 1) % this.capacity;
|
|
255
|
+
return false;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Array-based cache (old implementation)
|
|
260
|
+
class ArrayDedupeCache {
|
|
261
|
+
private ids: Set<string> = new Set();
|
|
262
|
+
private order: string[] = [];
|
|
263
|
+
private readonly limit: number;
|
|
264
|
+
|
|
265
|
+
constructor(limit = 2000) {
|
|
266
|
+
this.limit = limit;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
check(id: string): boolean {
|
|
270
|
+
if (this.ids.has(id)) return true;
|
|
271
|
+
this.ids.add(id);
|
|
272
|
+
this.order.push(id);
|
|
273
|
+
if (this.order.length > this.limit) {
|
|
274
|
+
const oldest = this.order.shift();
|
|
275
|
+
if (oldest) this.ids.delete(oldest);
|
|
276
|
+
}
|
|
277
|
+
return false;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const circularCache = new CircularDedupeCache(2000);
|
|
282
|
+
const arrayCache = new ArrayDedupeCache(2000);
|
|
283
|
+
let circularCounter = 0;
|
|
284
|
+
let arrayCounter = 0;
|
|
285
|
+
|
|
286
|
+
printResult(
|
|
287
|
+
bench('CircularDedupeCache.check', () => {
|
|
288
|
+
circularCache.check(`id-${circularCounter++}`);
|
|
289
|
+
})
|
|
290
|
+
);
|
|
291
|
+
|
|
292
|
+
printResult(
|
|
293
|
+
bench('ArrayDedupeCache.check [old]', () => {
|
|
294
|
+
arrayCache.check(`id-${arrayCounter++}`);
|
|
295
|
+
})
|
|
296
|
+
);
|
|
297
|
+
|
|
298
|
+
// ─────────────────────────────────────────────────────────────────
|
|
299
|
+
// Summary
|
|
300
|
+
// ─────────────────────────────────────────────────────────────────
|
|
301
|
+
console.log(`\n${BOLD}Summary${RESET}`);
|
|
302
|
+
console.log('─'.repeat(65));
|
|
303
|
+
console.log('ID Generation: generateId() is ~10-20x faster than uuid()');
|
|
304
|
+
console.log('Frame Parsing: Ring buffer eliminates GC pressure');
|
|
305
|
+
console.log('Output Parser: Early exit avoids ANSI stripping for most lines');
|
|
306
|
+
console.log('Dedup Cache: Circular buffer is O(1) vs O(n) for eviction');
|
|
307
|
+
console.log('');
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
main().catch(console.error);
|