Merge branch 'claude-code-best:main' into main
This commit is contained in:
commit
db805ed36f
|
|
@ -377,9 +377,6 @@ const cronJitterConfigModule =
|
||||||
require('../utils/cronJitterConfig.js') as typeof import('../utils/cronJitterConfig.js')
|
require('../utils/cronJitterConfig.js') as typeof import('../utils/cronJitterConfig.js')
|
||||||
const cronGate =
|
const cronGate =
|
||||||
require('@claude-code-best/builtin-tools/tools/ScheduleCronTool/prompt.js') as typeof import('@claude-code-best/builtin-tools/tools/ScheduleCronTool/prompt.js')
|
require('@claude-code-best/builtin-tools/tools/ScheduleCronTool/prompt.js') as typeof import('@claude-code-best/builtin-tools/tools/ScheduleCronTool/prompt.js')
|
||||||
const extractMemoriesModule = feature('EXTRACT_MEMORIES')
|
|
||||||
? (require('../services/extractMemories/extractMemories.js') as typeof import('../services/extractMemories/extractMemories.js'))
|
|
||||||
: null
|
|
||||||
/* eslint-enable @typescript-eslint/no-require-imports */
|
/* eslint-enable @typescript-eslint/no-require-imports */
|
||||||
|
|
||||||
const SHUTDOWN_TEAM_PROMPT = `<system-reminder>
|
const SHUTDOWN_TEAM_PROMPT = `<system-reminder>
|
||||||
|
|
@ -985,7 +982,14 @@ export async function runHeadless(
|
||||||
// the forked agent mid-flight. Gated by isExtractModeActive so the
|
// the forked agent mid-flight. Gated by isExtractModeActive so the
|
||||||
// tengu_slate_thimble flag controls non-interactive extraction end-to-end.
|
// tengu_slate_thimble flag controls non-interactive extraction end-to-end.
|
||||||
if (feature('EXTRACT_MEMORIES') && isExtractModeActive()) {
|
if (feature('EXTRACT_MEMORIES') && isExtractModeActive()) {
|
||||||
await extractMemoriesModule!.drainPendingExtraction()
|
try {
|
||||||
|
const { drainPendingExtraction } = await import(
|
||||||
|
'../services/extractMemories/extractMemories.js'
|
||||||
|
)
|
||||||
|
await drainPendingExtraction()
|
||||||
|
} catch {
|
||||||
|
// Module load failure — non-critical at shutdown
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
gracefulShutdownSync(
|
gracefulShutdownSync(
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ import { getDefaultCharacters, type SpinnerMode } from './Spinner/index.js';
|
||||||
import { SpinnerAnimationRow } from './Spinner/SpinnerAnimationRow.js';
|
import { SpinnerAnimationRow } from './Spinner/SpinnerAnimationRow.js';
|
||||||
import { useSettings } from '../hooks/useSettings.js';
|
import { useSettings } from '../hooks/useSettings.js';
|
||||||
import { isInProcessTeammateTask } from '../tasks/InProcessTeammateTask/types.js';
|
import { isInProcessTeammateTask } from '../tasks/InProcessTeammateTask/types.js';
|
||||||
|
import { isLocalAgentTask } from '../tasks/LocalAgentTask/LocalAgentTask.js';
|
||||||
import { isBackgroundTask } from '../tasks/types.js';
|
import { isBackgroundTask } from '../tasks/types.js';
|
||||||
import { getAllInProcessTeammateTasks } from '../tasks/InProcessTeammateTask/InProcessTeammateTask.js';
|
import { getAllInProcessTeammateTasks } from '../tasks/InProcessTeammateTask/InProcessTeammateTask.js';
|
||||||
import { getEffortSuffix } from '../utils/effort.js';
|
import { getEffortSuffix } from '../utils/effort.js';
|
||||||
|
|
@ -209,15 +210,22 @@ function SpinnerWithVerbInner({
|
||||||
const hasRunningTeammates = runningTeammates.length > 0;
|
const hasRunningTeammates = runningTeammates.length > 0;
|
||||||
const allIdle = hasRunningTeammates && runningTeammates.every(t => t.isIdle);
|
const allIdle = hasRunningTeammates && runningTeammates.every(t => t.isIdle);
|
||||||
|
|
||||||
// Gather aggregate token stats from all running swarm teammates
|
// Gather aggregate token stats from all running agents.
|
||||||
// In spinner-tree mode, skip aggregation (teammates have their own lines in the tree)
|
// In spinner-tree mode, skip in-process teammates (they have their own
|
||||||
|
// per-teammate lines in the tree) but still count local-agent tasks
|
||||||
|
// (background agents) which have no dedicated tree rows.
|
||||||
let teammateTokens = 0;
|
let teammateTokens = 0;
|
||||||
if (!showSpinnerTree) {
|
for (const task of Object.values(tasks)) {
|
||||||
for (const task of Object.values(tasks)) {
|
if (task.status !== 'running') continue;
|
||||||
if (isInProcessTeammateTask(task) && task.status === 'running') {
|
if (isInProcessTeammateTask(task)) {
|
||||||
if (task.progress?.tokenCount) {
|
if (!showSpinnerTree && task.progress?.tokenCount) {
|
||||||
teammateTokens += task.progress.tokenCount;
|
teammateTokens += task.progress.tokenCount;
|
||||||
}
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (isLocalAgentTask(task)) {
|
||||||
|
if (task.progress?.tokenCount) {
|
||||||
|
teammateTokens += task.progress.tokenCount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -39,9 +39,6 @@ import { getTaskListId, listTasks } from '../utils/tasks.js'
|
||||||
import { getAgentName, getTeamName, isTeammate } from '../utils/teammate.js'
|
import { getAgentName, getTeamName, isTeammate } from '../utils/teammate.js'
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/no-require-imports */
|
/* eslint-disable @typescript-eslint/no-require-imports */
|
||||||
const extractMemoriesModule = feature('EXTRACT_MEMORIES')
|
|
||||||
? (require('../services/extractMemories/extractMemories.js') as typeof import('../services/extractMemories/extractMemories.js'))
|
|
||||||
: null
|
|
||||||
const jobClassifierModule = feature('TEMPLATES')
|
const jobClassifierModule = feature('TEMPLATES')
|
||||||
? (require('../jobs/classifier.js') as typeof import('../jobs/classifier.js'))
|
? (require('../jobs/classifier.js') as typeof import('../jobs/classifier.js'))
|
||||||
: null
|
: null
|
||||||
|
|
@ -154,12 +151,16 @@ export async function* handleStopHooks(
|
||||||
// Fire-and-forget in both interactive and non-interactive. For -p/SDK,
|
// Fire-and-forget in both interactive and non-interactive. For -p/SDK,
|
||||||
// print.ts drains the in-flight promise after flushing the response
|
// print.ts drains the in-flight promise after flushing the response
|
||||||
// but before gracefulShutdownSync (see drainPendingExtraction).
|
// but before gracefulShutdownSync (see drainPendingExtraction).
|
||||||
void extractMemoriesModule!.executeExtractMemories(
|
void import('../services/extractMemories/extractMemories.js')
|
||||||
stopHookContext,
|
.then(({ executeExtractMemories }) =>
|
||||||
toolUseContext.appendSystemMessage as
|
executeExtractMemories(
|
||||||
| ((msg: import('../types/message.js').SystemMessage) => void)
|
stopHookContext,
|
||||||
| undefined,
|
toolUseContext.appendSystemMessage as
|
||||||
)
|
| ((msg: import('../types/message.js').SystemMessage) => void)
|
||||||
|
| undefined,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.catch(() => {})
|
||||||
}
|
}
|
||||||
if (!toolUseContext.agentId && !poorMode) {
|
if (!toolUseContext.agentId && !poorMode) {
|
||||||
void executeAutoDream(stopHookContext, toolUseContext.appendSystemMessage)
|
void executeAutoDream(stopHookContext, toolUseContext.appendSystemMessage)
|
||||||
|
|
|
||||||
|
|
@ -147,6 +147,22 @@ describe('isOpenAIThinkingEnabled', () => {
|
||||||
expect(isOpenAIThinkingEnabled('deepseek-coder')).toBe(true)
|
expect(isOpenAIThinkingEnabled('deepseek-coder')).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('returns true when model name is "mimo-v2-flash"', () => {
|
||||||
|
expect(isOpenAIThinkingEnabled('mimo-v2-flash')).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('returns true when model name is "mimo-v2-pro"', () => {
|
||||||
|
expect(isOpenAIThinkingEnabled('mimo-v2-pro')).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('returns true when model name is "mimo-v2.5-pro"', () => {
|
||||||
|
expect(isOpenAIThinkingEnabled('mimo-v2.5-pro')).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('returns true when model name contains "mimo"', () => {
|
||||||
|
expect(isOpenAIThinkingEnabled('MiMo-V2-Omni')).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
test('returns false when model name is "gpt-4o"', () => {
|
test('returns false when model name is "gpt-4o"', () => {
|
||||||
expect(isOpenAIThinkingEnabled('gpt-4o')).toBe(false)
|
expect(isOpenAIThinkingEnabled('gpt-4o')).toBe(false)
|
||||||
})
|
})
|
||||||
|
|
@ -197,7 +213,10 @@ describe('buildOpenAIRequestBody — thinking params', () => {
|
||||||
test('includes vLLM/self-hosted thinking format when enabled', () => {
|
test('includes vLLM/self-hosted thinking format when enabled', () => {
|
||||||
const body = buildOpenAIRequestBody({ ...baseParams, enableThinking: true })
|
const body = buildOpenAIRequestBody({ ...baseParams, enableThinking: true })
|
||||||
expect(body.enable_thinking).toBe(true)
|
expect(body.enable_thinking).toBe(true)
|
||||||
expect(body.chat_template_kwargs).toEqual({ thinking: true })
|
expect(body.chat_template_kwargs).toEqual({
|
||||||
|
thinking: true,
|
||||||
|
enable_thinking: true,
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test('includes both formats simultaneously when enabled', () => {
|
test('includes both formats simultaneously when enabled', () => {
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,11 @@ import type { ChatCompletionCreateParamsStreaming } from 'openai/resources/chat/
|
||||||
import { isEnvTruthy, isEnvDefinedFalsy } from '../../../utils/envUtils.js'
|
import { isEnvTruthy, isEnvDefinedFalsy } from '../../../utils/envUtils.js'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Detect whether DeepSeek-style thinking mode should be enabled.
|
* Detect whether thinking mode should be enabled for this model.
|
||||||
*
|
*
|
||||||
* Enabled when:
|
* Enabled when:
|
||||||
* 1. OPENAI_ENABLE_THINKING=1 is set (explicit enable), OR
|
* 1. OPENAI_ENABLE_THINKING=1 is set (explicit enable), OR
|
||||||
* 2. Model name contains "deepseek-reasoner" OR "DeepSeek-V3.2" (auto-detect, case-insensitive)
|
* 2. Model name contains "deepseek" or "mimo" (auto-detect, case-insensitive)
|
||||||
*
|
*
|
||||||
* Disabled when:
|
* Disabled when:
|
||||||
* - OPENAI_ENABLE_THINKING=0/false/no/off is explicitly set (overrides model detection)
|
* - OPENAI_ENABLE_THINKING=0/false/no/off is explicitly set (overrides model detection)
|
||||||
|
|
@ -23,9 +23,9 @@ export function isOpenAIThinkingEnabled(model: string): boolean {
|
||||||
if (isEnvDefinedFalsy(process.env.OPENAI_ENABLE_THINKING)) return false
|
if (isEnvDefinedFalsy(process.env.OPENAI_ENABLE_THINKING)) return false
|
||||||
// Explicit enable
|
// Explicit enable
|
||||||
if (isEnvTruthy(process.env.OPENAI_ENABLE_THINKING)) return true
|
if (isEnvTruthy(process.env.OPENAI_ENABLE_THINKING)) return true
|
||||||
// Auto-detect from model name (all DeepSeek models support thinking mode)
|
// Auto-detect from model name (DeepSeek and MiMo models support thinking mode)
|
||||||
const modelLower = model.toLowerCase()
|
const modelLower = model.toLowerCase()
|
||||||
return modelLower.includes('deepseek')
|
return modelLower.includes('deepseek') || modelLower.includes('mimo')
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -58,12 +58,12 @@ export function resolveOpenAIMaxTokens(
|
||||||
* Build the request body for OpenAI chat.completions.create().
|
* Build the request body for OpenAI chat.completions.create().
|
||||||
* Extracted for testability — the thinking mode params are injected here.
|
* Extracted for testability — the thinking mode params are injected here.
|
||||||
*
|
*
|
||||||
* DeepSeek thinking mode: inject thinking params via request body.
|
* Three thinking-mode formats are sent simultaneously; each endpoint uses the
|
||||||
* Two formats are added simultaneously to support different deployments:
|
* format it recognizes and ignores the others:
|
||||||
* - Official DeepSeek API: `thinking: { type: 'enabled' }`
|
* - Official DeepSeek API: `thinking: { type: 'enabled' }`
|
||||||
* - Self-hosted DeepSeek-V3.2: `enable_thinking: true` + `chat_template_kwargs: { thinking: true }`
|
* - Self-hosted DeepSeek: `enable_thinking: true` + `chat_template_kwargs: { thinking: true }`
|
||||||
|
* - MiMo (Xiaomi): `chat_template_kwargs: { enable_thinking: true }`
|
||||||
* OpenAI SDK passes unknown keys through to the HTTP body.
|
* OpenAI SDK passes unknown keys through to the HTTP body.
|
||||||
* Each endpoint will use the format it recognizes and ignore the others.
|
|
||||||
*/
|
*/
|
||||||
export function buildOpenAIRequestBody(params: {
|
export function buildOpenAIRequestBody(params: {
|
||||||
model: string
|
model: string
|
||||||
|
|
@ -76,7 +76,7 @@ export function buildOpenAIRequestBody(params: {
|
||||||
}): ChatCompletionCreateParamsStreaming & {
|
}): ChatCompletionCreateParamsStreaming & {
|
||||||
thinking?: { type: string }
|
thinking?: { type: string }
|
||||||
enable_thinking?: boolean
|
enable_thinking?: boolean
|
||||||
chat_template_kwargs?: { thinking: boolean }
|
chat_template_kwargs?: { thinking: boolean; enable_thinking: boolean }
|
||||||
} {
|
} {
|
||||||
const {
|
const {
|
||||||
model,
|
model,
|
||||||
|
|
@ -97,14 +97,15 @@ export function buildOpenAIRequestBody(params: {
|
||||||
}),
|
}),
|
||||||
stream: true,
|
stream: true,
|
||||||
stream_options: { include_usage: true },
|
stream_options: { include_usage: true },
|
||||||
// DeepSeek thinking mode: enable chain-of-thought output.
|
// Enable chain-of-thought output for DeepSeek and MiMo models.
|
||||||
// When active, temperature/top_p/presence_penalty/frequency_penalty are ignored by DeepSeek.
|
// When active, temperature/top_p/presence_penalty/frequency_penalty are ignored.
|
||||||
...(enableThinking && {
|
...(enableThinking && {
|
||||||
// Official DeepSeek API format
|
// Official DeepSeek API format
|
||||||
thinking: { type: 'enabled' },
|
thinking: { type: 'enabled' },
|
||||||
// Self-hosted DeepSeek-V3.2 format
|
// Self-hosted DeepSeek-V3.2 format
|
||||||
enable_thinking: true,
|
enable_thinking: true,
|
||||||
chat_template_kwargs: { thinking: true },
|
// Both DeepSeek self-hosted and MiMo formats in chat_template_kwargs
|
||||||
|
chat_template_kwargs: { thinking: true, enable_thinking: true },
|
||||||
}),
|
}),
|
||||||
// Only send temperature when thinking mode is off (DeepSeek ignores it anyway,
|
// Only send temperature when thinking mode is off (DeepSeek ignores it anyway,
|
||||||
// but other providers may respect it)
|
// but other providers may respect it)
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,6 @@ import { initMagicDocs } from '../services/MagicDocs/magicDocs.js'
|
||||||
import { initSkillImprovement } from './hooks/skillImprovement.js'
|
import { initSkillImprovement } from './hooks/skillImprovement.js'
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/no-require-imports */
|
/* eslint-disable @typescript-eslint/no-require-imports */
|
||||||
const extractMemoriesModule = feature('EXTRACT_MEMORIES')
|
|
||||||
? (require('../services/extractMemories/extractMemories.js') as typeof import('../services/extractMemories/extractMemories.js'))
|
|
||||||
: null
|
|
||||||
const registerProtocolModule = feature('LODESTONE')
|
const registerProtocolModule = feature('LODESTONE')
|
||||||
? (require('./deepLink/registerProtocol.js') as typeof import('./deepLink/registerProtocol.js'))
|
? (require('./deepLink/registerProtocol.js') as typeof import('./deepLink/registerProtocol.js'))
|
||||||
: null
|
: null
|
||||||
|
|
@ -32,7 +29,13 @@ export function startBackgroundHousekeeping(): void {
|
||||||
void initMagicDocs()
|
void initMagicDocs()
|
||||||
void initSkillImprovement()
|
void initSkillImprovement()
|
||||||
if (feature('EXTRACT_MEMORIES')) {
|
if (feature('EXTRACT_MEMORIES')) {
|
||||||
extractMemoriesModule!.initExtractMemories()
|
void import('../services/extractMemories/extractMemories.js')
|
||||||
|
.then(({ initExtractMemories }) => {
|
||||||
|
initExtractMemories()
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
// Module load failure — non-critical, memory extraction just won't run
|
||||||
|
})
|
||||||
}
|
}
|
||||||
initAutoDream()
|
initAutoDream()
|
||||||
void autoUpdateMarketplacesAndPluginsInBackground()
|
void autoUpdateMarketplacesAndPluginsInBackground()
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,12 @@ interface CacheWarningState {
|
||||||
// 模块级状态,每个 querySource 独立跟踪
|
// 模块级状态,每个 querySource 独立跟踪
|
||||||
const cacheWarningStateBySource = new Map<string, CacheWarningState>()
|
const cacheWarningStateBySource = new Map<string, CacheWarningState>()
|
||||||
|
|
||||||
|
// Limit the number of tracked sources to prevent unbounded Map growth.
|
||||||
|
// querySource strings are effectively unbounded (typed as `any`), so a
|
||||||
|
// long-running session that spawns many subagents could leak memory.
|
||||||
|
// Evict the oldest entry (by insertion order) when the limit is exceeded.
|
||||||
|
const MAX_SOURCE_ENTRIES = 50
|
||||||
|
|
||||||
const DEFAULT_CACHE_THRESHOLD = 80
|
const DEFAULT_CACHE_THRESHOLD = 80
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -81,6 +87,13 @@ export function shouldShowCacheWarning(
|
||||||
let state = cacheWarningStateBySource.get(querySource)
|
let state = cacheWarningStateBySource.get(querySource)
|
||||||
if (!state) {
|
if (!state) {
|
||||||
state = { lastHitRate: null, lastTimestamp: null }
|
state = { lastHitRate: null, lastTimestamp: null }
|
||||||
|
// Evict oldest entry when at capacity so the Map stays bounded
|
||||||
|
if (cacheWarningStateBySource.size >= MAX_SOURCE_ENTRIES) {
|
||||||
|
const oldestKey = cacheWarningStateBySource.keys().next().value
|
||||||
|
if (oldestKey !== undefined) {
|
||||||
|
cacheWarningStateBySource.delete(oldestKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
cacheWarningStateBySource.set(querySource, state)
|
cacheWarningStateBySource.set(querySource, state)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -132,3 +145,10 @@ export function createCacheWarningMessage(info: CacheHitRateInfo): Message {
|
||||||
isMeta: false,
|
isMeta: false,
|
||||||
} as Message
|
} as Message
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the per-source tracking state — only used in tests.
|
||||||
|
*/
|
||||||
|
export function _resetCacheWarningStateForTest(): void {
|
||||||
|
cacheWarningStateBySource.clear()
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,7 @@ import {
|
||||||
import type { CustomAgentDefinition } from '@claude-code-best/builtin-tools/tools/AgentTool/loadAgentsDir.js'
|
import type { CustomAgentDefinition } from '@claude-code-best/builtin-tools/tools/AgentTool/loadAgentsDir.js'
|
||||||
import { runAgent } from '@claude-code-best/builtin-tools/tools/AgentTool/runAgent.js'
|
import { runAgent } from '@claude-code-best/builtin-tools/tools/AgentTool/runAgent.js'
|
||||||
import { awaitClassifierAutoApproval } from '@claude-code-best/builtin-tools/tools/BashTool/bashPermissions.js'
|
import { awaitClassifierAutoApproval } from '@claude-code-best/builtin-tools/tools/BashTool/bashPermissions.js'
|
||||||
|
import type { AgentToolResult } from '@claude-code-best/builtin-tools/tools/AgentTool/agentToolUtils.js'
|
||||||
import { BASH_TOOL_NAME } from '@claude-code-best/builtin-tools/tools/BashTool/toolName.js'
|
import { BASH_TOOL_NAME } from '@claude-code-best/builtin-tools/tools/BashTool/toolName.js'
|
||||||
import { SEND_MESSAGE_TOOL_NAME } from '@claude-code-best/builtin-tools/tools/SendMessageTool/constants.js'
|
import { SEND_MESSAGE_TOOL_NAME } from '@claude-code-best/builtin-tools/tools/SendMessageTool/constants.js'
|
||||||
import { TASK_CREATE_TOOL_NAME } from '@claude-code-best/builtin-tools/tools/TaskCreateTool/constants.js'
|
import { TASK_CREATE_TOOL_NAME } from '@claude-code-best/builtin-tools/tools/TaskCreateTool/constants.js'
|
||||||
|
|
@ -63,7 +64,10 @@ import {
|
||||||
} from '../../utils/messages.js'
|
} from '../../utils/messages.js'
|
||||||
import { evictTaskOutput } from '../../utils/task/diskOutput.js'
|
import { evictTaskOutput } from '../../utils/task/diskOutput.js'
|
||||||
import { evictTerminalTask } from '../../utils/task/framework.js'
|
import { evictTerminalTask } from '../../utils/task/framework.js'
|
||||||
import { tokenCountWithEstimation } from '../../utils/tokens.js'
|
import {
|
||||||
|
tokenCountWithEstimation,
|
||||||
|
getTokenCountFromUsage,
|
||||||
|
} from '../../utils/tokens.js'
|
||||||
import { createAbortController } from '../abortController.js'
|
import { createAbortController } from '../abortController.js'
|
||||||
import { type AgentContext, runWithAgentContext } from '../agentContext.js'
|
import { type AgentContext, runWithAgentContext } from '../agentContext.js'
|
||||||
import {
|
import {
|
||||||
|
|
@ -915,6 +919,7 @@ export async function runInProcessTeammate(
|
||||||
invokingRequestId,
|
invokingRequestId,
|
||||||
} = config
|
} = config
|
||||||
const { setAppState } = toolUseContext
|
const { setAppState } = toolUseContext
|
||||||
|
const startTime = Date.now()
|
||||||
|
|
||||||
logForDebugging(
|
logForDebugging(
|
||||||
`[inProcessRunner] Starting agent loop for ${identity.agentId}`,
|
`[inProcessRunner] Starting agent loop for ${identity.agentId}`,
|
||||||
|
|
@ -1463,6 +1468,48 @@ export async function runInProcessTeammate(
|
||||||
// Mark as completed when exiting the loop
|
// Mark as completed when exiting the loop
|
||||||
let alreadyTerminal = false
|
let alreadyTerminal = false
|
||||||
let toolUseId: string | undefined
|
let toolUseId: string | undefined
|
||||||
|
|
||||||
|
// Compute result so the detail dialog can show token usage.
|
||||||
|
// Walk backwards for the last API usage (cumulative input_tokens from the
|
||||||
|
// Anthropic API already includes all prior context).
|
||||||
|
let completionTokens = 0
|
||||||
|
let completionToolUseCount = 0
|
||||||
|
let lastAssistantContent: AgentToolResult['content'] = []
|
||||||
|
let lastUsage: AgentToolResult['usage'] | undefined
|
||||||
|
for (let i = allMessages.length - 1; i >= 0; i--) {
|
||||||
|
const m = allMessages[i]!
|
||||||
|
if (m.type === 'assistant') {
|
||||||
|
const blocks = (m.message?.content ?? []) as any[]
|
||||||
|
for (const b of blocks) {
|
||||||
|
if (b?.type === 'tool_use') completionToolUseCount++
|
||||||
|
}
|
||||||
|
const textBlocks = blocks.filter((b: any) => b?.type === 'text')
|
||||||
|
if (textBlocks.length > 0 && lastAssistantContent.length === 0) {
|
||||||
|
lastAssistantContent = textBlocks.map((b: any) => ({
|
||||||
|
type: 'text' as const,
|
||||||
|
text: b.text,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
if (!lastUsage && m.message?.usage) {
|
||||||
|
lastUsage = m.message.usage as AgentToolResult['usage']
|
||||||
|
completionTokens = getTokenCountFromUsage(
|
||||||
|
m.message.usage as Parameters<typeof getTokenCountFromUsage>[0],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (completionTokens > 0 && lastAssistantContent.length > 0) break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const teammateResult: AgentToolResult = {
|
||||||
|
agentId: identity.agentId,
|
||||||
|
agentType: 'teammate',
|
||||||
|
content: lastAssistantContent,
|
||||||
|
totalToolUseCount: completionToolUseCount,
|
||||||
|
totalDurationMs: Date.now() - startTime,
|
||||||
|
totalTokens: completionTokens,
|
||||||
|
usage: lastUsage as AgentToolResult['usage'],
|
||||||
|
} as unknown as AgentToolResult
|
||||||
|
|
||||||
updateTaskState(
|
updateTaskState(
|
||||||
taskId,
|
taskId,
|
||||||
task => {
|
task => {
|
||||||
|
|
@ -1481,6 +1528,7 @@ export async function runInProcessTeammate(
|
||||||
status: 'completed' as const,
|
status: 'completed' as const,
|
||||||
notified: true,
|
notified: true,
|
||||||
endTime: Date.now(),
|
endTime: Date.now(),
|
||||||
|
result: teammateResult,
|
||||||
messages: task.messages?.length ? [task.messages.at(-1)!] : undefined,
|
messages: task.messages?.length ? [task.messages.at(-1)!] : undefined,
|
||||||
pendingUserMessages: [],
|
pendingUserMessages: [],
|
||||||
inProgressToolUseIDs: undefined,
|
inProgressToolUseIDs: undefined,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user