@roackb2/heddle 0.0.35 → 0.0.37

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 (132) hide show
  1. package/dist/src/cli/chat/App.d.ts.map +1 -1
  2. package/dist/src/cli/chat/App.js +94 -80
  3. package/dist/src/cli/chat/App.js.map +1 -1
  4. package/dist/src/cli/chat/components/PromptInput.d.ts +5 -0
  5. package/dist/src/cli/chat/components/PromptInput.d.ts.map +1 -1
  6. package/dist/src/cli/chat/components/PromptInput.js +131 -85
  7. package/dist/src/cli/chat/components/PromptInput.js.map +1 -1
  8. package/dist/src/cli/chat/hooks/tui-agent-turn-lifecycle.d.ts +6 -0
  9. package/dist/src/cli/chat/hooks/tui-agent-turn-lifecycle.d.ts.map +1 -0
  10. package/dist/src/cli/chat/hooks/tui-agent-turn-lifecycle.js +38 -0
  11. package/dist/src/cli/chat/hooks/tui-agent-turn-lifecycle.js.map +1 -0
  12. package/dist/src/cli/chat/hooks/tui-agent-turn-result.d.ts +48 -0
  13. package/dist/src/cli/chat/hooks/tui-agent-turn-result.d.ts.map +1 -0
  14. package/dist/src/cli/chat/hooks/tui-agent-turn-result.js +171 -0
  15. package/dist/src/cli/chat/hooks/tui-agent-turn-result.js.map +1 -0
  16. package/dist/src/cli/chat/hooks/tui-compaction-status.d.ts +24 -0
  17. package/dist/src/cli/chat/hooks/tui-compaction-status.d.ts.map +1 -0
  18. package/dist/src/cli/chat/hooks/tui-compaction-status.js +65 -0
  19. package/dist/src/cli/chat/hooks/tui-compaction-status.js.map +1 -0
  20. package/dist/src/cli/chat/hooks/tui-direct-shell-result.d.ts +28 -0
  21. package/dist/src/cli/chat/hooks/tui-direct-shell-result.d.ts.map +1 -0
  22. package/dist/src/cli/chat/hooks/tui-direct-shell-result.js +41 -0
  23. package/dist/src/cli/chat/hooks/tui-direct-shell-result.js.map +1 -0
  24. package/dist/src/cli/chat/hooks/tui-direct-shell.d.ts +22 -0
  25. package/dist/src/cli/chat/hooks/tui-direct-shell.d.ts.map +1 -0
  26. package/dist/src/cli/chat/hooks/tui-direct-shell.js +127 -0
  27. package/dist/src/cli/chat/hooks/tui-direct-shell.js.map +1 -0
  28. package/dist/src/cli/chat/hooks/tui-drift-observer.d.ts +13 -0
  29. package/dist/src/cli/chat/hooks/tui-drift-observer.d.ts.map +1 -0
  30. package/dist/src/cli/chat/hooks/tui-drift-observer.js +36 -0
  31. package/dist/src/cli/chat/hooks/tui-drift-observer.js.map +1 -0
  32. package/dist/src/cli/chat/hooks/tui-ordinary-turn.d.ts +31 -0
  33. package/dist/src/cli/chat/hooks/tui-ordinary-turn.d.ts.map +1 -0
  34. package/dist/src/cli/chat/hooks/tui-ordinary-turn.js +83 -0
  35. package/dist/src/cli/chat/hooks/tui-ordinary-turn.js.map +1 -0
  36. package/dist/src/cli/chat/hooks/tui-run-loop-events.d.ts +23 -0
  37. package/dist/src/cli/chat/hooks/tui-run-loop-events.d.ts.map +1 -0
  38. package/dist/src/cli/chat/hooks/tui-run-loop-events.js +74 -0
  39. package/dist/src/cli/chat/hooks/tui-run-loop-events.js.map +1 -0
  40. package/dist/src/cli/chat/hooks/tui-tool-approval.d.ts +8 -0
  41. package/dist/src/cli/chat/hooks/tui-tool-approval.d.ts.map +1 -0
  42. package/dist/src/cli/chat/hooks/tui-tool-approval.js +37 -0
  43. package/dist/src/cli/chat/hooks/tui-tool-approval.js.map +1 -0
  44. package/dist/src/cli/chat/hooks/useAgentRun.d.ts +1 -1
  45. package/dist/src/cli/chat/hooks/useAgentRun.d.ts.map +1 -1
  46. package/dist/src/cli/chat/hooks/useAgentRun.js +59 -551
  47. package/dist/src/cli/chat/hooks/useAgentRun.js.map +1 -1
  48. package/dist/src/cli/chat/hooks/useChatStatusSummary.d.ts +58 -0
  49. package/dist/src/cli/chat/hooks/useChatStatusSummary.d.ts.map +1 -0
  50. package/dist/src/cli/chat/hooks/useChatStatusSummary.js +85 -0
  51. package/dist/src/cli/chat/hooks/useChatStatusSummary.js.map +1 -0
  52. package/dist/src/cli/chat/hooks/usePromptSubmission.d.ts.map +1 -1
  53. package/dist/src/cli/chat/hooks/usePromptSubmission.js +1 -0
  54. package/dist/src/cli/chat/hooks/usePromptSubmission.js.map +1 -1
  55. package/dist/src/cli/chat/utils/format.d.ts.map +1 -1
  56. package/dist/src/cli/chat/utils/format.js +21 -0
  57. package/dist/src/cli/chat/utils/format.js.map +1 -1
  58. package/dist/src/cli/chat/utils/runtime.d.ts.map +1 -1
  59. package/dist/src/cli/chat/utils/runtime.js +5 -3
  60. package/dist/src/cli/chat/utils/runtime.js.map +1 -1
  61. package/dist/src/core/agent/run-agent.js +9 -8
  62. package/dist/src/core/agent/run-agent.js.map +1 -1
  63. package/dist/src/core/agent/tool-dispatch.d.ts.map +1 -1
  64. package/dist/src/core/agent/tool-dispatch.js +8 -7
  65. package/dist/src/core/agent/tool-dispatch.js.map +1 -1
  66. package/dist/src/core/agent/util.d.ts +0 -4
  67. package/dist/src/core/agent/util.d.ts.map +1 -1
  68. package/dist/src/core/agent/util.js +1 -7
  69. package/dist/src/core/agent/util.js.map +1 -1
  70. package/dist/src/core/chat/ordinary-turn.d.ts +34 -0
  71. package/dist/src/core/chat/ordinary-turn.d.ts.map +1 -0
  72. package/dist/src/core/chat/ordinary-turn.js +274 -0
  73. package/dist/src/core/chat/ordinary-turn.js.map +1 -0
  74. package/dist/src/core/chat/session-submit.d.ts +4 -4
  75. package/dist/src/core/chat/session-submit.d.ts.map +1 -1
  76. package/dist/src/core/chat/session-submit.js +23 -282
  77. package/dist/src/core/chat/session-submit.js.map +1 -1
  78. package/dist/src/core/chat/session-title.d.ts +15 -0
  79. package/dist/src/core/chat/session-title.d.ts.map +1 -0
  80. package/dist/src/core/chat/session-title.js +17 -0
  81. package/dist/src/core/chat/session-title.js.map +1 -0
  82. package/dist/src/core/chat/session-turn-preflight.d.ts +37 -0
  83. package/dist/src/core/chat/session-turn-preflight.d.ts.map +1 -0
  84. package/dist/src/core/chat/session-turn-preflight.js +43 -0
  85. package/dist/src/core/chat/session-turn-preflight.js.map +1 -0
  86. package/dist/src/core/chat/session-turn-result.d.ts +36 -0
  87. package/dist/src/core/chat/session-turn-result.d.ts.map +1 -0
  88. package/dist/src/core/chat/session-turn-result.js +60 -0
  89. package/dist/src/core/chat/session-turn-result.js.map +1 -0
  90. package/dist/src/core/chat/tool-approval-host.d.ts +22 -0
  91. package/dist/src/core/chat/tool-approval-host.d.ts.map +1 -0
  92. package/dist/src/core/chat/tool-approval-host.js +14 -0
  93. package/dist/src/core/chat/tool-approval-host.js.map +1 -0
  94. package/dist/src/core/chat/turn-host.d.ts +25 -0
  95. package/dist/src/core/chat/turn-host.d.ts.map +1 -0
  96. package/dist/src/core/chat/turn-host.js +2 -0
  97. package/dist/src/core/chat/turn-host.js.map +1 -0
  98. package/dist/src/core/llm/openai.d.ts +7 -0
  99. package/dist/src/core/llm/openai.d.ts.map +1 -1
  100. package/dist/src/core/llm/openai.js +56 -0
  101. package/dist/src/core/llm/openai.js.map +1 -1
  102. package/dist/src/core/memory/domain-prompt.d.ts.map +1 -1
  103. package/dist/src/core/memory/domain-prompt.js +48 -32
  104. package/dist/src/core/memory/domain-prompt.js.map +1 -1
  105. package/dist/src/core/runtime/agent-loop.d.ts.map +1 -1
  106. package/dist/src/core/runtime/agent-loop.js +9 -3
  107. package/dist/src/core/runtime/agent-loop.js.map +1 -1
  108. package/dist/src/core/runtime/default-tools.d.ts.map +1 -1
  109. package/dist/src/core/runtime/default-tools.js +1 -0
  110. package/dist/src/core/runtime/default-tools.js.map +1 -1
  111. package/dist/src/core/tools/file-edit-core.d.ts.map +1 -1
  112. package/dist/src/core/tools/file-edit-core.js +21 -2
  113. package/dist/src/core/tools/file-edit-core.js.map +1 -1
  114. package/dist/src/core/tools/memory-checkpoint.js +1 -1
  115. package/dist/src/core/tools/memory-checkpoint.js.map +1 -1
  116. package/dist/src/core/tools/record-knowledge.js +8 -4
  117. package/dist/src/core/tools/record-knowledge.js.map +1 -1
  118. package/dist/src/core/tools/view-image.d.ts.map +1 -1
  119. package/dist/src/core/tools/view-image.js +23 -58
  120. package/dist/src/core/tools/view-image.js.map +1 -1
  121. package/dist/src/core/tools/web-search.d.ts +2 -1
  122. package/dist/src/core/tools/web-search.d.ts.map +1 -1
  123. package/dist/src/core/tools/web-search.js +76 -3
  124. package/dist/src/core/tools/web-search.js.map +1 -1
  125. package/dist/src/server/features/control-plane/services/chat-session-events.d.ts +26 -0
  126. package/dist/src/server/features/control-plane/services/chat-session-events.d.ts.map +1 -0
  127. package/dist/src/server/features/control-plane/services/chat-session-events.js +61 -0
  128. package/dist/src/server/features/control-plane/services/chat-session-events.js.map +1 -0
  129. package/dist/src/server/features/control-plane/services/chat-sessions.d.ts.map +1 -1
  130. package/dist/src/server/features/control-plane/services/chat-sessions.js +21 -39
  131. package/dist/src/server/features/control-plane/services/chat-sessions.js.map +1 -1
  132. package/package.json +1 -1
