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.
Files changed (107) hide show
  1. package/README.md +42 -28
  2. package/README.zh_cn.md +42 -28
  3. package/THIRD_PARTY_NOTICES.md +11 -2
  4. package/backend/acp-checkpoint-store.js +198 -0
  5. package/backend/acp-runtime.js +306 -83
  6. package/backend/acp-session-state.js +202 -6
  7. package/backend/acp-transcript.js +112 -0
  8. package/backend/agent-activity.js +6 -157
  9. package/backend/agent-manager.js +1592 -741
  10. package/backend/agent-provider-session.js +17 -242
  11. package/backend/agent-runtime-binding.js +219 -0
  12. package/backend/agent-session-history.js +66 -1
  13. package/backend/auth.js +79 -6
  14. package/backend/codex-models.js +81 -84
  15. package/backend/codex-session-archive.js +54 -0
  16. package/backend/codex-terminal-profile.js +500 -0
  17. package/backend/codex-transcript-sanitizer.js +12 -0
  18. package/backend/codex-transcript.js +230 -8
  19. package/backend/config-manager.js +35 -0
  20. package/backend/control-api.js +192 -17
  21. package/backend/executable-discovery.js +2 -0
  22. package/backend/farming-net-pass.js +285 -0
  23. package/backend/farming-net-registry.js +112 -0
  24. package/backend/farming-net-server.js +298 -0
  25. package/backend/farming-session-store.js +5 -13
  26. package/backend/git-worktree-info.js +181 -0
  27. package/backend/local-session-engine.js +411 -186
  28. package/backend/main-page-session.js +5 -2
  29. package/backend/native-pty-controller-generation.js +106 -0
  30. package/backend/native-pty-host-client.js +275 -7
  31. package/backend/native-pty-host-identity.js +86 -0
  32. package/backend/native-pty-host.js +813 -114
  33. package/backend/native-session-engine.js +100 -28
  34. package/backend/packaged-node-pty.js +22 -2
  35. package/backend/provider-adapters.js +253 -0
  36. package/backend/provider-session-service.js +241 -0
  37. package/backend/runtime-observation.js +81 -0
  38. package/backend/server.js +354 -84
  39. package/backend/session-engine-bridge.js +21 -2
  40. package/backend/session-engine-router.js +1 -1
  41. package/backend/session-engine.js +1 -1
  42. package/backend/session-stream-protocol.js +185 -0
  43. package/backend/storage-layout.js +55 -0
  44. package/backend/terminal-attach-checkpoint.js +74 -0
  45. package/backend/terminal-exit-quiescence.js +39 -0
  46. package/backend/terminal-reducer-flow-control.js +97 -0
  47. package/backend/terminal-screen-state.js +11 -2
  48. package/backend/terminal-screen-worker-pool.js +59 -6
  49. package/backend/terminal-screen-worker-thread.js +97 -57
  50. package/backend/terminal-screen-worker.js +133 -51
  51. package/backend/terminal-state-serialization.js +127 -0
  52. package/backend/terminal-status.js +23 -4
  53. package/backend/usage-monitor.js +176 -17
  54. package/backend/workspace-directory.js +232 -0
  55. package/backend/workspace-file-router.js +182 -76
  56. package/backend/workspace-file-service.js +319 -4
  57. package/backend/workspace-root-registry.js +164 -0
  58. package/dist/assets/App-DWsa7DXG.js +206 -0
  59. package/dist/assets/{FileEditorMarkdownPreview-elKWc8Im.js → FileEditorMarkdownPreview-UlVd0O4D.js} +92 -92
  60. package/dist/assets/FileEditorPane-Dxx4rKOg.js +2 -0
  61. package/dist/assets/IconGlyphs-AU22gPDY.js +1 -0
  62. package/dist/assets/ProjectFilesSection-DE7Nb9Jk.js +12 -0
  63. package/dist/assets/ReviewPage-CeDKDyZh.js +3 -0
  64. package/dist/assets/code-dark-DPiuyeSp.css +1 -0
  65. package/dist/assets/file-icons-Bw2qd5iT.js +1 -0
  66. package/dist/assets/{index-BrbljRqn.js → index-CUFPJoa5.js} +3 -3
  67. package/dist/assets/main-Bqtb5kuz.css +1 -0
  68. package/dist/assets/{monaco.contribution-CyLosfZI.js → monaco.contribution-BXz-lfFB.js} +2 -2
  69. package/dist/assets/{tsMode-DGZTlTj8.js → tsMode-B0oUTcXQ.js} +1 -1
  70. package/dist/assets/workspace-editor-model-BQol4qbA.js +1 -0
  71. package/dist/assets/workspace-editor-monaco-BICJESG0.js +4 -0
  72. package/dist/assets/workspace-editor-monaco-DRJ7IaXk.js +1 -0
  73. package/dist/assets/workspace-view-state-DvYG_9PH.js +7 -0
  74. package/dist/assets/workspace-working-copy-D8-s_Sgh.js +1 -0
  75. package/dist/farming-2/site.webmanifest +2 -0
  76. package/dist/index.html +1 -1
  77. package/frontend/farming-net/app.css +625 -0
  78. package/frontend/farming-net/app.js +268 -0
  79. package/frontend/farming-net/index.html +86 -0
  80. package/frontend/reading-anchor.js +198 -0
  81. package/frontend/session-bridge.js +12 -3
  82. package/frontend/session-modal-bridge.js +5 -12
  83. package/frontend/skins/crt/app.js +1978 -793
  84. package/frontend/skins/crt/index.html +313 -23
  85. package/frontend/skins/crt/styles/billing.css +294 -223
  86. package/frontend/skins/crt/styles/monochrome-green.css +7 -2
  87. package/frontend/terminal-replay.js +372 -0
  88. package/package.json +10 -3
  89. package/shared/browser-protocol.d.ts +5 -0
  90. package/shared/browser-protocol.js +130 -0
  91. package/dist/assets/App-8dYAM6ql.js +0 -124
  92. package/dist/assets/FileEditorPane-RWiFD2cq.js +0 -5
  93. package/dist/assets/IconGlyphs-DfL0EBnj.js +0 -1
  94. package/dist/assets/ProjectFilesSection-Q4PDsWmM.js +0 -12
  95. package/dist/assets/ReviewPage-BaXu1ZdX.js +0 -3
  96. package/dist/assets/code-dark-CDkOQAtK.css +0 -1
  97. package/dist/assets/file-icons-EFUGSSwf.js +0 -1
  98. package/dist/assets/main-D073SnW4.css +0 -1
  99. package/dist/assets/qoder-C9LmmOSf.svg +0 -1
  100. package/dist/assets/qoder-Cf9gl0Y5.svg +0 -1
  101. package/dist/assets/qoder-gHCinseV.svg +0 -1
  102. package/dist/assets/workspace-view-state-CTyDzk2D.js +0 -1
  103. package/dist/assets/zsh-CLpveKlF.svg +0 -1
  104. package/dist/assets/zsh-FxSpMPbz.svg +0 -1
  105. /package/dist/assets/{api-D1lyBYIQ.js → api-D8nyOEbz.js} +0 -0
  106. /package/dist/assets/{core-ZlAPicox.js → core-D0LFJkDt.js} +0 -0
  107. /package/dist/assets/{useWorkspaceMenuKeyboard-CneKAZUJ.js → useWorkspaceMenuKeyboard-Brws6Ar9.js} +0 -0
