fecode-cli 1.0.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.
Files changed (74) hide show
  1. package/dist/App.d.ts +33 -0
  2. package/dist/App.js +2224 -0
  3. package/dist/approvalResolver.d.ts +12 -0
  4. package/dist/approvalResolver.js +112 -0
  5. package/dist/commands.d.ts +11 -0
  6. package/dist/commands.js +33 -0
  7. package/dist/index.d.ts +3 -0
  8. package/dist/index.js +15175 -0
  9. package/dist/ui/AppShell.d.ts +14 -0
  10. package/dist/ui/AppShell.js +25 -0
  11. package/dist/ui/ApprovalPrompt.d.ts +31 -0
  12. package/dist/ui/ApprovalPrompt.js +55 -0
  13. package/dist/ui/BlockedView.d.ts +13 -0
  14. package/dist/ui/BlockedView.js +7 -0
  15. package/dist/ui/CommandPalette.d.ts +8 -0
  16. package/dist/ui/CommandPalette.js +18 -0
  17. package/dist/ui/CurrentStepView.d.ts +16 -0
  18. package/dist/ui/CurrentStepView.js +28 -0
  19. package/dist/ui/DiagnosticsView.d.ts +45 -0
  20. package/dist/ui/DiagnosticsView.js +23 -0
  21. package/dist/ui/ExecutionTimeline.d.ts +15 -0
  22. package/dist/ui/ExecutionTimeline.js +51 -0
  23. package/dist/ui/ExecutionView.d.ts +42 -0
  24. package/dist/ui/ExecutionView.js +20 -0
  25. package/dist/ui/Header.d.ts +15 -0
  26. package/dist/ui/Header.js +47 -0
  27. package/dist/ui/HelpView.d.ts +3 -0
  28. package/dist/ui/HelpView.js +35 -0
  29. package/dist/ui/MessageBubble.d.ts +9 -0
  30. package/dist/ui/MessageBubble.js +58 -0
  31. package/dist/ui/PlanStep.d.ts +16 -0
  32. package/dist/ui/PlanStep.js +71 -0
  33. package/dist/ui/PlanView.d.ts +26 -0
  34. package/dist/ui/PlanView.js +24 -0
  35. package/dist/ui/ProgressBar.d.ts +10 -0
  36. package/dist/ui/ProgressBar.js +14 -0
  37. package/dist/ui/RecoveryView.d.ts +20 -0
  38. package/dist/ui/RecoveryView.js +19 -0
  39. package/dist/ui/ReplanView.d.ts +16 -0
  40. package/dist/ui/ReplanView.js +21 -0
  41. package/dist/ui/ResumeView.d.ts +15 -0
  42. package/dist/ui/ResumeView.js +25 -0
  43. package/dist/ui/RiskNotice.d.ts +10 -0
  44. package/dist/ui/RiskNotice.js +21 -0
  45. package/dist/ui/RunHistoryView.d.ts +15 -0
  46. package/dist/ui/RunHistoryView.js +39 -0
  47. package/dist/ui/StatusBar.d.ts +16 -0
  48. package/dist/ui/StatusBar.js +104 -0
  49. package/dist/ui/TaskInput.d.ts +15 -0
  50. package/dist/ui/TaskInput.js +8 -0
  51. package/dist/ui/ThinkingBlock.d.ts +8 -0
  52. package/dist/ui/ThinkingBlock.js +13 -0
  53. package/dist/ui/ThinkingIndicator.d.ts +7 -0
  54. package/dist/ui/ThinkingIndicator.js +20 -0
  55. package/dist/ui/TurnView.d.ts +13 -0
  56. package/dist/ui/TurnView.js +11 -0
  57. package/dist/ui/WorkspaceStatus.d.ts +20 -0
  58. package/dist/ui/WorkspaceStatus.js +23 -0
  59. package/dist/ui/index.d.ts +26 -0
  60. package/dist/ui/index.js +26 -0
  61. package/package.json +42 -0
  62. package/skills/accessibility/SKILL.md +98 -0
  63. package/skills/css/SKILL.md +90 -0
  64. package/skills/frontend-debugging/SKILL.md +201 -0
  65. package/skills/frontend-design/SKILL.md +358 -0
  66. package/skills/frontend-performance/SKILL.md +89 -0
  67. package/skills/frontend-testing/SKILL.md +87 -0
  68. package/skills/nextjs/SKILL.md +75 -0
  69. package/skills/react/SKILL.md +213 -0
  70. package/skills/responsive-design/SKILL.md +190 -0
  71. package/skills/svelte/SKILL.md +86 -0
  72. package/skills/tailwind/SKILL.md +74 -0
  73. package/skills/ui-review/SKILL.md +245 -0
  74. package/skills/vue/SKILL.md +72 -0
