@thegitai/cli 1.0.0-preview.52 → 1.0.0-preview.53

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.
@@ -403,6 +403,10 @@ export function drainBackgroundJobNotifications({ sessionId } = {}) {
403
403
  const hasReadableFinalOutput = pending.some((snapshot) => snapshot.status !== 'killed');
404
404
  return `${lines.join(' ')}${hasReadableFinalOutput ? ' Use shell_job_output to read any final output.' : ''}`;
405
405
  }
406
+ export function hasUnreportedBackgroundJobUpdate(id, { sessionId } = {}) {
407
+ const filter = sessionFilter(sessionId);
408
+ return pendingModelNotifications.some((snapshot) => snapshot.id === id && (!filter || snapshot.sessionId === filter));
409
+ }
406
410
  function unknownJobError(id) {
407
411
  const known = listBackgroundJobs().map((job) => job.id);
408
412
  const hint = known.length
@@ -1,11 +1,40 @@
1
- import { getBackgroundJob } from './background-jobs.js';
1
+ import { getBackgroundJob, hasUnreportedBackgroundJobUpdate } from './background-jobs.js';
2
2
  export const MAX_CHAINED_JOB_FOLLOW_UPS = 3;
3
+ function exitedOnItsOwn(id, sessionId) {
4
+ const job = getBackgroundJob(id, { sessionId });
5
+ return job !== null && job.status !== 'running' && job.status !== 'killed';
6
+ }
3
7
  export function decideJobFollowUp(jobIds, chainedFollowUps, sessionId) {
4
- const finished = jobIds.some((id) => {
5
- const job = getBackgroundJob(id, { sessionId });
6
- return job !== null && job.status !== 'running' && job.status !== 'killed';
7
- });
8
- if (!finished)
8
+ if (!jobIds.some((id) => exitedOnItsOwn(id, sessionId)))
9
9
  return 'wait';
10
10
  return chainedFollowUps >= MAX_CHAINED_JOB_FOLLOW_UPS ? 'stop' : 'start';
11
11
  }
12
+ export function remainingJobWait(jobIds, sessionId) {
13
+ return jobIds.filter((id) => {
14
+ const job = getBackgroundJob(id, { sessionId });
15
+ if (job?.status === 'running')
16
+ return true;
17
+ return exitedOnItsOwn(id, sessionId) && hasUnreportedBackgroundJobUpdate(id, { sessionId });
18
+ });
19
+ }
20
+ export function createJobWaitTracker(sessionId) {
21
+ let waiting = [];
22
+ let chained = 0;
23
+ return {
24
+ get waiting() {
25
+ return waiting;
26
+ },
27
+ turnStarted(jobFollowUp) {
28
+ chained = jobFollowUp ? chained + 1 : 0;
29
+ },
30
+ turnEnded(waitingForJobs) {
31
+ waiting = waitingForJobs ? [...waitingForJobs] : remainingJobWait(waiting, sessionId());
32
+ },
33
+ takeDecision() {
34
+ const decision = decideJobFollowUp(waiting, chained, sessionId());
35
+ if (decision !== 'wait')
36
+ waiting = [];
37
+ return decision;
38
+ },
39
+ };
40
+ }
@@ -14,7 +14,7 @@ import { agentModeLabel, nextAgentMode, } from '../agent-mode.js';
14
14
  import { chat, models } from '../api/index.js';
15
15
  import { isTurnCancelledError, isTurnOutcomeKnown } from '../api/chat.js';
16
16
  import { formatTurnRecoveryStatus } from '../api/turn-recovery.js';
17
- import { decideJobFollowUp } from '../job-follow-up.js';
17
+ import { createJobWaitTracker } from '../job-follow-up.js';
18
18
  import { getJobBufferedOutput, getJobOutputPreview, hasRunningBackgroundJobs, killAllBackgroundJobs, killBackgroundJob, listBackgroundJobs, setBackgroundJobSession, setBackgroundJobUpdateHook, } from '../background-jobs.js';
19
19
  import { clearTodos, listTodos, setTodoSession } from '../todo-list.js';
20
20
  import { setScratchSession } from '../scratch-dir.js';
@@ -2492,8 +2492,7 @@ export async function runClientInteractive({ appendPromptHistory, authConfig, de
2492
2492
  rollback('turn ended — sending as the next prompt');
2493
2493
  }
2494
2494
  };
2495
- let waitingJobIds = [];
2496
- let chainedJobFollowUps = 0;
2495
+ const jobWait = createJobWaitTracker(() => session.sessionId);
2497
2496
  const handleSubmit = async (rawInput, { jobFollowUp = false } = {}) => {
2498
2497
  const chunks = jobFollowUp ? [] : store.getState().pastedChunks;
2499
2498
  const expanded = chunks.length
@@ -2795,8 +2794,7 @@ export async function runClientInteractive({ appendPromptHistory, authConfig, de
2795
2794
  await flushQueuedMessage();
2796
2795
  return;
2797
2796
  }
2798
- waitingJobIds = [];
2799
- chainedJobFollowUps = jobFollowUp ? chainedJobFollowUps + 1 : 0;
2797
+ jobWait.turnStarted(jobFollowUp);
2800
2798
  latestUsageSummary = null;
2801
2799
  disarmExitConfirm();
2802
2800
  unacknowledged = new Map();
@@ -2917,7 +2915,7 @@ export async function runClientInteractive({ appendPromptHistory, authConfig, de
2917
2915
  status: result.waitingForApproval ? 'Awaiting approval' : 'Ready',
2918
2916
  tokenUsage: formatClientTokenUsage(Date.now() - turnStartedAt, latestUsageSummary),
2919
2917
  }));
2920
- waitingJobIds = result.waitingForJobs ?? [];
2918
+ jobWait.turnEnded(result.waitingForJobs);
2921
2919
  recoverUnacknowledgedMessages();
2922
2920
  await flushQueuedMessage();
2923
2921
  }
@@ -3110,16 +3108,15 @@ export async function runClientInteractive({ appendPromptHistory, authConfig, de
3110
3108
  }));
