-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathsession.ts
More file actions
143 lines (121 loc) · 5.98 KB
/
Copy pathsession.ts
File metadata and controls
143 lines (121 loc) · 5.98 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
import { AcpClient } from "./acp";
/** Live state for the dashboard dot. `cold` (no live process) is represented by
* the absence of a Session, so it isn't in this union. */
export type SessionStatus = "idle" | "working" | "needs-you" | "done" | "error";
/**
* All state that belongs to a single grok session — extracted from GrokSidebar so
* the sidebar can hold a *pool* of these (one live `grok agent stdio` process per
* session) and switch focus between them without tearing the others down.
*
* Today the sidebar keeps exactly one of these (the focused session). Steps C–F
* add the pool, a per-session generation guard (`gen`), the webview post buffer,
* and a derived `status` for the dashboard dots. For now this is a pure state bag
* so the extraction is behavior-preserving (the field set + defaults mirror the
* singletons it replaces 1:1).
*/
export class Session {
/** The live ACP client (one spawned `grok agent stdio` process), once started. */
client?: AcpClient;
/** YOLO: auto-approve every permission request for this session. */
autoApprove = false;
/** Plan-mode gate is up for this session (client-side enforcement mirror). */
planActive = false;
/**
* Deferred post-turn action. The CLI's exit_plan_mode arrives *during* an
* in-flight session/prompt, so we can't send a new prompt/set_mode from the
* approval handler — we'd collide with the running turn. We stash the action
* here and run it once the current prompt resolves (see handleSend).
*/
afterTurn?: () => Promise<void>;
/** This session has conversational history (vs. a fresh, empty one). */
hasHistory = false;
/**
* True for the whole session-start window (spawn → newSession/load → primer).
* Model/effort changes are settings that restart or race the session, so they
* are ignored while priming — the webview also disables the controls (busy),
* this is the host-side backstop for a click that slips through that window.
*/
priming = false;
/**
* False until the hidden primer has been sent on THIS session load. The primer
* is no longer sent at session start — it's deferred to the first outbound
* prompt (ensurePrimed), so a startup or glance-only restore costs nothing.
* It's (re-)sent on the first send of every load, new OR restored: a primer
* buried in a restored session's replayed history isn't reliably honored by
* grok (a /compact can drop it from effective context), so we re-assert it
* once before the first post-restore turn rather than trusting history.
*/
primed = false;
/**
* In-flight (or settled) hidden-primer turn for THIS session load, if one has
* been kicked off. The primer now fires eagerly + non-blocking the moment a
* session goes live (ensurePrimed in sidebar), so the user can send straight
* away; their first real prompt awaits this promise (grok can't run two turns
* at once) and is released the instant the silent primer acks. Reused so a
* concurrent send doesn't start a second primer; cleared on failure so the
* next send retries. undefined until the primer is first requested.
*/
primingPromise?: Promise<void>;
/** Drop streaming content from the webview (primer / summary injection). */
suppressContent = false;
/**
* Plan-reject specific suppression: drop streaming output (the false-approval
* ramble) but let lifecycle events through so the webview clears `busy` and
* re-enables the send button when the cancelled turn finally ends.
*/
suppressPlanReject = false;
/** Most recent plan text seen for this session (exit_plan_mode fallback). */
lastPlanText = "";
/**
* Plan text currently shown in the live exit_plan_mode card. Set when we post
* the card to the webview, read by persistPlanVerdict when the user picks a
* verdict, then cleared. Decoupled from lastPlanText (which gets nuked the
* moment we render the card) so the saved history actually has content.
*/
pendingPlanText = "";
/**
* Count of user messages that have entered this session (replayed + live).
* Persisted on each resolved plan as `afterUserMessage` so the resume view
* can render plan cards inline with the conversation rather than at the end.
*/
userMessageCount = 0;
/**
* True while a sequence of user_message_chunk events is mid-flight, so we
* only increment userMessageCount once per user message during replay.
*/
inUserMessage = false;
/**
* True only while replaying a resumed session (session/load). grok ≥0.2.33
* echoes the *live* prompt back as user_message_chunk too, so this gates the
* handler to replay-only — the live bubble already comes from send().
*/
replaying = false;
/** grok's id for this session (set on session/new or session/load). */
activeSessionId?: string;
titleGenerated = false;
firstUserMessageForTitle?: string;
/**
* Per-session generation counter — bumped only when THIS session's client is
* torn down/restarted. Replaces the old global `sessionGen`: in a pool a
* backgrounded session's in-flight events must not be judged "stale" just
* because focus moved to another session, so each session guards its own
* events against its own gen (captured when its handlers were wired).
*/
gen = 0;
/** Derived status for the dashboard dot (see SessionStatus). */
status: SessionStatus = "idle";
/**
* ms-epoch of the last time this session was made the focus, created, or put to
* work — its "recency" for the pool's LRU/TTL reaping (see session-pool.ts).
* 0 until the sidebar touches it (kept off the constructor so this stays a pure
* state bag — the host stamps it via `touch`).
*/
lastActiveAt = 0;
/**
* Every webview post that built this session's current view, in order. The
* focused session flushes straight to the webview; a backgrounded session
* buffers here, so re-focusing replays the buffer (clearMessages + replay)
* to reconstruct the view losslessly — no grok reload, no process kill.
*/
buffer: unknown[] = [];
}