@@ -1,18 +1,16 @@
1
1
  import { useMemo } from 'react';
2
- import { readFileSync, writeFileSync } from 'node:fs';
3
- import { createCyberLoopKinematicsObserver, createLogger, createLlmAdapter, createDefaultAgentTools, runAgentLoop, } from '../../../index.js';
4
- import { runMaintenanceForRecordedCandidates } from '../../../core/memory/maintenance-integration.js';
5
- import { DEFAULT_INSPECT_RULES, DEFAULT_MUTATE_RULES, runShellCommand } from '../../../core/tools/run-shell.js';
6
- import { previewEditFileInput } from '../../../core/tools/edit-file.js';
7
- import { appendDirectShellHistory, buildConversationMessages, countAssistantSteps, formatChatFailureMessage, formatEditPreviewHistoryMessage, formatPlanHistoryMessage, formatDirectShellResponse, shouldFallbackToMutate, summarizeTrace, summarizeToolCall, toLiveEvent, } from '../utils/format.js';
8
- import { saveTrace } from '../utils/runtime.js';
2
+ import { createLogger, createLlmAdapter, createDefaultAgentTools, } from '../../../index.js';
9
3
  import { formatMissingProviderCredentialMessage, hasProviderCredentialForModel, resolveApiKeyForModel, resolveProviderCredentialSourceForModel, } from '../../../core/runtime/api-keys.js';
