farming-code 2.2.8 → 2.2.12
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 +42 -28
- package/README.zh_cn.md +42 -28
- package/THIRD_PARTY_NOTICES.md +11 -2
- package/backend/acp-checkpoint-store.js +198 -0
- package/backend/acp-runtime.js +306 -83
- package/backend/acp-session-state.js +202 -6
- package/backend/acp-transcript.js +112 -0
- package/backend/agent-activity.js +6 -157
- package/backend/agent-manager.js +1592 -741
- package/backend/agent-provider-session.js +17 -242
- package/backend/agent-runtime-binding.js +219 -0
- package/backend/agent-session-history.js +66 -1
- package/backend/auth.js +79 -6
- package/backend/codex-models.js +81 -84
- package/backend/codex-session-archive.js +54 -0
- package/backend/codex-terminal-profile.js +500 -0
- package/backend/codex-transcript-sanitizer.js +12 -0
- package/backend/codex-transcript.js +230 -8
- package/backend/config-manager.js +35 -0
- package/backend/control-api.js +192 -17
- package/backend/executable-discovery.js +2 -0
- package/backend/farming-net-pass.js +285 -0
- package/backend/farming-net-registry.js +112 -0
- package/backend/farming-net-server.js +298 -0
- package/backend/farming-session-store.js +5 -13
- package/backend/git-worktree-info.js +181 -0
- package/backend/local-session-engine.js +411 -186
- package/backend/main-page-session.js +5 -2
- package/backend/native-pty-controller-generation.js +106 -0
- package/backend/native-pty-host-client.js +275 -7
- package/backend/native-pty-host-identity.js +86 -0
- package/backend/native-pty-host.js +813 -114
- package/backend/native-session-engine.js +100 -28
- package/backend/packaged-node-pty.js +22 -2
- package/backend/provider-adapters.js +253 -0
- package/backend/provider-session-service.js +241 -0
- package/backend/runtime-observation.js +81 -0
- package/backend/server.js +354 -84
- package/backend/session-engine-bridge.js +21 -2
- package/backend/session-engine-router.js +1 -1
- package/backend/session-engine.js +1 -1
- package/backend/session-stream-protocol.js +185 -0
- package/backend/storage-layout.js +55 -0
- package/backend/terminal-attach-checkpoint.js +74 -0
- package/backend/terminal-exit-quiescence.js +39 -0
- package/backend/terminal-reducer-flow-control.js +97 -0
- package/backend/terminal-screen-state.js +11 -2
- package/backend/terminal-screen-worker-pool.js +59 -6
- package/backend/terminal-screen-worker-thread.js +97 -57
- package/backend/terminal-screen-worker.js +133 -51
- package/backend/terminal-state-serialization.js +127 -0
- package/backend/terminal-status.js +23 -4
- package/backend/usage-monitor.js +176 -17
- package/backend/workspace-directory.js +232 -0
- package/backend/workspace-file-router.js +182 -76
- package/backend/workspace-file-service.js +319 -4
- package/backend/workspace-root-registry.js +164 -0
- package/dist/assets/App-DWsa7DXG.js +206 -0
- package/dist/assets/{FileEditorMarkdownPreview-elKWc8Im.js → FileEditorMarkdownPreview-UlVd0O4D.js} +92 -92
- package/dist/assets/FileEditorPane-Dxx4rKOg.js +2 -0
- package/dist/assets/IconGlyphs-AU22gPDY.js +1 -0
- package/dist/assets/ProjectFilesSection-DE7Nb9Jk.js +12 -0
- package/dist/assets/ReviewPage-CeDKDyZh.js +3 -0
- package/dist/assets/code-dark-DPiuyeSp.css +1 -0
- package/dist/assets/file-icons-Bw2qd5iT.js +1 -0
- package/dist/assets/{index-BrbljRqn.js → index-CUFPJoa5.js} +3 -3
- package/dist/assets/main-Bqtb5kuz.css +1 -0
- package/dist/assets/{monaco.contribution-CyLosfZI.js → monaco.contribution-BXz-lfFB.js} +2 -2
- package/dist/assets/{tsMode-DGZTlTj8.js → tsMode-B0oUTcXQ.js} +1 -1
- package/dist/assets/workspace-editor-model-BQol4qbA.js +1 -0
- package/dist/assets/workspace-editor-monaco-BICJESG0.js +4 -0
- package/dist/assets/workspace-editor-monaco-DRJ7IaXk.js +1 -0
- package/dist/assets/workspace-view-state-DvYG_9PH.js +7 -0
- package/dist/assets/workspace-working-copy-D8-s_Sgh.js +1 -0
- package/dist/farming-2/site.webmanifest +2 -0
- package/dist/index.html +1 -1
- package/frontend/farming-net/app.css +625 -0
- package/frontend/farming-net/app.js +268 -0
- package/frontend/farming-net/index.html +86 -0
- package/frontend/reading-anchor.js +198 -0
- package/frontend/session-bridge.js +12 -3
- package/frontend/session-modal-bridge.js +5 -12
- package/frontend/skins/crt/app.js +1978 -793
- package/frontend/skins/crt/index.html +313 -23
- package/frontend/skins/crt/styles/billing.css +294 -223
- package/frontend/skins/crt/styles/monochrome-green.css +7 -2
- package/frontend/terminal-replay.js +372 -0
- package/package.json +10 -3
- package/shared/browser-protocol.d.ts +5 -0
- package/shared/browser-protocol.js +130 -0
- package/dist/assets/App-8dYAM6ql.js +0 -124
- package/dist/assets/FileEditorPane-RWiFD2cq.js +0 -5
- package/dist/assets/IconGlyphs-DfL0EBnj.js +0 -1
- package/dist/assets/ProjectFilesSection-Q4PDsWmM.js +0 -12
- package/dist/assets/ReviewPage-BaXu1ZdX.js +0 -3
- package/dist/assets/code-dark-CDkOQAtK.css +0 -1
- package/dist/assets/file-icons-EFUGSSwf.js +0 -1
- package/dist/assets/main-D073SnW4.css +0 -1
- package/dist/assets/qoder-C9LmmOSf.svg +0 -1
- package/dist/assets/qoder-Cf9gl0Y5.svg +0 -1
- package/dist/assets/qoder-gHCinseV.svg +0 -1
- package/dist/assets/workspace-view-state-CTyDzk2D.js +0 -1
- package/dist/assets/zsh-CLpveKlF.svg +0 -1
- package/dist/assets/zsh-FxSpMPbz.svg +0 -1
- /package/dist/assets/{api-D1lyBYIQ.js → api-D8nyOEbz.js} +0 -0
- /package/dist/assets/{core-ZlAPicox.js → core-D0LFJkDt.js} +0 -0
- /package/dist/assets/{useWorkspaceMenuKeyboard-CneKAZUJ.js → useWorkspaceMenuKeyboard-Brws6Ar9.js} +0 -0
|
@@ -16,6 +16,9 @@ class SessionEngineBridge extends EventEmitter {
|
|
|
16
16
|
engine.on('session-output', (payload) => {
|
|
17
17
|
this.emit('session-output', { engineName, ...payload });
|
|
18
18
|
});
|
|
19
|
+
engine.on('session-transition', (payload) => {
|
|
20
|
+
this.emit('session-transition', { engineName, ...payload });
|
|
21
|
+
});
|
|
19
22
|
engine.on('session-sync', (payload) => {
|
|
20
23
|
this.emit('session-sync', { engineName, ...payload });
|
|
21
24
|
});
|
|
@@ -54,10 +57,10 @@ class SessionEngineBridge extends EventEmitter {
|
|
|
54
57
|
return resolution;
|
|
55
58
|
}
|
|
56
59
|
|
|
57
|
-
async sendInput(engineName, sessionId, input) {
|
|
60
|
+
async sendInput(engineName, sessionId, input, options = {}) {
|
|
58
61
|
const engine = this.getEngine(engineName);
|
|
59
62
|
if (!engine) return;
|
|
60
|
-
return engine.sendInput(sessionId, input);
|
|
63
|
+
return engine.sendInput(sessionId, input, options);
|
|
61
64
|
}
|
|
62
65
|
|
|
63
66
|
async resizeSession(engineName, sessionId, cols, rows) {
|
|
@@ -84,6 +87,12 @@ class SessionEngineBridge extends EventEmitter {
|
|
|
84
87
|
return engine.getSessionState(sessionId);
|
|
85
88
|
}
|
|
86
89
|
|
|
90
|
+
async getSessionAttachCheckpoint(engineName, sessionId) {
|
|
91
|
+
const engine = this.getEngine(engineName);
|
|
92
|
+
if (!engine || !engine.getSessionAttachCheckpoint) return null;
|
|
93
|
+
return engine.getSessionAttachCheckpoint(sessionId);
|
|
94
|
+
}
|
|
95
|
+
|
|
87
96
|
async getSessionPreview(engineName, sessionId) {
|
|
88
97
|
const engine = this.getEngine(engineName);
|
|
89
98
|
if (!engine) return '';
|
|
@@ -102,6 +111,16 @@ class SessionEngineBridge extends EventEmitter {
|
|
|
102
111
|
return recovered;
|
|
103
112
|
}
|
|
104
113
|
|
|
114
|
+
consumeRuntimeRotations() {
|
|
115
|
+
const rotations = [];
|
|
116
|
+
for (const [engineName, engine] of Object.entries(this.router.engines)) {
|
|
117
|
+
if (!engine || typeof engine.consumeRuntimeRotation !== 'function') continue;
|
|
118
|
+
const rotation = engine.consumeRuntimeRotation();
|
|
119
|
+
if (rotation) rotations.push({ engineName, ...rotation });
|
|
120
|
+
}
|
|
121
|
+
return rotations;
|
|
122
|
+
}
|
|
123
|
+
|
|
105
124
|
dispose(options = {}) {
|
|
106
125
|
return this.router.dispose(options);
|
|
107
126
|
}
|
|
@@ -10,7 +10,7 @@ class SessionEngineRouter {
|
|
|
10
10
|
: process.env.FARMING_NATIVE_PTY_HOST_PERSIST !== '0';
|
|
11
11
|
this.engines = {
|
|
12
12
|
native: new NativeSessionEngine({ configDir, preserveHostOnDispose: preserveNativeHost }),
|
|
13
|
-
local: new LocalSessionEngine()
|
|
13
|
+
local: new LocalSessionEngine({ configDir })
|
|
14
14
|
};
|
|
15
15
|
this.overrideEngineName = process.env.FARMING_SESSION_ENGINE || '';
|
|
16
16
|
this.defaultEngineName = options.defaultEngineName || this.overrideEngineName || 'native';
|
|
@@ -9,7 +9,7 @@ class SessionEngine extends EventEmitter {
|
|
|
9
9
|
throw new Error('sendInput() must be implemented by subclasses');
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
async interruptSession(_sessionId, _input) {
|
|
12
|
+
async interruptSession(_sessionId, _input, _options = {}) {
|
|
13
13
|
throw new Error('interruptSession() must be implemented by subclasses');
|
|
14
14
|
}
|
|
15
15
|
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
function finiteNumber(value) {
|
|
2
|
+
return Number.isFinite(value) ? value : undefined;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
function nonEmptyString(value) {
|
|
6
|
+
return typeof value === 'string' && value ? value : undefined;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function transitionKind(value) {
|
|
10
|
+
return value === 'resize' || value === 'clear' ? value : 'output';
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function transitionChunks(stream, data, runtimeEpoch, outputSeq, stateRevision) {
|
|
14
|
+
if (Array.isArray(stream.chunks)) {
|
|
15
|
+
return stream.chunks.map(chunk => ({
|
|
16
|
+
kind: transitionKind(chunk.kind),
|
|
17
|
+
data: typeof chunk.data === 'string' ? chunk.data : String(chunk.data || ''),
|
|
18
|
+
runtimeEpoch: nonEmptyString(chunk.runtimeEpoch),
|
|
19
|
+
outputSeq: finiteNumber(chunk.outputSeq),
|
|
20
|
+
stateRevision: finiteNumber(chunk.stateRevision),
|
|
21
|
+
cols: finiteNumber(chunk.cols),
|
|
22
|
+
rows: finiteNumber(chunk.rows),
|
|
23
|
+
}));
|
|
24
|
+
}
|
|
25
|
+
if (stream.replace === true || !runtimeEpoch || outputSeq === undefined || stateRevision === undefined) {
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
return [{
|
|
29
|
+
kind: transitionKind(stream.kind),
|
|
30
|
+
data,
|
|
31
|
+
runtimeEpoch,
|
|
32
|
+
outputSeq,
|
|
33
|
+
stateRevision,
|
|
34
|
+
cols: finiteNumber(stream.cols),
|
|
35
|
+
rows: finiteNumber(stream.rows),
|
|
36
|
+
}];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function normalizeSessionStream(stream) {
|
|
40
|
+
const data = typeof stream.data === 'string' ? stream.data : String(stream.data || '');
|
|
41
|
+
const runtimeEpoch = nonEmptyString(stream.runtimeEpoch);
|
|
42
|
+
const outputSeq = finiteNumber(stream.outputSeq);
|
|
43
|
+
const stateRevision = finiteNumber(stream.stateRevision);
|
|
44
|
+
return {
|
|
45
|
+
...stream,
|
|
46
|
+
kind: transitionKind(stream.kind),
|
|
47
|
+
data,
|
|
48
|
+
replace: stream.replace === true,
|
|
49
|
+
runtimeEpoch,
|
|
50
|
+
outputSeq,
|
|
51
|
+
stateRevision,
|
|
52
|
+
cols: finiteNumber(stream.cols),
|
|
53
|
+
rows: finiteNumber(stream.rows),
|
|
54
|
+
chunks: transitionChunks(stream, data, runtimeEpoch, outputSeq, stateRevision),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function coalesceSessionStream(existingStream, incomingStream) {
|
|
59
|
+
const incoming = normalizeSessionStream(incomingStream);
|
|
60
|
+
if (!existingStream) {
|
|
61
|
+
return incoming;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const existing = normalizeSessionStream(existingStream);
|
|
65
|
+
if (incoming.replace) {
|
|
66
|
+
if (
|
|
67
|
+
!existing.runtimeEpoch ||
|
|
68
|
+
!incoming.runtimeEpoch ||
|
|
69
|
+
existing.runtimeEpoch !== incoming.runtimeEpoch
|
|
70
|
+
) {
|
|
71
|
+
return incoming;
|
|
72
|
+
}
|
|
73
|
+
const existingRevision = finiteNumber(existing.stateRevision);
|
|
74
|
+
const incomingRevision = finiteNumber(incoming.stateRevision);
|
|
75
|
+
if (
|
|
76
|
+
existing.replace &&
|
|
77
|
+
existingRevision !== undefined &&
|
|
78
|
+
incomingRevision !== undefined &&
|
|
79
|
+
existingRevision > incomingRevision
|
|
80
|
+
) {
|
|
81
|
+
return existing;
|
|
82
|
+
}
|
|
83
|
+
if (incomingRevision === undefined) return incoming;
|
|
84
|
+
const uncoveredChunks = (existing.chunks || []).filter(chunk => (
|
|
85
|
+
chunk.runtimeEpoch === incoming.runtimeEpoch &&
|
|
86
|
+
Number.isFinite(chunk.stateRevision) &&
|
|
87
|
+
chunk.stateRevision > incomingRevision
|
|
88
|
+
));
|
|
89
|
+
return {
|
|
90
|
+
...incoming,
|
|
91
|
+
chunks: uncoveredChunks.length > 0 ? uncoveredChunks : undefined,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (
|
|
96
|
+
!existing.runtimeEpoch ||
|
|
97
|
+
!incoming.runtimeEpoch ||
|
|
98
|
+
existing.runtimeEpoch !== incoming.runtimeEpoch
|
|
99
|
+
) {
|
|
100
|
+
return incoming;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const incomingHasProof = incoming.outputSeq !== undefined && incoming.stateRevision !== undefined;
|
|
104
|
+
if (!incomingHasProof) {
|
|
105
|
+
// Do not let unproved bytes inherit a previous checkpoint's proof. The
|
|
106
|
+
// browser will enter its checkpoint barrier when it receives this stream.
|
|
107
|
+
return incoming;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (existing.replace) {
|
|
111
|
+
return {
|
|
112
|
+
...existing,
|
|
113
|
+
data: existing.data,
|
|
114
|
+
replace: true,
|
|
115
|
+
cols: existing.cols,
|
|
116
|
+
rows: existing.rows,
|
|
117
|
+
chunks: [
|
|
118
|
+
...(existing.chunks || []),
|
|
119
|
+
...(incoming.chunks || []),
|
|
120
|
+
],
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
...existing,
|
|
126
|
+
...incoming,
|
|
127
|
+
data: `${existing.data}${incoming.data}`,
|
|
128
|
+
replace: false,
|
|
129
|
+
runtimeEpoch: incoming.runtimeEpoch,
|
|
130
|
+
outputSeq: incoming.outputSeq,
|
|
131
|
+
stateRevision: incoming.stateRevision,
|
|
132
|
+
cols: incoming.cols ?? existing.cols,
|
|
133
|
+
rows: incoming.rows ?? existing.rows,
|
|
134
|
+
chunks: [
|
|
135
|
+
...(existing.chunks || []),
|
|
136
|
+
...(incoming.chunks || []),
|
|
137
|
+
],
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function shouldBroadcastSessionStreamImmediately(options = {}) {
|
|
142
|
+
const pendingCount = Number.isFinite(options.pendingCount) ? options.pendingCount : 0;
|
|
143
|
+
const lastBroadcastAt = Number.isFinite(options.lastBroadcastAt) ? options.lastBroadcastAt : 0;
|
|
144
|
+
const now = Number.isFinite(options.now) ? options.now : Date.now();
|
|
145
|
+
const intervalMs = Number.isFinite(options.intervalMs) ? Math.max(0, options.intervalMs) : 0;
|
|
146
|
+
return pendingCount === 0 && (lastBroadcastAt === 0 || now - lastBroadcastAt >= intervalMs);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function deliverSessionStreamToClients(clients, stream, options = {}) {
|
|
150
|
+
const openState = options.openState ?? 1;
|
|
151
|
+
const maxBufferedAmount = Number.isFinite(options.maxBufferedAmount)
|
|
152
|
+
? options.maxBufferedAmount
|
|
153
|
+
: 4 * 1024 * 1024;
|
|
154
|
+
const message = options.message || JSON.stringify({
|
|
155
|
+
type: 'session-output',
|
|
156
|
+
stream,
|
|
157
|
+
});
|
|
158
|
+
const result = { sent: 0, closed: 0, skipped: 0 };
|
|
159
|
+
|
|
160
|
+
for (const client of clients || []) {
|
|
161
|
+
if (!client || client.readyState !== openState) {
|
|
162
|
+
result.skipped += 1;
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
if (client.streamScope === 'focused' && client.focusedAgentId !== stream.agentId) {
|
|
166
|
+
result.skipped += 1;
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
if (client.bufferedAmount > maxBufferedAmount) {
|
|
170
|
+
client.close(1013, 'terminal stream backpressure');
|
|
171
|
+
result.closed += 1;
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
client.send(message);
|
|
175
|
+
result.sent += 1;
|
|
176
|
+
}
|
|
177
|
+
return result;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
module.exports = {
|
|
181
|
+
coalesceSessionStream,
|
|
182
|
+
deliverSessionStreamToClients,
|
|
183
|
+
normalizeSessionStream,
|
|
184
|
+
shouldBroadcastSessionStreamImmediately,
|
|
185
|
+
};
|
|
@@ -22,6 +22,10 @@ function sessionsDir(configDir) {
|
|
|
22
22
|
return path.join(configDir, 'sessions');
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
function acpCheckpointsDir(configDir) {
|
|
26
|
+
return path.join(sessionsDir(configDir), 'acp-checkpoints');
|
|
27
|
+
}
|
|
28
|
+
|
|
25
29
|
function historyDir(configDir) {
|
|
26
30
|
return path.join(configDir, 'history');
|
|
27
31
|
}
|
|
@@ -58,6 +62,22 @@ function nativePtyHostLogFile(configDir) {
|
|
|
58
62
|
return path.join(configDir, 'native-pty-host.log');
|
|
59
63
|
}
|
|
60
64
|
|
|
65
|
+
function nativePtyControllerGenerationFile(configDir) {
|
|
66
|
+
return path.join(configDir, 'native-pty-controller-generation');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function nativePtyControllerGenerationLockDir(configDir) {
|
|
70
|
+
return path.join(configDir, '.native-pty-controller-generation.lock');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function nativePtyRuntimeGenerationFile(configDir) {
|
|
74
|
+
return path.join(configDir, 'native-pty-runtime-generation');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function nativePtyRuntimeGenerationLockDir(configDir) {
|
|
78
|
+
return path.join(configDir, '.native-pty-runtime-generation.lock');
|
|
79
|
+
}
|
|
80
|
+
|
|
61
81
|
function updateStateFile(configDir) {
|
|
62
82
|
return path.join(configDir, 'farming-update.json');
|
|
63
83
|
}
|
|
@@ -66,6 +86,30 @@ function updateLogFile(configDir) {
|
|
|
66
86
|
return path.join(configDir, 'farming-update.log');
|
|
67
87
|
}
|
|
68
88
|
|
|
89
|
+
function farmingNetInstancesFile(configDir) {
|
|
90
|
+
return path.join(configDir, 'instances.json');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function farmingNetServerStateFile(configDir) {
|
|
94
|
+
return path.join(configDir, 'farming-net-server.json');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function farmingNetServerLogFile(configDir) {
|
|
98
|
+
return path.join(configDir, 'farming-net-server.log');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function farmingNetSigningPrivateKeyFile(configDir) {
|
|
102
|
+
return path.join(configDir, 'signing-private-key.pem');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function farmingNetSigningPublicKeyFile(configDir) {
|
|
106
|
+
return path.join(configDir, 'signing-public-key.pem');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function farmingNetTrustFile(configDir) {
|
|
110
|
+
return path.join(configDir, 'farming-net-trust.json');
|
|
111
|
+
}
|
|
112
|
+
|
|
69
113
|
function codexAppServerRuntimeDir(configDir) {
|
|
70
114
|
return path.join(configDir, 'c');
|
|
71
115
|
}
|
|
@@ -80,11 +124,22 @@ function codexAppServerAgentHome(configDir, agentId) {
|
|
|
80
124
|
}
|
|
81
125
|
|
|
82
126
|
module.exports = {
|
|
127
|
+
acpCheckpointsDir,
|
|
83
128
|
codexAppServerAgentHome,
|
|
84
129
|
codexAppServerRuntimeDir,
|
|
130
|
+
farmingNetInstancesFile,
|
|
131
|
+
farmingNetServerLogFile,
|
|
132
|
+
farmingNetServerStateFile,
|
|
133
|
+
farmingNetSigningPrivateKeyFile,
|
|
134
|
+
farmingNetSigningPublicKeyFile,
|
|
135
|
+
farmingNetTrustFile,
|
|
85
136
|
farmingConfigDir,
|
|
86
137
|
historyDir,
|
|
138
|
+
nativePtyControllerGenerationFile,
|
|
139
|
+
nativePtyControllerGenerationLockDir,
|
|
87
140
|
nativePtyHostLogFile,
|
|
141
|
+
nativePtyRuntimeGenerationFile,
|
|
142
|
+
nativePtyRuntimeGenerationLockDir,
|
|
88
143
|
runHistoryFile,
|
|
89
144
|
reviewSessionsFile,
|
|
90
145
|
reviewStateFile,
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
const DEFAULT_TERMINAL_ATTACH_CHECKPOINT_TIMEOUT_MS = 2000;
|
|
2
|
+
|
|
3
|
+
function finiteNonNegativeInteger(value) {
|
|
4
|
+
const parsed = Number(value);
|
|
5
|
+
return Number.isFinite(parsed) && parsed >= 0 ? Math.floor(parsed) : null;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function finitePositiveInteger(value) {
|
|
9
|
+
const parsed = Number(value);
|
|
10
|
+
return Number.isFinite(parsed) && parsed > 0 ? Math.floor(parsed) : null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async function captureTerminalAttachCheckpoint(session, options = {}) {
|
|
14
|
+
if (session?.finalCheckpoint) {
|
|
15
|
+
return { ...session.finalCheckpoint };
|
|
16
|
+
}
|
|
17
|
+
if (
|
|
18
|
+
!session
|
|
19
|
+
|| !session.screenWorker
|
|
20
|
+
|| session.stateProofAvailable === false
|
|
21
|
+
|| typeof session.runtimeEpoch !== 'string'
|
|
22
|
+
|| !session.runtimeEpoch
|
|
23
|
+
) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const timeoutMs = Number.isFinite(options.timeoutMs)
|
|
28
|
+
? Math.max(1, Math.floor(options.timeoutMs))
|
|
29
|
+
: DEFAULT_TERMINAL_ATTACH_CHECKPOINT_TIMEOUT_MS;
|
|
30
|
+
const state = await session.screenWorker.getState({ timeoutMs }).catch(() => null);
|
|
31
|
+
const outputSeq = finiteNonNegativeInteger(state?.outputSeq);
|
|
32
|
+
const stateRevision = finiteNonNegativeInteger(state?.stateRevision);
|
|
33
|
+
const currentOutputSeq = finiteNonNegativeInteger(session.outputSeq);
|
|
34
|
+
const currentStateRevision = finiteNonNegativeInteger(session.stateRevision);
|
|
35
|
+
const cols = finitePositiveInteger(state?.cols);
|
|
36
|
+
const rows = finitePositiveInteger(state?.rows);
|
|
37
|
+
const requireCurrentCut = options.requireCurrentCut === true;
|
|
38
|
+
|
|
39
|
+
if (
|
|
40
|
+
!state
|
|
41
|
+
|| state.runtimeEpoch !== session.runtimeEpoch
|
|
42
|
+
|| outputSeq === null
|
|
43
|
+
|| stateRevision === null
|
|
44
|
+
|| currentOutputSeq === null
|
|
45
|
+
|| currentStateRevision === null
|
|
46
|
+
|| stateRevision < outputSeq
|
|
47
|
+
|| outputSeq > currentOutputSeq
|
|
48
|
+
|| stateRevision > currentStateRevision
|
|
49
|
+
|| (requireCurrentCut && outputSeq !== currentOutputSeq)
|
|
50
|
+
|| (requireCurrentCut && stateRevision !== currentStateRevision)
|
|
51
|
+
|| cols === null
|
|
52
|
+
|| rows === null
|
|
53
|
+
|| typeof state.renderOutput !== 'string'
|
|
54
|
+
) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
runtimeEpoch: state.runtimeEpoch,
|
|
60
|
+
renderOutput: state.renderOutput,
|
|
61
|
+
outputSeq,
|
|
62
|
+
stateRevision,
|
|
63
|
+
cols,
|
|
64
|
+
rows,
|
|
65
|
+
previewText: typeof state.previewText === 'string' ? state.previewText : '',
|
|
66
|
+
previewSnapshot: state.previewSnapshot || null,
|
|
67
|
+
title: typeof state.title === 'string' ? state.title : '',
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
module.exports = {
|
|
72
|
+
DEFAULT_TERMINAL_ATTACH_CHECKPOINT_TIMEOUT_MS,
|
|
73
|
+
captureTerminalAttachCheckpoint,
|
|
74
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
const DEFAULT_TERMINAL_EXIT_DATA_FLUSH_MS = 250;
|
|
2
|
+
|
|
3
|
+
function delay(ms) {
|
|
4
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function acceptTerminalExitData(session) {
|
|
8
|
+
if (!session || session.exitDataClosed === true) return false;
|
|
9
|
+
if (session.exitFinalizing === true) {
|
|
10
|
+
session.exitDataGeneration = (session.exitDataGeneration || 0) + 1;
|
|
11
|
+
}
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async function waitForTerminalExitDataQuiescence(session, options = {}) {
|
|
16
|
+
const isCurrent = typeof options.isCurrent === 'function' ? options.isCurrent : () => true;
|
|
17
|
+
const flushMs = Number.isFinite(options.flushMs)
|
|
18
|
+
? Math.max(1, Math.floor(options.flushMs))
|
|
19
|
+
: DEFAULT_TERMINAL_EXIT_DATA_FLUSH_MS;
|
|
20
|
+
session.exitFinalizing = true;
|
|
21
|
+
session.exitDataGeneration = session.exitDataGeneration || 0;
|
|
22
|
+
|
|
23
|
+
for (;;) {
|
|
24
|
+
const observedGeneration = session.exitDataGeneration;
|
|
25
|
+
await delay(flushMs);
|
|
26
|
+
if (!isCurrent()) return false;
|
|
27
|
+
if (session.exitDataGeneration !== observedGeneration) continue;
|
|
28
|
+
// This synchronous cut closes late-data admission before the reducer is
|
|
29
|
+
// drained and its final checkpoint is captured.
|
|
30
|
+
session.exitDataClosed = true;
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
module.exports = {
|
|
36
|
+
DEFAULT_TERMINAL_EXIT_DATA_FLUSH_MS,
|
|
37
|
+
acceptTerminalExitData,
|
|
38
|
+
waitForTerminalExitDataQuiescence,
|
|
39
|
+
};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
const DEFAULT_REDUCER_HIGH_WATERMARK_BYTES = 512 * 1024;
|
|
2
|
+
const DEFAULT_REDUCER_LOW_WATERMARK_BYTES = 64 * 1024;
|
|
3
|
+
|
|
4
|
+
function positiveInteger(value, fallback) {
|
|
5
|
+
const parsed = Math.floor(Number(value));
|
|
6
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function createTerminalReducerFlowControl(options = {}) {
|
|
10
|
+
const highWatermarkBytes = positiveInteger(
|
|
11
|
+
options.highWatermarkBytes,
|
|
12
|
+
DEFAULT_REDUCER_HIGH_WATERMARK_BYTES,
|
|
13
|
+
);
|
|
14
|
+
const lowWatermarkBytes = Math.min(
|
|
15
|
+
highWatermarkBytes,
|
|
16
|
+
positiveInteger(options.lowWatermarkBytes, DEFAULT_REDUCER_LOW_WATERMARK_BYTES),
|
|
17
|
+
);
|
|
18
|
+
return {
|
|
19
|
+
pendingBytes: 0,
|
|
20
|
+
paused: false,
|
|
21
|
+
reducerBlocked: false,
|
|
22
|
+
externalBlocked: false,
|
|
23
|
+
highWatermarkBytes,
|
|
24
|
+
lowWatermarkBytes,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function ensureTerminalReducerFlowControl(session, options = {}) {
|
|
29
|
+
if (!session || typeof session !== 'object') {
|
|
30
|
+
return createTerminalReducerFlowControl(options);
|
|
31
|
+
}
|
|
32
|
+
if (!session.reducerFlowControl || typeof session.reducerFlowControl !== 'object') {
|
|
33
|
+
session.reducerFlowControl = createTerminalReducerFlowControl(options);
|
|
34
|
+
}
|
|
35
|
+
return session.reducerFlowControl;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function terminalReducerDataBytes(data) {
|
|
39
|
+
return Buffer.byteLength(String(data || ''), 'utf8');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function reconcileTerminalFlowControl(control, process) {
|
|
43
|
+
const shouldPause = control.reducerBlocked || control.externalBlocked;
|
|
44
|
+
if (shouldPause === control.paused) return null;
|
|
45
|
+
const method = shouldPause ? 'pause' : 'resume';
|
|
46
|
+
if (!process || typeof process[method] !== 'function') {
|
|
47
|
+
return new Error(`Native PTY does not support terminal flow-control ${method}`);
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
process[method]();
|
|
51
|
+
control.paused = shouldPause;
|
|
52
|
+
return null;
|
|
53
|
+
} catch (error) {
|
|
54
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function setTerminalExternalFlowControlBlocked(control, process, blocked) {
|
|
59
|
+
control.externalBlocked = blocked === true;
|
|
60
|
+
return reconcileTerminalFlowControl(control, process);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function enqueueTerminalReducerData(control, process, data) {
|
|
64
|
+
const bytes = terminalReducerDataBytes(data);
|
|
65
|
+
control.pendingBytes += bytes;
|
|
66
|
+
if (!control.reducerBlocked && control.pendingBytes > control.highWatermarkBytes) {
|
|
67
|
+
control.reducerBlocked = true;
|
|
68
|
+
}
|
|
69
|
+
return { bytes, error: reconcileTerminalFlowControl(control, process) };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function acknowledgeTerminalReducerData(control, process, bytes) {
|
|
73
|
+
const acknowledgedBytes = Math.max(0, Math.floor(Number(bytes) || 0));
|
|
74
|
+
control.pendingBytes = Math.max(0, control.pendingBytes - acknowledgedBytes);
|
|
75
|
+
if (control.reducerBlocked && control.pendingBytes < control.lowWatermarkBytes) {
|
|
76
|
+
control.reducerBlocked = false;
|
|
77
|
+
}
|
|
78
|
+
return reconcileTerminalFlowControl(control, process);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function resetTerminalReducerFlowControl(control, process) {
|
|
82
|
+
control.pendingBytes = 0;
|
|
83
|
+
control.reducerBlocked = false;
|
|
84
|
+
return reconcileTerminalFlowControl(control, process);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
module.exports = {
|
|
88
|
+
DEFAULT_REDUCER_HIGH_WATERMARK_BYTES,
|
|
89
|
+
DEFAULT_REDUCER_LOW_WATERMARK_BYTES,
|
|
90
|
+
acknowledgeTerminalReducerData,
|
|
91
|
+
createTerminalReducerFlowControl,
|
|
92
|
+
ensureTerminalReducerFlowControl,
|
|
93
|
+
enqueueTerminalReducerData,
|
|
94
|
+
resetTerminalReducerFlowControl,
|
|
95
|
+
setTerminalExternalFlowControlBlocked,
|
|
96
|
+
terminalReducerDataBytes,
|
|
97
|
+
};
|
|
@@ -141,6 +141,7 @@ class TerminalScreenState {
|
|
|
141
141
|
this.renderOutput = '';
|
|
142
142
|
this.previewText = '';
|
|
143
143
|
this.previewSnapshot = null;
|
|
144
|
+
this.previewDirty = false;
|
|
144
145
|
this.renderOutputDirty = true;
|
|
145
146
|
this.pendingWrite = Promise.resolve();
|
|
146
147
|
|
|
@@ -156,6 +157,7 @@ class TerminalScreenState {
|
|
|
156
157
|
this.previewSnapshot = this.includePreviewSnapshot
|
|
157
158
|
? collectViewportSnapshot(this.terminal)
|
|
158
159
|
: null;
|
|
160
|
+
this.previewDirty = false;
|
|
159
161
|
this.renderOutputDirty = true;
|
|
160
162
|
}
|
|
161
163
|
|
|
@@ -185,8 +187,12 @@ class TerminalScreenState {
|
|
|
185
187
|
() =>
|
|
186
188
|
new Promise((resolve) => {
|
|
187
189
|
this.terminal.write(data, () => {
|
|
188
|
-
this.
|
|
189
|
-
|
|
190
|
+
this.previewDirty = true;
|
|
191
|
+
this.renderOutputDirty = true;
|
|
192
|
+
resolve(this.getState({
|
|
193
|
+
includeRenderOutput: false,
|
|
194
|
+
refreshPreview: false,
|
|
195
|
+
}));
|
|
190
196
|
});
|
|
191
197
|
}),
|
|
192
198
|
);
|
|
@@ -215,6 +221,9 @@ class TerminalScreenState {
|
|
|
215
221
|
|
|
216
222
|
getState(options = {}) {
|
|
217
223
|
const includeRenderOutput = options.includeRenderOutput !== false;
|
|
224
|
+
if (options.refreshPreview !== false && this.previewDirty) {
|
|
225
|
+
this.refreshPreview();
|
|
226
|
+
}
|
|
218
227
|
if (includeRenderOutput && this.renderOutputDirty) {
|
|
219
228
|
this.refreshRenderOutput();
|
|
220
229
|
}
|