package/dist/App.js ADDED
@@ -0,0 +1,2224 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState, useCallback, useEffect, useRef } from "react";
3
+ import { Box, Text, useApp, useInput } from "ink";
4
+ import { DefaultSessionStore, SessionHistoryFormatter, DefaultGitRepository, GitStatusFormatter, DefaultCheckpointManager, CheckpointFormatter, DefaultRecoveryManager, RecoveryFormatter, DefaultTaskRiskPolicy, formatRunDiagnostics, RunHistoryFormatter, PlanFormatter, transitionPlanStatus, DefaultProductRuntime, createInitialUIState, reduceUIState } from "@fecode/agent";
5
+ import { filterCommands } from "./commands.js";
6
+ import { AppShell, Header, StatusBar, TaskInput, ThinkingIndicator, TurnView, PlanView, CurrentStepView, WorkspaceStatus, ApprovalPrompt, BlockedView, RecoveryView, ReplanView, ResumeView, DiagnosticsView, RunHistoryView, HelpView } from "./ui/index.js";
7
+ export const App = ({ agent, productRuntime: runtimeProp, cwd = process.cwd(), providerName, modelName, approvalResolver, onExit, configError, projectContext: _projectContext, sessionStore, initialSessionData, gitRepository: gitRepoProp, checkpointManager: checkpointManagerProp, recoveryManager: recoveryManagerProp, executionPolicy: executionPolicyProp }) => {
8
+ const gitRepo = gitRepoProp || new DefaultGitRepository();
9
+ const cpManager = checkpointManagerProp ||
10
+ (agent && "getCheckpointManager" in agent && typeof agent.getCheckpointManager === "function"
11
+ ? agent.getCheckpointManager()
12
+ : new DefaultCheckpointManager(undefined, gitRepo));
13
+ const recManager = recoveryManagerProp ||
14
+ (agent && "getRecoveryManager" in agent && typeof agent.getRecoveryManager === "function"
15
+ ? agent.getRecoveryManager()
16
+ : new DefaultRecoveryManager(undefined, gitRepo));
17
+ const _execPolicy = executionPolicyProp ||
18
+ (agent && "getExecutionPolicy" in agent && typeof agent.getExecutionPolicy === "function"
19
+ ? agent.getExecutionPolicy()
20
+ : new DefaultTaskRiskPolicy());
21
+ void _execPolicy;
22
+ void _projectContext;
23
+ // Instantiate or use ProductRuntime
24
+ const [runtime] = useState(() => {
25
+ if (runtimeProp)
26
+ return runtimeProp;
27
+ if (agent && "run" in agent && "assessTaskRisk" in agent) {
28
+ return new DefaultProductRuntime({
29
+ agentRuntime: agent,
30
+ gitRepository: gitRepo,
31
+ approvalResolver,
32
+ initialCwd: cwd,
33
+ initialSessionId: initialSessionData?.sessionId
34
+ });
35
+ }
36
+ return undefined;
37
+ });
38
+ const { exit } = useApp();
39
+ const [query, setQuery] = useState("");
40
+ const [activeView, setActiveView] = useState("main");
41
+ const [store] = useState(() => sessionStore || new DefaultSessionStore());
42
+ const [sessionId] = useState(() => initialSessionData?.sessionId ||
43
+ `session-${Date.now()}-${Math.random().toString(36).substring(2, 7)}`);
44
+ const [taskCount, setTaskCount] = useState(() => initialSessionData?.taskCount || 0);
45
+ const [completedSummaries, setCompletedSummaries] = useState(() => initialSessionData?.completedTaskSummaries || []);
46
+ const completedSummariesRef = useRef(initialSessionData?.completedTaskSummaries || []);
47
+ useEffect(() => {
48
+ completedSummariesRef.current = completedSummaries;
49
+ }, [completedSummaries]);
50
+ const [lastTaskStatus, setLastTaskStatus] = useState(() => initialSessionData?.status || "idle");
51
+ const [activeRequest, setActiveRequest] = useState(undefined);
52
+ const startedAtRef = useRef(initialSessionData?.startedAt
53
+ ? new Date(initialSessionData.startedAt)
54
+ : new Date());
55
+ const [turns, setTurns] = useState(() => {
56
+ if (initialSessionData) {
57
+ return [
58
+ {
59
+ id: `init-${Date.now()}`,
60
+ prompt: `--resume ${initialSessionData.sessionId}`,
61
+ response: SessionHistoryFormatter.formatResumeSummary(initialSessionData),
62
+ status: "done"
63
+ }
64
+ ];
65
+ }
66
+ return [];
67
+ });
68
+ const [isGenerating, setIsGenerating] = useState(false);
69
+ const [pendingQuery, setPendingQuery] = useState(null);
70
+ const [selectedSuggestion, setSelectedSuggestion] = useState(0);
71
+ const prevIsGeneratingRef = useRef(false);
72
+ const [pendingApproval, setPendingApproval] = useState(null);
73
+ const [approvalInput, setApprovalInput] = useState("");
74
+ const [blockedInput, setBlockedInput] = useState("");
75
+ const [recoveryInput, setRecoveryInput] = useState("");
76
+ const [replanInput, setReplanInput] = useState("");
77
+ const [resumeInput, setResumeInput] = useState("");
78
+ const [pendingResume, setPendingResume] = useState(null);
79
+ const [pendingReplan, setPendingReplan] = useState(null);
80
+ const [pendingPlanBlocked, setPendingPlanBlocked] = useState(null);
81
+ const [pendingRecovery, setPendingRecovery] = useState(null);
82
+ const [pendingRecoveryContinuation, setPendingRecoveryContinuation] = useState(null);
83
+ const hasModal = Boolean(pendingApproval) ||
84
+ Boolean(pendingPlanBlocked) ||
85
+ Boolean(pendingRecovery) ||
86
+ Boolean(pendingRecoveryContinuation) ||
87
+ Boolean(pendingReplan) ||
88
+ Boolean(pendingResume);
89
+ const commandSuggestions = !isGenerating && !hasModal && query.startsWith("/")
90
+ ? filterCommands(query)
91
+ : [];
92
+ const handleQueryChange = (val) => {
93
+ if (!hasModal && !isGenerating && activeView === "main" && query === "" && val === "?") {
94
+ setActiveView("help");
95
+ setQuery("");
96
+ setSelectedSuggestion(0);
97
+ return;
98
+ }
99
+ if (!hasModal && !isGenerating && activeView !== "main" && !val.startsWith("/") && val.length === 1) {
100
+ const char = val.toLowerCase();
101
+ if (char === "p") {
102
+ setActiveView(activeView === "plan" ? "main" : "plan");
103
+ setQuery("");
104
+ setSelectedSuggestion(0);
105
+ return;
106
+ }
107
+ if (char === "r") {
108
+ setActiveView(activeView === "runs" ? "main" : "runs");
109
+ setQuery("");
110
+ setSelectedSuggestion(0);
111
+ return;
112
+ }
113
+ if (char === "d") {
114
+ setActiveView(activeView === "diagnostics" ? "main" : "diagnostics");
115
+ setQuery("");
116
+ setSelectedSuggestion(0);
117
+ return;
118
+ }
119
+ if (char === "?" || char === "h") {
120
+ setActiveView(activeView === "help" ? "main" : "help");
121
+ setQuery("");
122
+ setSelectedSuggestion(0);
123
+ return;
124
+ }
125
+ }
126
+ setQuery(val);
127
+ setSelectedSuggestion(0);
128
+ };
129
+ // Subscribe to ProductRuntime event stream
130
+ const [uiState, setUiState] = useState(() => runtime?.getUIState?.());
131
+ useEffect(() => {
132
+ if (!runtime || !runtime.subscribe)
133
+ return;
134
+ const unsubscribe = runtime.subscribe((state) => {
135
+ setUiState(state);
136
+ });
137
+ return () => {
138
+ unsubscribe();
139
+ };
140
+ }, [runtime]);
141
+ // Hook into ApprovalResolver if present
142
+ useEffect(() => {
143
+ if (approvalResolver) {
144
+ approvalResolver.onRequest = (request) => {
145
+ setPendingApproval((prev) => {
146
+ if (prev && prev.id === request.id) {
147
+ return {
148
+ ...request,
149
+ changeReview: request.changeReview || prev.changeReview,
150
+ reason: request.reason || prev.reason
151
+ };
152
+ }
153
+ return request;
154
+ });
155
+ };
156
+ }
157
+ }, [approvalResolver]);
158
+ // Auto-submit queued prompt when generation completes
159
+ useEffect(() => {
160
+ if (prevIsGeneratingRef.current && !isGenerating && pendingQuery) {
161
+ const queued = pendingQuery;
162
+ setPendingQuery(null);
163
+ void handleSubmit(queued);
164
+ }
165
+ prevIsGeneratingRef.current = isGenerating;
166
+ }, [isGenerating, pendingQuery]);
167
+ const persistState = useCallback(async (status, newSummary, explicitTaskCount) => {
168
+ try {
169
+ const summaries = newSummary
170
+ ? [...completedSummariesRef.current, newSummary]
171
+ : completedSummariesRef.current;
172
+ if (newSummary) {
173
+ completedSummariesRef.current = summaries;
174
+ setCompletedSummaries(summaries);
175
+ }
176
+ const state = agent?.getState?.();
177
+ const rawMessages = state?.messages || [];
178
+ // Sanitize messages and summaries to avoid persisting empty records
179
+ const validSummaries = summaries.filter((s) => s && (s.request || s.status || s.taskId));
180
+ const validMessages = rawMessages.filter((m) => {
181
+ if (!m)
182
+ return false;
183
+ if (typeof m.content === "string") {
184
+ return (m.content.trim().length > 0 ||
185
+ Boolean(m.toolCalls && m.toolCalls.length > 0));
186
+ }
187
+ return true;
188
+ });
189
+ await store.save({
190
+ version: 1,
191
+ sessionId,
192
+ workingDirectory: cwd,
193
+ provider: providerName || "unknown",
194
+ model: modelName || "unknown",
195
+ startedAt: startedAtRef.current.toISOString(),
196
+ updatedAt: new Date().toISOString(),
197
+ taskCount: explicitTaskCount !== undefined ? explicitTaskCount : taskCount,
198
+ status,
199
+ completedTaskSummaries: validSummaries,
200
+ messages: validMessages
201
+ });
202
+ }
203
+ catch {
204
+ // Silently catch persistence error
205
+ }
206
+ }, [
207
+ store,
208
+ sessionId,
209
+ cwd,
210
+ providerName,
211
+ modelName,
212
+ taskCount,
213
+ agent
214
+ ]);
215
+ const handleExit = useCallback(async () => {
216
+ try {
217
+ await persistState(lastTaskStatus);
218
+ }
219
+ catch {
220
+ // ignore
221
+ }
222
+ if (onExit) {
223
+ onExit();
224
+ }
225
+ else {
226
+ exit();
227
+ }
228
+ }, [onExit, exit, persistState, lastTaskStatus]);
229
+ // Keyboard navigation & shortcuts
230
+ useInput((input, key) => {
231
+ // Tab: select top autocomplete suggestion
232
+ if (key.tab && commandSuggestions.length > 0) {
233
+ const clamped = Math.max(0, Math.min(selectedSuggestion, commandSuggestions.length - 1));
234
+ const selected = commandSuggestions[clamped];
235
+ if (selected) {
236
+ setQuery(selected.command + " ");
237
+ setSelectedSuggestion(0);
238
+ }
239
+ return;
240
+ }
241
+ // ArrowDown / ArrowUp: cycle suggestions
242
+ if (key.downArrow && commandSuggestions.length > 0) {
243
+ setSelectedSuggestion((prev) => (prev + 1) % commandSuggestions.length);
244
+ return;
245
+ }
246
+ if (key.upArrow && commandSuggestions.length > 0) {
247
+ setSelectedSuggestion((prev) => prev <= 0 ? commandSuggestions.length - 1 : prev - 1);
248
+ return;
249
+ }
250
+ const cancelActiveModal = () => {
251
+ if (pendingApproval) {
252
+ if (approvalResolver) {
253
+ approvalResolver.cancelPending("Approval request cancelled via Ctrl+C");
254
+ }
255
+ setPendingApproval(null);
256
+ setApprovalInput("");
257
+ return true;
258
+ }
259
+ if (pendingPlanBlocked) {
260
+ const pb = pendingPlanBlocked;
261
+ setPendingPlanBlocked(null);
262
+ setBlockedInput("");
263
+ try {
264
+ if (agent && "resolveExecutionDecision" in agent) {
265
+ void agent
266
+ .resolveExecutionDecision(pb.plan.planId, "cancel", { cwd })
267
+ .catch(() => { });
268
+ }
269
+ if (agent && "cancel" in agent) {
270
+ void agent.cancel().catch(() => { });
271
+ }
272
+ }
273
+ catch {
274
+ // ignore
275
+ }
276
+ setTurns((prev) => [
277
+ ...prev,
278
+ {
279
+ id: `cmd-${Date.now()}`,
280
+ prompt: "",
281
+ response: `${PlanFormatter.formatCancelNotice()}\n`,
282
+ status: "done"
283
+ }
284
+ ]);
285
+ return true;
286
+ }
287
+ if (pendingRecovery) {
288
+ setPendingRecovery(null);
289
+ setRecoveryInput("");
290
+ setTurns((prev) => [
291
+ ...prev,
292
+ {
293
+ id: `cmd-${Date.now()}`,
294
+ prompt: "",
295
+ response: "✓ Recovery cancelled\n",
296
+ status: "done"
297
+ }
298
+ ]);
299
+ return true;
300
+ }
301
+ if (pendingRecoveryContinuation) {
302
+ setPendingRecoveryContinuation(null);
303
+ setRecoveryInput("");
304
+ setTurns((prev) => [
305
+ ...prev,
306
+ {
307
+ id: `cmd-${Date.now()}`,
308
+ prompt: "",
309
+ response: "Continuation cancelled.\n",
310
+ status: "done"
311
+ }
312
+ ]);
313
+ return true;
314
+ }
315
+ if (pendingReplan) {
316
+ setPendingReplan(null);
317
+ setReplanInput("");
318
+ setTurns((prev) => [
319
+ ...prev,
320
+ {
321
+ id: `cmd-${Date.now()}`,
322
+ prompt: "",
323
+ response: "Replanning cancelled.\n",
324
+ status: "done"
325
+ }
326
+ ]);
327
+ return true;
328
+ }
329
+ if (pendingResume) {
330
+ setPendingResume(null);
331
+ setResumeInput("");
332
+ setTurns((prev) => [
333
+ ...prev,
334
+ {
335
+ id: `cmd-${Date.now()}`,
336
+ prompt: "",
337
+ response: "✗ Resume cancelled by user.\n",
338
+ status: "done"
339
+ }
340
+ ]);
341
+ return true;
342
+ }
343
+ return false;
344
+ };
345
+ // Ctrl+C cancellation
346
+ if (key.ctrl && input === "c") {
347
+ if (pendingQuery) {
348
+ setPendingQuery(null);
349
+ return;
350
+ }
351
+ if (cancelActiveModal()) {
352
+ return;
353
+ }
354
+ if (isGenerating && agent) {
355
+ agent.cancel().catch(() => { });
356
+ setIsGenerating(false);
357
+ setPendingQuery(null);
358
+ setLastTaskStatus("cancelled");
359
+ setActiveRequest(undefined);
360
+ const cancelledSummary = {
361
+ taskIndex: taskCount,
362
+ request: activeRequest || "Task",
363
+ status: "cancelled",
364
+ completedFiles: [],
365
+ verifiedCommands: [],
366
+ completedRequirements: [],
367
+ remainingRequirements: []
368
+ };
369
+ setCompletedSummaries((prev) => [...prev, cancelledSummary]);
370
+ persistState("cancelled", cancelledSummary).catch(() => { });
371
+ setTurns((prev) => {
372
+ if (prev.length === 0)
373
+ return prev;
374
+ const updated = [...prev];
375
+ const lastIndex = updated.length - 1;
376
+ updated[lastIndex] = {
377
+ ...updated[lastIndex],
378
+ status: "cancelled",
379
+ error: "Generation cancelled."
380
+ };
381
+ return updated;
382
+ });
383
+ }
384
+ else {
385
+ handleExit();
386
+ }
387
+ return;
388
+ }
389
+ // Non-main view navigation shortcuts: p, r, d, ?, Esc (only when not typing a slash command)
390
+ if (!hasModal && !isGenerating && activeView !== "main" && !query.startsWith("/")) {
391
+ if (input === "p" || input === "P") {
392
+ setActiveView(activeView === "plan" ? "main" : "plan");
393
+ setQuery("");
394
+ return;
395
+ }
396
+ if (input === "r" || input === "R") {
397
+ setActiveView(activeView === "runs" ? "main" : "runs");
398
+ setQuery("");
399
+ return;
400
+ }
401
+ if (input === "d" || input === "D") {
402
+ setActiveView(activeView === "diagnostics" ? "main" : "diagnostics");
403
+ setQuery("");
404
+ return;
405
+ }
406
+ if (input === "?" || input === "h" || input === "H") {
407
+ setActiveView(activeView === "help" ? "main" : "help");
408
+ setQuery("");
409
+ return;
410
+ }
411
+ }
412
+ // Quick help shortcut from empty query on main view
413
+ if (!hasModal && !isGenerating && activeView === "main" && query === "" && input === "?") {
414
+ setActiveView("help");
415
+ setQuery("");
416
+ return;
417
+ }
418
+ // Escape returns to main view or cancels active modal
419
+ if (key.escape) {
420
+ if (cancelActiveModal()) {
421
+ return;
422
+ }
423
+ setActiveView("main");
424
+ setQuery("");
425
+ return;
426
+ }
427
+ }, { isActive: true });
428
+ const handleApprovalSubmit = (value) => {
429
+ const trimmed = value.trim();
430
+ setApprovalInput("");
431
+ setPendingApproval(null);
432
+ if (approvalResolver) {
433
+ approvalResolver.submitDecision(trimmed);
434
+ }
435
+ };
436
+ const handleSubmit = async (value) => {
437
+ let trimmed = value.trim();
438
+ // Queue the prompt if agent is currently running and no modal is active
439
+ const modalActive = Boolean(pendingApproval) ||
440
+ Boolean(pendingPlanBlocked) ||
441
+ Boolean(pendingRecovery) ||
442
+ Boolean(pendingRecoveryContinuation) ||
443
+ Boolean(pendingReplan) ||
444
+ Boolean(pendingResume);
445
+ if (!trimmed && !modalActive)
446
+ return;
447
+ // Apply safe defaults for modals when user hits Enter with empty input
448
+ if (!trimmed && modalActive) {
449
+ if (pendingPlanBlocked) {
450
+ trimmed = "x";
451
+ }
452
+ else {
453
+ trimmed = "n";
454
+ }
455
+ }
456
+ if (isGenerating && !modalActive) {
457
+ setPendingQuery(trimmed);
458
+ setQuery("");
459
+ return;
460
+ }
461
+ if (isGenerating)
462
+ return;
463
+ // Handle modal submissions first
464
+ if (pendingRecoveryContinuation) {
465
+ setQuery("");
466
+ setRecoveryInput("");
467
+ const prc = pendingRecoveryContinuation;
468
+ setPendingRecoveryContinuation(null);
469
+ const choice = trimmed.toLowerCase();
470
+ if (choice === "y" ||
471
+ choice === "yes" ||
472
+ choice === "c" ||
473
+ choice === "continue") {
474
+ const contTurnId = `turn-${Date.now()}`;
475
+ setTurns((prev) => [
476
+ ...prev,
477
+ {
478
+ id: contTurnId,
479
+ prompt: trimmed,
480
+ response: "Starting continuation...\n",
481
+ status: "streaming"
482
+ }
483
+ ]);
484
+ if (agent && "continueRecoveredPlan" in agent) {
485
+ try {
486
+ for await (const ev of agent.continueRecoveredPlan(prc.preparation, {
487
+ runId: prc.plan.runId,
488
+ planId: prc.plan.planId,
489
+ decision: "continue",
490
+ approved: true,
491
+ cwd
492
+ })) {
493
+ if (ev.type === "plan_step_started") {
494
+ setTurns((prev) => prev.map((t) => t.id === contTurnId
495
+ ? {
496
+ ...t,
497
+ response: t.response +
498
+ `[${ev.stepIndex + 1}/${prc.plan.steps.length}] ${ev.title || "Step"} ...\n`
499
+ }
500
+ : t));
501
+ }
502
+ else if (ev.type === "plan_step_completed") {
503
+ setTurns((prev) => prev.map((t) => t.id === contTurnId
504
+ ? {
505
+ ...t,
506
+ response: t.response + "✓ COMPLETED\n\n"
507
+ }
508
+ : t));
509
+ }
510
+ else if (ev.type === "recovery_continuation_completed" ||
511
+ ev.type === "plan_execution_completed") {
512
+ setTurns((prev) => prev.map((t) => t.id === contTurnId
513
+ ? {
514
+ ...t,
515
+ status: "done",
516
+ response: t.response.includes("✓ Plan completed")
517
+ ? t.response
518
+ : t.response + "✓ Plan completed\n"
519
+ }
520
+ : t));
521
+ }
522
+ else if (ev.type === "recovery_continuation_blocked") {
523
+ setTurns((prev) => prev.map((t) => t.id === contTurnId
524
+ ? {
525
+ ...t,
526
+ status: "done",
527
+ response: t.response +
528
+ `⚠ Continuation blocked: ${ev.blockingReasons.join("; ")}\n`
529
+ }
530
+ : t));
531
+ }
532
+ else if (ev.type === "recovery_continuation_cancelled") {
533
+ setTurns((prev) => prev.map((t) => t.id === contTurnId
534
+ ? {
535
+ ...t,
536
+ status: "done",
537
+ response: t.response + "Continuation cancelled.\n"
538
+ }
539
+ : t));
540
+ }
541
+ }
542
+ }
543
+ catch (err) {
544
+ const msg = err instanceof Error ? err.message : String(err);
545
+ setTurns((prev) => prev.map((t) => t.id === contTurnId
546
+ ? {
547
+ ...t,
548
+ status: "error",
549
+ response: t.response + `✗ Continuation error: ${msg}\n`
550
+ }
551
+ : t));
552
+ }
553
+ }
554
+ }
555
+ else {
556
+ setTurns((prev) => [
557
+ ...prev,
558
+ {
559
+ id: `cmd-${Date.now()}`,
560
+ prompt: trimmed,
561
+ response: "Continuation cancelled.\n",
562
+ status: "done"
563
+ }
564
+ ]);
565
+ }
566
+ return;
567
+ }
568
+ if (pendingRecovery) {
569
+ setQuery("");
570
+ setRecoveryInput("");
571
+ const pr = pendingRecovery;
572
+ setPendingRecovery(null);
573
+ const choice = trimmed.toLowerCase();
574
+ if (choice === "y" ||
575
+ choice === "yes" ||
576
+ choice === "p" ||
577
+ choice === "proceed") {
578
+ const recTurnId = `turn-${Date.now()}`;
579
+ setTurns((prev) => [
580
+ ...prev,
581
+ {
582
+ id: recTurnId,
583
+ prompt: trimmed,
584
+ response: "Recovery started\n",
585
+ status: "streaming"
586
+ }
587
+ ]);
588
+ try {
589
+ if (agent && "executeExecutionRecovery" in agent) {
590
+ for await (const ev of agent.executeExecutionRecovery(pr.assessment, { cwd, approved: true })) {
591
+ if (ev.type === "recovery_step_started") {
592
+ setTurns((prev) => prev.map((t) => t.id === recTurnId
593
+ ? {
594
+ ...t,
595
+ response: t.response +
596
+ `[${ev.stepIndex}/${ev.totalSteps}] ${ev.title} ... `
597
+ }
598
+ : t));
599
+ }
600
+ else if (ev.type === "recovery_step_completed") {
601
+ setTurns((prev) => prev.map((t) => t.id === recTurnId
602
+ ? {
603
+ ...t,
604
+ response: t.response + `${ev.success ? "✓" : "✗"}\n`
605
+ }
606
+ : t));
607
+ }
608
+ else if (ev.type === "recovery_verification_started") {
609
+ setTurns((prev) => prev.map((t) => t.id === recTurnId
610
+ ? {
611
+ ...t,
612
+ response: t.response + `\nVerification\n`
613
+ }
614
+ : t));
615
+ }
616
+ else if (ev.type === "recovery_verification_completed") {
617
+ setTurns((prev) => prev.map((t) => t.id === recTurnId
618
+ ? {
619
+ ...t,
620
+ response: t.response +
621
+ `${ev.success ? "✓" : "✗"} ${ev.command}\n\n`
622
+ }
623
+ : t));
624
+ }
625
+ else if (ev.type === "recovery_reconciliation_started") {
626
+ setTurns((prev) => prev.map((t) => t.id === recTurnId
627
+ ? {
628
+ ...t,
629
+ response: t.response + `Workspace reconciliation\n`
630
+ }
631
+ : t));
632
+ }
633
+ else if (ev.type === "recovery_reconciliation_completed") {
634
+ setTurns((prev) => prev.map((t) => t.id === recTurnId
635
+ ? {
636
+ ...t,
637
+ response: t.response +
638
+ `${ev.result.consistent ? "✓ Workspace consistent" : "⚠ Inconsistent"}\n\n`
639
+ }
640
+ : t));
641
+ }
642
+ else if (ev.type === "recovery_outcome_determined") {
643
+ const outcomeText = PlanFormatter.formatRecoveryOutcome(ev.result);
644
+ if (ev.outcome === "recovered" ||
645
+ ev.outcome === "recovered_with_changes") {
646
+ let prep;
647
+ if (agent && "prepareRecoveryContinuation" in agent) {
648
+ try {
649
+ prep = await agent.prepareRecoveryContinuation({
650
+ cwd,
651
+ recoveryResult: ev.result,
652
+ recoveryOutcome: ev.outcome
653
+ });
654
+ }
655
+ catch {
656
+ // ignore
657
+ }
658
+ }
659
+ if (prep && prep.canContinue) {
660
+ setPendingRecoveryContinuation({
661
+ plan: pr.plan,
662
+ preparation: prep
663
+ });
664
+ const contText = PlanFormatter.formatRecoveryContinuationPrompt(prep);
665
+ setTurns((prev) => prev.map((t) => t.id === recTurnId
666
+ ? {
667
+ ...t,
668
+ status: "done",
669
+ response: `${outcomeText}\n\n${contText}\n`
670
+ }
671
+ : t));
672
+ }
673
+ else {
674
+ const incompleteSteps = pr.plan.steps.filter((s) => s.status !== "completed" && s.status !== "skipped");
675
+ if (incompleteSteps.length > 0 && !prep) {
676
+ const fallbackPrep = {
677
+ eligible: true,
678
+ canContinue: true,
679
+ planId: pr.plan.planId,
680
+ runId: pr.plan.runId,
681
+ recoveryOutcome: ev.outcome,
682
+ remainingSteps: incompleteSteps,
683
+ completedSteps: pr.plan.steps.filter((s) => s.status === "completed"),
684
+ skippedSteps: pr.plan.steps.filter((s) => s.status === "skipped"),
685
+ reconciliationConsistent: true,
686
+ requiresExplicitApproval: true
687
+ };
688
+ setPendingRecoveryContinuation({
689
+ plan: pr.plan,
690
+ preparation: fallbackPrep
691
+ });
692
+ const contText = PlanFormatter.formatRecoveryContinuationPrompt(fallbackPrep);
693
+ setTurns((prev) => prev.map((t) => t.id === recTurnId
694
+ ? {
695
+ ...t,
696
+ status: "done",
697
+ response: `${outcomeText}\n\n${contText}\n`
698
+ }
699
+ : t));
700
+ }
701
+ else {
702
+ setTurns((prev) => prev.map((t) => t.id === recTurnId
703
+ ? {
704
+ ...t,
705
+ status: "done",
706
+ response: `${outcomeText}\n\n✓ Plan completed\n`
707
+ }
708
+ : t));
709
+ }
710
+ }
711
+ }
712
+ else if (ev.outcome === "still_blocked") {
713
+ const defaultAdaptation = {
714
+ planId: pr.plan.planId,
715
+ assessedAt: Date.now(),
716
+ canContinue: false,
717
+ canRetry: false,
718
+ canAdapt: true,
719
+ feedback: [],
720
+ affectedSteps: [...ev.result.affectedSteps],
721
+ currentRiskLevel: "normal",
722
+ requiresUserConfirmation: true,
723
+ recommendedAction: "replan"
724
+ };
725
+ setPendingPlanBlocked({
726
+ plan: pr.plan,
727
+ assessment: defaultAdaptation,
728
+ reconciliationResult: ev.result.reconciliationResult
729
+ });
730
+ setTurns((prev) => prev.map((t) => t.id === recTurnId
731
+ ? {
732
+ ...t,
733
+ status: "done",
734
+ response: `${outcomeText}\n`
735
+ }
736
+ : t));
737
+ }
738
+ else {
739
+ setTurns((prev) => prev.map((t) => t.id === recTurnId
740
+ ? {
741
+ ...t,
742
+ status: "done",
743
+ response: `${outcomeText}\n`
744
+ }
745
+ : t));
746
+ }
747
+ }
748
+ else if (ev.type === "recovery_completed") {
749
+ setTurns((prev) => prev.map((t) => t.id === recTurnId
750
+ ? {
751
+ ...t,
752
+ status: "done",
753
+ response: t.response.includes("Recovery completed")
754
+ ? t.response
755
+ : t.response + "✓ Recovery completed\n"
756
+ }
757
+ : t));
758
+ }
759
+ }
760
+ }
761
+ }
762
+ catch (err) {
763
+ const msg = err instanceof Error ? err.message : String(err);
764
+ setTurns((prev) => prev.map((t) => t.id === recTurnId
765
+ ? {
766
+ ...t,
767
+ status: "error",
768
+ response: t.response + `✗ Recovery error: ${msg}\n`
769
+ }
770
+ : t));
771
+ }
772
+ }
773
+ else {
774
+ setTurns((prev) => [
775
+ ...prev,
776
+ {
777
+ id: `cmd-${Date.now()}`,
778
+ prompt: trimmed,
779
+ response: "✓ Recovery cancelled\n",
780
+ status: "done"
781
+ }
782
+ ]);
783
+ }
784
+ return;
785
+ }
786
+ if (pendingPlanBlocked) {
787
+ setQuery("");
788
+ const pb = pendingPlanBlocked;
789
+ setPendingPlanBlocked(null);
790
+ const choice = trimmed.toLowerCase();
791
+ const isRecon = !!pb.reconciliationResult;
792
+ if (isRecon && (choice === "r" || choice === "recover" || choice === "recovery")) {
793
+ try {
794
+ if (agent && "assessExecutionRecovery" in agent) {
795
+ const assessment = await agent.assessExecutionRecovery(pb.plan.planId, {
796
+ cwd,
797
+ reconciliationResult: pb.reconciliationResult
798
+ });
799
+ setPendingRecovery({ assessment, plan: pb.plan });
800
+ const promptText = PlanFormatter.formatRecoveryAssessment(assessment);
801
+ setTurns((prev) => [
802
+ ...prev,
803
+ {
804
+ id: `cmd-${Date.now()}`,
805
+ prompt: trimmed,
806
+ response: `${promptText}\n`,
807
+ status: "done"
808
+ }
809
+ ]);
810
+ return;
811
+ }
812
+ }
813
+ catch (err) {
814
+ const msg = err instanceof Error ? err.message : String(err);
815
+ setTurns((prev) => [
816
+ ...prev,
817
+ {
818
+ id: `cmd-${Date.now()}`,
819
+ prompt: trimmed,
820
+ response: `✗ Recovery assessment error: ${msg}\n`,
821
+ status: "done"
822
+ }
823
+ ]);
824
+ return;
825
+ }
826
+ }
827
+ if ((!isRecon && (choice === "r" || choice === "replan")) ||
828
+ (isRecon && (choice === "p" || choice === "replan"))) {
829
+ try {
830
+ if (agent && "resolveExecutionDecision" in agent) {
831
+ await agent.resolveExecutionDecision(pb.plan.planId, "replan", { cwd });
832
+ }
833
+ if (agent && "prepareReplan" in agent) {
834
+ const assessment = await agent.prepareReplan(pb.plan.planId, { cwd });
835
+ if (!assessment.eligible) {
836
+ setTurns((prev) => [
837
+ ...prev,
838
+ {
839
+ id: `cmd-${Date.now()}`,
840
+ prompt: trimmed,
841
+ response: `✗ Replanning not eligible: ${assessment.reason}\n`,
842
+ status: "done"
843
+ }
844
+ ]);
845
+ }
846
+ else {
847
+ setPendingReplan({ planId: pb.plan.planId, assessment });
848
+ const promptText = PlanFormatter.formatReplanPrompt(assessment);
849
+ const replanNotice = PlanFormatter.formatReplanNotice();
850
+ setTurns((prev) => [
851
+ ...prev,
852
+ {
853
+ id: `cmd-${Date.now()}`,
854
+ prompt: trimmed,
855
+ response: `${replanNotice}\n\n${promptText}\n`,
856
+ status: "done"
857
+ }
858
+ ]);
859
+ }
860
+ }
861
+ }
862
+ catch (err) {
863
+ const msg = err instanceof Error ? err.message : String(err);
864
+ setTurns((prev) => [
865
+ ...prev,
866
+ {
867
+ id: `cmd-${Date.now()}`,
868
+ prompt: trimmed,
869
+ response: `✗ Replanning error: ${msg}\n`,
870
+ status: "done"
871
+ }
872
+ ]);
873
+ }
874
+ }
875
+ else if (choice === "c" ||
876
+ choice === "continue" ||
877
+ choice === "re-check" ||
878
+ choice === "recheck") {
879
+ let resumeNotice = `↻ Resuming plan ${pb.plan.planId}...\n`;
880
+ try {
881
+ if (agent && "resolveExecutionDecision" in agent) {
882
+ const decisionResult = await agent.resolveExecutionDecision(pb.plan.planId, "continue", { cwd });
883
+ const incompleteStep = pb.plan.steps.find((s) => s.stepId === decisionResult.resumedStepId) ||
884
+ pb.plan.steps[0];
885
+ if (incompleteStep) {
886
+ resumeNotice = `${PlanFormatter.formatResumeNotice(pb.plan.planId, incompleteStep.order, pb.plan.steps.length, incompleteStep.title)}\n`;
887
+ }
888
+ }
889
+ if (agent && "getTaskPlan" in agent) {
890
+ transitionPlanStatus(pb.plan, "executing");
891
+ }
892
+ }
893
+ catch {
894
+ // ignore
895
+ }
896
+ setTurns((prev) => [
897
+ ...prev,
898
+ {
899
+ id: `cmd-${Date.now()}`,
900
+ prompt: trimmed,
901
+ response: resumeNotice,
902
+ status: "done"
903
+ }
904
+ ]);
905
+ }
906
+ else {
907
+ // default / 'x' / 'cancel'
908
+ try {
909
+ if (agent && "resolveExecutionDecision" in agent) {
910
+ await agent.resolveExecutionDecision(pb.plan.planId, "cancel", { cwd });
911
+ }
912
+ if (agent && "cancel" in agent) {
913
+ await agent.cancel();
914
+ }
915
+ }
916
+ catch {
917
+ // ignore
918
+ }
919
+ setTurns((prev) => [
920
+ ...prev,
921
+ {
922
+ id: `cmd-${Date.now()}`,
923
+ prompt: trimmed,
924
+ response: `${PlanFormatter.formatCancelNotice()}\n`,
925
+ status: "done"
926
+ }
927
+ ]);
928
+ }
929
+ return;
930
+ }
931
+ if (pendingReplan) {
932
+ setQuery("");
933
+ const pr = pendingReplan;
934
+ setPendingReplan(null);
935
+ if (trimmed.toLowerCase() === "y" || trimmed.toLowerCase() === "yes") {
936
+ try {
937
+ if (agent && "executeReplan" in agent) {
938
+ const replanResult = await agent.executeReplan({
939
+ runId: `run-replan-${Date.now()}`,
940
+ previousPlanId: pr.planId,
941
+ reason: pr.assessment.reason,
942
+ explanation: pr.assessment.explanation,
943
+ failedStepId: pr.assessment.affectedStepId,
944
+ cwd,
945
+ userRequest: pr.assessment.previousPlan?.userRequestSummary || "",
946
+ requestedBy: "user"
947
+ });
948
+ if (replanResult.status === "created" && replanResult.newPlan) {
949
+ const formattedPlan = PlanFormatter.formatPlanDetail(replanResult.newPlan);
950
+ setTurns((prev) => [
951
+ ...prev,
952
+ {
953
+ id: `cmd-${Date.now()}`,
954
+ prompt: trimmed,
955
+ response: `✓ Created replacement plan: ${replanResult.newPlanId}\n\n${formattedPlan}\n\nType /plan to inspect or approve when ready.\n`,
956
+ status: "done"
957
+ }
958
+ ]);
959
+ }
960
+ else {
961
+ setTurns((prev) => [
962
+ ...prev,
963
+ {
964
+ id: `cmd-${Date.now()}`,
965
+ prompt: trimmed,
966
+ response: `✗ Replanning failed: ${replanResult.reason}\n`,
967
+ status: "done"
968
+ }
969
+ ]);
970
+ }
971
+ }
972
+ }
973
+ catch (err) {
974
+ const msg = err instanceof Error ? err.message : String(err);
975
+ setTurns((prev) => [
976
+ ...prev,
977
+ {
978
+ id: `cmd-${Date.now()}`,
979
+ prompt: trimmed,
980
+ response: `✗ Error creating replan: ${msg}\n`,
981
+ status: "done"
982
+ }
983
+ ]);
984
+ }
985
+ }
986
+ else {
987
+ setTurns((prev) => [
988
+ ...prev,
989
+ {
990
+ id: `cmd-${Date.now()}`,
991
+ prompt: trimmed,
992
+ response: "Replanning cancelled.\n",
993
+ status: "done"
994
+ }
995
+ ]);
996
+ }
997
+ return;
998
+ }
999
+ if (pendingResume) {
1000
+ setQuery("");
1001
+ const pr = pendingResume;
1002
+ setPendingResume(null);
1003
+ if (trimmed.toLowerCase() === "y" || trimmed.toLowerCase() === "yes") {
1004
+ setTurns((prev) => [
1005
+ ...prev,
1006
+ {
1007
+ id: `cmd-${Date.now()}`,
1008
+ prompt: trimmed,
1009
+ response: `✓ Resuming run ${pr.runId} as ${pr.prep.newRunId}...\n`,
1010
+ status: "done"
1011
+ }
1012
+ ]);
1013
+ if (agent && "resumeRun" in agent) {
1014
+ setIsGenerating(true);
1015
+ const nextTaskCount = taskCount + 1;
1016
+ setTaskCount(nextTaskCount);
1017
+ setActiveRequest(pr.prep.originalRun.userRequestSummary);
1018
+ setLastTaskStatus("in_progress");
1019
+ const turnId = `turn-${Date.now()}`;
1020
+ const newTurn = {
1021
+ id: turnId,
1022
+ prompt: `Resume task: ${pr.prep.originalRun.userRequestSummary}`,
1023
+ response: "",
1024
+ status: "streaming"
1025
+ };
1026
+ setTurns((prev) => [...prev, newTurn]);
1027
+ try {
1028
+ for await (const event of agent.resumeRun(pr.runId, { cwd })) {
1029
+ if (event.type === "text") {
1030
+ setTurns((prev) => prev.map((t) => t.id === turnId
1031
+ ? { ...t, response: t.response + event.content }
1032
+ : t));
1033
+ }
1034
+ else if (event.type === "done") {
1035
+ setTurns((prev) => prev.map((t) => t.id === turnId ? { ...t, status: "done" } : t));
1036
+ }
1037
+ }
1038
+ }
1039
+ catch (err) {
1040
+ const msg = err instanceof Error ? err.message : String(err);
1041
+ setTurns((prev) => prev.map((t) => t.id === turnId
1042
+ ? { ...t, status: "error", error: `✗ ${msg}` }
1043
+ : t));
1044
+ }
1045
+ finally {
1046
+ setIsGenerating(false);
1047
+ }
1048
+ }
1049
+ }
1050
+ else {
1051
+ setTurns((prev) => [
1052
+ ...prev,
1053
+ {
1054
+ id: `cmd-${Date.now()}`,
1055
+ prompt: trimmed,
1056
+ response: "✗ Resume cancelled by user.\n",
1057
+ status: "done"
1058
+ }
1059
+ ]);
1060
+ }
1061
+ return;
1062
+ }
1063
+ // Slash command processing
1064
+ if (trimmed.startsWith("/")) {
1065
+ setQuery("");
1066
+ const [rawCmd, ...rawArgs] = trimmed.split(/\s+/);
1067
+ const cmd = (rawCmd ?? "").toLowerCase();
1068
+ const arg = rawArgs.join(" ").trim();
1069
+ if (cmd === "/help") {
1070
+ setActiveView("help");
1071
+ setTurns((prev) => [
1072
+ ...prev,
1073
+ {
1074
+ id: `cmd-${Date.now()}`,
1075
+ prompt: trimmed,
1076
+ response: "Available commands:\n /help - Show this help message\n /status - Show provider, model, and session status\n /history - Show task history in this session\n /tasks - List completed tasks\n /task <num> - View details of a specific task\n /sessions - List past sessions\n /clear - Clear conversation history\n /exit - Exit FeCode\n",
1077
+ status: "done"
1078
+ }
1079
+ ]);
1080
+ return;
1081
+ }
1082
+ if (cmd === "/status") {
1083
+ setTurns((prev) => [
1084
+ ...prev,
1085
+ {
1086
+ id: `cmd-${Date.now()}`,
1087
+ prompt: trimmed,
1088
+ response: `FeCode\n\nProvider: ${providerName || "unknown"}\nModel: ${modelName || "unknown"}\nWorking directory: ${cwd}\nSession: ${sessionId}\n`,
1089
+ status: "done"
1090
+ }
1091
+ ]);
1092
+ return;
1093
+ }
1094
+ if (cmd === "/clear") {
1095
+ setTurns([]);
1096
+ setTurns([
1097
+ {
1098
+ id: `cmd-${Date.now()}`,
1099
+ prompt: trimmed,
1100
+ response: "✓ Conversation cleared\n\nSession history remains available through:\n /history - View completed tasks\n /tasks - List task summaries\n /status - Session details\n",
1101
+ status: "done"
1102
+ }
1103
+ ]);
1104
+ return;
1105
+ }
1106
+ if (cmd === "/exit" || cmd === "/quit") {
1107
+ await handleExit();
1108
+ return;
1109
+ }
1110
+ if (cmd === "/history") {
1111
+ const historyText = SessionHistoryFormatter.formatHistory(completedSummaries);
1112
+ setTurns((prev) => [
1113
+ ...prev,
1114
+ {
1115
+ id: `cmd-${Date.now()}`,
1116
+ prompt: trimmed,
1117
+ response: historyText,
1118
+ status: "done"
1119
+ }
1120
+ ]);
1121
+ return;
1122
+ }
1123
+ if (cmd === "/tasks") {
1124
+ const tasksText = SessionHistoryFormatter.formatTaskList(completedSummaries);
1125
+ setTurns((prev) => [
1126
+ ...prev,
1127
+ {
1128
+ id: `cmd-${Date.now()}`,
1129
+ prompt: trimmed,
1130
+ response: tasksText,
1131
+ status: "done"
1132
+ }
1133
+ ]);
1134
+ return;
1135
+ }
1136
+ if (cmd === "/task") {
1137
+ if (!arg) {
1138
+ setTurns((prev) => [
1139
+ ...prev,
1140
+ {
1141
+ id: `cmd-${Date.now()}`,
1142
+ prompt: trimmed,
1143
+ response: "Current Task\n\nNo active task.\n\nUse /tasks to see completed tasks in this session.\n",
1144
+ status: "done"
1145
+ }
1146
+ ]);
1147
+ return;
1148
+ }
1149
+ const taskNum = parseInt(arg, 10);
1150
+ const summary = completedSummaries.find((s) => s.taskIndex === taskNum);
1151
+ if (summary) {
1152
+ const detailText = SessionHistoryFormatter.formatTaskDetail(summary);
1153
+ setTurns((prev) => [
1154
+ ...prev,
1155
+ {
1156
+ id: `cmd-${Date.now()}`,
1157
+ prompt: trimmed,
1158
+ response: detailText,
1159
+ status: "done"
1160
+ }
1161
+ ]);
1162
+ }
1163
+ else {
1164
+ setTurns((prev) => [
1165
+ ...prev,
1166
+ {
1167
+ id: `cmd-${Date.now()}`,
1168
+ prompt: trimmed,
1169
+ response: `✗ Task not found: ${arg}\n\nUse /tasks to see available tasks (1-${completedSummaries.length}).\n`,
1170
+ status: "done"
1171
+ }
1172
+ ]);
1173
+ }
1174
+ return;
1175
+ }
1176
+ if (cmd === "/git") {
1177
+ try {
1178
+ const statusResult = await gitRepo.getStatus(cwd);
1179
+ const formatted = GitStatusFormatter.formatGitStatus(statusResult);
1180
+ setTurns((prev) => [
1181
+ ...prev,
1182
+ {
1183
+ id: `cmd-${Date.now()}`,
1184
+ prompt: trimmed,
1185
+ response: formatted,
1186
+ status: "done"
1187
+ }
1188
+ ]);
1189
+ }
1190
+ catch (err) {
1191
+ const msg = err instanceof Error ? err.message : String(err);
1192
+ setTurns((prev) => [
1193
+ ...prev,
1194
+ {
1195
+ id: `cmd-${Date.now()}`,
1196
+ prompt: trimmed,
1197
+ response: `✗ Git error: ${msg}\n`,
1198
+ status: "done"
1199
+ }
1200
+ ]);
1201
+ }
1202
+ return;
1203
+ }
1204
+ if (cmd === "/checkpoints") {
1205
+ try {
1206
+ const list = await cpManager.list();
1207
+ const formatted = CheckpointFormatter.formatCheckpointsList(list);
1208
+ setTurns((prev) => [
1209
+ ...prev,
1210
+ {
1211
+ id: `cmd-${Date.now()}`,
1212
+ prompt: trimmed,
1213
+ response: formatted,
1214
+ status: "done"
1215
+ }
1216
+ ]);
1217
+ }
1218
+ catch (err) {
1219
+ const msg = err instanceof Error ? err.message : String(err);
1220
+ setTurns((prev) => [
1221
+ ...prev,
1222
+ {
1223
+ id: `cmd-${Date.now()}`,
1224
+ prompt: trimmed,
1225
+ response: `✗ Checkpoints error: ${msg}\n`,
1226
+ status: "done"
1227
+ }
1228
+ ]);
1229
+ }
1230
+ return;
1231
+ }
1232
+ if (cmd === "/checkpoint") {
1233
+ try {
1234
+ const cpRes = await cpManager.create({
1235
+ cwd,
1236
+ reason: arg || "Manual checkpoint"
1237
+ });
1238
+ const formatted = cpRes.checkpoint
1239
+ ? CheckpointFormatter.formatCheckpointCreated(cpRes.checkpoint)
1240
+ : "✓ Checkpoint created\n";
1241
+ setTurns((prev) => [
1242
+ ...prev,
1243
+ {
1244
+ id: `cmd-${Date.now()}`,
1245
+ prompt: trimmed,
1246
+ response: formatted,
1247
+ status: "done"
1248
+ }
1249
+ ]);
1250
+ }
1251
+ catch (err) {
1252
+ const msg = err instanceof Error ? err.message : String(err);
1253
+ setTurns((prev) => [
1254
+ ...prev,
1255
+ {
1256
+ id: `cmd-${Date.now()}`,
1257
+ prompt: trimmed,
1258
+ response: `✗ Checkpoint error: ${msg}\n`,
1259
+ status: "done"
1260
+ }
1261
+ ]);
1262
+ }
1263
+ return;
1264
+ }
1265
+ if (cmd === "/recover") {
1266
+ if (!arg || arg === "status") {
1267
+ try {
1268
+ const statusRec = recManager.getLastRecord();
1269
+ const formatted = RecoveryFormatter.formatRecoveryStatus(statusRec);
1270
+ setTurns((prev) => [
1271
+ ...prev,
1272
+ {
1273
+ id: `cmd-${Date.now()}`,
1274
+ prompt: trimmed,
1275
+ response: formatted,
1276
+ status: "done"
1277
+ }
1278
+ ]);
1279
+ }
1280
+ catch (err) {
1281
+ const msg = err instanceof Error ? err.message : String(err);
1282
+ setTurns((prev) => [
1283
+ ...prev,
1284
+ {
1285
+ id: `cmd-${Date.now()}`,
1286
+ prompt: trimmed,
1287
+ response: `✗ Recovery error: ${msg}\n`,
1288
+ status: "done"
1289
+ }
1290
+ ]);
1291
+ }
1292
+ return;
1293
+ }
1294
+ if (arg.startsWith("preview")) {
1295
+ const cpId = arg.replace("preview", "").trim();
1296
+ if (!cpId) {
1297
+ setTurns((prev) => [
1298
+ ...prev,
1299
+ {
1300
+ id: `cmd-${Date.now()}`,
1301
+ prompt: trimmed,
1302
+ response: "✗ Please specify a checkpoint ID for preview.\n",
1303
+ status: "done"
1304
+ }
1305
+ ]);
1306
+ return;
1307
+ }
1308
+ try {
1309
+ const preview = await recManager.preview(cpId, cwd);
1310
+ const formatted = RecoveryFormatter.formatRecoveryPreview(preview);
1311
+ setTurns((prev) => [
1312
+ ...prev,
1313
+ {
1314
+ id: `cmd-${Date.now()}`,
1315
+ prompt: trimmed,
1316
+ response: formatted,
1317
+ status: "done"
1318
+ }
1319
+ ]);
1320
+ }
1321
+ catch (err) {
1322
+ const msg = err instanceof Error ? err.message : String(err);
1323
+ setTurns((prev) => [
1324
+ ...prev,
1325
+ {
1326
+ id: `cmd-${Date.now()}`,
1327
+ prompt: trimmed,
1328
+ response: `✗ Recovery preview error: ${msg}\n`,
1329
+ status: "done"
1330
+ }
1331
+ ]);
1332
+ }
1333
+ return;
1334
+ }
1335
+ }
1336
+ if (cmd === "/debug" || cmd === "/diagnostics") {
1337
+ setActiveView("diagnostics");
1338
+ let summary;
1339
+ if (agent && "getRunSummary" in agent) {
1340
+ summary = agent.getRunSummary(arg || undefined);
1341
+ }
1342
+ else if (runtime && runtime.getDiagnosticsSummary) {
1343
+ summary = runtime.getDiagnosticsSummary(arg || undefined);
1344
+ }
1345
+ if (summary) {
1346
+ const formatted = formatRunDiagnostics(summary);
1347
+ setTurns((prev) => [
1348
+ ...prev,
1349
+ {
1350
+ id: `cmd-${Date.now()}`,
1351
+ prompt: trimmed,
1352
+ response: formatted,
1353
+ status: "done"
1354
+ }
1355
+ ]);
1356
+ }
1357
+ else {
1358
+ setTurns((prev) => [
1359
+ ...prev,
1360
+ {
1361
+ id: `cmd-${Date.now()}`,
1362
+ prompt: trimmed,
1363
+ response: "No run diagnostics available.\n",
1364
+ status: "done"
1365
+ }
1366
+ ]);
1367
+ }
1368
+ return;
1369
+ }
1370
+ if (cmd === "/runs") {
1371
+ setActiveView("runs");
1372
+ let runs = [];
1373
+ if (agent && "listHistoricalRuns" in agent) {
1374
+ runs = await agent.listHistoricalRuns();
1375
+ }
1376
+ else if (runtime && runtime.getHistoricalRuns) {
1377
+ runs = await runtime.getHistoricalRuns();
1378
+ }
1379
+ const formatted = RunHistoryFormatter.formatRunsList(runs);
1380
+ setTurns((prev) => [
1381
+ ...prev,
1382
+ {
1383
+ id: `cmd-${Date.now()}`,
1384
+ prompt: trimmed,
1385
+ response: formatted,
1386
+ status: "done"
1387
+ }
1388
+ ]);
1389
+ return;
1390
+ }
1391
+ if (cmd === "/run") {
1392
+ if (!arg) {
1393
+ setTurns((prev) => [
1394
+ ...prev,
1395
+ {
1396
+ id: `cmd-${Date.now()}`,
1397
+ prompt: trimmed,
1398
+ response: "✗ Please specify a run ID: /run <id>\n",
1399
+ status: "done"
1400
+ }
1401
+ ]);
1402
+ return;
1403
+ }
1404
+ let run = null;
1405
+ if (agent && "getHistoricalRun" in agent) {
1406
+ run = await agent.getHistoricalRun(arg);
1407
+ }
1408
+ else if (runtime && runtime.getHistoricalRun) {
1409
+ run = await runtime.getHistoricalRun(arg);
1410
+ }
1411
+ if (run) {
1412
+ const formatted = RunHistoryFormatter.formatRunDetail(run);
1413
+ setTurns((prev) => [
1414
+ ...prev,
1415
+ {
1416
+ id: `cmd-${Date.now()}`,
1417
+ prompt: trimmed,
1418
+ response: formatted,
1419
+ status: "done"
1420
+ }
1421
+ ]);
1422
+ }
1423
+ else {
1424
+ setTurns((prev) => [
1425
+ ...prev,
1426
+ {
1427
+ id: `cmd-${Date.now()}`,
1428
+ prompt: trimmed,
1429
+ response: `✗ Run not found: ${arg}\n`,
1430
+ status: "done"
1431
+ }
1432
+ ]);
1433
+ }
1434
+ return;
1435
+ }
1436
+ if (cmd === "/sessions") {
1437
+ try {
1438
+ const list = await store.list();
1439
+ const formatted = SessionHistoryFormatter.formatSessionsList(list);
1440
+ setTurns((prev) => [
1441
+ ...prev,
1442
+ {
1443
+ id: `cmd-${Date.now()}`,
1444
+ prompt: trimmed,
1445
+ response: formatted,
1446
+ status: "done"
1447
+ }
1448
+ ]);
1449
+ }
1450
+ catch (err) {
1451
+ const msg = err instanceof Error ? err.message : String(err);
1452
+ setTurns((prev) => [
1453
+ ...prev,
1454
+ {
1455
+ id: `cmd-${Date.now()}`,
1456
+ prompt: trimmed,
1457
+ response: `✗ Error listing sessions: ${msg}\n`,
1458
+ status: "done"
1459
+ }
1460
+ ]);
1461
+ }
1462
+ return;
1463
+ }
1464
+ if (cmd === "/delete-session") {
1465
+ if (!arg) {
1466
+ setTurns((prev) => [
1467
+ ...prev,
1468
+ {
1469
+ id: `cmd-${Date.now()}`,
1470
+ prompt: trimmed,
1471
+ response: "✗ Please specify a session ID to delete.\n",
1472
+ status: "done"
1473
+ }
1474
+ ]);
1475
+ return;
1476
+ }
1477
+ try {
1478
+ const deleted = await store.delete(arg);
1479
+ setTurns((prev) => [
1480
+ ...prev,
1481
+ {
1482
+ id: `cmd-${Date.now()}`,
1483
+ prompt: trimmed,
1484
+ response: deleted
1485
+ ? `✓ Deleted session: ${arg}\n`
1486
+ : `✗ Session not found: ${arg}\n`,
1487
+ status: "done"
1488
+ }
1489
+ ]);
1490
+ }
1491
+ catch (err) {
1492
+ const msg = err instanceof Error ? err.message : String(err);
1493
+ setTurns((prev) => [
1494
+ ...prev,
1495
+ {
1496
+ id: `cmd-${Date.now()}`,
1497
+ prompt: trimmed,
1498
+ response: `✗ Error deleting session: ${msg}\n`,
1499
+ status: "done"
1500
+ }
1501
+ ]);
1502
+ }
1503
+ return;
1504
+ }
1505
+ if (cmd === "/resume") {
1506
+ if (!arg) {
1507
+ setTurns((prev) => [
1508
+ ...prev,
1509
+ {
1510
+ id: `cmd-${Date.now()}`,
1511
+ prompt: trimmed,
1512
+ response: "✗ Please specify a session or run ID to resume.\n",
1513
+ status: "done"
1514
+ }
1515
+ ]);
1516
+ return;
1517
+ }
1518
+ // Try SessionStore first
1519
+ try {
1520
+ const sessionData = await store.load(arg);
1521
+ if (sessionData) {
1522
+ try {
1523
+ const fs = await import("fs/promises");
1524
+ await fs.stat(sessionData.workingDirectory);
1525
+ }
1526
+ catch {
1527
+ setTurns((prev) => [
1528
+ ...prev,
1529
+ {
1530
+ id: `cmd-${Date.now()}`,
1531
+ prompt: trimmed,
1532
+ response: `⚠ Working directory no longer exists\n\nPath:\n ${sessionData.workingDirectory}\n`,
1533
+ status: "done"
1534
+ }
1535
+ ]);
1536
+ return;
1537
+ }
1538
+ if (agent && "restoreSession" in agent) {
1539
+ agent.restoreSession(sessionData);
1540
+ }
1541
+ const summary = SessionHistoryFormatter.formatResumeSummary(sessionData);
1542
+ setTurns((prev) => [
1543
+ ...prev,
1544
+ {
1545
+ id: `cmd-${Date.now()}`,
1546
+ prompt: trimmed,
1547
+ response: summary,
1548
+ status: "done"
1549
+ }
1550
+ ]);
1551
+ return;
1552
+ }
1553
+ }
1554
+ catch {
1555
+ // Fall through to durable run resume
1556
+ }
1557
+ if (agent && "prepareResume" in agent) {
1558
+ try {
1559
+ const prep = await agent.prepareResume(arg, cwd);
1560
+ if (!prep.canResume) {
1561
+ setTurns((prev) => [
1562
+ ...prev,
1563
+ {
1564
+ id: `cmd-${Date.now()}`,
1565
+ prompt: trimmed,
1566
+ response: `✗ Cannot resume run: ${prep.explanation}\n`,
1567
+ status: "done"
1568
+ }
1569
+ ]);
1570
+ }
1571
+ else {
1572
+ setPendingResume({ runId: arg, prep });
1573
+ const promptText = RunHistoryFormatter.formatResumePrompt(prep);
1574
+ setTurns((prev) => [
1575
+ ...prev,
1576
+ {
1577
+ id: `cmd-${Date.now()}`,
1578
+ prompt: trimmed,
1579
+ response: `${promptText}\n`,
1580
+ status: "done"
1581
+ }
1582
+ ]);
1583
+ }
1584
+ }
1585
+ catch (err) {
1586
+ const msg = err instanceof Error ? err.message : String(err);
1587
+ setTurns((prev) => [
1588
+ ...prev,
1589
+ {
1590
+ id: `cmd-${Date.now()}`,
1591
+ prompt: trimmed,
1592
+ response: `✗ Resume error: ${msg}\n`,
1593
+ status: "done"
1594
+ }
1595
+ ]);
1596
+ }
1597
+ }
1598
+ return;
1599
+ }
1600
+ if (cmd === "/replan") {
1601
+ if (agent && "prepareReplan" in agent) {
1602
+ try {
1603
+ const assessment = await agent.prepareReplan(arg || undefined, { cwd });
1604
+ if (!assessment.eligible) {
1605
+ setTurns((prev) => [
1606
+ ...prev,
1607
+ {
1608
+ id: `cmd-${Date.now()}`,
1609
+ prompt: trimmed,
1610
+ response: `✗ Replanning not eligible: ${assessment.reason}\n`,
1611
+ status: "done"
1612
+ }
1613
+ ]);
1614
+ }
1615
+ else {
1616
+ setPendingReplan({ planId: arg || "active-plan", assessment });
1617
+ const promptText = PlanFormatter.formatReplanPrompt(assessment);
1618
+ const replanNotice = PlanFormatter.formatReplanNotice();
1619
+ setTurns((prev) => [
1620
+ ...prev,
1621
+ {
1622
+ id: `cmd-${Date.now()}`,
1623
+ prompt: trimmed,
1624
+ response: `${replanNotice}\n\n${promptText}\n`,
1625
+ status: "done"
1626
+ }
1627
+ ]);
1628
+ }
1629
+ }
1630
+ catch (err) {
1631
+ const msg = err instanceof Error ? err.message : String(err);
1632
+ setTurns((prev) => [
1633
+ ...prev,
1634
+ {
1635
+ id: `cmd-${Date.now()}`,
1636
+ prompt: trimmed,
1637
+ response: `✗ Replan error: ${msg}\n`,
1638
+ status: "done"
1639
+ }
1640
+ ]);
1641
+ }
1642
+ }
1643
+ return;
1644
+ }
1645
+ if (cmd === "/plan") {
1646
+ setActiveView("plan");
1647
+ if (arg) {
1648
+ let summary;
1649
+ if (agent &&
1650
+ "getRunSummary" in agent &&
1651
+ typeof agent.getRunSummary === "function") {
1652
+ summary = agent.getRunSummary(arg);
1653
+ }
1654
+ else if (runtime && runtime.getDiagnosticsSummary) {
1655
+ summary = runtime.getDiagnosticsSummary(arg);
1656
+ }
1657
+ if (summary) {
1658
+ const lines = [
1659
+ `Task Plan for Run: ${summary.runId}`,
1660
+ `Plan ID: ${summary.planId || "unknown"}`,
1661
+ `Status: ${summary.planStatus || "unknown"}`,
1662
+ `Progress: ${summary.completedPlanSteps ?? 0}/${summary.totalPlanSteps ?? 0} completed`
1663
+ ];
1664
+ if (summary.planSummary) {
1665
+ lines.push(`Summary: ${summary.planSummary}`);
1666
+ }
1667
+ setTurns((prev) => [
1668
+ ...prev,
1669
+ {
1670
+ id: `cmd-${Date.now()}`,
1671
+ prompt: trimmed,
1672
+ response: lines.join("\n") + "\n",
1673
+ status: "done"
1674
+ }
1675
+ ]);
1676
+ return;
1677
+ }
1678
+ }
1679
+ let activePlan;
1680
+ if (agent &&
1681
+ "getTaskPlan" in agent &&
1682
+ typeof agent.getTaskPlan === "function") {
1683
+ activePlan = agent.getTaskPlan();
1684
+ }
1685
+ if (activePlan) {
1686
+ const detail = PlanFormatter.formatPlanDetail(activePlan);
1687
+ setTurns((prev) => [
1688
+ ...prev,
1689
+ {
1690
+ id: `cmd-${Date.now()}`,
1691
+ prompt: trimmed,
1692
+ response: detail,
1693
+ status: "done"
1694
+ }
1695
+ ]);
1696
+ }
1697
+ else {
1698
+ setTurns((prev) => [
1699
+ ...prev,
1700
+ {
1701
+ id: `cmd-${Date.now()}`,
1702
+ prompt: trimmed,
1703
+ response: "No active plan found.\n",
1704
+ status: "done"
1705
+ }
1706
+ ]);
1707
+ }
1708
+ return;
1709
+ }
1710
+ if (cmd === "/clear") {
1711
+ setTurns([]);
1712
+ setQuery("");
1713
+ return;
1714
+ }
1715
+ // Unknown command
1716
+ setTurns((prev) => [
1717
+ ...prev,
1718
+ {
1719
+ id: `cmd-${Date.now()}`,
1720
+ prompt: trimmed,
1721
+ response: `✗ Unknown command: ${cmd}. Type /help for available commands.\n`,
1722
+ status: "done"
1723
+ }
1724
+ ]);
1725
+ return;
1726
+ }
1727
+ // Standard task submission
1728
+ setQuery("");
1729
+ setIsGenerating(true);
1730
+ const nextTaskCount = taskCount + 1;
1731
+ setTaskCount(nextTaskCount);
1732
+ setActiveRequest(trimmed);
1733
+ setLastTaskStatus("in_progress");
1734
+ let initialResponse = "";
1735
+ let riskAssessment;
1736
+ if (agent &&
1737
+ "assessTaskRisk" in agent &&
1738
+ typeof agent.assessTaskRisk === "function") {
1739
+ try {
1740
+ riskAssessment = agent.assessTaskRisk({
1741
+ userMessage: trimmed,
1742
+ cwd,
1743
+ affectedFiles: [],
1744
+ operations: []
1745
+ });
1746
+ }
1747
+ catch {
1748
+ // ignore
1749
+ }
1750
+ }
1751
+ else {
1752
+ const isDepChange = trimmed.includes("npm install") ||
1753
+ trimmed.includes("yarn add") ||
1754
+ trimmed.includes("pnpm add") ||
1755
+ trimmed.includes("dependencies");
1756
+ if (isDepChange) {
1757
+ riskAssessment = {
1758
+ level: "elevated",
1759
+ reasons: ["Modifies project dependencies or configuration"],
1760
+ affectedFiles: 1,
1761
+ requiresCheckpoint: true,
1762
+ requiresExplicitApproval: true
1763
+ };
1764
+ }
1765
+ }
1766
+ if (riskAssessment &&
1767
+ (riskAssessment.level === "elevated" ||
1768
+ riskAssessment.level === "critical")) {
1769
+ const riskHeader = `${riskAssessment.level === "critical" ? "Critical" : "Elevated"}-risk task\n`;
1770
+ const cpReq = riskAssessment.requiresCheckpoint
1771
+ ? "Checkpoint required\n"
1772
+ : "";
1773
+ initialResponse = `${riskHeader}${cpReq}\n`;
1774
+ }
1775
+ const turnId = `turn-${Date.now()}`;
1776
+ const turnStartMs = Date.now();
1777
+ const newTurn = {
1778
+ id: turnId,
1779
+ prompt: trimmed,
1780
+ response: initialResponse,
1781
+ status: "streaming"
1782
+ };
1783
+ setTurns((prev) => [...prev, newTurn]);
1784
+ try {
1785
+ if (!agent) {
1786
+ throw new Error(configError || "Agent runtime is not initialized.");
1787
+ }
1788
+ const stream = agent.run({
1789
+ message: trimmed,
1790
+ cwd,
1791
+ sessionId
1792
+ });
1793
+ let accumulatedRawText = "";
1794
+ const toolCallMap = new Map();
1795
+ for await (const event of stream) {
1796
+ setUiState((prev) => reduceUIState(prev ||
1797
+ createInitialUIState({
1798
+ cwd,
1799
+ sessionId
1800
+ }), event));
1801
+ if (event.type === "text") {
1802
+ const chunk = event.content;
1803
+ accumulatedRawText += chunk;
1804
+ const elapsed = Date.now() - turnStartMs;
1805
+ const thinkingMatch = accumulatedRawText.match(/<(?:think|thinking)>([\s\S]*?)<\/(?:think|thinking)>/);
1806
+ const inProgressThinking = !thinkingMatch &&
1807
+ (accumulatedRawText.includes("<think>") ||
1808
+ accumulatedRawText.includes("<thinking>") ||
1809
+ accumulatedRawText.startsWith("<think"));
1810
+ if (thinkingMatch) {
1811
+ const thinkingContent = thinkingMatch[1].trim();
1812
+ const firstLine = thinkingContent.split("\n")[0]?.trim() || "";
1813
+ const cleanContent = accumulatedRawText
1814
+ .replace(/<(?:think|thinking)>[\s\S]*?<\/(?:think|thinking)>/g, "")
1815
+ .replace(/<\/?(?:think|thinking)>/g, "")
1816
+ .trim();
1817
+ setTurns((prev) => prev.map((t) => t.id === turnId
1818
+ ? {
1819
+ ...t,
1820
+ thinkingMs: t.thinkingMs !== undefined
1821
+ ? t.thinkingMs
1822
+ : elapsed > 0
1823
+ ? elapsed
1824
+ : 1000,
1825
+ thinkingSummary: firstLine,
1826
+ response: initialResponse ? initialResponse + cleanContent : cleanContent
1827
+ }
1828
+ : t));
1829
+ }
1830
+ else if (inProgressThinking) {
1831
+ const partialThinking = accumulatedRawText.replace(/[\s\S]*<(?:think|thinking)>/, "").trim();
1832
+ const firstLine = partialThinking.split("\n")[0]?.trim() || "Thinking...";
1833
+ setTurns((prev) => prev.map((t) => t.id === turnId
1834
+ ? {
1835
+ ...t,
1836
+ thinkingMs: elapsed > 0 ? elapsed : 1000,
1837
+ thinkingSummary: firstLine
1838
+ }
1839
+ : t));
1840
+ }
1841
+ else {
1842
+ setTurns((prev) => {
1843
+ const turn = prev.find((t) => t.id === turnId);
1844
+ const isFirstToken = !turn?.response || turn.response === initialResponse;
1845
+ const sanitizedChunk = chunk.replace(/<\/?(?:think|thinking)>/g, "");
1846
+ return prev.map((t) => t.id === turnId
1847
+ ? {
1848
+ ...t,
1849
+ thinkingMs: t.thinkingMs !== undefined
1850
+ ? t.thinkingMs
1851
+ : isFirstToken && elapsed > 500
1852
+ ? elapsed
1853
+ : undefined,
1854
+ response: t.response + sanitizedChunk
1855
+ }
1856
+ : t);
1857
+ });
1858
+ }
1859
+ }
1860
+ else if (event.type === "skills_activated") {
1861
+ if (event.skills && event.skills.length > 0) {
1862
+ setTurns((prev) => prev.map((t) => t.id === turnId
1863
+ ? {
1864
+ ...t,
1865
+ response: t.response +
1866
+ `⚡ Skills: ${event.skills.join(", ")}\n`
1867
+ }
1868
+ : t));
1869
+ }
1870
+ }
1871
+ else if (event.type === "error") {
1872
+ setTurns((prev) => prev.map((t) => t.id === turnId
1873
+ ? {
1874
+ ...t,
1875
+ status: "error",
1876
+ error: `✗ ${event.error.message}`
1877
+ }
1878
+ : t));
1879
+ setLastTaskStatus("blocked");
1880
+ }
1881
+ else if (event.type === "approval_required") {
1882
+ setPendingApproval(event.request);
1883
+ }
1884
+ else if (event.type === "checkpoint_approval_requested" ||
1885
+ event.type === "execution_handoff_waiting_approval") {
1886
+ setPendingApproval({
1887
+ id: "checkpointId" in event ? event.checkpointId : "req-cp",
1888
+ toolName: "checkpoint",
1889
+ category: "write",
1890
+ arguments: {},
1891
+ reason: event.reason
1892
+ });
1893
+ }
1894
+ else if (event.type === "tool_call") {
1895
+ toolCallMap.set(event.call.id, event.call.name);
1896
+ }
1897
+ else if (event.type === "tool_result") {
1898
+ const toolName = toolCallMap.get(event.callId) || "tool";
1899
+ if (event.result.success) {
1900
+ setTurns((prev) => prev.map((t) => t.id === turnId
1901
+ ? {
1902
+ ...t,
1903
+ response: t.response.includes(`✓ ${toolName}`)
1904
+ ? t.response
1905
+ : t.response + `✓ ${toolName} executed\n`
1906
+ }
1907
+ : t));
1908
+ }
1909
+ else if (event.result.error) {
1910
+ const errCode = event.result.error.code;
1911
+ let recoveryMsg = "";
1912
+ if (errCode === "NOT_FOUND") {
1913
+ recoveryMsg = `⚠ ${toolName}: File not found — searching again\n`;
1914
+ }
1915
+ else if (errCode === "EDIT_CONFLICT") {
1916
+ recoveryMsg = `⚠ ${toolName}: Edit conflict — refreshing file context\n`;
1917
+ }
1918
+ else if (errCode === "REPEATED_CALL_LOOP") {
1919
+ recoveryMsg = `⚠ ${toolName}: Repeated call loop detected — adapting strategy\n`;
1920
+ }
1921
+ else if (errCode === "COMMAND_TIMEOUT") {
1922
+ recoveryMsg = `⚠ ${toolName}: Command timed out\n`;
1923
+ }
1924
+ else {
1925
+ recoveryMsg = `✗ ${toolName}: ${event.result.error.message || "Execution error"}\n`;
1926
+ }
1927
+ if (recoveryMsg) {
1928
+ setTurns((prev) => prev.map((t) => t.id === turnId
1929
+ ? { ...t, response: t.response + recoveryMsg }
1930
+ : t));
1931
+ }
1932
+ }
1933
+ }
1934
+ else if (event.type === "plan_created") {
1935
+ const planSummary = `Plan: ${event.plan.objective}\n\n`;
1936
+ const formattedPlan = PlanFormatter.formatPlanDetail(event.plan);
1937
+ setTurns((prev) => prev.map((t) => t.id === turnId
1938
+ ? {
1939
+ ...t,
1940
+ response: (t.response ? t.response + "\n\n" : "") +
1941
+ planSummary +
1942
+ formattedPlan +
1943
+ "\n"
1944
+ }
1945
+ : t));
1946
+ }
1947
+ else if (event.type === "final_reconciliation_failed") {
1948
+ const plan = (agent &&
1949
+ "getTaskPlan" in agent &&
1950
+ typeof agent.getTaskPlan === "function"
1951
+ ? agent.getTaskPlan()
1952
+ : undefined) || {
1953
+ planId: event.result.planId,
1954
+ runId: event.result.runId,
1955
+ createdAt: Date.now(),
1956
+ userRequestSummary: "Reconciliation failed",
1957
+ objective: "Reconciliation failed",
1958
+ status: "blocked",
1959
+ steps: [],
1960
+ risks: []
1961
+ };
1962
+ const defaultAdaptation = {
1963
+ planId: event.result.planId,
1964
+ assessedAt: Date.now(),
1965
+ canContinue: false,
1966
+ canRetry: false,
1967
+ canAdapt: true,
1968
+ feedback: [],
1969
+ affectedSteps: event.result.missingFiles || [],
1970
+ currentRiskLevel: "normal",
1971
+ requiresUserConfirmation: true,
1972
+ recommendedAction: "replan"
1973
+ };
1974
+ setPendingPlanBlocked({
1975
+ plan,
1976
+ assessment: defaultAdaptation,
1977
+ reconciliationResult: event.result
1978
+ });
1979
+ const promptText = PlanFormatter.formatReconciliationBlockedPrompt(plan, event.result);
1980
+ setTurns((prev) => prev.map((t) => t.id === turnId
1981
+ ? {
1982
+ ...t,
1983
+ response: (t.response ? t.response + "\n\n" : "") + promptText + "\n"
1984
+ }
1985
+ : t));
1986
+ }
1987
+ else if (event.type === "plan_blocked" ||
1988
+ event.type === "plan_adaptation_required") {
1989
+ const plan = (agent &&
1990
+ "getTaskPlan" in agent &&
1991
+ typeof agent.getTaskPlan === "function"
1992
+ ? agent.getTaskPlan()
1993
+ : undefined) || {
1994
+ planId: event.planId,
1995
+ runId: "runId" in event ? event.runId : "run",
1996
+ createdAt: Date.now(),
1997
+ userRequestSummary: "Blocked plan",
1998
+ objective: "Blocked plan",
1999
+ status: "blocked",
2000
+ steps: [],
2001
+ risks: []
2002
+ };
2003
+ const planId = event.planId;
2004
+ const runId = "runId" in event ? event.runId : "run";
2005
+ const assessment = {
2006
+ planId,
2007
+ assessedAt: Date.now(),
2008
+ canContinue: true,
2009
+ canRetry: true,
2010
+ canAdapt: true,
2011
+ feedback: [
2012
+ {
2013
+ feedbackId: "fb-1",
2014
+ runId,
2015
+ planId,
2016
+ kind: "verification_failed",
2017
+ severity: "blocking",
2018
+ summary: event.reason,
2019
+ detectedAt: Date.now(),
2020
+ requiresReplanning: true,
2021
+ requiresUserConfirmation: true,
2022
+ recommendedAction: "replan"
2023
+ }
2024
+ ],
2025
+ affectedSteps: event.affectedSteps || [],
2026
+ currentRiskLevel: "normal",
2027
+ requiresUserConfirmation: true,
2028
+ recommendedAction: "replan"
2029
+ };
2030
+ setPendingPlanBlocked({ plan, assessment });
2031
+ const promptText = PlanFormatter.formatPlanBlockedPrompt(plan, assessment);
2032
+ setTurns((prev) => prev.map((t) => t.id === turnId
2033
+ ? {
2034
+ ...t,
2035
+ response: (t.response ? t.response + "\n\n" : "") + promptText + "\n"
2036
+ }
2037
+ : t));
2038
+ }
2039
+ else if (event.type === "plan_execution_started") {
2040
+ setTurns((prev) => prev.map((t) => t.id === turnId
2041
+ ? {
2042
+ ...t,
2043
+ response: t.response +
2044
+ `Plan approved. Executing ${event.totalSteps} steps...\n\n`
2045
+ }
2046
+ : t));
2047
+ }
2048
+ else if (event.type === "plan_step_started") {
2049
+ setTurns((prev) => prev.map((t) => t.id === turnId
2050
+ ? {
2051
+ ...t,
2052
+ response: t.response +
2053
+ `[${event.stepIndex + 1}] ${event.title || "Step"}\nEXECUTING\n`
2054
+ }
2055
+ : t));
2056
+ }
2057
+ else if (event.type === "plan_step_completed") {
2058
+ setTurns((prev) => prev.map((t) => t.id === turnId
2059
+ ? {
2060
+ ...t,
2061
+ response: t.response + "✓ COMPLETED\n\n"
2062
+ }
2063
+ : t));
2064
+ }
2065
+ else if (event.type === "plan_step_skipped") {
2066
+ setTurns((prev) => prev.map((t) => t.id === turnId
2067
+ ? {
2068
+ ...t,
2069
+ response: t.response +
2070
+ `⊘ SKIPPED (${event.reason || "Skipped"})\n\n`
2071
+ }
2072
+ : t));
2073
+ }
2074
+ else if (event.type === "plan_execution_completed") {
2075
+ setTurns((prev) => prev.map((t) => t.id === turnId
2076
+ ? {
2077
+ ...t,
2078
+ response: t.response +
2079
+ `✓ Plan completed (${event.completedSteps}/${event.totalSteps} steps).\n`
2080
+ }
2081
+ : t));
2082
+ }
2083
+ else if (event.type === "execution_feedback_detected") {
2084
+ setTurns((prev) => prev.map((t) => t.id === turnId
2085
+ ? {
2086
+ ...t,
2087
+ response: t.response + `⚠ Feedback: ${event.summary}\n`
2088
+ }
2089
+ : t));
2090
+ }
2091
+ else if (event.type === "step_retry_started") {
2092
+ setTurns((prev) => prev.map((t) => t.id === turnId
2093
+ ? {
2094
+ ...t,
2095
+ response: t.response +
2096
+ `⟳ Retrying Step ${event.stepId} (attempt ${event.attempt}/${event.maxAttempts}): ${event.reason}\n`
2097
+ }
2098
+ : t));
2099
+ }
2100
+ else if (event.type === "step_retry_completed") {
2101
+ setTurns((prev) => prev.map((t) => t.id === turnId
2102
+ ? {
2103
+ ...t,
2104
+ response: t.response +
2105
+ (event.success
2106
+ ? `✓ Step retry succeeded on attempt ${event.attempt}\n`
2107
+ : `✗ Step retry failed on attempt ${event.attempt}\n`)
2108
+ }
2109
+ : t));
2110
+ }
2111
+ else if (event.type === "plan_step_failed") {
2112
+ setTurns((prev) => prev.map((t) => t.id === turnId
2113
+ ? {
2114
+ ...t,
2115
+ response: t.response +
2116
+ `✗ Step ${event.stepId} failed${event.error ? `: ${event.error}` : ""}\n`
2117
+ }
2118
+ : t));
2119
+ }
2120
+ else if (event.type === "plan_execution_failed") {
2121
+ setTurns((prev) => prev.map((t) => t.id === turnId
2122
+ ? {
2123
+ ...t,
2124
+ status: "error",
2125
+ error: event.reason,
2126
+ response: t.response +
2127
+ `\n✗ Plan execution failed: ${event.reason || "Unknown error"}\n`
2128
+ }
2129
+ : t));
2130
+ setLastTaskStatus("blocked");
2131
+ }
2132
+ else if (event.type === "plan_execution_cancelled") {
2133
+ setTurns((prev) => prev.map((t) => t.id === turnId
2134
+ ? {
2135
+ ...t,
2136
+ status: "cancelled",
2137
+ response: t.response +
2138
+ `\n⊘ Plan execution cancelled: ${event.reason || "Cancelled by user"}\n`
2139
+ }
2140
+ : t));
2141
+ setLastTaskStatus("cancelled");
2142
+ }
2143
+ else if (event.type === "task_summary") {
2144
+ let summaryHeader = "";
2145
+ if (event.summary.status === "completed") {
2146
+ summaryHeader = "✓ Task completed\n\n";
2147
+ }
2148
+ else if (event.summary.status === "blocked") {
2149
+ summaryHeader = "⚠ Task blocked\n\n";
2150
+ }
2151
+ else if (event.summary.status === "cancelled") {
2152
+ summaryHeader = "⊘ Task cancelled\n\n";
2153
+ }
2154
+ let summaryFormatted = SessionHistoryFormatter.formatTaskDetail(event.summary);
2155
+ if (event.summary.verifiedCommands &&
2156
+ event.summary.verifiedCommands.length > 0) {
2157
+ summaryFormatted = summaryFormatted.replace("Verification:", "Verified:");
2158
+ }
2159
+ const fullSummary = summaryHeader + summaryFormatted;
2160
+ setTurns((prev) => prev.map((t) => t.id === turnId
2161
+ ? {
2162
+ ...t,
2163
+ response: (t.response ? t.response + "\n\n" : "") +
2164
+ fullSummary +
2165
+ "\n"
2166
+ }
2167
+ : t));
2168
+ setCompletedSummaries((prev) => [...prev, event.summary]);
2169
+ setLastTaskStatus(event.summary.status);
2170
+ persistState(event.summary.status, event.summary, nextTaskCount).catch(() => { });
2171
+ }
2172
+ else if (event.type === "done") {
2173
+ setTurns((prev) => prev.map((t) => t.id === turnId
2174
+ ? {
2175
+ ...t,
2176
+ status: "done",
2177
+ response: t.response && t.response.trim().length > 0
2178
+ ? t.response
2179
+ : "Completed (no output returned by the model)."
2180
+ }
2181
+ : t));
2182
+ }
2183
+ }
2184
+ }
2185
+ catch (err) {
2186
+ const msg = err instanceof Error ? err.message : String(err);
2187
+ setTurns((prev) => prev.map((t) => t.id === turnId
2188
+ ? { ...t, status: "error", error: `✗ ${msg}` }
2189
+ : t));
2190
+ setLastTaskStatus("blocked");
2191
+ }
2192
+ finally {
2193
+ setIsGenerating(false);
2194
+ setActiveRequest(undefined);
2195
+ }
2196
+ };
2197
+ return (_jsxs(AppShell, { header: _jsx(Header, { projectName: "fecode", providerName: providerName, modelName: modelName, cwd: cwd, status: uiState?.status || lastTaskStatus, gitBranch: uiState?.workspace?.gitBranch, isGitClean: uiState?.workspace ? !uiState.workspace.isGitDirty : true, runId: uiState?.runId, sessionId: sessionId }), modal: hasModal ? (_jsxs(Box, { flexDirection: "column", children: [pendingApproval && (_jsx(ApprovalPrompt, { toolName: pendingApproval.toolName, reason: pendingApproval.reason, changeReview: pendingApproval.changeReview, value: approvalInput, onChange: setApprovalInput, onSubmit: handleApprovalSubmit })), pendingPlanBlocked && (_jsx(BlockedView, { planId: pendingPlanBlocked.plan.planId, stepInfo: `${pendingPlanBlocked.plan.steps.find((s) => s.status === "in_progress")?.order || 1}/${pendingPlanBlocked.plan.steps.length}`, reason: pendingPlanBlocked.assessment.feedback[0]?.summary, affectedSteps: pendingPlanBlocked.assessment.affectedSteps, isReconciliation: Boolean(pendingPlanBlocked.reconciliationResult), value: blockedInput, onChange: setBlockedInput, onSubmit: handleSubmit })), pendingRecovery && (_jsx(RecoveryView, { strategy: pendingRecovery.assessment.strategy, outcome: pendingRecovery.assessment.eligible ? "ELIGIBLE" : "NOT_ELIGIBLE", value: recoveryInput, onChange: setRecoveryInput, onSubmit: handleSubmit })), pendingRecoveryContinuation && (_jsx(RecoveryView, { isAwaitingContinuation: true, remainingStepsCount: pendingRecoveryContinuation.preparation.remainingSteps.length, value: recoveryInput, onChange: setRecoveryInput, onSubmit: handleSubmit })), pendingReplan && (_jsx(ReplanView, { originalPlanId: pendingReplan.planId, reason: pendingReplan.assessment.reason, replanDepth: pendingReplan.assessment.replanDepth, value: replanInput, onChange: setReplanInput, onSubmit: handleSubmit })), pendingResume && (_jsx(ResumeView, { runId: pendingResume.runId, originalRequest: pendingResume.prep.originalRun.userRequestSummary, riskLevel: pendingResume.prep.reassessedRisk.level, value: resumeInput, onChange: setResumeInput, onSubmit: handleSubmit }))] })) : null, footer: _jsx(StatusBar, { status: uiState?.status || lastTaskStatus, isGenerating: isGenerating, hasModal: hasModal, activeView: activeView, isReconciliation: Boolean(pendingPlanBlocked?.reconciliationResult), recoveryMode: pendingRecoveryContinuation
2198
+ ? "continuation"
2199
+ : pendingRecovery
2200
+ ? "confirm"
2201
+ : undefined, modalType: pendingApproval
2202
+ ? "approval"
2203
+ : pendingPlanBlocked
2204
+ ? "blocked"
2205
+ : pendingRecovery || pendingRecoveryContinuation
2206
+ ? "recovery"
2207
+ : pendingReplan
2208
+ ? "replan"
2209
+ : pendingResume
2210
+ ? "resume"
2211
+ : undefined }), children: [configError && (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "red", paddingX: 1, marginY: 1, children: [_jsx(Text, { bold: true, color: "red", children: "\u26A0 Configuration Error" }), _jsx(Text, { color: "white", children: configError }), _jsx(Text, { color: "gray", children: "Please check your environment variables (.env) or configuration file." })] })), activeView === "help" && _jsx(HelpView, {}), activeView === "git" && (_jsx(WorkspaceStatus, { cwd: cwd, branch: uiState?.workspace?.gitBranch, isClean: uiState?.workspace ? !uiState.workspace.isGitDirty : true, riskLevel: uiState?.riskLevel, modifiedFiles: uiState?.workspace?.modifiedFiles, stagedFiles: uiState?.workspace?.stagedFiles, untrackedFiles: uiState?.workspace?.untrackedFiles, hasDrift: uiState?.workspace?.hasDrift, driftReason: uiState?.workspace?.driftReason })), activeView === "runs" && (_jsx(RunHistoryView, { runs: uiState?.diagnostics
2212
+ ? [
2213
+ {
2214
+ runId: uiState.diagnostics.runId,
2215
+ status: uiState.diagnostics.finalStatus || "executing",
2216
+ userRequestSummary: uiState.diagnostics.userRequestSummary,
2217
+ durationMs: uiState.diagnostics.durationMs
2218
+ }
2219
+ ]
2220
+ : [] })), activeView === "diagnostics" && (_jsx(DiagnosticsView, { summary: uiState?.diagnostics })), activeView === "plan" && uiState?.activePlan && (_jsx(PlanView, { planId: uiState.activePlan.planId, objective: uiState.activePlan.objective, summary: uiState.activePlan.userRequestSummary, status: uiState.activePlan.status, steps: uiState.activePlan.steps, completedCount: uiState.activePlan.completedStepsCount, totalCount: uiState.activePlan.totalStepsCount })), isGenerating && uiState?.activeTool && (_jsx(CurrentStepView, { toolName: uiState.activeTool.toolName, target: uiState.activeTool.target, riskLevel: uiState.riskLevel })), _jsxs(Box, { flexDirection: "column", children: [turns.map((turn, idx) => (_jsx(TurnView, { prompt: turn.prompt, response: turn.response, status: turn.status, error: turn.error, isLast: idx === turns.length - 1, thinkingMs: turn.thinkingMs, thinkingTokens: turn.thinkingTokens, thinkingSummary: turn.thinkingSummary }, turn.id))), isGenerating && (_jsx(Box, { marginTop: 0, children: _jsx(ThinkingIndicator, { isActive: isGenerating, label: "Agent is working..." }) })), !hasModal && (_jsx(TaskInput, { value: query, onChange: handleQueryChange, onSubmit: handleSubmit, isDisabled: Boolean(pendingQuery), placeholder: isGenerating
2221
+ ? "Type next task (will be queued)..."
2222
+ : "Describe task or type /help...", label: turns.length === 0 ? "Task" : undefined, pendingQuery: pendingQuery, suggestions: commandSuggestions, selectedSuggestion: selectedSuggestion }))] })] }));
2223
+ };
2224
+ //# sourceMappingURL=App.js.map