Skip to content

Commit 13cb93b

Browse files
authored
Merge pull request #81 from AgentWorkforce/feature/base-wrapper
Auth tightening
2 parents a1019e1 + b1b6179 commit 13cb93b

File tree

13 files changed

+649
-169
lines changed

13 files changed

+649
-169
lines changed

.beads/issues.jsonl

Lines changed: 87 additions & 87 deletions
Large diffs are not rendered by default.
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
{
2+
"id": "traj_xy9vifpqet80",
3+
"version": 1,
4+
"task": {
5+
"title": "Extract BaseWrapper from PtyWrapper and TmuxWrapper",
6+
"source": {
7+
"system": "plain",
8+
"id": "agent-relay-wrap1"
9+
}
10+
},
11+
"status": "completed",
12+
"startedAt": "2026-01-06T12:27:13.004Z",
13+
"agents": [
14+
{
15+
"name": "khaliqgant",
16+
"role": "lead",
17+
"joinedAt": "2026-01-06T12:27:13.007Z"
18+
}
19+
],
20+
"chapters": [
21+
{
22+
"id": "chap_8t7vbaqwiz3f",
23+
"title": "Work",
24+
"agentName": "default",
25+
"startedAt": "2026-01-06T12:27:45.698Z",
26+
"events": [
27+
{
28+
"ts": 1767702465699,
29+
"type": "decision",
30+
"content": "Using abstract class with protected methods for shared implementation: Using abstract class with protected methods for shared implementation",
31+
"raw": {
32+
"question": "Using abstract class with protected methods for shared implementation",
33+
"chosen": "Using abstract class with protected methods for shared implementation",
34+
"alternatives": [],
35+
"reasoning": "Allows subclasses to override while providing default behavior"
36+
},
37+
"significance": "high"
38+
},
39+
{
40+
"ts": 1767703025592,
41+
"type": "decision",
42+
"content": "Created BaseWrapper abstract class with comprehensive tests: Created BaseWrapper abstract class with comprehensive tests",
43+
"raw": {
44+
"question": "Created BaseWrapper abstract class with comprehensive tests",
45+
"chosen": "Created BaseWrapper abstract class with comprehensive tests",
46+
"alternatives": [],
47+
"reasoning": "Extracted shared functionality from PtyWrapper/TmuxWrapper into base class. Tests cover message queue, spawn/release, continuity, and relay command handling."
48+
},
49+
"significance": "high"
50+
}
51+
],
52+
"endedAt": "2026-01-06T12:58:24.003Z"
53+
}
54+
],
55+
"commits": [],
56+
"filesChanged": [],
57+
"projectId": "/Users/khaliqgant/Projects/agent-workforce/relay",
58+
"tags": [],
59+
"completedAt": "2026-01-06T12:58:24.003Z",
60+
"retrospective": {
61+
"summary": "Extracted BaseWrapper abstract class from PtyWrapper and TmuxWrapper using TDD approach. Created 29 new tests, removed ~900 lines of duplicate code, both wrappers now inherit shared functionality.",
62+
"approach": "Standard approach",
63+
"confidence": 0.9
64+
}
65+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Trajectory: Extract BaseWrapper from PtyWrapper and TmuxWrapper
2+
3+
> **Status:** ✅ Completed
4+
> **Task:** agent-relay-wrap1
5+
> **Confidence:** 90%
6+
> **Started:** January 6, 2026 at 01:27 PM
7+
> **Completed:** January 6, 2026 at 01:58 PM
8+
9+
---
10+
11+
## Summary
12+
13+
Extracted BaseWrapper abstract class from PtyWrapper and TmuxWrapper using TDD approach. Created 29 new tests, removed ~900 lines of duplicate code, both wrappers now inherit shared functionality.
14+
15+
**Approach:** Standard approach
16+
17+
---
18+
19+
## Key Decisions
20+
21+
### Using abstract class with protected methods for shared implementation
22+
- **Chose:** Using abstract class with protected methods for shared implementation
23+
- **Reasoning:** Allows subclasses to override while providing default behavior
24+
25+
### Created BaseWrapper abstract class with comprehensive tests
26+
- **Chose:** Created BaseWrapper abstract class with comprehensive tests
27+
- **Reasoning:** Extracted shared functionality from PtyWrapper/TmuxWrapper into base class. Tests cover message queue, spawn/release, continuity, and relay command handling.
28+
29+
---
30+
31+
## Chapters
32+
33+
### 1. Work
34+
*Agent: default*
35+
36+
- Using abstract class with protected methods for shared implementation: Using abstract class with protected methods for shared implementation
37+
- Created BaseWrapper abstract class with comprehensive tests: Created BaseWrapper abstract class with comprehensive tests

.trajectories/index.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"version": 1,
3-
"lastUpdated": "2026-01-06T08:25:20.797Z",
3+
"lastUpdated": "2026-01-06T12:58:24.029Z",
44
"trajectories": {
55
"traj_ozd98si6a7ns": {
66
"title": "Fix thinking indicator showing on all messages",
@@ -344,6 +344,13 @@
344344
"startedAt": "2026-01-06T08:24:36.222Z",
345345
"completedAt": "2026-01-06T08:25:20.777Z",
346346
"path": "/home/user/relay/.trajectories/completed/2026-01/traj_ui9b4tqxoa7j.json"
347+
},
348+
"traj_xy9vifpqet80": {
349+
"title": "Extract BaseWrapper from PtyWrapper and TmuxWrapper",
350+
"status": "completed",
351+
"startedAt": "2026-01-06T12:27:13.004Z",
352+
"completedAt": "2026-01-06T12:58:24.003Z",
353+
"path": "/Users/khaliqgant/Projects/agent-workforce/relay/.trajectories/completed/2026-01/traj_xy9vifpqet80.json"
347354
}
348355
}
349356
}

