@ebowwa/coder 0.7.64 → 0.7.66
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/index.js +36233 -32
- package/dist/interfaces/ui/terminal/cli/index.js +34318 -158
- package/dist/interfaces/ui/terminal/native/README.md +53 -0
- package/dist/interfaces/ui/terminal/native/claude_code_native.darwin-x64.node +0 -0
- package/dist/interfaces/ui/terminal/native/claude_code_native.dylib +0 -0
- package/dist/interfaces/ui/terminal/native/index.d.ts +0 -0
- package/dist/interfaces/ui/terminal/native/index.darwin-arm64.node +0 -0
- package/dist/interfaces/ui/terminal/native/index.js +43 -0
- package/dist/interfaces/ui/terminal/native/index.node +0 -0
- package/dist/interfaces/ui/terminal/native/package.json +34 -0
- package/dist/native/README.md +53 -0
- package/dist/native/claude_code_native.darwin-x64.node +0 -0
- package/dist/native/claude_code_native.dylib +0 -0
- package/dist/native/index.d.ts +0 -480
- package/dist/native/index.darwin-arm64.node +0 -0
- package/dist/native/index.js +43 -1625
- package/dist/native/index.node +0 -0
- package/dist/native/package.json +34 -0
- package/native/index.darwin-arm64.node +0 -0
- package/native/index.js +33 -19
- package/package.json +3 -2
- package/packages/src/core/agent-loop/__tests__/compaction.test.ts +17 -14
- package/packages/src/core/agent-loop/compaction.ts +6 -2
- package/packages/src/core/agent-loop/index.ts +2 -0
- package/packages/src/core/agent-loop/loop-state.ts +1 -1
- package/packages/src/core/agent-loop/turn-executor.ts +4 -0
- package/packages/src/core/agent-loop/types.ts +4 -0
- package/packages/src/core/api-client-impl.ts +377 -176
- package/packages/src/core/cognitive-security/hooks.ts +2 -1
- package/packages/src/core/config/todo +7 -0
- package/packages/src/core/context/__tests__/integration.test.ts +334 -0
- package/packages/src/core/context/compaction.ts +170 -0
- package/packages/src/core/context/constants.ts +58 -0
- package/packages/src/core/context/extraction.ts +85 -0
- package/packages/src/core/context/index.ts +66 -0
- package/packages/src/core/context/summarization.ts +251 -0
- package/packages/src/core/context/token-estimation.ts +98 -0
- package/packages/src/core/context/types.ts +59 -0
- package/packages/src/core/models.ts +81 -4
- package/packages/src/core/normalizers/todo +5 -1
- package/packages/src/core/providers/README.md +230 -0
- package/packages/src/core/providers/__tests__/providers.test.ts +135 -0
- package/packages/src/core/providers/index.ts +419 -0
- package/packages/src/core/providers/types.ts +132 -0
- package/packages/src/core/retry.ts +10 -0
- package/packages/src/ecosystem/tools/index.ts +174 -0
- package/packages/src/index.ts +23 -2
- package/packages/src/interfaces/ui/index.ts +17 -20
- package/packages/src/interfaces/ui/spinner.ts +2 -2
- package/packages/src/interfaces/ui/terminal/bridge/index.ts +370 -0
- package/packages/src/interfaces/ui/terminal/bridge/ipc.ts +829 -0
- package/packages/src/interfaces/ui/terminal/bridge/screen-export.ts +968 -0
- package/packages/src/interfaces/ui/terminal/bridge/types.ts +226 -0
- package/packages/src/interfaces/ui/terminal/bridge/useBridge.ts +210 -0
- package/packages/src/interfaces/ui/terminal/cli/bootstrap.ts +132 -0
- package/packages/src/interfaces/ui/terminal/cli/index.ts +200 -13
- package/packages/src/interfaces/ui/terminal/cli/interactive/index.ts +110 -0
- package/packages/src/interfaces/ui/terminal/cli/interactive/input-handler.ts +402 -0
- package/packages/src/interfaces/ui/terminal/cli/interactive/interactive-runner.ts +820 -0
- package/packages/src/interfaces/ui/terminal/cli/interactive/message-store.ts +299 -0
- package/packages/src/interfaces/ui/terminal/cli/interactive/types.ts +274 -0
- package/packages/src/interfaces/ui/terminal/shared/index.ts +13 -0
- package/packages/src/interfaces/ui/terminal/shared/query.ts +9 -3
- package/packages/src/interfaces/ui/terminal/shared/setup.ts +5 -1
- package/packages/src/interfaces/ui/terminal/shared/spinner-frames.ts +73 -0
- package/packages/src/interfaces/ui/terminal/shared/status-line.ts +10 -2
- package/packages/src/native/index.ts +404 -27
- package/packages/src/native/tui_v2_types.ts +39 -0
- package/packages/src/teammates/coordination.test.ts +279 -0
- package/packages/src/teammates/coordination.ts +646 -0
- package/packages/src/teammates/index.ts +95 -25
- package/packages/src/teammates/integration.test.ts +272 -0
- package/packages/src/teammates/runner.test.ts +235 -0
- package/packages/src/teammates/runner.ts +750 -0
- package/packages/src/teammates/schemas.ts +673 -0
- package/packages/src/types/index.ts +1 -0
- package/packages/src/core/context-compaction.ts +0 -578
- package/packages/src/interfaces/ui/Screenshot 2026-03-02 at 9.23.10/342/200/257PM.png +0 -0
- package/packages/src/interfaces/ui/Screenshot 2026-03-03 at 10.55.11/342/200/257AM.png +0 -0
- package/packages/src/interfaces/ui/terminal/tui/HelpPanel.tsx +0 -262
- package/packages/src/interfaces/ui/terminal/tui/InputContext.tsx +0 -232
- package/packages/src/interfaces/ui/terminal/tui/InputField.tsx +0 -62
- package/packages/src/interfaces/ui/terminal/tui/InteractiveTUI.tsx +0 -537
- package/packages/src/interfaces/ui/terminal/tui/MessageArea.tsx +0 -107
- package/packages/src/interfaces/ui/terminal/tui/MessageStore.tsx +0 -240
- package/packages/src/interfaces/ui/terminal/tui/StatusBar.tsx +0 -54
- package/packages/src/interfaces/ui/terminal/tui/commands.ts +0 -438
- package/packages/src/interfaces/ui/terminal/tui/components/InteractiveElements.tsx +0 -584
- package/packages/src/interfaces/ui/terminal/tui/components/MultilineInput.tsx +0 -614
- package/packages/src/interfaces/ui/terminal/tui/components/PaneManager.tsx +0 -333
- package/packages/src/interfaces/ui/terminal/tui/components/Sidebar.tsx +0 -604
- package/packages/src/interfaces/ui/terminal/tui/components/index.ts +0 -118
- package/packages/src/interfaces/ui/terminal/tui/console.ts +0 -49
- package/packages/src/interfaces/ui/terminal/tui/index.ts +0 -90
- package/packages/src/interfaces/ui/terminal/tui/run.tsx +0 -42
- package/packages/src/interfaces/ui/terminal/tui/spinner.ts +0 -69
- package/packages/src/interfaces/ui/terminal/tui/tui-app.tsx +0 -390
- package/packages/src/interfaces/ui/terminal/tui/tui-footer.ts +0 -422
- package/packages/src/interfaces/ui/terminal/tui/types.ts +0 -186
- package/packages/src/interfaces/ui/terminal/tui/useInputHandler.ts +0 -104
- package/packages/src/interfaces/ui/terminal/tui/useNativeInput.ts +0 -239
|
@@ -0,0 +1,750 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TeammateModeRunner - Manages teammate mode lifecycle
|
|
3
|
+
*
|
|
4
|
+
* Responsibilities:
|
|
5
|
+
* - Load team config from TeammateManager
|
|
6
|
+
* - Poll for incoming messages from file-based inbox
|
|
7
|
+
* - Inject messages into conversation via callback
|
|
8
|
+
* - Report idle status to team
|
|
9
|
+
* - Coordinate with task system
|
|
10
|
+
* - File locking and progress reporting
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { TeammateManager, generateTeammateId } from "./index.js";
|
|
14
|
+
import {
|
|
15
|
+
CoordinationManager,
|
|
16
|
+
type CoordinationConfig,
|
|
17
|
+
type CoordinationEvent,
|
|
18
|
+
type ProgressReport,
|
|
19
|
+
type FileClaim,
|
|
20
|
+
type CoordinationEventType,
|
|
21
|
+
} from "./coordination.js";
|
|
22
|
+
import type { Teammate, Team, TeammateMessage, TeammateStatus } from "../types/index.js";
|
|
23
|
+
|
|
24
|
+
// ============================================
|
|
25
|
+
// TYPES
|
|
26
|
+
// ============================================
|
|
27
|
+
|
|
28
|
+
export interface TeammateModeConfig {
|
|
29
|
+
/** Team name to join */
|
|
30
|
+
teamName: string;
|
|
31
|
+
/** Agent ID (if resuming) */
|
|
32
|
+
agentId?: string;
|
|
33
|
+
/** Agent display name */
|
|
34
|
+
agentName?: string;
|
|
35
|
+
/** Agent color for UI */
|
|
36
|
+
agentColor?: string;
|
|
37
|
+
/** Prompt/instructions for this teammate */
|
|
38
|
+
prompt?: string;
|
|
39
|
+
/** Working directory */
|
|
40
|
+
workingDirectory: string;
|
|
41
|
+
/** Message injection callback */
|
|
42
|
+
onMessage?: (message: TeammateMessage) => void;
|
|
43
|
+
/** Idle callback */
|
|
44
|
+
onIdle?: () => void;
|
|
45
|
+
/** Polling interval in ms (default: 2000) */
|
|
46
|
+
pollInterval?: number;
|
|
47
|
+
/** Coordination configuration */
|
|
48
|
+
coordination?: CoordinationConfig;
|
|
49
|
+
/** Callback when another teammate reports progress */
|
|
50
|
+
onTeammateProgress?: (teammateId: string, progress: ProgressReport) => void;
|
|
51
|
+
/** Callback when a file is claimed by another teammate */
|
|
52
|
+
onFileClaimed?: (claim: FileClaim) => void;
|
|
53
|
+
/** Callback when a file is released */
|
|
54
|
+
onFileReleased?: (filePath: string, teammateId: string) => void;
|
|
55
|
+
/** Callback for any coordination event */
|
|
56
|
+
onCoordinationEvent?: (event: CoordinationEvent) => void;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface TeammateModeState {
|
|
60
|
+
/** Whether teammate mode is active */
|
|
61
|
+
active: boolean;
|
|
62
|
+
/** Current teammate instance */
|
|
63
|
+
teammate: Teammate | null;
|
|
64
|
+
/** Team instance */
|
|
65
|
+
team: Team | null;
|
|
66
|
+
/** Message polling timer */
|
|
67
|
+
pollTimer: Timer | null;
|
|
68
|
+
/** Last activity timestamp */
|
|
69
|
+
lastActivity: number;
|
|
70
|
+
/** Current status */
|
|
71
|
+
status: TeammateStatus;
|
|
72
|
+
/** Pending messages waiting to be processed */
|
|
73
|
+
pendingMessages: TeammateMessage[];
|
|
74
|
+
/** Files currently claimed by this teammate */
|
|
75
|
+
claimedFiles: Set<string>;
|
|
76
|
+
/** Current progress report */
|
|
77
|
+
currentProgress?: ProgressReport;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ============================================
|
|
81
|
+
// TEAMMATE MODE RUNNER
|
|
82
|
+
// ============================================
|
|
83
|
+
|
|
84
|
+
export class TeammateModeRunner {
|
|
85
|
+
private manager: TeammateManager;
|
|
86
|
+
private coordination: CoordinationManager;
|
|
87
|
+
private config: TeammateModeConfig;
|
|
88
|
+
private state: TeammateModeState;
|
|
89
|
+
private idleCheckTimer: Timer | null = null;
|
|
90
|
+
private readonly IDLE_TIMEOUT = 60000; // 60 seconds of no activity = idle
|
|
91
|
+
|
|
92
|
+
constructor(config: TeammateModeConfig) {
|
|
93
|
+
this.config = config;
|
|
94
|
+
this.manager = new TeammateManager();
|
|
95
|
+
this.coordination = new CoordinationManager(
|
|
96
|
+
this.manager,
|
|
97
|
+
config.coordination || {}
|
|
98
|
+
);
|
|
99
|
+
this.state = {
|
|
100
|
+
active: false,
|
|
101
|
+
teammate: null,
|
|
102
|
+
team: null,
|
|
103
|
+
pollTimer: null,
|
|
104
|
+
lastActivity: Date.now(),
|
|
105
|
+
status: "pending",
|
|
106
|
+
pendingMessages: [],
|
|
107
|
+
claimedFiles: new Set(),
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
// Set up coordination callbacks
|
|
111
|
+
this.setupCoordinationCallbacks();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Set up coordination event callbacks
|
|
116
|
+
*/
|
|
117
|
+
private setupCoordinationCallbacks(): void {
|
|
118
|
+
const coordinationConfig = this.config.coordination || {};
|
|
119
|
+
|
|
120
|
+
// Merge user callbacks with internal handling
|
|
121
|
+
this.coordination = new CoordinationManager(this.manager, {
|
|
122
|
+
...coordinationConfig,
|
|
123
|
+
onProgress: (teammateId, progress) => {
|
|
124
|
+
if (this.config.onTeammateProgress) {
|
|
125
|
+
this.config.onTeammateProgress(teammateId, progress);
|
|
126
|
+
}
|
|
127
|
+
if (coordinationConfig.onProgress) {
|
|
128
|
+
coordinationConfig.onProgress(teammateId, progress);
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
onFileClaimed: (claim) => {
|
|
132
|
+
if (this.config.onFileClaimed) {
|
|
133
|
+
this.config.onFileClaimed(claim);
|
|
134
|
+
}
|
|
135
|
+
if (coordinationConfig.onFileClaimed) {
|
|
136
|
+
coordinationConfig.onFileClaimed(claim);
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
onFileReleased: (filePath, teammateId) => {
|
|
140
|
+
if (this.config.onFileReleased) {
|
|
141
|
+
this.config.onFileReleased(filePath, teammateId);
|
|
142
|
+
}
|
|
143
|
+
if (coordinationConfig.onFileReleased) {
|
|
144
|
+
coordinationConfig.onFileReleased(filePath, teammateId);
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
onCoordinationEvent: (event) => {
|
|
148
|
+
if (this.config.onCoordinationEvent) {
|
|
149
|
+
this.config.onCoordinationEvent(event);
|
|
150
|
+
}
|
|
151
|
+
if (coordinationConfig.onCoordinationEvent) {
|
|
152
|
+
coordinationConfig.onCoordinationEvent(event);
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// ============================================
|
|
159
|
+
// LIFECYCLE
|
|
160
|
+
// ============================================
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Start teammate mode
|
|
164
|
+
* Creates or resumes teammate, joins team, starts polling
|
|
165
|
+
*/
|
|
166
|
+
async start(): Promise<Teammate> {
|
|
167
|
+
if (this.state.active) {
|
|
168
|
+
throw new Error("Teammate mode already active");
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Load or create team
|
|
172
|
+
let team = this.manager.getTeam(this.config.teamName);
|
|
173
|
+
if (!team) {
|
|
174
|
+
// Create team if it doesn't exist
|
|
175
|
+
team = this.manager.createTeam({
|
|
176
|
+
name: this.config.teamName,
|
|
177
|
+
description: `Team ${this.config.teamName}`,
|
|
178
|
+
teammates: [],
|
|
179
|
+
taskListId: `${this.config.teamName}-tasks`,
|
|
180
|
+
coordination: {
|
|
181
|
+
dependencyOrder: [],
|
|
182
|
+
communicationProtocol: "broadcast",
|
|
183
|
+
taskAssignmentStrategy: "manual",
|
|
184
|
+
},
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
this.state.team = team;
|
|
188
|
+
|
|
189
|
+
// Create or resume teammate
|
|
190
|
+
let teammate: Teammate;
|
|
191
|
+
if (this.config.agentId) {
|
|
192
|
+
// Resume existing teammate
|
|
193
|
+
const existing = this.manager.getTeammate(this.config.agentId);
|
|
194
|
+
if (!existing) {
|
|
195
|
+
throw new Error(`Teammate not found: ${this.config.agentId}`);
|
|
196
|
+
}
|
|
197
|
+
teammate = existing;
|
|
198
|
+
} else {
|
|
199
|
+
// Create new teammate
|
|
200
|
+
teammate = {
|
|
201
|
+
teammateId: this.config.agentId || generateTeammateId(),
|
|
202
|
+
name: this.config.agentName || `agent-${Date.now().toString(36)}`,
|
|
203
|
+
teamName: this.config.teamName,
|
|
204
|
+
color: this.config.agentColor || "blue",
|
|
205
|
+
prompt: this.config.prompt || "",
|
|
206
|
+
planModeRequired: false,
|
|
207
|
+
insideTmux: !!process.env.TMUX,
|
|
208
|
+
status: "pending",
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
// Add to team
|
|
212
|
+
this.manager.addTeammate(this.config.teamName, teammate);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
this.state.teammate = teammate;
|
|
216
|
+
this.state.active = true;
|
|
217
|
+
this.state.status = "in_progress";
|
|
218
|
+
this.state.lastActivity = Date.now();
|
|
219
|
+
|
|
220
|
+
// Update status
|
|
221
|
+
this.manager.updateTeammateStatus(teammate.teammateId, "in_progress");
|
|
222
|
+
|
|
223
|
+
// Initialize coordination
|
|
224
|
+
this.coordination.initialize(teammate.teammateId, this.config.teamName);
|
|
225
|
+
|
|
226
|
+
// Start message polling
|
|
227
|
+
this.startPolling();
|
|
228
|
+
|
|
229
|
+
// Start idle check
|
|
230
|
+
this.startIdleCheck();
|
|
231
|
+
|
|
232
|
+
// Broadcast join
|
|
233
|
+
this.broadcast(`Teammate ${teammate.name} joined the team`);
|
|
234
|
+
|
|
235
|
+
return teammate;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Stop teammate mode
|
|
240
|
+
* Stops polling, updates status, cleans up
|
|
241
|
+
*/
|
|
242
|
+
async stop(): Promise<void> {
|
|
243
|
+
if (!this.state.active) return;
|
|
244
|
+
|
|
245
|
+
// Stop polling
|
|
246
|
+
this.stopPolling();
|
|
247
|
+
this.stopIdleCheck();
|
|
248
|
+
|
|
249
|
+
// Release all file claims
|
|
250
|
+
this.releaseAllFiles();
|
|
251
|
+
|
|
252
|
+
// Shutdown coordination
|
|
253
|
+
this.coordination.shutdown();
|
|
254
|
+
|
|
255
|
+
// Update status to idle
|
|
256
|
+
if (this.state.teammate) {
|
|
257
|
+
this.manager.updateTeammateStatus(this.state.teammate.teammateId, "idle");
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Persist team state
|
|
261
|
+
if (this.state.team) {
|
|
262
|
+
await this.manager.persistAllTeams();
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
this.state.active = false;
|
|
266
|
+
this.state.status = "idle";
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// ============================================
|
|
270
|
+
// MESSAGE POLLING
|
|
271
|
+
// ============================================
|
|
272
|
+
|
|
273
|
+
private startPolling(): void {
|
|
274
|
+
if (this.state.pollTimer) return;
|
|
275
|
+
|
|
276
|
+
const interval = this.config.pollInterval || 2000;
|
|
277
|
+
|
|
278
|
+
this.state.pollTimer = setInterval(() => {
|
|
279
|
+
this.pollMessages();
|
|
280
|
+
}, interval);
|
|
281
|
+
|
|
282
|
+
// Also poll immediately
|
|
283
|
+
this.pollMessages();
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
private stopPolling(): void {
|
|
287
|
+
if (this.state.pollTimer) {
|
|
288
|
+
clearInterval(this.state.pollTimer);
|
|
289
|
+
this.state.pollTimer = null;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
private pollMessages(): void {
|
|
294
|
+
if (!this.state.teammate) return;
|
|
295
|
+
|
|
296
|
+
// Check for new messages
|
|
297
|
+
const messages = this.manager.getMessages(this.state.teammate.teammateId);
|
|
298
|
+
|
|
299
|
+
for (const message of messages) {
|
|
300
|
+
// Track activity
|
|
301
|
+
this.state.lastActivity = Date.now();
|
|
302
|
+
|
|
303
|
+
// Add to pending
|
|
304
|
+
this.state.pendingMessages.push(message);
|
|
305
|
+
|
|
306
|
+
// Notify via callback
|
|
307
|
+
if (this.config.onMessage) {
|
|
308
|
+
this.config.onMessage(message);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Get pending messages and clear queue
|
|
315
|
+
*/
|
|
316
|
+
getPendingMessages(): TeammateMessage[] {
|
|
317
|
+
const messages = [...this.state.pendingMessages];
|
|
318
|
+
this.state.pendingMessages = [];
|
|
319
|
+
return messages;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Check if there are pending messages
|
|
324
|
+
*/
|
|
325
|
+
hasPendingMessages(): boolean {
|
|
326
|
+
return this.state.pendingMessages.length > 0;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Peek at pending messages without clearing
|
|
331
|
+
*/
|
|
332
|
+
peekPendingMessages(): TeammateMessage[] {
|
|
333
|
+
return [...this.state.pendingMessages];
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// ============================================
|
|
337
|
+
// IDLE DETECTION
|
|
338
|
+
// ============================================
|
|
339
|
+
|
|
340
|
+
private startIdleCheck(): void {
|
|
341
|
+
if (this.idleCheckTimer) return;
|
|
342
|
+
|
|
343
|
+
this.idleCheckTimer = setInterval(() => {
|
|
344
|
+
this.checkIdle();
|
|
345
|
+
}, 10000); // Check every 10 seconds
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
private stopIdleCheck(): void {
|
|
349
|
+
if (this.idleCheckTimer) {
|
|
350
|
+
clearInterval(this.idleCheckTimer);
|
|
351
|
+
this.idleCheckTimer = null;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
private checkIdle(): void {
|
|
356
|
+
if (!this.state.teammate) return;
|
|
357
|
+
|
|
358
|
+
const now = Date.now();
|
|
359
|
+
const timeSinceActivity = now - this.state.lastActivity;
|
|
360
|
+
|
|
361
|
+
if (timeSinceActivity > this.IDLE_TIMEOUT && this.state.status !== "idle") {
|
|
362
|
+
// Transition to idle
|
|
363
|
+
this.state.status = "idle";
|
|
364
|
+
this.manager.updateTeammateStatus(this.state.teammate.teammateId, "idle");
|
|
365
|
+
|
|
366
|
+
// Notify via callback
|
|
367
|
+
if (this.config.onIdle) {
|
|
368
|
+
this.config.onIdle();
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Report activity (resets idle timer)
|
|
375
|
+
*/
|
|
376
|
+
reportActivity(): void {
|
|
377
|
+
this.state.lastActivity = Date.now();
|
|
378
|
+
|
|
379
|
+
if (this.state.status !== "in_progress" && this.state.teammate) {
|
|
380
|
+
this.state.status = "in_progress";
|
|
381
|
+
this.manager.updateTeammateStatus(this.state.teammate.teammateId, "in_progress");
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// ============================================
|
|
386
|
+
// MESSAGING
|
|
387
|
+
// ============================================
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Send a direct message to another teammate
|
|
391
|
+
*/
|
|
392
|
+
sendDirectMessage(toId: string, content: string): void {
|
|
393
|
+
if (!this.state.teammate) {
|
|
394
|
+
throw new Error("Teammate mode not active");
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
this.manager.sendDirect(toId, this.state.teammate.teammateId, content);
|
|
398
|
+
this.reportActivity();
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Broadcast a message to all teammates
|
|
403
|
+
*/
|
|
404
|
+
broadcast(content: string): void {
|
|
405
|
+
if (!this.state.teammate) {
|
|
406
|
+
throw new Error("Teammate mode not active");
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
this.manager.broadcast(this.config.teamName, content, this.state.teammate.teammateId);
|
|
410
|
+
this.reportActivity();
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Inject a user message (for external integration)
|
|
415
|
+
*/
|
|
416
|
+
injectUserMessage(content: string): void {
|
|
417
|
+
if (!this.state.teammate) {
|
|
418
|
+
throw new Error("Teammate mode not active");
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
this.manager.injectUserMessageToTeammate(this.state.teammate.teammateId, content);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// ============================================
|
|
425
|
+
// STATUS & INFO
|
|
426
|
+
// ============================================
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Get current teammate info
|
|
430
|
+
*/
|
|
431
|
+
getTeammate(): Teammate | null {
|
|
432
|
+
return this.state.teammate;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Get team info
|
|
437
|
+
*/
|
|
438
|
+
getTeam(): Team | null {
|
|
439
|
+
return this.state.team;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Get current status
|
|
444
|
+
*/
|
|
445
|
+
getStatus(): TeammateStatus {
|
|
446
|
+
return this.state.status;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* Update status
|
|
451
|
+
*/
|
|
452
|
+
updateStatus(status: TeammateStatus): void {
|
|
453
|
+
this.state.status = status;
|
|
454
|
+
if (this.state.teammate) {
|
|
455
|
+
this.manager.updateTeammateStatus(this.state.teammate.teammateId, status);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Check if teammate mode is active
|
|
461
|
+
*/
|
|
462
|
+
isActive(): boolean {
|
|
463
|
+
return this.state.active;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Get inbox statistics
|
|
468
|
+
*/
|
|
469
|
+
getInboxStats(): { pending: number; processed: number } {
|
|
470
|
+
if (!this.state.teammate) {
|
|
471
|
+
return { pending: 0, processed: 0 };
|
|
472
|
+
}
|
|
473
|
+
return this.manager.getInboxStats(this.state.teammate.teammateId);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// ============================================
|
|
477
|
+
// TEAM OPERATIONS
|
|
478
|
+
// ============================================
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* Get all teammates in the team
|
|
482
|
+
*/
|
|
483
|
+
getTeamMembers(): Teammate[] {
|
|
484
|
+
if (!this.state.team) return [];
|
|
485
|
+
return this.state.team.teammates;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Wait for all teammates to become idle
|
|
490
|
+
*/
|
|
491
|
+
async waitForTeamIdle(options?: { timeout?: number; pollInterval?: number }): Promise<{
|
|
492
|
+
success: boolean;
|
|
493
|
+
timedOut: boolean;
|
|
494
|
+
statuses: Record<string, TeammateStatus>;
|
|
495
|
+
}> {
|
|
496
|
+
if (!this.state.team) {
|
|
497
|
+
return { success: false, timedOut: false, statuses: {} };
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
return this.manager.waitForTeammatesToBecomeIdle(this.config.teamName, options);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// ============================================
|
|
504
|
+
// COORDINATION - PROGRESS REPORTING
|
|
505
|
+
// ============================================
|
|
506
|
+
|
|
507
|
+
/**
|
|
508
|
+
* Report progress to teammates
|
|
509
|
+
* This broadcasts progress information to all team members
|
|
510
|
+
*/
|
|
511
|
+
reportProgress(progress: ProgressReport): void {
|
|
512
|
+
if (!this.state.active) return;
|
|
513
|
+
|
|
514
|
+
this.state.currentProgress = progress;
|
|
515
|
+
this.coordination.reportProgress(progress);
|
|
516
|
+
this.reportActivity();
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/**
|
|
520
|
+
* Report that you're blocked on something
|
|
521
|
+
*/
|
|
522
|
+
reportBlocked(reason: string, blockedBy?: string): void {
|
|
523
|
+
if (!this.state.active) return;
|
|
524
|
+
|
|
525
|
+
this.updateStatus("pending");
|
|
526
|
+
this.coordination.reportBlocked(reason, blockedBy);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/**
|
|
530
|
+
* Report that you're unblocked
|
|
531
|
+
*/
|
|
532
|
+
reportUnblocked(): void {
|
|
533
|
+
if (!this.state.active) return;
|
|
534
|
+
|
|
535
|
+
this.updateStatus("in_progress");
|
|
536
|
+
this.coordination.reportUnblocked();
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* Get progress reports from all teammates
|
|
541
|
+
*/
|
|
542
|
+
getTeammateProgress(): Map<string, ProgressReport> {
|
|
543
|
+
// This would require storing progress in TeammateManager
|
|
544
|
+
// For now, return empty map
|
|
545
|
+
return new Map();
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// ============================================
|
|
549
|
+
// COORDINATION - FILE LOCKING
|
|
550
|
+
// ============================================
|
|
551
|
+
|
|
552
|
+
/**
|
|
553
|
+
* Claim a file for exclusive editing
|
|
554
|
+
* Returns true if claim was successful
|
|
555
|
+
*/
|
|
556
|
+
claimFile(filePath: string, reason?: string, expiresIn?: number): boolean {
|
|
557
|
+
if (!this.state.active) {
|
|
558
|
+
throw new Error("Teammate mode not active");
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
const claimed = this.coordination.claimFile(filePath, reason, expiresIn);
|
|
562
|
+
if (claimed) {
|
|
563
|
+
this.state.claimedFiles.add(filePath);
|
|
564
|
+
}
|
|
565
|
+
return claimed;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
/**
|
|
569
|
+
* Release a file claim
|
|
570
|
+
*/
|
|
571
|
+
releaseFile(filePath: string): void {
|
|
572
|
+
if (!this.state.active) return;
|
|
573
|
+
|
|
574
|
+
this.coordination.releaseFile(filePath);
|
|
575
|
+
this.state.claimedFiles.delete(filePath);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
/**
|
|
579
|
+
* Release all file claims
|
|
580
|
+
*/
|
|
581
|
+
releaseAllFiles(): void {
|
|
582
|
+
if (!this.state.active) return;
|
|
583
|
+
|
|
584
|
+
for (const file of this.state.claimedFiles) {
|
|
585
|
+
this.coordination.releaseFile(file);
|
|
586
|
+
}
|
|
587
|
+
this.state.claimedFiles.clear();
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
/**
|
|
591
|
+
* Check if a file is claimed by another teammate
|
|
592
|
+
*/
|
|
593
|
+
isFileClaimed(filePath: string): boolean {
|
|
594
|
+
return this.coordination.isFileClaimed(filePath);
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
/**
|
|
598
|
+
* Get the claim on a file (if any)
|
|
599
|
+
*/
|
|
600
|
+
getFileClaim(filePath: string): FileClaim | undefined {
|
|
601
|
+
return this.coordination.getFileClaim(filePath);
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
/**
|
|
605
|
+
* Get all files claimed by this teammate
|
|
606
|
+
*/
|
|
607
|
+
getClaimedFiles(): string[] {
|
|
608
|
+
return Array.from(this.state.claimedFiles);
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
/**
|
|
612
|
+
* Get all claims across the team
|
|
613
|
+
*/
|
|
614
|
+
getAllClaims(): FileClaim[] {
|
|
615
|
+
return this.coordination.getAllClaims();
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
/**
|
|
619
|
+
* Attempt to claim a file, wait if blocked
|
|
620
|
+
*/
|
|
621
|
+
async waitForFileClaim(
|
|
622
|
+
filePath: string,
|
|
623
|
+
options?: {
|
|
624
|
+
timeout?: number;
|
|
625
|
+
pollInterval?: number;
|
|
626
|
+
reason?: string;
|
|
627
|
+
}
|
|
628
|
+
): Promise<boolean> {
|
|
629
|
+
const { timeout = 30000, pollInterval = 1000, reason } = options || {};
|
|
630
|
+
const startTime = Date.now();
|
|
631
|
+
|
|
632
|
+
while (Date.now() - startTime < timeout) {
|
|
633
|
+
if (this.claimFile(filePath, reason)) {
|
|
634
|
+
return true;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
// Check if current claimant has finished
|
|
638
|
+
const claim = this.getFileClaim(filePath);
|
|
639
|
+
if (claim) {
|
|
640
|
+
// Wait and retry
|
|
641
|
+
await new Promise(resolve => setTimeout(resolve, pollInterval));
|
|
642
|
+
} else {
|
|
643
|
+
// Try again immediately
|
|
644
|
+
if (this.claimFile(filePath, reason)) {
|
|
645
|
+
return true;
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
return false;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
// ============================================
|
|
654
|
+
// COORDINATION - UTILITIES
|
|
655
|
+
// ============================================
|
|
656
|
+
|
|
657
|
+
/**
|
|
658
|
+
* Check if we should work on a file (not claimed by others)
|
|
659
|
+
*/
|
|
660
|
+
canWorkOnFile(filePath: string): boolean {
|
|
661
|
+
if (this.isFileClaimed(filePath)) {
|
|
662
|
+
const claim = this.getFileClaim(filePath);
|
|
663
|
+
// Can work if we're the claimant
|
|
664
|
+
return claim?.teammateId === this.state.teammate?.teammateId;
|
|
665
|
+
}
|
|
666
|
+
return true;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
/**
|
|
670
|
+
* Claim multiple files atomically
|
|
671
|
+
* Returns true if all claims were successful
|
|
672
|
+
*/
|
|
673
|
+
claimFiles(files: string[], reason?: string): boolean {
|
|
674
|
+
const claimed: string[] = [];
|
|
675
|
+
|
|
676
|
+
for (const file of files) {
|
|
677
|
+
if (this.claimFile(file, reason)) {
|
|
678
|
+
claimed.push(file);
|
|
679
|
+
} else {
|
|
680
|
+
// Rollback - release all claimed files
|
|
681
|
+
for (const f of claimed) {
|
|
682
|
+
this.releaseFile(f);
|
|
683
|
+
}
|
|
684
|
+
return false;
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
return true;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
// ============================================
|
|
692
|
+
// TASK INTEGRATION
|
|
693
|
+
// ============================================
|
|
694
|
+
|
|
695
|
+
/**
|
|
696
|
+
* Report task completion
|
|
697
|
+
*/
|
|
698
|
+
reportTaskComplete(taskId: string, taskSubject: string): void {
|
|
699
|
+
// Broadcast first, then update status (broadcast calls reportActivity which resets to in_progress)
|
|
700
|
+
this.broadcast(`Task completed: ${taskSubject} (${taskId})`);
|
|
701
|
+
this.updateStatus("completed");
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
/**
|
|
705
|
+
* Report task failure
|
|
706
|
+
*/
|
|
707
|
+
reportTaskFailed(taskId: string, taskSubject: string, error: string): void {
|
|
708
|
+
// Broadcast first, then update status
|
|
709
|
+
this.broadcast(`Task failed: ${taskSubject} (${taskId}) - ${error}`);
|
|
710
|
+
this.updateStatus("failed");
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
/**
|
|
714
|
+
* Request task assignment
|
|
715
|
+
*/
|
|
716
|
+
requestTask(): void {
|
|
717
|
+
// Update status first, then broadcast (broadcast would reset to in_progress)
|
|
718
|
+
this.updateStatus("idle");
|
|
719
|
+
this.broadcast("Ready for task assignment");
|
|
720
|
+
// Ensure status stays idle after broadcast
|
|
721
|
+
this.state.status = "idle";
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
// ============================================
|
|
726
|
+
// SINGLETON FOR GLOBAL ACCESS
|
|
727
|
+
// ============================================
|
|
728
|
+
|
|
729
|
+
let globalRunner: TeammateModeRunner | null = null;
|
|
730
|
+
|
|
731
|
+
/**
|
|
732
|
+
* Get the global teammate mode runner (if active)
|
|
733
|
+
*/
|
|
734
|
+
export function getTeammateRunner(): TeammateModeRunner | null {
|
|
735
|
+
return globalRunner;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
/**
|
|
739
|
+
* Set the global teammate mode runner
|
|
740
|
+
*/
|
|
741
|
+
export function setTeammateRunner(runner: TeammateModeRunner | null): void {
|
|
742
|
+
globalRunner = runner;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
/**
|
|
746
|
+
* Check if teammate mode is globally active
|
|
747
|
+
*/
|
|
748
|
+
export function isTeammateModeActive(): boolean {
|
|
749
|
+
return globalRunner !== null && globalRunner.isActive();
|
|
750
|
+
}
|