@crouton-kit/humanloop 0.3.38 → 0.4.0
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/dist/api.d.ts +3 -2
- package/dist/api.js +2 -2
- package/dist/browser/server.d.ts +15 -5
- package/dist/browser/server.js +110 -14
- package/dist/cli.js +0 -0
- package/dist/editor/roundtrip.d.ts +5 -0
- package/dist/editor/roundtrip.js +36 -0
- package/dist/inbox/completion.js +1 -27
- package/dist/inbox/controller.d.ts +32 -3
- package/dist/inbox/controller.js +427 -35
- package/dist/inbox/convention.d.ts +9 -2
- package/dist/inbox/convention.js +51 -3
- package/dist/inbox/deck-adapter.d.ts +10 -2
- package/dist/inbox/deck-adapter.js +23 -12
- package/dist/inbox/deck-schema.d.ts +2 -0
- package/dist/inbox/deck-schema.js +4 -4
- package/dist/inbox/followup.d.ts +57 -0
- package/dist/inbox/followup.js +117 -0
- package/dist/inbox/registry.d.ts +4 -0
- package/dist/inbox/registry.js +9 -4
- package/dist/inbox/tickets.d.ts +5 -0
- package/dist/inbox/tickets.js +25 -32
- package/dist/inbox/tui.d.ts +1 -1
- package/dist/inbox/tui.js +58 -25
- package/dist/inbox/visual.d.ts +130 -0
- package/dist/inbox/visual.js +747 -0
- package/dist/index.d.ts +5 -3
- package/dist/index.js +2 -1
- package/dist/tui/app.d.ts +3 -6
- package/dist/tui/app.js +130 -97
- package/dist/tui/input.d.ts +5 -1
- package/dist/tui/input.js +41 -15
- package/dist/tui/log.d.ts +2 -0
- package/dist/tui/log.js +53 -0
- package/dist/tui/render.js +44 -27
- package/dist/tui/terminal.d.ts +1 -0
- package/dist/tui/terminal.js +5 -0
- package/dist/tui/tmux.js +44 -22
- package/dist/types.d.ts +71 -7
- package/dist/web/assets/{index-DhbBiRqS.js → index-YFwgZZMg.js} +2 -2
- package/dist/web/index.html +1 -1
- package/package.json +6 -3
- package/dist/conversation/reader.d.ts +0 -6
- package/dist/conversation/reader.js +0 -58
- package/dist/visuals/generate.d.ts +0 -9
- package/dist/visuals/generate.js +0 -79
package/dist/api.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import type { Deck, ResolutionEnvelope } from './types.js';
|
|
1
|
+
import type { Deck, ResolutionEnvelope, VisualProvider } from './types.js';
|
|
2
2
|
export interface AskOpts {
|
|
3
3
|
/** Interaction directory. Defaults to a managed temp dir under os.tmpdir(). */
|
|
4
4
|
dir?: string;
|
|
5
|
-
|
|
5
|
+
visualProvider?: VisualProvider;
|
|
6
6
|
cols?: number;
|
|
7
7
|
rows?: number;
|
|
8
8
|
}
|
|
@@ -18,6 +18,7 @@ export interface InboxOpts {
|
|
|
18
18
|
cols?: number;
|
|
19
19
|
rows?: number;
|
|
20
20
|
roots?: string[];
|
|
21
|
+
visualProvider?: VisualProvider;
|
|
21
22
|
}
|
|
22
23
|
/** Open the centralized inbox controller in the current human terminal. */
|
|
23
24
|
export declare function openInbox(opts?: InboxOpts): Promise<void>;
|
package/dist/api.js
CHANGED
|
@@ -27,7 +27,7 @@ export async function ask(deck, opts = {}) {
|
|
|
27
27
|
stampCanvasNode(deck);
|
|
28
28
|
atomicWriteJson(deckPath(dir), deck);
|
|
29
29
|
const { responses, completedAt, responsePath, deck: answeredDeck } = await resolveInteractionDir(dir, deck, {
|
|
30
|
-
|
|
30
|
+
visualProvider: opts.visualProvider,
|
|
31
31
|
cols: opts.cols,
|
|
32
32
|
rows: opts.rows,
|
|
33
33
|
});
|
|
@@ -48,6 +48,6 @@ export async function notify(title, body) {
|
|
|
48
48
|
}
|
|
49
49
|
/** Open the centralized inbox controller in the current human terminal. */
|
|
50
50
|
export async function openInbox(opts = {}) {
|
|
51
|
-
const controller = new InboxController({ roots: opts.roots, cols: opts.cols, rows: opts.rows });
|
|
51
|
+
const controller = new InboxController({ roots: opts.roots, cols: opts.cols, rows: opts.rows, visualProvider: opts.visualProvider });
|
|
52
52
|
await controller.run();
|
|
53
53
|
}
|
package/dist/browser/server.d.ts
CHANGED
|
@@ -9,12 +9,18 @@ export interface WebServerOpts {
|
|
|
9
9
|
* exactly once per server instance, only for the first accepted submit
|
|
10
10
|
* (later submits get a 409 and never re-fire this). `responsePath`/
|
|
11
11
|
* `completedAt` are already persisted to disk — the caller converges the
|
|
12
|
-
* host surface, it must NOT write the result again.
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
12
|
+
* host surface, it must NOT write the result again. For a connected caller,
|
|
13
|
+
* fires only after its HTTP 200 finishes, so this callback can `stop()` the
|
|
14
|
+
* server without racing that ack. If the client disconnects after accepted
|
|
15
|
+
* persistence, it fires without waiting for an impossible finish.
|
|
16
16
|
*/
|
|
17
|
-
onSubmit?: (responses: InteractionResponse[], completedAt: string, responsePath: string) => void
|
|
17
|
+
onSubmit?: (responses: InteractionResponse[], completedAt: string, responsePath: string) => void | Promise<void>;
|
|
18
|
+
/** Controller-owned finalization for a claimed inbox ticket. When supplied,
|
|
19
|
+
* this server never writes response.json itself. */
|
|
20
|
+
finalize?: (responses: InteractionResponse[]) => Promise<{
|
|
21
|
+
completedAt: string;
|
|
22
|
+
responsePath: string;
|
|
23
|
+
}>;
|
|
18
24
|
}
|
|
19
25
|
export interface ReviewWebServerOpts {
|
|
20
26
|
/** Shared review job dir — carries review.vim and progress.json. */
|
|
@@ -47,6 +53,10 @@ export interface WebServerHandle {
|
|
|
47
53
|
* acks (closed, network hiccup) can't hang take-back forever. Safe to call
|
|
48
54
|
* with zero open sockets (e.g. the browser was never opened). */
|
|
49
55
|
requestTakeBack(): Promise<void>;
|
|
56
|
+
/** The accepted submit currently completing its HTTP ack and owner convergence,
|
|
57
|
+
* if one has crossed the server boundary. `true` means the normal submit
|
|
58
|
+
* finish path completed; `false` means finalization was rejected. */
|
|
59
|
+
pendingSubmitLifecycle?(): Promise<boolean> | undefined;
|
|
50
60
|
/** Stop listening, close all sockets (WS and HTTP), and tear down. */
|
|
51
61
|
stop(): Promise<void>;
|
|
52
62
|
}
|
package/dist/browser/server.js
CHANGED
|
@@ -4,7 +4,7 @@ import { existsSync } from 'node:fs';
|
|
|
4
4
|
import { join, extname, dirname, basename } from 'node:path';
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
6
|
import { WebSocketServer } from 'ws';
|
|
7
|
-
import { deckPath, readJson, writeResponse, clearProgress } from '../inbox/convention.js';
|
|
7
|
+
import { deckPath, progressPath, readJson, writeResponse, clearProgress } from '../inbox/convention.js';
|
|
8
8
|
import { buildDraftFeedbackResult, buildFinalFeedbackResult, parseFeedbackComments, readReviewDraft, serializeFeedbackResult, writeReviewDraft, writeSubmitFlag, } from '../editor/feedback.js';
|
|
9
9
|
// See $CRTR_CONTEXT_DIR/phase1-server-contract.md for the deck HTTP/WS API this
|
|
10
10
|
// module implements — this file is the reference implementation for the deck
|
|
@@ -118,6 +118,12 @@ async function startDeckServer(opts) {
|
|
|
118
118
|
let deck = opts.deck;
|
|
119
119
|
const dir = opts.dir;
|
|
120
120
|
let submitted = null;
|
|
121
|
+
let submitting = false;
|
|
122
|
+
// Created synchronously when an accepted request enters its finalizer, then
|
|
123
|
+
// kept pending through response flush and async owner convergence. A
|
|
124
|
+
// take-back can therefore serialize with the complete submit lifecycle,
|
|
125
|
+
// rather than racing only its persistence portion.
|
|
126
|
+
let pendingSubmitLifecycle;
|
|
121
127
|
const scaffold = createServerScaffold();
|
|
122
128
|
scaffold.setHandler(async (req, res) => {
|
|
123
129
|
const url = req.url ?? '/';
|
|
@@ -132,7 +138,9 @@ async function startDeckServer(opts) {
|
|
|
132
138
|
const onDisk = readJson(deckPath(dir));
|
|
133
139
|
if (onDisk !== null)
|
|
134
140
|
deck = onDisk;
|
|
135
|
-
|
|
141
|
+
const progress = readJson(progressPath(dir));
|
|
142
|
+
const responses = Array.isArray(progress?.responses) ? progress.responses : [];
|
|
143
|
+
sendJson(res, 200, { dir, deck, responses });
|
|
136
144
|
return;
|
|
137
145
|
}
|
|
138
146
|
if (url === '/api/submit' && req.method === 'POST') {
|
|
@@ -156,22 +164,107 @@ async function startDeckServer(opts) {
|
|
|
156
164
|
sendJson(res, 409, { ok: false, error: 'already_submitted', ...submitted });
|
|
157
165
|
return;
|
|
158
166
|
}
|
|
167
|
+
if (submitting) {
|
|
168
|
+
sendJson(res, 409, { ok: false, error: 'submit_in_progress' });
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
submitting = true;
|
|
159
172
|
const responses = parsed.responses;
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
173
|
+
let resolveSubmitLifecycle;
|
|
174
|
+
let submitLifecycleSettled = false;
|
|
175
|
+
const settleSubmitLifecycle = (didSubmit) => {
|
|
176
|
+
if (submitLifecycleSettled)
|
|
177
|
+
return;
|
|
178
|
+
submitLifecycleSettled = true;
|
|
179
|
+
resolveSubmitLifecycle(didSubmit);
|
|
180
|
+
};
|
|
181
|
+
pendingSubmitLifecycle = new Promise((resolve) => { resolveSubmitLifecycle = resolve; });
|
|
182
|
+
let completedAt;
|
|
183
|
+
let responsePath;
|
|
184
|
+
// Convergence (owner onSubmit) must run exactly once for a persisted
|
|
185
|
+
// success, regardless of whether the client is still connected. The
|
|
186
|
+
// `finish` gate below only exists to preserve ack-ordering for a live
|
|
187
|
+
// caller; once the client is gone there is no ordering to protect.
|
|
188
|
+
let convergenceStarted = false;
|
|
189
|
+
const runConvergence = () => {
|
|
190
|
+
if (convergenceStarted)
|
|
191
|
+
return;
|
|
192
|
+
convergenceStarted = true;
|
|
193
|
+
void Promise.resolve(opts.onSubmit?.(responses, completedAt, responsePath)).then(() => settleSubmitLifecycle(true), () => settleSubmitLifecycle(false));
|
|
194
|
+
};
|
|
195
|
+
// Observe client disconnect BEFORE awaiting the finalizer. If the
|
|
196
|
+
// client leaves while finalization is still pending we must NOT declare
|
|
197
|
+
// the accepted submit lost — the finalizer may still persist, in which
|
|
198
|
+
// case convergence must run. Defer judgment until the finalizer settles.
|
|
199
|
+
let finalizePending = true;
|
|
200
|
+
let successReady = false;
|
|
201
|
+
let responseFinished = false;
|
|
202
|
+
let clientGone = false;
|
|
203
|
+
const onResponseFinished = () => {
|
|
204
|
+
responseFinished = true;
|
|
205
|
+
if (successReady && submitted !== null)
|
|
206
|
+
runConvergence();
|
|
207
|
+
};
|
|
208
|
+
const onClientGone = () => {
|
|
209
|
+
// `writableFinished` only means end() was called; a peer can vanish
|
|
210
|
+
// before the `finish` event. Only finish proves the live caller got
|
|
211
|
+
// through the response boundary.
|
|
212
|
+
if (responseFinished)
|
|
213
|
+
return;
|
|
214
|
+
if (finalizePending || !successReady) {
|
|
215
|
+
clientGone = true; // decide once finalization and convergence broadcast settle
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
if (submitted !== null) {
|
|
219
|
+
// Accepted submit persisted but the client left before its body
|
|
220
|
+
// flushed — converge once; no live caller remains to protect.
|
|
221
|
+
runConvergence();
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
// The finalizer rejected: the submit was genuinely lost.
|
|
225
|
+
settleSubmitLifecycle(false);
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
req.once('aborted', onClientGone);
|
|
229
|
+
res.once('finish', onResponseFinished);
|
|
230
|
+
res.once('close', onClientGone);
|
|
231
|
+
res.once('error', onClientGone);
|
|
232
|
+
try {
|
|
233
|
+
if (opts.finalize !== undefined) {
|
|
234
|
+
({ completedAt, responsePath } = await opts.finalize(responses));
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
// Standalone browser handoff retains its direct local persistence.
|
|
238
|
+
completedAt = new Date().toISOString();
|
|
239
|
+
responsePath = writeResponse(dir, responses, completedAt, deck);
|
|
240
|
+
clearProgress(dir);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
catch (error) {
|
|
244
|
+
finalizePending = false;
|
|
245
|
+
submitting = false;
|
|
246
|
+
settleSubmitLifecycle(false);
|
|
247
|
+
sendJson(res, 400, { error: 'invalid_submit', message: error instanceof Error ? error.message : String(error) });
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
finalizePending = false;
|
|
164
251
|
submitted = { responsePath, completedAt };
|
|
165
|
-
|
|
166
|
-
//
|
|
167
|
-
// trigger caller-side stop() — that ordering is what closes the race
|
|
168
|
-
// between a lost WS frame and the teardown it precedes.
|
|
252
|
+
// Broadcast convergence before permitting owner cleanup, so connected
|
|
253
|
+
// observers receive this state transition before the server can close.
|
|
169
254
|
await scaffold.broadcast({ type: 'converged' });
|
|
255
|
+
successReady = true;
|
|
256
|
+
if (clientGone) {
|
|
257
|
+
// The client disconnected during finalization; converge now rather
|
|
258
|
+
// than waiting for a `finish` that will never fire. The 200 is
|
|
259
|
+
// best-effort — the socket may already be closed.
|
|
260
|
+
runConvergence();
|
|
261
|
+
sendJson(res, 200, { ok: true, responsePath, completedAt });
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
170
264
|
// Ack-ordering guarantee: the HTTP caller must always receive its 200
|
|
171
|
-
// body before any lifecycle teardown can close its socket.
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
});
|
|
265
|
+
// body before any lifecycle teardown can close its socket. The finish
|
|
266
|
+
// observer registered above converges then; `onClientGone` converges if
|
|
267
|
+
// the peer disappears before finish.
|
|
175
268
|
sendJson(res, 200, { ok: true, responsePath, completedAt });
|
|
176
269
|
return;
|
|
177
270
|
}
|
|
@@ -192,6 +285,9 @@ async function startDeckServer(opts) {
|
|
|
192
285
|
async requestTakeBack() {
|
|
193
286
|
await scaffold.broadcast({ type: 'taken-back' });
|
|
194
287
|
},
|
|
288
|
+
pendingSubmitLifecycle() {
|
|
289
|
+
return pendingSubmitLifecycle;
|
|
290
|
+
},
|
|
195
291
|
stop() {
|
|
196
292
|
return scaffold.stop();
|
|
197
293
|
},
|
package/dist/cli.js
CHANGED
|
File without changes
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { readFileSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { spawnSync } from 'node:child_process';
|
|
3
|
+
import { randomUUID } from 'node:crypto';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
/** Run $EDITOR against a temporary buffer. Terminal ownership stays with the host. */
|
|
7
|
+
export function editBufferInEditor(buffer) {
|
|
8
|
+
const tmpFile = join(tmpdir(), `hl-input-${randomUUID()}.txt`);
|
|
9
|
+
const editor = process.env.EDITOR || 'vi';
|
|
10
|
+
try {
|
|
11
|
+
writeFileSync(tmpFile, buffer);
|
|
12
|
+
// $EDITOR may be a shell fragment such as "code --wait".
|
|
13
|
+
const result = spawnSync('/bin/sh', ['-c', `${editor} "$1"`, 'sh', tmpFile], { stdio: 'inherit' });
|
|
14
|
+
if (result.error)
|
|
15
|
+
return { text: buffer, error: `$EDITOR ("${editor}") failed to launch: ${result.error.message}` };
|
|
16
|
+
if (result.signal !== null)
|
|
17
|
+
return { text: buffer, error: `$EDITOR ("${editor}") was killed by signal ${result.signal}` };
|
|
18
|
+
if (result.status === 127 || result.status === 126)
|
|
19
|
+
return { text: buffer, error: `$EDITOR ("${editor}") failed to launch (shell exit ${result.status})` };
|
|
20
|
+
if (result.status !== 0)
|
|
21
|
+
return { text: buffer, error: `$EDITOR ("${editor}") exited with status ${result.status}` };
|
|
22
|
+
let text = readFileSync(tmpFile, 'utf8');
|
|
23
|
+
if (text.endsWith('\n') && !buffer.endsWith('\n'))
|
|
24
|
+
text = text.slice(0, -1);
|
|
25
|
+
return { text };
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
return { text: buffer, error: `$EDITOR round-trip failed: ${error instanceof Error ? error.message : String(error)}` };
|
|
29
|
+
}
|
|
30
|
+
finally {
|
|
31
|
+
try {
|
|
32
|
+
unlinkSync(tmpFile);
|
|
33
|
+
}
|
|
34
|
+
catch { /* best-effort cleanup */ }
|
|
35
|
+
}
|
|
36
|
+
}
|
package/dist/inbox/completion.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { readdirSync, statSync, unlinkSync } from 'node:fs';
|
|
2
|
-
import { spawn } from 'node:child_process';
|
|
3
2
|
import { basename, resolve } from 'node:path';
|
|
4
|
-
import { atomicWriteJson, deliveryErrorPath, deliveryPath, responsePath, readJson, reviewPath, withExclusiveDirectoryLockAsync } from './convention.js';
|
|
3
|
+
import { atomicWriteJson, deliveryErrorPath, deliveryPath, responsePath, readJson, reviewPath, runHandler, withExclusiveDirectoryLockAsync } from './convention.js';
|
|
5
4
|
import { registeredInboxRoot } from './registry.js';
|
|
6
5
|
import { readTicketResult, ticketRoot } from './tickets.js';
|
|
7
6
|
import { validateReviewProjection } from './deck-schema.js';
|
|
@@ -12,31 +11,6 @@ function receiptMatches(dir) {
|
|
|
12
11
|
function eventFor(root, dir, result) {
|
|
13
12
|
return { schema: 'humanloop.completion/v1', root, dir, ticketId: basename(dir), kind: result.kind, outcome: result.kind === 'canceled' ? 'canceled' : 'resolved', responsePath: responsePath(dir) };
|
|
14
13
|
}
|
|
15
|
-
async function runHandler(command, args, event) {
|
|
16
|
-
await new Promise((resolvePromise, rejectPromise) => {
|
|
17
|
-
// stdout stays ignored (a handler acknowledges by exit code, never stdout),
|
|
18
|
-
// but stderr is captured so a nonzero exit surfaces the handler's own
|
|
19
|
-
// diagnostics in the delivery-error record instead of a bare exit code.
|
|
20
|
-
const child = spawn(command, args, { stdio: ['pipe', 'ignore', 'pipe'] });
|
|
21
|
-
let stderr = '';
|
|
22
|
-
child.stderr?.on('data', (chunk) => { stderr += chunk; if (stderr.length > 8192)
|
|
23
|
-
stderr = stderr.slice(-8192); });
|
|
24
|
-
let timedOut = false;
|
|
25
|
-
const timeout = setTimeout(() => { timedOut = true; child.kill('SIGKILL'); }, 30_000);
|
|
26
|
-
child.once('error', (error) => { clearTimeout(timeout); rejectPromise(error); });
|
|
27
|
-
child.once('exit', (code, signal) => {
|
|
28
|
-
clearTimeout(timeout);
|
|
29
|
-
const detail = stderr.trim() === '' ? '' : `: ${stderr.trim()}`;
|
|
30
|
-
if (timedOut)
|
|
31
|
-
rejectPromise(new Error(`completion handler timed out after 30 seconds${detail}`));
|
|
32
|
-
else if (code === 0)
|
|
33
|
-
resolvePromise();
|
|
34
|
-
else
|
|
35
|
-
rejectPromise(new Error(`completion handler failed (${signal ?? code ?? 'unknown'})${detail}`));
|
|
36
|
-
});
|
|
37
|
-
child.stdin.end(`${JSON.stringify(event)}\n`);
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
14
|
function projectReview(dir, result) {
|
|
41
15
|
if (result.kind !== 'review')
|
|
42
16
|
return;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { InteractionResponse, TicketSummary } from '../types.js';
|
|
1
|
+
import type { InteractionResponse, TicketSummary, VisualProvider } from '../types.js';
|
|
2
2
|
import type { Key } from '../tui/terminal.js';
|
|
3
3
|
import { startWebServer } from '../browser/server.js';
|
|
4
4
|
export interface InboxControllerOptions {
|
|
@@ -9,6 +9,7 @@ export interface InboxControllerOptions {
|
|
|
9
9
|
completeDeck?: (dir: string, responses: InteractionResponse[], token: string) => Promise<unknown>;
|
|
10
10
|
startDeckBrowser?: typeof startWebServer;
|
|
11
11
|
openBrowser?: (url: string) => void;
|
|
12
|
+
visualProvider?: VisualProvider;
|
|
12
13
|
}
|
|
13
14
|
type Screen = 'list' | 'detail';
|
|
14
15
|
/** One terminal owner for scanning, stable selection, claims, and frame diffs. */
|
|
@@ -19,13 +20,25 @@ export declare class InboxController {
|
|
|
19
20
|
private items;
|
|
20
21
|
private selectedDir;
|
|
21
22
|
private selectedIndex;
|
|
23
|
+
/** Scroll state belongs to the passive preview, never to an editable deck. */
|
|
24
|
+
private previewScrollOffset;
|
|
22
25
|
private screen;
|
|
23
26
|
private adapter;
|
|
27
|
+
private activeDeck;
|
|
24
28
|
private reviewAdapter;
|
|
25
29
|
private deckBrowser;
|
|
30
|
+
/** Blocks terminal input until browser finalization/shutdown is quiescent. */
|
|
31
|
+
private deckBrowserTakingBack;
|
|
32
|
+
/** The single accepted submit that crossed the browser boundary. */
|
|
33
|
+
private deckBrowserFinalizing;
|
|
26
34
|
private deckBrowserStarting;
|
|
35
|
+
private deckBrowserStartingGeneration;
|
|
36
|
+
/** Invalidates an in-flight asynchronous browser start when ownership ends. */
|
|
37
|
+
private deckBrowserGeneration;
|
|
27
38
|
private claim;
|
|
28
39
|
private reconciling;
|
|
40
|
+
private visualReconciling;
|
|
41
|
+
private readonly visualCleanupExecutor;
|
|
29
42
|
private suspended;
|
|
30
43
|
private submittingDir;
|
|
31
44
|
private stdinListener;
|
|
@@ -56,16 +69,24 @@ export declare class InboxController {
|
|
|
56
69
|
* in ReviewAdapter; the controller only owns the terminal handoff. */
|
|
57
70
|
private activateReview;
|
|
58
71
|
reloadSelectedDeck(): void;
|
|
72
|
+
private followUpHandlers;
|
|
73
|
+
private readDeck;
|
|
74
|
+
private followUpViewState;
|
|
75
|
+
private refreshFollowUp;
|
|
59
76
|
/** Hand the selected deck to its browser surface while retaining this ticket's claim. */
|
|
60
77
|
private openDeckBrowser;
|
|
61
|
-
/**
|
|
78
|
+
/** Finalize through this controller's claim-safe lifecycle before HTTP acks. */
|
|
79
|
+
private finalizeDeckBrowser;
|
|
80
|
+
/** The browser has finalized through the controller; reconcile its owner delivery. */
|
|
62
81
|
private finishDeckBrowser;
|
|
63
82
|
/** Return browser authority to the terminal deck without changing its draft. */
|
|
64
83
|
private takeBackDeckBrowser;
|
|
65
84
|
close(): void;
|
|
66
85
|
run(): Promise<void>;
|
|
67
86
|
private complete;
|
|
68
|
-
/**
|
|
87
|
+
/** The controller owns the terminal handoff and repaint around $EDITOR. */
|
|
88
|
+
private editActiveDeckInput;
|
|
89
|
+
/** Give the raw TTY to a child process (native review editor or $EDITOR). */
|
|
69
90
|
private suspendForChild;
|
|
70
91
|
/** Retake the TTY after the child exits and force a full repaint. */
|
|
71
92
|
private resumeAfterChild;
|
|
@@ -73,10 +94,18 @@ export declare class InboxController {
|
|
|
73
94
|
/** Owner-boundary reconciliation for resolved results still lacking an ack.
|
|
74
95
|
* Guarded so overlapping fs events cannot stack concurrent scans. */
|
|
75
96
|
private reconcileRoots;
|
|
97
|
+
/** Reconciliation owns stale-claim retirement and durable cleanup, never a Visual start. */
|
|
98
|
+
private reconcileVisualWork;
|
|
99
|
+
/** Automatic ticket capability is deliberately marker + current-root-handler only. */
|
|
100
|
+
private visualProviderFor;
|
|
101
|
+
private startTicketVisual;
|
|
76
102
|
private leaveDetail;
|
|
77
103
|
private select;
|
|
104
|
+
private scrollPreview;
|
|
78
105
|
private detailSize;
|
|
79
106
|
private detailLines;
|
|
107
|
+
private passiveDetailLines;
|
|
108
|
+
private previewViewport;
|
|
80
109
|
private withStatus;
|
|
81
110
|
private repaint;
|
|
82
111
|
private watchRoots;
|