mirror of
https://github.com/stablyai/orca.git
synced 2026-10-31 17:17:38 +00:00
Kimi sessions keep resuming across repeated Orca restarts: register Kimi's SessionStart hook (matcher ^resume$, so new sessions pay nothing) and map a resume to an idle session-start status that refreshes the resume record; recognise npm-installed Kimi (node_modules/@moonshot-ai/kimi-code/dist/main.mjs) on Windows so the pane isn't mistaken for a shell. Fixes #31311
This commit is contained in:
@@ -21,13 +21,14 @@ function deleteEndMarker(text: string): string {
|
||||
}
|
||||
|
||||
describe('kimi managed hooks TOML block', () => {
|
||||
it('installs every managed event without a matcher', () => {
|
||||
it('installs every managed event, scoping only SessionStart to resumes', () => {
|
||||
const block = buildManagedKimiHooksBlock(COMMAND)
|
||||
for (const event of KIMI_HOOK_EVENTS) {
|
||||
expect(block).toContain(`event = "${event}"`)
|
||||
}
|
||||
// Kimi treats matcher as a regex; omitting it matches all tools.
|
||||
expect(block).not.toContain('matcher')
|
||||
expect(block.match(/matcher = .*/g)).toEqual(['matcher = "^resume$"'])
|
||||
expect(block).toContain('event = "SessionStart"\nmatcher = "^resume$"')
|
||||
expect(
|
||||
readManagedKimiHookEvents(applyManagedKimiHooks('', COMMAND, isManaged), isManaged)
|
||||
).toEqual(new Set(KIMI_HOOK_EVENTS))
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
} from '../agent-hooks/managed-toml-ownership'
|
||||
|
||||
export const KIMI_HOOK_EVENTS = [
|
||||
'SessionStart',
|
||||
'UserPromptSubmit',
|
||||
'PreToolUse',
|
||||
'PostToolUse',
|
||||
@@ -123,11 +124,13 @@ function tomlBasicString(value: string): string {
|
||||
|
||||
export function buildManagedKimiHooksBlock(command: string, eol = '\n'): string {
|
||||
const commandLiteral = tomlBasicString(command)
|
||||
// Omit matcher: Kimi treats it as regex, so * is invalid. Host timeout backs up the normal curl deadline.
|
||||
// Kimi treats matcher as regex (so * is invalid); omit it except to scope SessionStart. Host timeout backs up the normal curl deadline.
|
||||
const entries = KIMI_HOOK_EVENTS.map((event) =>
|
||||
[
|
||||
HOOK_TABLE_HEADER,
|
||||
`event = "${event}"`,
|
||||
// Why: Kimi awaits SessionStart during a new session's first prompt; only a resume is used.
|
||||
...(event === 'SessionStart' ? ['matcher = "^resume$"'] : []),
|
||||
`command = ${commandLiteral}`,
|
||||
`timeout = ${MANAGED_HOOK_TIMEOUT_SECONDS}`
|
||||
].join(eol)
|
||||
|
||||
@@ -116,6 +116,29 @@ describe('shared agent-hook-listener', () => {
|
||||
expect(stopped?.providerSession).toMatchObject({ key: 'session_id', id: 'session_abc' })
|
||||
})
|
||||
|
||||
// #31311: a resumed Kimi pane must report its session before any prompt, or quit captures nothing.
|
||||
it('lands a resumed Kimi SessionStart as an idle boundary carrying its session', () => {
|
||||
const sessionStart = (source: string) =>
|
||||
normalizeHookPayload(
|
||||
state,
|
||||
'kimi',
|
||||
{
|
||||
paneKey: PANE_KEY,
|
||||
payload: { hook_event_name: 'SessionStart', session_id: 'session_abc', source }
|
||||
},
|
||||
'production'
|
||||
)
|
||||
|
||||
const resumed = sessionStart('resume')
|
||||
expect(resumed?.payload).toMatchObject({
|
||||
agentType: 'kimi',
|
||||
state: 'done',
|
||||
sessionBoundary: true
|
||||
})
|
||||
expect(resumed?.providerSession).toMatchObject({ key: 'session_id', id: 'session_abc' })
|
||||
expect(sessionStart('startup')).toBeNull()
|
||||
})
|
||||
|
||||
// Why: Kimi shares Claude-compatible compact/harness hooks; cover the same sticky-working
|
||||
// guards so a Kimi-only regression cannot slip past the Claude-only tests (issue #11352).
|
||||
it('ignores harness-injected UserPromptSubmit for Kimi', () => {
|
||||
|
||||
@@ -27,6 +27,18 @@ export function normalizeKimiEvent(
|
||||
if (shouldIgnoreCompactContinuationUserPromptSubmit(eventName, promptText)) {
|
||||
return null
|
||||
}
|
||||
if (eventName === 'SessionStart') {
|
||||
// Why: a resumed session's only pre-prompt signal, so its id stays resumable at the next quit
|
||||
// (#31311); `startup` fires mid-submit of the first message, so it is not an idle boundary.
|
||||
return hookPayload['source'] === 'resume'
|
||||
? normalizeAgentStatusPayload({
|
||||
state: 'done',
|
||||
prompt: '',
|
||||
agentType: 'kimi',
|
||||
sessionBoundary: true
|
||||
})
|
||||
: null
|
||||
}
|
||||
|
||||
const toolName = readString(hookPayload, 'tool_name')
|
||||
const isUserInputTool = isKimiUserInputTool(toolName)
|
||||
|
||||
@@ -49,5 +49,12 @@ export const EXACT_NODE_ENTRYPOINT_IDENTITIES: readonly {
|
||||
/(?:^|\/)(?:node_modules\/@qwen-code\/qwen-code\/(?:cli-entry|cli|scripts\/cli-entry|dist\/cli)|qwen-code\/lib\/cli(?:-entry)?)\.js$/,
|
||||
agent: 'qwen-code',
|
||||
processName: 'qwen'
|
||||
},
|
||||
// Why: Kimi Code's npm bin is a generic `dist/main.mjs`, and on Windows its process stays
|
||||
// node.exe instead of retitling to `kimi-code`, so only the package path identifies it.
|
||||
{
|
||||
pattern: /(?:^|\/)node_modules\/@moonshot-ai\/kimi-code\/dist\/main\.mjs$/,
|
||||
agent: 'kimi',
|
||||
processName: 'kimi'
|
||||
}
|
||||
]
|
||||
|
||||
@@ -306,6 +306,16 @@ describe('agent process recognition', () => {
|
||||
expect(isRecognizedAgentType('kimi-code')).toBe(true)
|
||||
})
|
||||
|
||||
// #31311: the npm install on Windows runs as node.exe, observed with this exact .bin shim path.
|
||||
it('recognizes Kimi Code launched by its npm shim on Windows', () => {
|
||||
expect(
|
||||
recognizeAgentProcessFromCommandLine(
|
||||
'"C:\\Program Files\\nodejs\\node.exe" C:\\Users\\dev\\proj\\node_modules\\.bin/../@moonshot-ai/kimi-code/dist/main.mjs --session session_1'
|
||||
)
|
||||
).toEqual({ agent: 'kimi', processName: 'kimi' })
|
||||
expect(recognizeAgentProcessFromCommandLine('node C:\\tools\\app\\dist\\main.mjs')).toBeNull()
|
||||
})
|
||||
|
||||
it('recognizes Qwen Code by its installed qwen executable', () => {
|
||||
expect(recognizeAgentProcess('/home/dev/.local/bin/qwen')).toEqual({
|
||||
agent: 'qwen-code',
|
||||
|
||||
Reference in New Issue
Block a user