@@ -0,0 +1,241 @@
1
+ const path = require('path');
2
+ const { findAgentSession } = require('./agent-session-history');
3
+ const { listCodexSessions } = require('./codex-session-history');
4
+ const { isLinkedWorktreeOf } = require('./git-worktree-info');
5
+ const { mainPageAgentSessionKey } = require('./main-page-session');
6
+ const { isTemporaryProviderSessionId } = require('./provider-session-id');
7
+
8
+ const CODEX_RESOLVE_COOLDOWN_MS = 1000;
9
+ const CODEX_MATCH_GRACE_MS = 30 * 1000;
10
+ const TITLE_RESOLVE_COOLDOWN_MS = 30 * 1000;
11
+
12
+ function normalizePath(value) {
13
+ if (typeof value !== 'string') return '';
14
+ const trimmed = value.trim();
15
+ if (!trimmed || trimmed === path.sep) return trimmed;
16
+ return trimmed.replace(/[\\/]+$/, '');
17
+ }
18
+
19
+ function timestampMs(value) {
20
+ const parsed = Date.parse(value || '');
21
+ return Number.isFinite(parsed) ? parsed : 0;
22
+ }
23
+
24
+ class ProviderSessionService {
25
+ constructor(options = {}) {
26
+ this.agents = options.agents || new Map();
27
+ this.getProviderHomes = options.getProviderHomes || (() => undefined);
28
+ this.commit = options.commit || (() => {});
29
+ this.listCodexSessions = options.listCodexSessions || listCodexSessions;
30
+ this.findAgentSession = options.findAgentSession || findAgentSession;
31
+ this.isLinkedWorktreeOf = options.isLinkedWorktreeOf || isLinkedWorktreeOf;
32
+ this.resolutions = new Map();
33
+ }
34
+
35
+ activate(agentId) {
36
+ const agent = this.agents.get(agentId);
37
+ if (!agent?.providerSessionProvider || !agent.providerSessionId) return;
38
+
39
+ if (agent.providerSessionProvider === 'codex' && agent.providerSessionTemporary === true) {
40
+ this.observe(agentId, { force: true });
41
+ return;
42
+ }
43
+
44
+ this.resolutions.delete(`codex:${agentId}`);
45
+ this.commit(agent, { kind: 'known-session' });
46
+ void this.resolveTitle(agentId, { force: true });
47
+ }
48
+
49
+ observe(agentId, options = {}) {
50
+ void this.resolveTemporaryCodex(agentId, options);
51
+ void this.resolveTitle(agentId, options);
52
+ }
53
+
54
+ stop(agentId) {
55
+ this.resolutions.delete(`codex:${agentId}`);
56
+ this.resolutions.delete(`title:${agentId}`);
57
+ }
58
+
59
+ dispose() {
60
+ this.resolutions.clear();
61
+ }
62
+
63
+ bindConfirmed(agentId, provider, sessionId) {
64
+ const agent = this.agents.get(agentId);
65
+ if (!agent || !provider || !sessionId || isTemporaryProviderSessionId(sessionId)) return null;
66
+ agent.providerSessionProvider = provider;
67
+ agent.providerSessionId = sessionId;
68
+ agent.providerSessionKey = mainPageAgentSessionKey(provider, sessionId, agent.providerHomeId || '');
69
+ agent.providerSessionTemporary = false;
70
+ return agent;
71
+ }
72
+
73
+ runResolution(kind, agentId, cooldownMs, force, task) {
74
+ const key = `${kind}:${agentId}`;
75
+ const current = this.resolutions.get(key);
76
+ if (current?.promise) return current.promise;
77
+ const now = Date.now();
78
+ if (!force && current && now - current.lastAttemptAt < cooldownMs) {
79
+ return Promise.resolve(false);
80
+ }
81
+
82
+ const resolution = { lastAttemptAt: now, promise: null };
83
+ const attempt = Promise.resolve()
84
+ .then(task)
85
+ .catch(() => false)
86
+ .finally(() => {
87
+ if (this.resolutions.get(key) === resolution) resolution.promise = null;
88
+ });
89
+ resolution.promise = attempt;
90
+ this.resolutions.set(key, resolution);
91
+ return attempt;
92
+ }
93
+
94
+ resolveTemporaryCodex(agentId, options = {}) {
95
+ const agent = this.agents.get(agentId);
96
+ if (!agent || agent.providerSessionProvider !== 'codex' || agent.providerSessionTemporary !== true) {
97
+ this.resolutions.delete(`codex:${agentId}`);
98
+ return Promise.resolve(false);
99
+ }
100
+
101
+ return this.runResolution(
102
+ 'codex',
103
+ agentId,
104
+ CODEX_RESOLVE_COOLDOWN_MS,
105
+ options.force === true,
106
+ () => this.findTemporaryCodexSession(agent).then((session) => {
107
+ if (!session?.id) return false;
108
+ return this.confirm(agentId, {
109
+ provider: 'codex',
110
+ sessionId: session.id,
111
+ source: 'codex-rollout',
112
+ title: session.title || '',
113
+ workspace: session.workspace || session.cwd || '',
114
+ });
115
+ }),
116
+ );
117
+ }
118
+
119
+ resolveTitle(agentId, options = {}) {
120
+ const agent = this.agents.get(agentId);
121
+ if (
122
+ !agent?.providerSessionProvider
123
+ || !agent.providerSessionId
124
+ || agent.providerSessionTemporary === true
125
+ || isTemporaryProviderSessionId(agent.providerSessionId)
126
+ || String(agent.providerSessionTitle || '').trim()
127
+ ) {
128
+ this.resolutions.delete(`title:${agentId}`);
129
+ return Promise.resolve(false);
130
+ }
131
+
132
+ const provider = agent.providerSessionProvider;
133
+ const sessionId = agent.providerSessionId;
134
+ return this.runResolution(
135
+ 'title',
136
+ agentId,
137
+ TITLE_RESOLVE_COOLDOWN_MS,
138
+ options.force === true,
139
+ () => this.findAgentSession(provider, sessionId, {
140
+ limit: 200,
141
+ providerLimit: 200,
142
+ providerHomeId: agent.providerHomeId || 'default',
143
+ providerHomes: this.getProviderHomes(),
144
+ }).then((session) => {
145
+ const title = String(session?.title || '').trim().slice(0, 160);
146
+ if (!title) return false;
147
+
148
+ const current = this.agents.get(agentId);
149
+ if (
150
+ !current
151
+ || current.providerSessionProvider !== provider
152
+ || current.providerSessionId !== sessionId
153
+ || current.providerSessionTemporary === true
154
+ || String(current.providerSessionTitle || '').trim()
155
+ ) {
156
+ return false;
157
+ }
158
+
159
+ current.providerSessionTitle = title;
160
+ this.commit(current, {
161
+ kind: 'session-updated',
162
+ event: { agentId, provider, sessionId, title, temporary: false },
163
+ });
164
+ return true;
165
+ }),
166
+ );
167
+ }
168
+
169
+ async findTemporaryCodexSession(agent) {
170
+ const sessions = await this.listCodexSessions({
171
+ codexHome: agent.providerHomePath || undefined,
172
+ limit: 100,
173
+ scanLimit: 1000,
174
+ });
175
+ const workspace = normalizePath(
176
+ agent?.gitWorktree?.workspace || agent?.projectWorkspace || agent?.cwd || ''
177
+ );
178
+ const startedAt = Number(agent.startedAt) || 0;
179
+ const homeId = String(agent.providerHomeId || 'default').trim() || 'default';
180
+ const claimedSessionIds = new Set([...this.agents.values()]
181
+ .filter(candidate => candidate?.id !== agent.id
182
+ && candidate?.providerSessionProvider === 'codex'
183
+ && (String(candidate.providerHomeId || 'default').trim() || 'default') === homeId
184
+ && candidate.providerSessionId
185
+ && candidate.providerSessionTemporary !== true)
186
+ .map(candidate => candidate.providerSessionId));
187
+ const candidates = sessions
188
+ .filter(session => {
189
+ if (!session?.id || claimedSessionIds.has(session.id)) return false;
190
+ const sessionWorkspace = normalizePath(session.workspace || session.cwd);
191
+ if (workspace && !sessionWorkspace) return false;
192
+ const sessionTime = timestampMs(session.createdAt || session.updatedAt);
193
+ if (!sessionTime || !startedAt) return true;
194
+ return sessionTime >= startedAt - CODEX_MATCH_GRACE_MS;
195
+ })
196
+ .sort((a, b) => {
197
+ const aTime = timestampMs(a.createdAt || a.updatedAt);
198
+ const bTime = timestampMs(b.createdAt || b.updatedAt);
199
+ const aDistance = startedAt && aTime ? Math.abs(aTime - startedAt) : Number.MAX_SAFE_INTEGER;
200
+ const bDistance = startedAt && bTime ? Math.abs(bTime - startedAt) : Number.MAX_SAFE_INTEGER;
201
+ if (aDistance !== bDistance) return aDistance - bDistance;
202
+ return bTime - aTime;
203
+ });
204
+
205
+ const exact = candidates.find(session => (
206
+ !workspace || workspace === normalizePath(session.workspace || session.cwd)
207
+ ));
208
+ if (exact) return exact;
209
+ if (!workspace) return candidates[0] || null;
210
+
211
+ for (const session of candidates.slice(0, 12)) {
212
+ const sessionWorkspace = normalizePath(session.workspace || session.cwd);
213
+ if (!sessionWorkspace) continue;
214
+ if (await this.isLinkedWorktreeOf(workspace, sessionWorkspace)) return session;
215
+ }
216
+ return null;
217
+ }
218
+
219
+ confirm(agentId, { provider, sessionId, source, title, workspace }) {
220
+ const previousSessionId = this.agents.get(agentId)?.providerSessionId || '';
221
+ const agent = this.bindConfirmed(agentId, provider, sessionId);
222
+ if (!agent) return false;
223
+ const providerSessionTitle = String(title || '').trim().slice(0, 160);
224
+ agent.providerSessionSource = source || agent.providerSessionSource || '';
225
+ agent.providerSessionResolvedAt = Date.now();
226
+ if (typeof workspace === 'string' && workspace.trim()) {
227
+ agent.providerSessionWorkspace = normalizePath(workspace);
228
+ }
229
+ if (providerSessionTitle) agent.providerSessionTitle = providerSessionTitle;
230
+
231
+ this.stop(agentId);
232
+ this.commit(agent, {
233
+ kind: 'session-updated',
234
+ event: { agentId, provider, sessionId, previousSessionId, temporary: false },
235
+ refreshWorkspace: agent.providerSessionWorkspace || '',
236
+ });
237
+ return true;
238
+ }
239
+ }
240
+
241
+ module.exports = { ProviderSessionService };
@@ -0,0 +1,81 @@
1
+ const { runtimeKind, runtimeState } = require('./agent-runtime-binding');
2
+ const { deriveTerminalStatus } = require('./terminal-status');
3
+
4
+ const WORKING_STATES = new Set(['working', 'interrupting']);
5
+ const WAITING_STATES = new Set(['waiting-for-input', 'waiting-for-permission']);
6
+ const IDLE_STATES = new Set(['idle', 'connected', 'ready']);
7
+
8
+ function providerObservationKind(agent) {
9
+ const provider = String(agent?.providerSessionProvider || '').toLowerCase();
10
+ if (provider === 'codex' || provider === 'claude') return provider;
11
+ return provider ? 'process' : 'unknown';
12
+ }
13
+
14
+ function structuredPhase(agent) {
15
+ const state = runtimeState(agent);
16
+ if (WORKING_STATES.has(state)) return 'working';
17
+ if (WAITING_STATES.has(state)) return 'waiting';
18
+ if (IDLE_STATES.has(state)) return 'idle';
19
+ if (state === 'starting' || state === 'loading') return 'starting';
20
+ if (state === 'stopped' || state === 'dead' || state === 'exited') return 'exited';
21
+ return 'unknown';
22
+ }
23
+
24
+ function terminalStatusFor(agent) {
25
+ if (agent?.terminalStatus) return agent.terminalStatus;
26
+ return deriveTerminalStatus({
27
+ command: agent?.command,
28
+ cwd: agent?.cwd,
29
+ status: agent?.status === 'running' ? 'running' : agent?.status,
30
+ title: agent?.sessionTitle,
31
+ previewText: agent?.previewText || agent?.output,
32
+ terminalBusy: typeof agent?.terminalBusy === 'boolean' ? agent.terminalBusy : null,
33
+ shellLastEvent: agent?.shellLastEvent,
34
+ shellLastExitCode: agent?.shellLastExitCode,
35
+ shellCommand: agent?.shellCommand,
36
+ shellLastCommand: agent?.shellLastCommand,
37
+ shellCommandStartedAt: agent?.shellCommandStartedAt,
38
+ shellLastCommandStartedAt: agent?.shellLastCommandStartedAt,
39
+ shellLastCommandFinishedAt: agent?.shellLastCommandFinishedAt,
40
+ shellLastCommandDurationMs: agent?.shellLastCommandDurationMs,
41
+ });
42
+ }
43
+
44
+ function terminalPhase(agent, status) {
45
+ if (agent?.status === 'pending') return 'starting';
46
+ if (agent?.status === 'stopped' || agent?.status === 'dead' || status.activity === 'exited') return 'exited';
47
+ if (status.activity === 'busy') return 'working';
48
+ if (status.activity === 'idle') return 'idle';
49
+ return 'unknown';
50
+ }
51
+
52
+ function deriveRuntimeObservation(agent) {
53
+ const observedAt = Number(agent?.lastActivity || agent?.startedAt) || 0;
54
+ if (runtimeKind(agent) !== 'terminal') {
55
+ return {
56
+ kind: providerObservationKind(agent),
57
+ phase: agent?.status === 'pending'
58
+ ? 'starting'
59
+ : (agent?.status === 'stopped' || agent?.status === 'dead' ? 'exited' : structuredPhase(agent)),
60
+ confidence: 'authoritative',
61
+ source: 'structured-runtime',
62
+ observerVersion: 'structured-v1',
63
+ observedAt,
64
+ };
65
+ }
66
+
67
+ const status = terminalStatusFor(agent);
68
+ const shellMarker = status.source === 'shell-status-marker';
69
+ return {
70
+ kind: status.kind || 'unknown',
71
+ phase: terminalPhase(agent, status),
72
+ confidence: shellMarker ? 'high' : 'heuristic',
73
+ source: shellMarker ? 'shell-marker' : 'terminal-observer',
74
+ observerVersion: shellMarker ? 'shell-marker-v1' : 'terminal-observer-v1',
75
+ observedAt,
76
+ };
77
+ }
78
+
79
+ module.exports = {
80
+ deriveRuntimeObservation,
81
+ };