10
- import { acquireSessionLease, getSessionLeaseConflict, releaseSessionLease } from '../../../core/chat/session-lease.js';
11
- import { createProjectApprovalRuleForCall, describeProjectApprovalRule } from '../state/approval-rules.js';
12
- import { buildCompactionRunningContext, compactChatHistoryWithArchive, estimateChatHistoryTokens } from '../state/compaction.js';
13
- import { isGenericSessionName, readChatSession, touchSession } from '../state/storage.js';
4
+ import { releaseSessionLease } from '../../../core/chat/session-lease.js';
5
+ import { generateSessionTitle } from '../../../core/chat/session-title.js';
6
+ import { isGenericSessionName } from '../state/storage.js';
14
7
  import { normalizeSessionTitle } from '../utils/format.js';
15
8
  import { useProjectApprovals } from './useProjectApprovals.js';
9
+ import { beginTuiAgentTurn, finishTuiAgentTurn, } from './tui-agent-turn-lifecycle.js';
10
+ import { createTuiChatDriftObserver } from './tui-drift-observer.js';
11
+ import { executeTuiDirectShell } from './tui-direct-shell.js';
12
+ import { applyTuiAgentTurnFailure } from './tui-agent-turn-result.js';
13
+ import { executeTuiOrdinaryTurn } from './tui-ordinary-turn.js';
16
14
  const PLAN_ITEM_STATUSES = new Set(['pending', 'in_progress', 'completed']);
17
15
  export function useAgentRun(args) {
18
16
  const { runtime, activeModel, sessionTitleModel, activeSessionId, sessions, state, updateSessionById, updateActiveSession } = args;
@@ -34,7 +32,15 @@ export function useAgentRun(args) {
34
32
  searchIgnoreDirs: runtime.searchIgnoreDirs,
35
33
  includePlanTool: true,
36
34
  });