AGENTS.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -525,3 +525,31 @@ The dashboard automatically tracks:
525525

526526
Agents can view their status at the dashboard URL provided at startup.
527527
<!-- prpm:snippet:end @agent-relay/[email protected] -->
528+
529+
# Git Workflow Rules
530+
531+
## NEVER Push Directly to Main
532+
533+
**CRITICAL: Agents must NEVER push directly to the main branch.**
534+
535+
- Always work on a feature branch
536+
- Commit and push to the feature branch only
537+
- Let the user decide when to merge to main
538+
- Do not merge to main without explicit user approval
539+
540+
```bash
541+
# CORRECT workflow
542+
git checkout -b feature/my-feature
543+
# ... do work ...
544+
git add .
545+
git commit -m "My changes"
546+
git push origin feature/my-feature
547+
# STOP HERE - let user merge
548+
549+
# WRONG - never do this
550+
git checkout main
551+
git merge feature/my-feature
552+
git push origin main # NO!
553+
```
554+
555+
This ensures the user maintains control over what goes into the main branch.

src/cloud/api/onboarding.ts

Lines changed: 57 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -343,30 +343,67 @@ onboardingRouter.post('/cli/:provider/complete/:sessionId', async (req: Request,
343343
session.status = 'success';
344344
}
345345

346-
// Fetch credentials from workspace
346+
// Fetch credentials from workspace with retry
347+
// Credentials may not be immediately available after OAuth completes
347348
if (!accessToken) {
348-
try {
349-
const credsResponse = await fetch(
350-
`${session.workspaceUrl}/auth/cli/${provider}/creds/${session.workspaceSessionId}`
351-
);
352-
if (credsResponse.ok) {
353-
const creds = await credsResponse.json() as {
354-
token?: string;
355-
refreshToken?: string;
356-
expiresAt?: string;
349+
const MAX_CREDS_RETRIES = 5;
350+
const CREDS_RETRY_DELAY = 1000; // 1 second between retries
351+
352+
for (let attempt = 1; attempt <= MAX_CREDS_RETRIES; attempt++) {
353+
try {
354+
console.log(`[onboarding] Fetching credentials from workspace (attempt ${attempt}/${MAX_CREDS_RETRIES})`);
355+
const credsResponse = await fetch(
356+
`${session.workspaceUrl}/auth/cli/${provider}/creds/${session.workspaceSessionId}`
357+
);
358+
359+
if (credsResponse.ok) {
360+
const creds = await credsResponse.json() as {
361+
token?: string;
362+
refreshToken?: string;
363+
tokenExpiresAt?: string;
364+
};
365+
accessToken = creds.token;
366+
refreshToken = creds.refreshToken;
367+
if (creds.tokenExpiresAt) {
368+
tokenExpiresAt = new Date(creds.tokenExpiresAt);
369+
}
370+
console.log('[onboarding] Fetched credentials from workspace:', {
371+
hasToken: !!accessToken,
372+
hasRefreshToken: !!refreshToken,
373+
attempt,
374+
});
375+
break; // Success, exit retry loop
376+
}
377+
378+
// Check if it's an error state (not just "not ready yet")
379+
const errorBody = await credsResponse.json().catch(() => ({})) as {
380+
status?: string;
381+
error?: string;
382+
errorHint?: string;
383+
recoverable?: boolean;
357384
};
358-
accessToken = creds.token;
359-
refreshToken = creds.refreshToken;
360-
if (creds.expiresAt) {
361-
tokenExpiresAt = new Date(creds.expiresAt);
385+
386+
if (errorBody.status === 'error') {
387+
// Auth failed, don't retry
388+
console.error('[onboarding] Auth failed in workspace:', errorBody);
389+
return res.status(400).json({
390+
error: errorBody.error || 'Authentication failed',
391+
errorHint: errorBody.errorHint,
392+
recoverable: errorBody.recoverable,
393+
});
394+
}
395+
396+
// If not ready yet and we have more retries, wait and try again
397+
if (attempt < MAX_CREDS_RETRIES) {
398+
console.log(`[onboarding] Credentials not ready yet, retrying in ${CREDS_RETRY_DELAY}ms...`);
399+
await new Promise(resolve => setTimeout(resolve, CREDS_RETRY_DELAY));
400+
}
401+
} catch (err) {
402+
console.error(`[onboarding] Failed to get credentials from workspace (attempt ${attempt}):`, err);
403+
if (attempt < MAX_CREDS_RETRIES) {
404+
await new Promise(resolve => setTimeout(resolve, CREDS_RETRY_DELAY));
362405
}
363-
console.log('[onboarding] Fetched credentials from workspace:', {
364-
hasToken: !!accessToken,
365-
hasRefreshToken: !!refreshToken,
366-
});
367406
}
368-
} catch (err) {
369-
console.error('[onboarding] Failed to get credentials from workspace:', err);
370407
}
371408
}
372409
}

0 commit comments

Comments
 (0)