3111
3109
  };
3112
3110
  const scheduleJobFollowUp = () => {
3113
- if (waitingJobIds.length === 0)
3111
+ if (jobWait.waiting.length === 0)
3114
3112
  return;
3115
3113
  setTimeout(async () => {
3116
3114
  const state = store.getState();
3117
3115
  if (exiting || state.busy || state.modelPickerOpen || state.resumePickerOpen)
3118
3116
  return;
3119
- const decision = decideJobFollowUp(waitingJobIds, chainedJobFollowUps, session.sessionId);
3117
+ const decision = jobWait.takeDecision();
3120
3118
  if (decision === 'wait')
3121
3119
  return;
3122
- waitingJobIds = [];
3123
3120
  if (decision === 'stop') {
3124
3121
  appendStaticEntry({
3125
3122
  body: 'A background job finished. Send a message to continue.',
@@ -3212,7 +3209,7 @@ export async function runClientInteractive({ appendPromptHistory, authConfig, de
3212
3209
  };
3213
3210
  const unsubscribe = store.subscribe(() => {
3214
3211
  renderCurrentFrame();
3215
- if (waitingJobIds.length > 0 && !store.getState().busy)
3212
+ if (jobWait.waiting.length > 0 && !store.getState().busy)
3216
3213
  scheduleJobFollowUp();
3217
3214
  });
3218
3215
  const spinnerTimer = setInterval(() => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thegitai/cli",
3
- "version": "1.0.0-preview.52",
3
+ "version": "1.0.0-preview.53",
4
4
  "description": "TheGitAI is an agentic AI coding tool for your terminal. It reads and searches your repository, writes and edits files, runs your tests, and verifies the change before handing it back.",
5
5
  "keywords": [
6
6
  "agentic-ai",
@@ -45,11 +45,11 @@
45
45
  "@lydell/node-pty-linux-x64": "1.1.0",
46
46
  "@lydell/node-pty-win32-arm64": "1.1.0",
47
47
  "@lydell/node-pty-win32-x64": "1.1.0",
48
- "@thegitai/tui-darwin-arm64": "1.0.0-preview.52",
49
- "@thegitai/tui-darwin-x64": "1.0.0-preview.52",
50
- "@thegitai/tui-linux-arm64": "1.0.0-preview.52",
51
- "@thegitai/tui-linux-x64": "1.0.0-preview.52",
52
- "@thegitai/tui-win32-x64": "1.0.0-preview.52",
48
+ "@thegitai/tui-darwin-arm64": "1.0.0-preview.53",
49
+ "@thegitai/tui-darwin-x64": "1.0.0-preview.53",
50
+ "@thegitai/tui-linux-arm64": "1.0.0-preview.53",
51
+ "@thegitai/tui-linux-x64": "1.0.0-preview.53",
52
+ "@thegitai/tui-win32-x64": "1.0.0-preview.53",
53
53
  "@vscode/ripgrep": "1.18.0"
54
54
  },
55
55
  "repository": {