37
- }, [activeApiKey, activeCredentialSource, activeModel, runtime.memoryDir, runtime.searchIgnoreDirs, runtime.workspaceRoot]);
35
+ }, [
36
+ activeApiKey,
37
+ activeCredentialSource,
38
+ activeModel,
39
+ runtime.credentialStorePath,
40
+ runtime.memoryDir,
41
+ runtime.searchIgnoreDirs,
42
+ runtime.workspaceRoot,
43
+ ]);
38
44
  const logger = useMemo(() => createLogger({
39
45
  pretty: false,
40
46
  level: 'debug',
@@ -48,17 +54,12 @@ export function useAgentRun(args) {
48
54
  }
49
55
  void (async () => {
50
56
  try {
51
- const result = await titleLlm.chat([
52
- {
53
- role: 'system',
54
- content: 'You name terminal chat sessions. Return only a short 3 to 6 word title in plain text. No quotes, no punctuation, no prefix.',
55
- },
56
- {
57
- role: 'user',
58
- content: `User prompt:\n${prompt}\n\nAssistant or tool summary:\n${responseText}\n\nCreate a concise session title.`,
59
- },
60
- ], []);
61
- const title = normalizeSessionTitle(result.content);
57
+ const title = await generateSessionTitle({
58
+ llm: titleLlm,
59
+ prompt,
60
+ responseText,
61
+ normalize: normalizeSessionTitle,
62
+ });
62
63
  if (!title) {
63
64
  return;
64
65
  }
@@ -110,7 +111,7 @@ export function useAgentRun(args) {
110
111
  };
111
112
  }
112
113
  export async function executeAgentTurn(args) {
113
- const { prompt, displayText, sessionId, sessionHistory, runtime, llm, tools, logger, state, updateSessionById, referenceAssistantText, maybeAutoNameSession, isProjectApproved, rememberProjectApproval, drift, } = args;
114
+ const { prompt, displayText, sessionId, sessionHistory, runtime, llm, logger, state, updateSessionById, referenceAssistantText, maybeAutoNameSession, isProjectApproved, rememberProjectApproval, drift, } = args;
114
115
  if (!prompt || state.isRunning) {
115
116
  return undefined;
116
117
  }
@@ -119,96 +120,16 @@ export async function executeAgentTurn(args) {
119
120
  state.setStatus('Error');
120
121
  return undefined;
121
122
  }
122
- state.setError(undefined);
123
- state.setIsRunning(true);
124
- state.setStatus('Running');
125
- state.interruptRequestedRef.current = false;
126
- state.setInterruptRequested(false);
127
- state.abortControllerRef.current = new AbortController();
128
- state.setLiveEvents([]);
129
- state.setCurrentEditPreview(undefined);
130
- state.setCurrentPlan(undefined);
131
- state.setCurrentAssistantText(undefined);
123
+ const turnAbortController = beginTuiAgentTurn(state);
132
124
  updateSessionById(sessionId, (session) => ({ ...session, lastContinuePrompt: prompt }));
133
- const appendedEditPreviewIds = new Set();
134
- const appendedPlanSteps = new Set();
135
- const streamingBuffers = new Map();
136
125
  drift?.onRunStart?.();
137
- let historyForRun = sessionHistory;
138
126
  const leaseOwner = {
139
127
  ownerKind: 'tui',
140
128
  ownerId: `tui-${process.pid}`,
141
129
  clientLabel: 'terminal chat',
142
130
  };
143
- const persistedSession = readChatSession(runtime.sessionCatalogFile, sessionId, true);
144
- const leaseConflict = persistedSession ? getSessionLeaseConflict(persistedSession, leaseOwner) : undefined;
145
- if (leaseConflict) {
146
- state.setError(leaseConflict);
147
- state.setStatus('Blocked');
148
- state.setIsRunning(false);
149
- state.interruptRequestedRef.current = false;
150
- state.setInterruptRequested(false);
151
- state.abortControllerRef.current = undefined;
152
- updateSessionById(sessionId, (sessionToUpdate) => ({
153
- ...sessionToUpdate,
154
- lastContinuePrompt: undefined,
155
- messages: [
156
- ...sessionToUpdate.messages,
157
- { id: state.nextLocalId(), role: 'assistant', text: leaseConflict },
158
- ],
159
- }));
160
- return undefined;
161
- }
162
- if (persistedSession) {
163
- const leasedSession = touchSession(acquireSessionLease(persistedSession, leaseOwner));
164
- historyForRun = leasedSession.history;
165
- updateSessionById(sessionId, () => leasedSession);
166
- }
167
- const toolNames = tools.map((tool) => tool.name);
168
- const emitCompactionStatus = (event, sourceHistory) => {
169
- if (event.status === 'running') {
170
- state.setStatus('Compacting');
171
- state.setLiveEvents((current) => [...current, { id: state.nextLocalId(), text: 'Compacting earlier conversation history…' }].slice(-8));
172
- updateSessionById(sessionId, (sessionToUpdate) => ({
173
- ...sessionToUpdate,
174
- history: sourceHistory,
175
- context: buildCompactionRunningContext({
176
- history: sourceHistory,
177
- previous: sessionToUpdate.context,
178
- archiveCount: sessionToUpdate.archives?.length,
179
- currentSummaryPath: sessionToUpdate.context?.currentSummaryPath,
180
- lastArchivePath: event.archivePath,
181
- }),
182
- }));
183
- return;
184
- }
185
- if (event.status === 'failed') {
186
- state.setLiveEvents((current) => [...current, { id: state.nextLocalId(), text: `Compaction failed: ${event.error ?? 'unknown error'}` }].slice(-8));
187
- return;
188
- }
189
- state.setLiveEvents((current) => [...current, { id: state.nextLocalId(), text: 'Compaction finished.' }].slice(-8));
190
- };
191
- const preflightCompacted = await compactChatHistoryWithArchive({
192
- history: historyForRun,
193
- model: llm.info?.model ?? runtime.model,
194
- sessionId,
195
- stateRoot: runtime.stateRoot,
196
- systemContext: runtime.systemContext,
197
- toolNames,
198
- goal: prompt,
199
- summarizer: { credentialSource: runtime.providerCredentialSource },
200
- onStatusChange: (event) => emitCompactionStatus(event, historyForRun),
201
- });
202
- historyForRun = preflightCompacted.history;
203
- updateSessionById(sessionId, (sessionToUpdate) => ({
204
- ...sessionToUpdate,
205
- history: preflightCompacted.history,
206
- context: preflightCompacted.context,
207
- archives: preflightCompacted.archives,
208
- messages: buildConversationMessages(preflightCompacted.history),
209
- }));
210
131
  state.setStatus('Running');
211
- const driftObserver = await createChatDriftObserver({
132
+ const driftObserver = await createTuiChatDriftObserver({
212
133
  prompt,
213
134
  referenceAssistantText,
214
135
  llm,
@@ -216,247 +137,40 @@ export async function executeAgentTurn(args) {
216
137
  logger,
217
138
  options: drift,
218
139
  });
219
- if (displayText) {
220
- updateSessionById(sessionId, (session) => ({
221
- ...session,
222
- messages: [...session.messages, { id: state.nextLocalId(), role: 'user', text: displayText }],
223
- }));
224
- }
225
140
  try {
226
- const result = await runAgentLoop({
227
- goal: prompt,
228
- model: llm.info?.model ?? runtime.model,
229
- workspaceRoot: runtime.workspaceRoot,
230
- memoryDir: runtime.memoryDir,
231
- searchIgnoreDirs: runtime.searchIgnoreDirs,
232
- llm,
233
- tools,
234
- includeDefaultTools: false,
235
- maxSteps: runtime.maxSteps,
236
- logger,
237
- history: historyForRun,
238
- systemContext: runtime.systemContext,
239
- onAssistantStream: (update) => {
240
- streamingBuffers.set(update.step, update.text);
241
- state.setCurrentAssistantText(update.text || undefined);
242
- if (update.done) {
243
- streamingBuffers.delete(update.step);
244
- }
245
- },
246
- onTraceEvent: (event) => {
247
- if (event.type === 'assistant.turn' && event.content.trim()) {
248
- streamingBuffers.delete(event.step);
249
- state.setCurrentAssistantText(event.content);
250
- }
251
- if (event.type === 'tool.call' && event.call.tool === 'edit_file') {
252
- void previewEditFileInput(event.call.input).then((preview) => {
253
- if (!preview || appendedEditPreviewIds.has(event.call.id)) {
254
- return;
255
- }
256
- appendedEditPreviewIds.add(event.call.id);
257
- updateSessionById(sessionId, (session) => ({
258
- ...session,
259
- messages: [
260
- ...session.messages,
261
- {
262
- id: state.nextLocalId(),
263
- role: 'assistant',
264
- text: formatEditPreviewHistoryMessage(preview),
265
- },
266
- ],
267
- }));
268
- });
269
- }
270
- if (event.type === 'tool.result') {
271
- if (event.tool === 'update_plan') {
272
- state.setCurrentPlan(parsePlanStateFromToolResult(event.result.output));
273
- if (!appendedPlanSteps.has(event.step)) {
274
- const renderedPlan = formatPlanHistoryMessage(event.result.output);
275
- if (renderedPlan) {
276
- appendedPlanSteps.add(event.step);
277
- updateSessionById(sessionId, (session) => ({
278
- ...session,
279
- messages: [
280
- ...session.messages,
281
- {
282
- id: state.nextLocalId(),
283
- role: 'assistant',
284
- text: renderedPlan,
285
- },
286
- ],
287
- }));
288
- }
289
- }
290
- }
291
- }
292
- const next = toLiveEvent(event);
293
- if (!next) {
294
- return;
295
- }
296
- state.setLiveEvents((current) => {
297
- const previous = current[current.length - 1];
298
- if (previous?.text === next) {
299
- return current;
300
- }
301
- return [...current, { id: state.nextLocalId(), text: next }].slice(-8);
302
- });
303
- },
304
- onEvent: (event) => {
305
- driftObserver?.observer.handleEvent(event);
306
- },
307
- approveToolCall: async (call, tool) => {
308
- if (isProjectApproved(call)) {
309
- return {
310
- approved: true,
311
- reason: 'Approved by saved project rule',
312
- };
313
- }
314
- const editPreview = call.tool === 'edit_file' ? await previewEditFileInput(call.input) : undefined;
315
- return new Promise((resolve) => {
316
- const rememberedRule = createProjectApprovalRuleForCall(call);
317
- state.setPendingApproval({
318
- call,
319
- tool,
320
- editPreview,
321
- rememberForProject: () => rememberProjectApproval(call),
322
- rememberLabel: rememberedRule ? describeProjectApprovalRule(rememberedRule) : undefined,
323
- resolve,
324
- });
325
- });
326
- },
327
- shouldStop: () => state.interruptRequestedRef.current,
328
- abortSignal: state.abortControllerRef.current.signal,
329
- });
330
- await driftObserver?.observer.flush();
331
- if (driftObserver?.annotations.length) {
332
- result.trace.push(...driftObserver.annotations);
333
- }
334
- const compacted = await compactChatHistoryWithArchive({
335
- history: result.transcript,
336
- model: llm.info?.model ?? runtime.model,
337
- sessionId,
338
- stateRoot: runtime.stateRoot,
339
- usage: result.usage,
340
- systemContext: runtime.systemContext,
341
- toolNames,
342
- goal: prompt,
343
- summarizer: { credentialSource: runtime.providerCredentialSource },
344
- onStatusChange: (event) => emitCompactionStatus(event, result.transcript),
345
- });
346
- updateSessionById(sessionId, (sessionToUpdate) => ({
347
- ...sessionToUpdate,
348
- history: compacted.history,
349
- context: compacted.context,
350
- archives: compacted.archives,
351
- }));
352
- const traceFile = saveTrace(runtime.traceDir, result.trace);
353
- const nextTurn = {
354
- id: state.nextLocalId(),
141
+ const result = await executeTuiOrdinaryTurn({
355
142
  prompt,
356
- outcome: result.outcome,
357
- summary: result.summary,
358
- steps: countAssistantSteps(result.trace),
359
- traceFile,
360
- events: summarizeTrace(result.trace),
361
- };
362
- updateSessionById(sessionId, (sessionToUpdate) => ({
363
- ...sessionToUpdate,
364
- turns: [...sessionToUpdate.turns, nextTurn].slice(-8),
365
- }));
366
- const formattedSummary = result.outcome === 'error' ?
367
- formatChatFailureMessage(result.summary, {
368
- model: llm.info?.model ?? runtime.model,
369
- estimatedHistoryTokens: estimateChatHistoryTokens(historyForRun),
370
- })
371
- : result.summary;
372
- state.setCurrentAssistantText(undefined);
373
- updateSessionById(sessionId, (sessionToUpdate) => ({
374
- ...sessionToUpdate,
375
- messages: [
376
- ...sessionToUpdate.messages,
377
- {
378
- id: state.nextLocalId(),
379
- role: 'assistant',
380
- text: result.outcome === 'done' ? formattedSummary : `Run stopped: ${formattedSummary}`,
381
- },
382
- ],
383
- }));
384
- const assistantText = formattedSummary;
385
- maybeAutoNameSession(sessionId, prompt, assistantText);
386
- if (result.outcome === 'error') {
387
- state.setError(formattedSummary);
388
- }
389
- state.setStatus(result.outcome === 'done' ? 'Idle' : `Stopped: ${result.outcome}`);
390
- scheduleBackgroundMemoryMaintenance({
391
- runtime,
392
- llm,
143
+ displayText,
393
144
  sessionId,
394
- trace: result.trace,
395
- traceFile,
145
+ runtime,
146
+ state,
396
147
  updateSessionById,
397
- nextLocalId: state.nextLocalId,
398
- setLiveEvents: state.setLiveEvents,
399
- setIsMemoryUpdating: state.setIsMemoryUpdating,
148
+ parsePlanState: parsePlanStateFromToolResult,
149
+ maybeAutoNameSession,
150
+ isProjectApproved,
151
+ rememberProjectApproval,
152
+ driftObserver,
153
+ turnAbortSignal: turnAbortController.signal,
154
+ leaseOwner,
400
155
  });
156
+ await driftObserver?.observer.flush();
401
157
  return result;
402
158
  }
403
159
  catch (runError) {
404
160
  await driftObserver?.observer.flush();
405
- const message = runError instanceof Error ? runError.message : String(runError);
406
- const formattedMessage = formatChatFailureMessage(message, {
161
+ await applyTuiAgentTurnFailure({
162
+ error: runError,
163
+ promptHistory: sessionHistory,
407
164
  model: llm.info?.model ?? runtime.model,
408
- estimatedHistoryTokens: estimateChatHistoryTokens(historyForRun),
165
+ state,
166
+ sessionId,
167
+ updateSessionById,
409
168
  });
410
- state.setError(formattedMessage);
411
- state.setStatus('Error');
412
- updateSessionById(sessionId, (sessionToUpdate) => ({
413
- ...sessionToUpdate,
414
- messages: [
415
- ...sessionToUpdate.messages,
416
- { id: state.nextLocalId(), role: 'assistant', text: `Run failed before a final answer: ${formattedMessage}` },
417
- ],
418
- }));
419
169
  return undefined;
420
170
  }
421
171
  finally {
422
172
  updateSessionById(sessionId, (sessionToUpdate) => releaseSessionLease(sessionToUpdate, leaseOwner));
423
- state.setIsRunning(false);
424
- state.interruptRequestedRef.current = false;
425
- state.setInterruptRequested(false);
426
- state.abortControllerRef.current = undefined;
427
- }
428
- }
429
- async function createChatDriftObserver(args) {
430
- const { prompt, referenceAssistantText, llm, runtime, logger, options } = args;
431
- if (!options?.enabled) {
432
- return undefined;
433
- }
434
- const llmInfo = llm.info;
435
- const credentialSource = llmInfo?.provider === 'openai' ?
436
- resolveProviderCredentialSourceForModel(llmInfo.model, runtime)
437
- : undefined;
438
- if (credentialSource?.type === 'oauth') {
439
- const message = 'CyberLoop drift detection requires OpenAI Platform API-key mode for embeddings; active auth is OpenAI account sign-in.';
440
- logger.debug({ model: llmInfo?.model, credentialSource: credentialSource.type }, message);
441
- options.onError?.(new Error(message));
442
- return undefined;
443
- }
444
- try {
445
- return await createCyberLoopKinematicsObserver({
446
- goal: prompt,
447
- referenceText: referenceAssistantText,
448
- apiKey: llm.info?.provider === 'openai' ? resolveApiKeyForModel(llm.info.model, runtime) : undefined,
449
- onAnnotation: options.onAnnotation,
450
- onError: (error) => {
451
- logger.debug({ error: error instanceof Error ? error.message : String(error) }, 'CyberLoop drift observer failed');
452
- options.onError?.(error);
453
- },
454
- });
455
- }
456
- catch (error) {
457
- logger.debug({ error: error instanceof Error ? error.message : String(error) }, 'CyberLoop drift observer unavailable');
458
- options.onError?.(error);
459
- return undefined;
173
+ finishTuiAgentTurn(state);
460
174
  }
461
175
  }
462
176
  function previousAssistantOutput(session) {
@@ -517,225 +231,19 @@ async function runDirectShellAction(args) {
517
231
  if (!command || state.isRunning || !activeSession) {
518
232
  return;
519
233
  }
520
- const shellDisplay = `!${command}`;
521
- const leaseOwner = {
522
- ownerKind: 'tui',
523
- ownerId: `tui-${process.pid}`,
524
- clientLabel: 'terminal chat',
525
- };
526
- const persistedSession = readChatSession(runtime.sessionCatalogFile, activeSessionId, true) ?? activeSession;
527
- const leaseConflict = getSessionLeaseConflict(persistedSession, leaseOwner);
528
- if (leaseConflict) {
529
- state.setError(leaseConflict);
530
- state.setStatus('Blocked');
531
- updateActiveSession((session) => ({
532
- ...session,
533
- messages: [...session.messages, { id: state.nextLocalId(), role: 'assistant', text: leaseConflict }],
534
- }));
535
- return;
536
- }
537
- updateActiveSession(() => touchSession(acquireSessionLease(persistedSession, leaseOwner)));
538
- state.setError(undefined);
539
- state.setIsRunning(true);
540
- state.setStatus('Running');
541
- state.interruptRequestedRef.current = false;
542
- state.setInterruptRequested(false);
543
- state.abortControllerRef.current = new AbortController();
544
- state.setLiveEvents([{ id: state.nextLocalId(), text: `running direct shell (${command})` }]);
545
- updateActiveSession((session) => ({
546
- ...session,
547
- messages: [...session.messages, { id: state.nextLocalId(), role: 'user', text: shellDisplay }],
548
- lastContinuePrompt: undefined,
549
- }));
550
- try {
551
- const inspectCall = {
552
- id: `direct-shell-${Date.now()}-inspect`,
553
- tool: 'run_shell_inspect',
554
- input: { command },
555
- };
556
- const inspectResult = await runShellCommand(inspectCall.input, {
557
- toolName: inspectCall.tool,
558
- rules: DEFAULT_INSPECT_RULES,
559
- allowUnknown: false,
560
- }, state.abortControllerRef.current.signal);
561
- let chosenCall = inspectCall;
562
- let chosenResult = inspectResult;
563
- if (shouldFallbackToMutate(inspectResult.error)) {
564
- const mutateCall = {
565
- id: `direct-shell-${Date.now()}-mutate`,
566
- tool: 'run_shell_mutate',
567
- input: { command },
568
- };
569
- if (runtime.directShellApproval === 'always') {
570
- const directShellTool = tools.find((tool) => tool.name === 'run_shell_mutate');
571
- if (!directShellTool) {
572
- throw new Error('run_shell_mutate tool is not registered');
573
- }
574
- const approval = isProjectApproved(mutateCall) ?
575
- { approved: true, reason: 'Approved by saved project rule' }
576
- : await new Promise((resolve) => {
577
- const rememberedRule = createProjectApprovalRuleForCall(mutateCall);
578
- state.setPendingApproval({
579
- call: mutateCall,
580
- tool: directShellTool,
581
- rememberForProject: () => rememberProjectApproval(mutateCall),
582
- rememberLabel: rememberedRule ? describeProjectApprovalRule(rememberedRule) : undefined,
583
- resolve,
584
- });
585
- });
586
- if (!approval.approved) {
587
- const denialMessage = approval.reason ? `Command denied.\n${approval.reason}` : 'Command denied.';
588
- updateActiveSession((session) => ({
589
- ...session,
590
- messages: [...session.messages, { id: state.nextLocalId(), role: 'assistant', text: denialMessage }],
591
- }));
592
- state.setLiveEvents([
593
- {
594
- id: state.nextLocalId(),
595
- text: `approval denied for ${summarizeToolCall(mutateCall.tool, mutateCall.input)}`,
596
- },
597
- ]);
598
- state.setStatus('Idle');
599
- return;
600
- }
601
- }
602
- chosenCall = mutateCall;
603
- chosenResult = await runShellCommand(mutateCall.input, {
604
- toolName: mutateCall.tool,
605
- rules: DEFAULT_MUTATE_RULES,
606
- allowUnknown: true,
607
- }, state.abortControllerRef.current.signal);
608
- }
609
- const responseText = formatDirectShellResponse(chosenCall.tool, command, chosenResult);
610
- const directShellHistory = appendDirectShellHistory(activeSession.history, shellDisplay, chosenCall.tool, chosenResult);
611
- const compacted = await compactChatHistoryWithArchive({
612
- history: directShellHistory,
613
- model,
614
- sessionId: activeSessionId,
615
- stateRoot: runtime.stateRoot,
616
- systemContext: runtime.systemContext,
617
- toolNames: tools.map((tool) => tool.name),
618
- goal: shellDisplay,
619
- summarizer: { credentialSource: runtime.providerCredentialSource },
620
- onStatusChange: (event) => {
621
- if (event.status === 'running') {
622
- state.setStatus('Compacting');
623
- state.setLiveEvents((current) => [...current, { id: state.nextLocalId(), text: 'Compacting earlier conversation history…' }].slice(-8));
624
- updateActiveSession((session) => ({
625
- ...session,
626
- context: buildCompactionRunningContext({
627
- history: directShellHistory,
628
- previous: session.context,
629
- archiveCount: session.archives?.length,
630
- currentSummaryPath: session.context?.currentSummaryPath,
631
- lastArchivePath: event.archivePath,
632
- }),
633
- }));
634
- }
635
- else if (event.status === 'failed') {
636
- state.setLiveEvents((current) => [...current, { id: state.nextLocalId(), text: `Compaction failed: ${event.error ?? 'unknown error'}` }].slice(-8));
637
- }
638
- else {
639
- state.setLiveEvents((current) => [...current, { id: state.nextLocalId(), text: 'Compaction finished.' }].slice(-8));
640
- }
641
- },
642
- });
643
- updateActiveSession((session) => ({
644
- ...session,
645
- history: compacted.history,
646
- context: compacted.context,
647
- archives: compacted.archives,
648
- messages: buildConversationMessages(compacted.history),
649
- }));
650
- state.setLiveEvents([
651
- {
652
- id: state.nextLocalId(),
653
- text: chosenResult.ok ?
654
- `${summarizeToolCall(chosenCall.tool, chosenCall.input)} completed`
655
- : `${summarizeToolCall(chosenCall.tool, chosenCall.input)} failed`,
656
- },
657
- ]);
658
- state.setStatus(chosenResult.ok ? 'Idle' : 'Stopped: error');
659
- if (!chosenResult.ok && chosenResult.error) {
660
- state.setError(chosenResult.error);
661
- }
662
- maybeAutoNameSession(activeSessionId, shellDisplay, responseText);
663
- }
664
- catch (shellError) {
665
- const message = shellError instanceof Error ? shellError.message : String(shellError);
666
- state.setError(message);
667
- state.setStatus('Error');
668
- updateActiveSession((session) => ({
669
- ...session,
670
- messages: [
671
- ...session.messages,
672
- { id: state.nextLocalId(), role: 'assistant', text: `Direct shell execution failed:\n${message}` },
673
- ],
674
- }));
675
- }
676
- finally {
677
- updateActiveSession((session) => releaseSessionLease(session, leaseOwner));
678
- state.setPendingApproval(undefined);
679
- state.setApprovalChoice('approve');
680
- state.interruptRequestedRef.current = false;
681
- state.setInterruptRequested(false);
682
- state.abortControllerRef.current = undefined;
683
- state.setIsRunning(false);
684
- }
685
- }
686
- function scheduleBackgroundMemoryMaintenance(args) {
687
- void (async () => {
688
- const maintenance = await runMaintenanceForRecordedCandidates({
689
- memoryRoot: args.runtime.memoryDir,
690
- llm: args.llm,
691
- source: `terminal chat session ${args.sessionId}`,
692
- trace: args.trace,
693
- maxSteps: 20,
694
- onTraceEvent: (event) => {
695
- if (event.type === 'memory.maintenance_started') {
696
- args.setIsMemoryUpdating(true);
697
- }
698
- const next = toLiveEvent(event);
699
- if (!next) {
700
- return;
701
- }
702
- args.setLiveEvents((current) => [...current, { id: args.nextLocalId(), text: next }].slice(-8));
703
- },
704
- });
705
- if (maintenance.events.length === 0) {
706
- return;
707
- }
708
- const currentTrace = readTraceEvents(args.traceFile);
709
- const nextTrace = [...currentTrace, ...maintenance.events];
710
- writeFileSync(args.traceFile, `${JSON.stringify(nextTrace, null, 2)}\n`, 'utf8');
711
- args.updateSessionById(args.sessionId, (session) => touchSession({
712
- ...session,
713
- turns: session.turns.map((turn, index) => (index === session.turns.length - 1 ?
714
- {
715
- ...turn,
716
- events: summarizeTrace(nextTrace),
717
- }
718
- : turn)),
719
- }));
720
- args.setIsMemoryUpdating(false);
721
- })().catch((error) => {
722
- args.setIsMemoryUpdating(false);
723
- args.setLiveEvents((current) => [
724
- ...current,
725
- {
726
- id: args.nextLocalId(),
727
- text: `Memory maintenance failed: ${error instanceof Error ? error.message : String(error)}`,
728
- },
729
- ].slice(-8));
234
+ await executeTuiDirectShell({
235
+ command,
236
+ shellDisplay: `!${command}`,
237
+ model,
238
+ activeSessionId,
239
+ activeSession,
240
+ runtime,
241
+ tools,
242
+ state,
243
+ updateActiveSession,
244
+ maybeAutoNameSession,
245
+ isProjectApproved,
246
+ rememberProjectApproval,
730
247
  });
731
248
  }
732
- function readTraceEvents(path) {
733
- try {
734
- const parsed = JSON.parse(readFileSync(path, 'utf8'));
735
- return Array.isArray(parsed) ? parsed : [];
736
- }
737
- catch {
738
- return [];
739
- }
740
- }
741
249
  //# sourceMappingURL=useAgentRun.js.map