-
Notifications
You must be signed in to change notification settings - Fork 6
better sync checks #292
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
better sync checks #292
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1140,6 +1140,52 @@ export async function handleContextGraphRoutes(ctx: RequestContext): Promise<voi | |
|
|
||
| const shouldSyncSharedMemory = | ||
| (includeSharedMemory ?? includeWorkspace) !== false; | ||
|
|
||
| const subMap = (agent as any).subscribedContextGraphs as | ||
| | Map<string, { subscribed: boolean; synced: boolean; metaSynced?: boolean; name?: string; [k: string]: unknown }> | ||
| | undefined; | ||
| const existingSub = subMap?.get(paranetId); | ||
| const existingJobId = catchupTracker.latestByParanet.get(paranetId); | ||
| const existingJob = existingJobId ? catchupTracker.jobs.get(existingJobId) : undefined; | ||
|
|
||
| if (existingSub?.subscribed) { | ||
| if (existingJob && (existingJob.status === "queued" || existingJob.status === "running")) { | ||
| return jsonResponse(res, 200, { | ||
| subscribed: paranetId, | ||
| catchup: { | ||
| status: existingJob.status, | ||
| includeWorkspace: existingJob.includeWorkspace, | ||
| jobId: existingJob.jobId, | ||
| }, | ||
| }); | ||
| } | ||
|
|
||
| if (existingSub.synced) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 Bug: |
||
| const jobId = existingJob?.jobId ?? `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`; | ||
| if (!existingJob) { | ||
| const syntheticJob: CatchupJob = { | ||
| jobId, | ||
| paranetId, | ||
| includeWorkspace: shouldSyncSharedMemory, | ||
| status: "done", | ||
| queuedAt: Date.now(), | ||
| startedAt: Date.now(), | ||
| finishedAt: Date.now(), | ||
| }; | ||
| catchupTracker.jobs.set(jobId, syntheticJob); | ||
| catchupTracker.latestByParanet.set(paranetId, jobId); | ||
| } | ||
| return jsonResponse(res, 200, { | ||
| subscribed: paranetId, | ||
| catchup: { | ||
| status: "done", | ||
| includeWorkspace: shouldSyncSharedMemory, | ||
| jobId, | ||
| }, | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| console.log(`[subscribe] contextGraph=${paranetId} includeSharedMemory=${shouldSyncSharedMemory}`); | ||
| agent.subscribeToContextGraph(paranetId); | ||
|
|
||
|
|
@@ -1208,9 +1254,6 @@ export async function handleContextGraphRoutes(ctx: RequestContext): Promise<voi | |
|
|
||
| if (job.status === "done") { | ||
| if (cleanResponse) { | ||
| const subMap = (agent as any).subscribedContextGraphs as | ||
| | Map<string, { subscribed: boolean; synced: boolean; metaSynced?: boolean; name?: string; [k: string]: unknown }> | ||
| | undefined; | ||
| const sub = subMap?.get(paranetId); | ||
| if (sub) { | ||
| sub.synced = true; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -70,6 +70,7 @@ export function JoinProjectModal({ open, onClose, initialContextGraphId }: JoinP | |
| const [progress, setProgress] = useState(''); | ||
| const [error, setError] = useState<string | null>(null); | ||
| const [success, setSuccess] = useState(false); | ||
| const [syncInProgress, setSyncInProgress] = useState(false); | ||
| const [requestSent, setRequestSent] = useState(false); | ||
| const [sendingRequest, setSendingRequest] = useState(false); | ||
| const [accessDenied, setAccessDenied] = useState(false); | ||
|
|
@@ -93,6 +94,7 @@ export function JoinProjectModal({ open, onClose, initialContextGraphId }: JoinP | |
| setInviteCode(initialContextGraphId ?? ''); | ||
| setError(null); | ||
| setSuccess(false); | ||
| setSyncInProgress(false); | ||
| setRequestSent(false); | ||
| setAccessDenied(false); | ||
| setProgress(''); | ||
|
|
@@ -112,6 +114,7 @@ export function JoinProjectModal({ open, onClose, initialContextGraphId }: JoinP | |
| setJoining(true); | ||
| setError(null); | ||
| setSuccess(false); | ||
| setSyncInProgress(false); | ||
| setRequestSent(false); | ||
| setAccessDenied(false); | ||
|
|
||
|
|
@@ -128,6 +131,7 @@ export function JoinProjectModal({ open, onClose, initialContextGraphId }: JoinP | |
|
|
||
| setProgress('Subscribing to project…'); | ||
| const subResult = await subscribeToContextGraph(cgId); | ||
| const subscribed = !!subResult?.subscribed; | ||
|
|
||
| setProgress('Syncing knowledge from peers…'); | ||
|
|
||
|
|
@@ -152,26 +156,13 @@ export function JoinProjectModal({ open, onClose, initialContextGraphId }: JoinP | |
| } | ||
|
|
||
| if (catchup.status === 'timeout') { | ||
| // A poll timeout is NOT evidence of ACL denial — it just means | ||
| // no peer finished the catchup within ~90s. Common reasons: | ||
| // - project is public but peers are slow / offline, | ||
| // - network path is congested, | ||
| // - our subscribe hasn't reached a peer that holds the CG yet. | ||
| // Flipping `accessDenied` here used to push users of public | ||
| // projects straight into the "Access Restricted — send signed | ||
| // join request" flow, which is misleading and cuts them off | ||
| // from just retrying. Surface a neutral network error instead | ||
| // and let them retry; a real ACL denial lands in the `denied` | ||
| // branch above, or in the `err.message` check at the bottom | ||
| // of this function. (HEAD tier-4c G3; v10-rc's copy "syncing | ||
| // still in progress" was milder but still implied success — | ||
| // we'd rather the user retry explicitly than think the subscribe | ||
| // finished when the background sync never landed data.) | ||
| setError( | ||
| 'Timed out waiting for peers to respond. The project may be slow to catch up, or no peer currently holds the data. Try again in a moment.', | ||
| ); | ||
| setProgress(''); | ||
| return; | ||
| if (!subscribed) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 Bug: |
||
| setError( | ||
| 'Timed out waiting for peers to respond. The project may be slow to catch up, or no peer currently holds the data. Try again in a moment.', | ||
| ); | ||
| setProgress(''); | ||
| return; | ||
| } | ||
| } | ||
|
|
||
| setProgress('Refreshing project list…'); | ||
|
|
@@ -184,7 +175,15 @@ export function JoinProjectModal({ open, onClose, initialContextGraphId }: JoinP | |
| openTab({ id: `project:${joined.id}`, label: joined.name || joined.id, closable: true }); | ||
| } | ||
|
|
||
| if (catchup.status === 'timeout') { | ||
| setSuccess(true); | ||
| setSyncInProgress(true); | ||
| setProgress(''); | ||
| return; | ||
| } | ||
|
|
||
| setSuccess(true); | ||
| setSyncInProgress(false); | ||
| setProgress(''); | ||
| // Phase 8: transition into wire-workspace step instead of | ||
| // auto-closing. The joiner can either install workspace files | ||
|
|
@@ -291,10 +290,13 @@ export function JoinProjectModal({ open, onClose, initialContextGraphId }: JoinP | |
| {success && ( | ||
| <div style={{ | ||
| padding: '10px 14px', borderRadius: 8, marginBottom: 16, fontSize: 12, | ||
| background: 'rgba(34, 197, 94, 0.08)', border: '1px solid rgba(34, 197, 94, 0.25)', | ||
| color: 'var(--accent-green)', | ||
| background: syncInProgress ? 'rgba(59, 130, 246, 0.08)' : 'rgba(34, 197, 94, 0.08)', | ||
| border: syncInProgress ? '1px solid rgba(59, 130, 246, 0.25)' : '1px solid rgba(34, 197, 94, 0.25)', | ||
| color: syncInProgress ? 'var(--accent-primary, #3b82f6)' : 'var(--accent-green)', | ||
| }}> | ||
| Successfully joined! Syncing knowledge from peers… | ||
| {syncInProgress | ||
| ? 'Project joined successfully. Initial sync is still in progress and data will appear as peers respond.' | ||
| : 'Successfully joined! Syncing knowledge from peers…'} | ||
| </div> | ||
| )} | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔴 Bug: This changes the subscribe response contract from the existing "queued job" shape to arbitrary statuses (
runninghere, syntheticdonebelow) without returning a full catch-up result. Current callers distinguish "completed" vs "background" by the presence ofpeersTried/result, so these payloads will still be interpreted as "queued in background". Keep the old wire shapes here or update the typed clients/callers in the same PR.