@thegitai/cli 1.0.0-preview.47 → 1.0.0-preview.49

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -52,6 +52,31 @@ CLI login tokens use a rolling 48-hour inactivity timeout. If one expires during
52
52
  any server request, the CLI removes the expired credential and signs you in
53
53
  again on the next run.
54
54
 
55
+ ## When the connection drops
56
+
57
+ A dropped connection does not end your turn. The CLI shows
58
+ `Connection interrupted · retrying in 10s · retry 1/3` under the working timer,
59
+ waits ten seconds, and reconnects to the same turn — it never starts the request
60
+ over, and a command, edit or browser action that already ran is not run again.
61
+ It tries three times. Once it is back, the turn simply continues and a short
62
+ `Connection restored` note stays in the transcript.
63
+
64
+ The same applies when you start `ai` while TheGitAI is briefly unavailable:
65
+ startup shows `Couldn't reach TheGitAI (…) · retrying in 10s · retry 1/3` and
66
+ waits up to three times before giving up.
67
+
68
+ If TheGitAI itself restarted while it was working, the turn cannot resume
69
+ where it was. It continues instead: the CLI sends your request once more with
70
+ the steps that already finished on your machine and everything you told it
71
+ during the turn, and the work picks up from there — a command that already ran
72
+ is not run again, and a correction you made still stands — with a short
73
+ `Server restarted` note in the transcript.
74
+
75
+ Press Esc at any point to stop the turn instead. If all three retries fail, the
76
+ turn ends with an error that names what was interrupted, notes that some actions
77
+ may already have completed, and leaves anything you had queued in the composer
78
+ rather than sending it on top of a turn whose outcome is unknown.
79
+
55
80
  ## Chrome browser control
56
81
 
57
82
  The TheGitAI Chrome extension is separately installed. After installing it,
package/dist/bin/ai.js CHANGED
@@ -4,7 +4,9 @@ import { ServerApi } from '../src/api/index.js';
4
4
  import { DEFAULT_THEGITAI_HOST } from '../src/api/default-host.js';
5
5
  import { isSignInCancelled } from '../src/api/browser-login.js';
6
6
  import { runSignIn } from '../src/signin.js';
7
- import { STARTUP_RETRY_BUDGET, authenticationErrorMessage, isAuthenticationError, isTransientNetworkError, } from '../src/api/http.js';
7
+ import { ServerApiError, authenticationErrorMessage, isAuthenticationError, isTransientNetworkError, } from '../src/api/http.js';
8
+ import { loadStartupHandshake } from '../src/api/startup-handshake.js';
9
+ import { TURN_RECOVERY_DELAY_MS, TURN_RECOVERY_RETRIES, } from '../src/api/turn-recovery.js';
8
10
  import { formatCliHelpText } from '../src/help-text.js';
9
11
  import { createSession, disposeSession } from '../src/session.js';
10
12
  import { applySessionSnapshot, listSessionMetadata, loadSessionSnapshot, saveSessionState, sessionHasUserMessage, } from '../src/session-store.js';
@@ -172,16 +174,24 @@ export async function main() {
172
174
  const authConfig = await ensureCliAuthConfig();
173
175
  const serverSessionClient = sessions.createServerSessionClient({ config: authConfig });
174
176
  const cachedModels = models.selectCacheForServer(models.readCachedServerModels(), authConfig.serverUrl);
175
- const [modelsOutcome, whoamiOutcome] = await Promise.allSettled([
176
- models.fetchServerModels({
177
- config: authConfig,
178
- budget: STARTUP_RETRY_BUDGET,
179
- }),
180
- auth.fetchWhoamiResponse({
181
- config: authConfig,
182
- budget: STARTUP_RETRY_BUDGET,
183
- }),
184
- ]);
177
+ const { models: modelsOutcome, whoami: whoamiOutcome } = await loadStartupHandshake({
178
+ config: authConfig,
179
+ canStartOffline: Boolean(cachedModels?.models.length),
180
+ onStatus: (status, lastFailure) => {
181
+ if (status.phase === 'waiting') {
182
+ const reason = lastFailure instanceof ServerApiError
183
+ ? `HTTP ${lastFailure.status}`
184
+ : unreachableReason(lastFailure);
185
+ console.error(chalk.yellow(`⚠ Couldn't reach TheGitAI (${reason}) · retrying in ${TURN_RECOVERY_DELAY_MS / 1000}s · retry ${status.retry}/${TURN_RECOVERY_RETRIES}`));
186
+ }
187
+ else if (status.phase === 'reconnecting') {
188
+ console.error(chalk.dim(` Reconnecting · retry ${status.retry}/${TURN_RECOVERY_RETRIES}`));
189
+ }
190
+ else if (status.phase === 'restored') {
191
+ console.error(chalk.green('✓ Reconnected to TheGitAI'));
192
+ }
193
+ },
194
+ });
185
195
  let offlineNotice = null;
186
196
  let serverModels;
187
197
  if (modelsOutcome.status === 'fulfilled') {