mirror of
https://github.com/wu736139669/hapi.git
synced 2026-11-08 01:43:07 +00:00
* feat(codex): load user MCP servers in HAPI sessions * feat(codex): proxy Windows MCP command shims * fix(codex): address MCP review feedback * fix(codex): use HAPI-owned MCP proxy * fix(codex): preserve Windows MCP argv boundaries * fix(codex): keep remote MCP placement unchanged
695 lines
25 KiB
TypeScript
695 lines
25 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import type { EnhancedMode } from '../loop';
|
|
import {
|
|
buildThreadStartParams,
|
|
buildTurnStartParams,
|
|
buildUserInputFromMessage,
|
|
codexCollaborationSpawnAgentInstructions,
|
|
supportsReasoningSummary
|
|
} from './appServerConfig';
|
|
import { codexSystemPrompt } from './systemPrompt';
|
|
|
|
describe('appServerConfig', () => {
|
|
const mcpServers = { hapi: { command: 'node', args: ['mcp'] } };
|
|
const withCollaborationInstructions = (developerInstructions: string): string => {
|
|
return `${developerInstructions}\n\n${codexCollaborationSpawnAgentInstructions}`;
|
|
};
|
|
|
|
it('preserves Codex built-in base instructions by omitting the default override', () => {
|
|
const params = buildThreadStartParams({
|
|
cwd: '/workspace/project',
|
|
mode: { permissionMode: 'default', collaborationMode: 'default' },
|
|
mcpServers
|
|
});
|
|
|
|
expect(params).not.toHaveProperty('baseInstructions');
|
|
expect(params.developerInstructions).toBe(codexSystemPrompt);
|
|
});
|
|
|
|
it('keeps an explicit base instruction override separate from HAPI developer instructions', () => {
|
|
const params = buildThreadStartParams({
|
|
cwd: '/workspace/project',
|
|
mode: { permissionMode: 'default', collaborationMode: 'default' },
|
|
mcpServers,
|
|
baseInstructions: 'Custom base instructions.'
|
|
});
|
|
|
|
expect(params.baseInstructions).toBe('Custom base instructions.');
|
|
expect(params.developerInstructions).toBe(codexSystemPrompt);
|
|
});
|
|
|
|
it('applies CLI overrides when permission mode is default', () => {
|
|
const params = buildThreadStartParams({
|
|
cwd: '/workspace/project',
|
|
mode: { permissionMode: 'default', collaborationMode: 'default' },
|
|
mcpServers,
|
|
cliOverrides: { sandbox: 'danger-full-access', approvalPolicy: 'never' }
|
|
});
|
|
|
|
expect(params.cwd).toBe('/workspace/project');
|
|
expect(params.sandbox).toBe('danger-full-access');
|
|
expect(params.approvalPolicy).toBe('never');
|
|
expect(params.baseInstructions).toBeUndefined();
|
|
expect(params.developerInstructions).toBe(codexSystemPrompt);
|
|
expect(params.config).toEqual({
|
|
'mcp_servers.hapi': {
|
|
command: 'node',
|
|
args: ['mcp']
|
|
},
|
|
developer_instructions: codexSystemPrompt
|
|
});
|
|
});
|
|
|
|
it('uses on-request approvals for default Codex threads', () => {
|
|
const params = buildThreadStartParams({
|
|
cwd: '/workspace/project',
|
|
mode: { permissionMode: 'default', collaborationMode: 'default' },
|
|
mcpServers
|
|
});
|
|
|
|
expect(params.sandbox).toBe('workspace-write');
|
|
expect(params.approvalPolicy).toBe('on-request');
|
|
});
|
|
|
|
it('passes MCP per-tool approval config through thread config', () => {
|
|
const params = buildThreadStartParams({
|
|
cwd: '/workspace/project',
|
|
mode: { permissionMode: 'default', collaborationMode: 'default' },
|
|
mcpServers: {
|
|
hapi: {
|
|
command: 'node',
|
|
args: ['mcp'],
|
|
tools: {
|
|
change_title: {
|
|
approval_mode: 'approve'
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
expect(params.config).toEqual({
|
|
'mcp_servers.hapi': {
|
|
command: 'node',
|
|
args: ['mcp'],
|
|
tools: {
|
|
change_title: {
|
|
approval_mode: 'approve'
|
|
}
|
|
}
|
|
},
|
|
developer_instructions: codexSystemPrompt
|
|
});
|
|
});
|
|
|
|
it('preserves user MCP transport fields when building thread config', () => {
|
|
const params = buildThreadStartParams({
|
|
cwd: '/workspace/project',
|
|
mode: { permissionMode: 'default', collaborationMode: 'default' },
|
|
mcpServers: {
|
|
'package-manager': {
|
|
command: 'uvx',
|
|
args: ['example-mcp', 'serve'],
|
|
env: { EXAMPLE_TOKEN: 'from-process-environment' }
|
|
},
|
|
remote: {
|
|
url: 'https://example.test/mcp',
|
|
bearer_token_env_var: 'REMOTE_MCP_TOKEN'
|
|
},
|
|
hapi: {
|
|
command: 'node',
|
|
args: ['mcp']
|
|
}
|
|
}
|
|
});
|
|
|
|
expect(params.config).toMatchObject({
|
|
'mcp_servers.package-manager': {
|
|
command: 'uvx',
|
|
args: ['example-mcp', 'serve'],
|
|
env: { EXAMPLE_TOKEN: 'from-process-environment' }
|
|
},
|
|
'mcp_servers.remote': {
|
|
url: 'https://example.test/mcp',
|
|
bearer_token_env_var: 'REMOTE_MCP_TOKEN'
|
|
}
|
|
});
|
|
});
|
|
|
|
it('ignores CLI overrides when permission mode is not default', () => {
|
|
const params = buildThreadStartParams({
|
|
cwd: '/workspace/project',
|
|
mode: { permissionMode: 'yolo', collaborationMode: 'default' },
|
|
mcpServers,
|
|
cliOverrides: { sandbox: 'read-only', approvalPolicy: 'never' }
|
|
});
|
|
|
|
expect(params.sandbox).toBe('danger-full-access');
|
|
expect(params.approvalPolicy).toEqual({
|
|
granular: {
|
|
sandbox_approval: false,
|
|
rules: false,
|
|
skill_approval: false,
|
|
request_permissions: false,
|
|
mcp_elicitations: true
|
|
}
|
|
});
|
|
});
|
|
|
|
it('keeps supported escalation approvals for safe-yolo threads', () => {
|
|
const params = buildThreadStartParams({
|
|
cwd: '/workspace/project',
|
|
mode: { permissionMode: 'safe-yolo', collaborationMode: 'default' },
|
|
mcpServers
|
|
});
|
|
|
|
expect(params.sandbox).toBe('workspace-write');
|
|
expect(params.approvalPolicy).toBe('on-request');
|
|
});
|
|
|
|
it('allows MCP elicitation without enabling sandbox prompts for read-only threads', () => {
|
|
const params = buildThreadStartParams({
|
|
cwd: '/workspace/project',
|
|
mode: { permissionMode: 'read-only', collaborationMode: 'default' },
|
|
mcpServers
|
|
});
|
|
|
|
expect(params.sandbox).toBe('read-only');
|
|
expect(params.approvalPolicy).toEqual({
|
|
granular: {
|
|
sandbox_approval: false,
|
|
rules: false,
|
|
skill_approval: false,
|
|
request_permissions: false,
|
|
mcp_elicitations: true
|
|
}
|
|
});
|
|
});
|
|
|
|
it('concatenates custom developer instructions after HAPI instructions without overriding base instructions', () => {
|
|
const params = buildThreadStartParams({
|
|
cwd: '/workspace/project',
|
|
mode: { permissionMode: 'default', collaborationMode: 'default' },
|
|
mcpServers,
|
|
developerInstructions: 'Only respond in Chinese.'
|
|
});
|
|
|
|
expect(params.baseInstructions).toBeUndefined();
|
|
expect(params.developerInstructions).toBe(`${codexSystemPrompt}\n\nOnly respond in Chinese.`);
|
|
expect(params.config).toEqual({
|
|
'mcp_servers.hapi': {
|
|
command: 'node',
|
|
args: ['mcp']
|
|
},
|
|
developer_instructions: `${codexSystemPrompt}\n\nOnly respond in Chinese.`
|
|
});
|
|
});
|
|
|
|
it('passes model reasoning effort via thread config', () => {
|
|
const params = buildThreadStartParams({
|
|
cwd: '/workspace/project',
|
|
mode: { permissionMode: 'default', modelReasoningEffort: 'ultra', collaborationMode: 'default' },
|
|
mcpServers
|
|
});
|
|
|
|
expect(params.config).toEqual({
|
|
'mcp_servers.hapi': {
|
|
command: 'node',
|
|
args: ['mcp']
|
|
},
|
|
developer_instructions: codexSystemPrompt,
|
|
model_reasoning_effort: 'ultra'
|
|
});
|
|
});
|
|
|
|
it('passes effective Codex context management settings via thread config', () => {
|
|
const params = buildThreadStartParams({
|
|
cwd: '/workspace/project',
|
|
mode: { permissionMode: 'default', collaborationMode: 'default' },
|
|
mcpServers,
|
|
contextManagementConfig: {
|
|
modelContextWindow: 400_000,
|
|
modelAutoCompactTokenLimit: 300_000
|
|
}
|
|
});
|
|
|
|
expect(params.config).toMatchObject({
|
|
model_context_window: 400_000,
|
|
model_auto_compact_token_limit: 300_000
|
|
});
|
|
});
|
|
|
|
it('translates Fast to the advertised app-server tier (priority) in thread params', () => {
|
|
const params = buildThreadStartParams({
|
|
cwd: '/workspace/project',
|
|
mode: { permissionMode: 'default', collaborationMode: 'default', serviceTier: 'fast' },
|
|
mcpServers
|
|
});
|
|
|
|
expect(params.serviceTier).toBe('priority');
|
|
});
|
|
|
|
it('translates explicit Standard to app-server null in thread params', () => {
|
|
const params = buildThreadStartParams({
|
|
cwd: '/workspace/project',
|
|
mode: { permissionMode: 'default', collaborationMode: 'default', serviceTier: 'standard' },
|
|
mcpServers
|
|
});
|
|
|
|
expect(params.serviceTier).toBeNull();
|
|
});
|
|
|
|
it('omits service tier from thread params when untouched (undefined or null)', () => {
|
|
const undefinedParams = buildThreadStartParams({
|
|
cwd: '/workspace/project',
|
|
mode: { permissionMode: 'default', collaborationMode: 'default' },
|
|
mcpServers
|
|
});
|
|
expect('serviceTier' in undefinedParams).toBe(false);
|
|
|
|
const nullParams = buildThreadStartParams({
|
|
cwd: '/workspace/project',
|
|
mode: { permissionMode: 'default', collaborationMode: 'default', serviceTier: null },
|
|
mcpServers
|
|
});
|
|
expect('serviceTier' in nullParams).toBe(false);
|
|
});
|
|
|
|
it('translates Fast to the advertised app-server tier (priority) in turn params', () => {
|
|
const params = buildTurnStartParams({
|
|
threadId: 'thread-1',
|
|
message: 'hello',
|
|
cwd: '/workspace/project',
|
|
mode: { permissionMode: 'default', model: 'gpt-5.5', collaborationMode: 'default', serviceTier: 'fast' }
|
|
});
|
|
|
|
expect(params.serviceTier).toBe('priority');
|
|
});
|
|
|
|
it('translates explicit Standard to app-server null in turn params', () => {
|
|
const params = buildTurnStartParams({
|
|
threadId: 'thread-1',
|
|
message: 'hello',
|
|
cwd: '/workspace/project',
|
|
mode: { permissionMode: 'default', model: 'gpt-5.5', collaborationMode: 'default', serviceTier: 'standard' }
|
|
});
|
|
|
|
expect(params.serviceTier).toBeNull();
|
|
});
|
|
|
|
it('omits service tier from turn params when untouched (undefined or null)', () => {
|
|
const undefinedParams = buildTurnStartParams({
|
|
threadId: 'thread-1',
|
|
message: 'hello',
|
|
cwd: '/workspace/project',
|
|
mode: { permissionMode: 'default', model: 'gpt-5.5', collaborationMode: 'default' }
|
|
});
|
|
expect('serviceTier' in undefinedParams).toBe(false);
|
|
|
|
const nullParams = buildTurnStartParams({
|
|
threadId: 'thread-1',
|
|
message: 'hello',
|
|
cwd: '/workspace/project',
|
|
mode: { permissionMode: 'default', model: 'gpt-5.5', collaborationMode: 'default', serviceTier: null }
|
|
});
|
|
expect('serviceTier' in nullParams).toBe(false);
|
|
});
|
|
|
|
it('forwards personality only when explicitly set on the mode', () => {
|
|
const omitted = buildTurnStartParams({
|
|
threadId: 'thread-1',
|
|
message: 'hello',
|
|
cwd: '/workspace/project',
|
|
mode: { permissionMode: 'default', model: 'gpt-5.5', collaborationMode: 'default' }
|
|
});
|
|
expect('personality' in omitted).toBe(false);
|
|
|
|
const set = buildTurnStartParams({
|
|
threadId: 'thread-1',
|
|
message: 'hello',
|
|
cwd: '/workspace/project',
|
|
mode: {
|
|
permissionMode: 'default',
|
|
model: 'gpt-5.5',
|
|
collaborationMode: 'default',
|
|
personality: 'pragmatic'
|
|
}
|
|
});
|
|
expect(set.personality).toBe('pragmatic');
|
|
|
|
const thread = buildThreadStartParams({
|
|
cwd: '/workspace/project',
|
|
mode: {
|
|
permissionMode: 'default',
|
|
model: 'gpt-5.5',
|
|
collaborationMode: 'default',
|
|
personality: 'friendly'
|
|
},
|
|
mcpServers
|
|
});
|
|
expect(thread.personality).toBe('friendly');
|
|
});
|
|
|
|
it('builds turn params with mode defaults', () => {
|
|
const params = buildTurnStartParams({
|
|
threadId: 'thread-1',
|
|
message: 'hello',
|
|
cwd: '/workspace/project',
|
|
mode: {
|
|
permissionMode: 'read-only',
|
|
model: 'o3',
|
|
modelReasoningEffort: 'high',
|
|
collaborationMode: 'default'
|
|
}
|
|
});
|
|
|
|
expect(params.threadId).toBe('thread-1');
|
|
expect(params.cwd).toBe('/workspace/project');
|
|
expect(params.input).toEqual([{ type: 'text', text: 'hello' }]);
|
|
expect(params.approvalPolicy).toEqual({
|
|
granular: {
|
|
sandbox_approval: false,
|
|
rules: false,
|
|
skill_approval: false,
|
|
request_permissions: false,
|
|
mcp_elicitations: true
|
|
}
|
|
});
|
|
expect(params.sandboxPolicy).toEqual({ type: 'readOnly' });
|
|
expect(params.effort).toBe('high');
|
|
expect(params.summary).toBeUndefined();
|
|
expect(params.collaborationMode).toEqual({
|
|
mode: 'default',
|
|
settings: {
|
|
model: 'o3',
|
|
reasoning_effort: 'high',
|
|
developer_instructions: withCollaborationInstructions(codexSystemPrompt)
|
|
}
|
|
});
|
|
expect(params.model).toBeUndefined();
|
|
});
|
|
|
|
it('omits reasoning summary for models that do not support it', () => {
|
|
const params = buildTurnStartParams({
|
|
threadId: 'thread-1',
|
|
message: 'hello',
|
|
cwd: '/workspace/project',
|
|
mode: {
|
|
permissionMode: 'default',
|
|
model: 'gpt-5.3-codex-spark',
|
|
modelReasoningEffort: 'high',
|
|
collaborationMode: 'default'
|
|
}
|
|
});
|
|
|
|
expect(params.effort).toBe('high');
|
|
expect(params.summary).toBeUndefined();
|
|
expect(params.collaborationMode).toEqual({
|
|
mode: 'default',
|
|
settings: {
|
|
model: 'gpt-5.3-codex-spark',
|
|
reasoning_effort: 'high',
|
|
developer_instructions: withCollaborationInstructions(codexSystemPrompt)
|
|
}
|
|
});
|
|
});
|
|
|
|
it('detects namespaced models that do not support reasoning summary', () => {
|
|
const params = buildTurnStartParams({
|
|
threadId: 'thread-1',
|
|
message: 'hello',
|
|
cwd: '/workspace/project',
|
|
mode: {
|
|
permissionMode: 'default',
|
|
model: 'codex/gpt-5.3-codex-spark',
|
|
modelReasoningEffort: 'high',
|
|
collaborationMode: 'default'
|
|
}
|
|
});
|
|
|
|
expect(params.effort).toBe('high');
|
|
expect(params.summary).toBeUndefined();
|
|
});
|
|
|
|
it('normalizes reasoning summary model support checks', () => {
|
|
expect(supportsReasoningSummary(' Codex/GPT-5.3-CODEX-SPARK ')).toBe(false);
|
|
expect(supportsReasoningSummary('gpt-5.5')).toBe(true);
|
|
expect(supportsReasoningSummary(undefined)).toBe(true);
|
|
});
|
|
|
|
it('omits reasoning summary for non-collaboration turns on unsupported models', () => {
|
|
const params = buildTurnStartParams({
|
|
threadId: 'thread-1',
|
|
message: 'hello',
|
|
cwd: '/workspace/project',
|
|
mode: {
|
|
permissionMode: 'default',
|
|
model: 'gpt-5.3-codex-spark',
|
|
modelReasoningEffort: 'high'
|
|
} as EnhancedMode
|
|
});
|
|
|
|
expect(params.effort).toBe('high');
|
|
expect(params.summary).toBeUndefined();
|
|
expect(params.model).toBe('gpt-5.3-codex-spark');
|
|
expect(params.collaborationMode).toBeUndefined();
|
|
});
|
|
|
|
it('keeps reasoning summary for non-collaboration turns on supported models', () => {
|
|
const params = buildTurnStartParams({
|
|
threadId: 'thread-1',
|
|
message: 'hello',
|
|
cwd: '/workspace/project',
|
|
mode: {
|
|
permissionMode: 'default',
|
|
model: 'o3',
|
|
modelReasoningEffort: 'high'
|
|
} as EnhancedMode
|
|
});
|
|
|
|
expect(params.effort).toBe('high');
|
|
expect(params.summary).toBe('detailed');
|
|
expect(params.model).toBe('o3');
|
|
expect(params.collaborationMode).toBeUndefined();
|
|
});
|
|
|
|
it('keeps yolo access while using Codex built-in plan instructions', () => {
|
|
const params = buildTurnStartParams({
|
|
threadId: 'thread-1',
|
|
message: 'hello',
|
|
cwd: '/workspace/project',
|
|
mode: {
|
|
permissionMode: 'yolo',
|
|
model: 'o3',
|
|
modelReasoningEffort: 'high',
|
|
collaborationMode: 'plan'
|
|
}
|
|
});
|
|
|
|
expect(params.collaborationMode).toEqual({
|
|
mode: 'plan',
|
|
settings: {
|
|
model: 'o3',
|
|
reasoning_effort: 'high',
|
|
developer_instructions: null
|
|
}
|
|
});
|
|
expect(params.sandboxPolicy).toEqual({ type: 'dangerFullAccess' });
|
|
expect(params.model).toBeUndefined();
|
|
});
|
|
|
|
it('does not override Codex built-in plan instructions', () => {
|
|
const params = buildTurnStartParams({
|
|
threadId: 'thread-1',
|
|
message: 'hello',
|
|
cwd: '/workspace/project',
|
|
mode: { permissionMode: 'default', model: 'o3', collaborationMode: 'plan' },
|
|
developerInstructions: 'Only respond in Chinese.'
|
|
});
|
|
|
|
expect(params.collaborationMode).toEqual({
|
|
mode: 'plan',
|
|
settings: {
|
|
model: 'o3',
|
|
developer_instructions: null
|
|
}
|
|
});
|
|
expect(params.collaborationMode?.settings).not.toHaveProperty('reasoning_effort');
|
|
});
|
|
|
|
it('injects spawn_agent argument rules into collaboration mode instructions', () => {
|
|
const params = buildTurnStartParams({
|
|
threadId: 'thread-1',
|
|
message: 'hello',
|
|
cwd: '/workspace/project',
|
|
mode: { permissionMode: 'default', model: 'o3', collaborationMode: 'default' }
|
|
});
|
|
|
|
const instructions = params.collaborationMode?.settings.developer_instructions;
|
|
expect(instructions).toContain('Treat omitted fork_context the same as fork_context: true');
|
|
expect(instructions).toContain('do not set agent_type, model, or reasoning_effort');
|
|
expect(instructions).toContain('set fork_context: false');
|
|
expect(instructions).toContain('Do not rely on parent turn reasoning settings for spawned agents');
|
|
});
|
|
|
|
it('injects proactive multi-agent instructions when /agent mode is enabled', () => {
|
|
const params = buildTurnStartParams({
|
|
threadId: 'thread-1',
|
|
message: 'work',
|
|
cwd: '/repo',
|
|
mode: {
|
|
permissionMode: 'default',
|
|
model: 'o3',
|
|
collaborationMode: 'default',
|
|
proactiveMultiAgent: true
|
|
}
|
|
});
|
|
|
|
expect(params.collaborationMode?.settings.developer_instructions)
|
|
.toContain('Proactive multi-agent delegation is active.');
|
|
});
|
|
|
|
it('rejects collaboration mode payloads without a resolved model', () => {
|
|
expect(() => buildTurnStartParams({
|
|
threadId: 'thread-1',
|
|
message: 'hello',
|
|
cwd: '/workspace/project',
|
|
mode: { permissionMode: 'default', collaborationMode: 'plan' }
|
|
})).toThrow("Collaboration mode 'plan' requires a resolved model");
|
|
});
|
|
|
|
it('applies CLI overrides for turns when permission mode is default', () => {
|
|
const params = buildTurnStartParams({
|
|
threadId: 'thread-1',
|
|
message: 'hello',
|
|
cwd: '/workspace/project',
|
|
mode: { permissionMode: 'default', model: 'o3', collaborationMode: 'default' },
|
|
cliOverrides: { sandbox: 'danger-full-access', approvalPolicy: 'never' }
|
|
});
|
|
|
|
expect(params.approvalPolicy).toBe('never');
|
|
expect(params.sandboxPolicy).toEqual({ type: 'dangerFullAccess' });
|
|
expect(params.collaborationMode).toEqual({
|
|
mode: 'default',
|
|
settings: {
|
|
model: 'o3',
|
|
developer_instructions: withCollaborationInstructions(codexSystemPrompt)
|
|
}
|
|
});
|
|
});
|
|
|
|
it('ignores CLI overrides for turns when permission mode is not default', () => {
|
|
const params = buildTurnStartParams({
|
|
threadId: 'thread-1',
|
|
message: 'hello',
|
|
cwd: '/workspace/project',
|
|
mode: { permissionMode: 'safe-yolo', model: 'o3', collaborationMode: 'default' },
|
|
cliOverrides: { sandbox: 'read-only', approvalPolicy: 'never' }
|
|
});
|
|
|
|
expect(params.approvalPolicy).toBe('on-request');
|
|
expect(params.sandboxPolicy).toEqual({ type: 'workspaceWrite' });
|
|
expect(params.collaborationMode).toEqual({
|
|
mode: 'default',
|
|
settings: {
|
|
model: 'o3',
|
|
developer_instructions: withCollaborationInstructions(codexSystemPrompt)
|
|
}
|
|
});
|
|
});
|
|
|
|
it('prefers turn overrides', () => {
|
|
const params = buildTurnStartParams({
|
|
threadId: 'thread-1',
|
|
message: 'hello',
|
|
cwd: '/workspace/project',
|
|
mode: { permissionMode: 'default', collaborationMode: 'default' },
|
|
overrides: { approvalPolicy: 'on-request', model: 'gpt-5' }
|
|
});
|
|
|
|
expect(params.approvalPolicy).toBe('on-request');
|
|
expect(params.collaborationMode).toEqual({
|
|
mode: 'default',
|
|
settings: {
|
|
model: 'gpt-5',
|
|
developer_instructions: withCollaborationInstructions(codexSystemPrompt)
|
|
}
|
|
});
|
|
expect(params.model).toBeUndefined();
|
|
});
|
|
|
|
it('can suppress collaboration mode while preserving top-level model', () => {
|
|
const params = buildTurnStartParams({
|
|
threadId: 'thread-1',
|
|
message: 'hello',
|
|
cwd: '/workspace/project',
|
|
mode: { permissionMode: 'default', model: 'o3', collaborationMode: 'plan' },
|
|
overrides: { suppressCollaborationMode: true }
|
|
});
|
|
|
|
expect(params.collaborationMode).toBeUndefined();
|
|
expect(params.model).toBe('o3');
|
|
});
|
|
|
|
it('builds mention inputs from quoted @file tokens', () => {
|
|
expect(buildUserInputFromMessage('please inspect @"src/index.ts" now')).toEqual([
|
|
{ type: 'text', text: 'please inspect ' },
|
|
{ type: 'mention', name: 'index.ts', path: 'src/index.ts' },
|
|
{ type: 'text', text: ' now' }
|
|
]);
|
|
});
|
|
|
|
it('builds a structured leading skill input from the native catalog', () => {
|
|
expect(buildUserInputFromMessage('$hapi inspect @"README.md"', [{
|
|
name: 'hapi',
|
|
path: '/home/user/.agents/skills/hapi/SKILL.md',
|
|
description: 'Manage HAPI',
|
|
scope: 'user',
|
|
enabled: true
|
|
}])).toEqual([
|
|
{ type: 'skill', name: 'hapi', path: '/home/user/.agents/skills/hapi/SKILL.md' },
|
|
{ type: 'text', text: ' inspect ' },
|
|
{ type: 'mention', name: 'README.md', path: 'README.md' }
|
|
]);
|
|
});
|
|
|
|
it('keeps unknown and disabled skill references as text', () => {
|
|
const skills = [{
|
|
name: 'disabled-skill',
|
|
path: '/skills/disabled/SKILL.md',
|
|
description: 'Disabled',
|
|
scope: 'user' as const,
|
|
enabled: false
|
|
}];
|
|
|
|
expect(buildUserInputFromMessage('$unknown run', skills)).toEqual([
|
|
{ type: 'text', text: '$unknown run' }
|
|
]);
|
|
expect(buildUserInputFromMessage('$disabled-skill run', skills)).toEqual([
|
|
{ type: 'text', text: '$disabled-skill run' }
|
|
]);
|
|
});
|
|
|
|
it('builds mention inputs from quoted @file tokens with spaces', () => {
|
|
expect(buildUserInputFromMessage('please inspect @"docs/My File.md" now')).toEqual([
|
|
{ type: 'text', text: 'please inspect ' },
|
|
{ type: 'mention', name: 'My File.md', path: 'docs/My File.md' },
|
|
{ type: 'text', text: ' now' }
|
|
]);
|
|
});
|
|
|
|
it('builds mention inputs from quoted root-level @file tokens', () => {
|
|
expect(buildUserInputFromMessage('please inspect @"package.json" now')).toEqual([
|
|
{ type: 'text', text: 'please inspect ' },
|
|
{ type: 'mention', name: 'package.json', path: 'package.json' },
|
|
{ type: 'text', text: ' now' }
|
|
]);
|
|
});
|
|
|
|
it('keeps literal at-mentions as text', () => {
|
|
expect(buildUserInputFromMessage('please ask @alice to upgrade @types/node.')).toEqual([
|
|
{ type: 'text', text: 'please ask @alice to upgrade @types/node.' }
|
|
]);
|
|
});
|
|
});
|