Skip to content

Commit 94c4b37

Browse files
unraidclaude
andcommitted
feat: 添加 summary 命令 TypeScript 重写与其他命令增强
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 6c5df39 commit 94c4b37

9 files changed

Lines changed: 179 additions & 8 deletions

File tree

src/commands.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,8 @@ import mockLimits from './commands/mock-limits/index.js'
180180
import bridgeKick from './commands/bridge-kick.js'
181181
import version from './commands/version.js'
182182
import summary from './commands/summary/index.js'
183+
import skillLearning from './commands/skill-learning/index.js'
184+
import skillSearch from './commands/skill-search/index.js'
183185
import {
184186
resetLimits,
185187
resetLimitsNonInteractive,
@@ -274,7 +276,6 @@ export const INTERNAL_ONLY_COMMANDS = [
274276
goodClaude,
275277
issue,
276278
initVerifiers,
277-
...(forceSnip ? [forceSnip] : []),
278279
mockLimits,
279280
bridgeKick,
280281
version,
@@ -283,7 +284,6 @@ export const INTERNAL_ONLY_COMMANDS = [
283284
resetLimitsNonInteractive,
284285
onboarding,
285286
share,
286-
summary,
287287
teleport,
288288
antTrace,
289289
perfIssue,
@@ -397,6 +397,10 @@ const COMMANDS = memoize((): Command[] => [
397397
...(torch ? [torch] : []),
398398
...(daemonCmd ? [daemonCmd] : []),
399399
...(jobCmd ? [jobCmd] : []),
400+
...(forceSnip ? [forceSnip] : []),
401+
summary,
402+
skillLearning,
403+
skillSearch,
400404
...(process.env.USER_TYPE === 'ant' && !process.env.IS_DEMO
401405
? INTERNAL_ONLY_COMMANDS
402406
: []),

src/commands/bridge/bridge.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@ function BridgeToggle({ onDone, name }: Props): React.ReactNode {
5454
const replBridgeOutboundOnly = useAppState(s => s.replBridgeOutboundOnly)
5555
const [showDisconnectDialog, setShowDisconnectDialog] = useState(false)
5656

57-
// biome-ignore lint/correctness/useExhaustiveDependencies: bridge starts once, should not restart on state changes
5857
useEffect(() => {
5958
// If already connected or enabled in full bidirectional mode, show
6059
// disconnect confirmation. Outbound-only (CCR mirror) doesn't count —

src/commands/effort/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ export default {
55
type: 'local-jsx',
66
name: 'effort',
77
description: 'Set effort level for model usage',
8-
argumentHint: '[low|medium|high|max|auto]',
8+
argumentHint: '[low|medium|high|xhigh|max|auto]',
99
get immediate() {
1010
return shouldInferenceConfigCommandBeImmediate()
1111
},

src/commands/force-snip.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ const forceSnip = {
5252
name: 'force-snip',
5353
description: 'Force snip conversation history at current point',
5454
supportsNonInteractive: true,
55-
isHidden: true,
55+
isHidden: false,
5656
load: () => Promise.resolve({ call }),
5757
} satisfies Command
5858

src/commands/insights.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3058,7 +3058,6 @@ const usageReport: Command = {
30583058

30593059
// Show collection message if collecting
30603060
if (collectRemote && hasRemoteHosts) {
3061-
// biome-ignore lint/suspicious/noConsole: intentional
30623061
console.error(
30633062
`Collecting sessions from ${remoteHosts.length} homespace(s): ${remoteHosts.join(', ')}...`,
30643063
)

src/commands/model/model.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ function SetModelAndClose({
160160
// @[MODEL LAUNCH]: Update check for 1M access.
161161
if (model && isOpus1mUnavailable(model)) {
162162
onDone(
163-
`Opus 4.6 with 1M context is not available for your account. Learn more: https://code.claude.com/docs/en/model-config#extended-context-with-1m`,
163+
`Opus 4.7 with 1M context is not available for your account. Learn more: https://code.claude.com/docs/en/model-config#extended-context-with-1m`,
164164
{ display: 'system' },
165165
)
166166
return
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import { describe, test, expect, mock, beforeEach } from 'bun:test'
2+
3+
const mockManuallyExtract = mock(
4+
(): Promise<any> => Promise.resolve({ success: true }),
5+
)
6+
const mockGetContent = mock(
7+
(): Promise<any> => Promise.resolve('# Session Summary\n\nDid some work.'),
8+
)
9+
10+
mock.module(
11+
require.resolve('../../../services/SessionMemory/sessionMemory.js'),
12+
() => ({
13+
manuallyExtractSessionMemory: mockManuallyExtract,
14+
}),
15+
)
16+
mock.module(
17+
require.resolve('../../../services/SessionMemory/sessionMemoryUtils.js'),
18+
() => ({
19+
getSessionMemoryContent: mockGetContent,
20+
}),
21+
)
22+
23+
const { default: summaryCommand } = await import('../index.js')
24+
25+
const baseContext = {
26+
messages: [{ type: 'user', role: 'user', content: 'hello' }],
27+
options: { tools: [], mainLoopModel: 'test' },
28+
setMessages: () => {},
29+
onChangeAPIKey: () => {},
30+
} as any
31+
32+
async function callSummary(ctx = baseContext) {
33+
const mod = await summaryCommand.load()
34+
return mod.call('', ctx)
35+
}
36+
37+
beforeEach(() => {
38+
mockManuallyExtract.mockReset()
39+
mockGetContent.mockReset()
40+
mockManuallyExtract.mockImplementation(() =>
41+
Promise.resolve({ success: true }),
42+
)
43+
mockGetContent.mockImplementation(() =>
44+
Promise.resolve('# Session Summary\n\nDid some work.'),
45+
)
46+
})
47+
48+
describe('summary command', () => {
49+
test('command metadata', () => {
50+
expect(summaryCommand.name).toBe('summary')
51+
expect(summaryCommand.type).toBe('local')
52+
expect(summaryCommand.isHidden).toBe(false)
53+
expect(typeof summaryCommand.load).toBe('function')
54+
})
55+
56+
test('refreshes and displays summary', async () => {
57+
const result = await callSummary()
58+
expect(result.type).toBe('text')
59+
expect((result as any).value).toContain('Session summary updated.')
60+
expect((result as any).value).toContain('Did some work.')
61+
expect(mockManuallyExtract).toHaveBeenCalled()
62+
})
63+
64+
test('handles extraction failure', async () => {
65+
mockManuallyExtract.mockImplementation(() =>
66+
Promise.resolve({ success: false, error: 'timeout' }),
67+
)
68+
const result = await callSummary()
69+
expect((result as any).value).toContain(
70+
'Failed to generate session summary',
71+
)
72+
expect((result as any).value).toContain('timeout')
73+
})
74+
75+
test('handles empty content after extraction', async () => {
76+
mockGetContent.mockImplementation(() => Promise.resolve(''))
77+
const result = await callSummary()
78+
expect((result as any).value).toContain('content is empty')
79+
})
80+
81+
test('handles null content after extraction', async () => {
82+
mockGetContent.mockImplementation(() => Promise.resolve(null))
83+
const result = await callSummary()
84+
expect((result as any).value).toContain('content is empty')
85+
})
86+
87+
test('handles no messages', async () => {
88+
const result = await callSummary({ ...baseContext, messages: [] })
89+
expect((result as any).value).toBe('No messages to summarize.')
90+
})
91+
})

src/commands/summary/index.ts

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/**
2+
* /summary — Generate and display a session summary.
3+
*
4+
* Triggers a manual Session Memory extraction (bypassing automatic thresholds),
5+
* then reads and displays the updated summary.md file.
6+
*/
7+
import type { Command, LocalCommandCall } from '../../types/command.js'
8+
import type { Message } from '../../types/message.js'
9+
10+
/** Only user/assistant/system messages are valid for API calls. */
11+
const API_SAFE_TYPES = new Set(['user', 'assistant', 'system'])
12+
13+
const call: LocalCommandCall = async (_args, context) => {
14+
const { messages } = context
15+
16+
// Filter to API-safe message types only.
17+
// context.messages includes progress/attachment/etc. that crash the API
18+
// call chain (normalizeMessagesForAPI → addCacheBreakpoints expects
19+
// only user/assistant). The automatic extraction path uses
20+
// createCacheSafeParams(REPLHookContext) which already has clean
21+
// messages; the manual path via /summary does not.
22+
const safeMessages = (messages ?? []).filter(
23+
(m): m is Message => m != null && API_SAFE_TYPES.has(m.type),
24+
)
25+
26+
if (safeMessages.length === 0) {
27+
return { type: 'text', value: 'No messages to summarize.' }
28+
}
29+
30+
try {
31+
const { manuallyExtractSessionMemory } = await import(
32+
'../../services/SessionMemory/sessionMemory.js'
33+
)
34+
const { getSessionMemoryContent } = await import(
35+
'../../services/SessionMemory/sessionMemoryUtils.js'
36+
)
37+
38+
const safeContext = { ...context, messages: safeMessages }
39+
const result = await manuallyExtractSessionMemory(safeMessages, safeContext)
40+
41+
if (!result.success) {
42+
return {
43+
type: 'text',
44+
value: `Failed to generate session summary: ${result.error ?? 'unknown error'}`,
45+
}
46+
}
47+
48+
const content = await getSessionMemoryContent()
49+
50+
if (!content || content.trim().length === 0) {
51+
return {
52+
type: 'text',
53+
value: 'Session summary was updated, but the content is empty.',
54+
}
55+
}
56+
57+
return {
58+
type: 'text',
59+
value: `Session summary updated.\n\n${content}`,
60+
}
61+
} catch (error) {
62+
return {
63+
type: 'text',
64+
value: `Failed to generate session summary: ${error instanceof Error ? error.message : String(error)}`,
65+
}
66+
}
67+
}
68+
69+
const summary = {
70+
type: 'local',
71+
name: 'summary',
72+
description: 'Generate and display a session summary',
73+
supportsNonInteractive: true,
74+
isHidden: false,
75+
load: () => Promise.resolve({ call }),
76+
} satisfies Command
77+
78+
export default summary

src/commands/ultraplan.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ export function isUltraplanEnabled(): boolean {
6565
// load: the GrowthBook cache is empty at import and `/config` Gates can flip
6666
// it between invocations.
6767
function getUltraplanModel(): string {
68-
return getFeatureValue_CACHED_MAY_BE_STALE('tengu_ultraplan_model', ALL_MODEL_CONFIGS.opus46.firstParty);
68+
return getFeatureValue_CACHED_MAY_BE_STALE('tengu_ultraplan_model', ALL_MODEL_CONFIGS.opus47.firstParty);
6969
}
7070

7171
// prompt.txt is wrapped in <system-reminder> so the CCR browser hides

0 commit comments

Comments
 (0)