@hanhnd/agent-kit 1.0.31 → 1.0.33
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.
- package/dist/cli/commands/cli-digest-pending.test.js +111 -0
- package/dist/cli/commands/cli-memory.test.js +34 -0
- package/dist/cli/commands/memory.d.ts +1 -0
- package/dist/cli/commands/memory.js +90 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +9 -0
- package/dist/cli/utils/args.d.ts +8 -0
- package/dist/cli/utils/args.js +31 -0
- package/dist/cli.js +16489 -0
- package/dist/{tools/config.d.ts → core/config/index.d.ts} +4 -9
- package/dist/{tools/config.js → core/config/index.js} +20 -23
- package/dist/core/handoffs/index.d.ts +16 -0
- package/dist/{tools/handoffs.js → core/handoffs/index.js} +4 -5
- package/dist/{tools/security.d.ts → core/security/index.d.ts} +0 -19
- package/dist/{tools/security.js → core/security/index.js} +3 -80
- package/dist/entrypoints/cli.d.ts +2 -0
- package/dist/entrypoints/cli.js +11 -0
- package/dist/entrypoints/server.js +37 -0
- package/dist/{tools → mcp}/agent.js +3 -14
- package/dist/{tools → mcp}/core.d.ts +1 -1
- package/dist/{tools → mcp}/core.js +3 -5
- package/dist/{tools → mcp}/integration.js +5 -11
- package/dist/mcp/memory.d.ts +15 -0
- package/dist/{tools → mcp}/memory.js +40 -12
- package/dist/{kit-server.js → server.js} +720 -224
- package/dist/services/digest/background.d.ts +11 -0
- package/dist/services/digest/background.js +258 -0
- package/dist/services/digest/constants.d.ts +17 -0
- package/dist/services/digest/constants.js +19 -0
- package/dist/services/digest/digest-background.test.js +206 -0
- package/dist/services/digest/digest-init.test.js +33 -0
- package/dist/services/digest/digest-llama-local.test.js +21 -0
- package/dist/services/digest/digest-pending.test.d.ts +1 -0
- package/dist/services/digest/digest-pending.test.js +192 -0
- package/dist/services/digest/digest-processor.test.d.ts +1 -0
- package/dist/services/digest/digest-processor.test.js +32 -0
- package/dist/services/digest/files.d.ts +6 -0
- package/dist/services/digest/files.js +45 -0
- package/dist/services/digest/model-registry.d.ts +2 -0
- package/dist/services/digest/model-registry.js +49 -0
- package/dist/services/digest/processor.d.ts +13 -0
- package/dist/services/digest/processor.js +188 -0
- package/dist/services/digest/providers/llama-local.d.ts +2 -0
- package/dist/services/digest/providers/llama-local.js +135 -0
- package/dist/services/digest/types.d.ts +117 -0
- package/dist/services/digest/types.js +6 -0
- package/dist/services/digest/worker.d.ts +2 -0
- package/dist/services/digest/worker.js +51 -0
- package/dist/{utils → services/integration}/credentials.js +1 -1
- package/dist/{utils/parser.js → services/integration/parser/adf.js} +3 -6
- package/dist/{memory → services/memory}/chunker.d.ts +0 -2
- package/dist/{memory → services/memory}/chunker.js +5 -7
- package/dist/services/memory/chunker.test.d.ts +1 -0
- package/dist/{memory/__tests__ → services/memory}/chunker.test.js +1 -34
- package/dist/services/memory/constants.d.ts +5 -0
- package/dist/services/memory/constants.js +13 -0
- package/dist/{memory → services/memory}/embedder.d.ts +3 -2
- package/dist/{memory → services/memory}/embedder.js +15 -6
- package/dist/services/memory/embedder.test.d.ts +1 -0
- package/dist/services/memory/embedder.test.js +13 -0
- package/dist/{memory → services/memory}/index.d.ts +1 -1
- package/dist/{memory → services/memory}/index.js +1 -1
- package/dist/{memory → services/memory}/indexer.d.ts +6 -2
- package/dist/{memory → services/memory}/indexer.js +15 -37
- package/dist/services/memory/indexer.test.d.ts +1 -0
- package/dist/{memory/__tests__ → services/memory}/indexer.test.js +11 -15
- package/dist/services/memory/memory.test.d.ts +1 -0
- package/dist/{tools/__tests__ → services/memory}/memory.test.js +82 -19
- package/dist/{memory → services/memory}/store.js +6 -18
- package/dist/services/memory/store.test.d.ts +1 -0
- package/dist/{memory/__tests__ → services/memory}/store.test.js +18 -5
- package/dist/{memory → services/memory}/types.d.ts +8 -2
- package/dist/services/memory/types.js +6 -0
- package/dist/utils/async.d.ts +1 -0
- package/dist/utils/async.js +15 -0
- package/dist/utils/files.d.ts +7 -0
- package/dist/utils/files.js +77 -0
- package/dist/utils/files.test.d.ts +1 -0
- package/dist/utils/files.test.js +81 -0
- package/dist/utils/hash.d.ts +1 -0
- package/dist/utils/hash.js +4 -0
- package/dist/utils/index.d.ts +6 -0
- package/dist/utils/index.js +6 -0
- package/dist/utils/json.d.ts +1 -0
- package/dist/utils/json.js +3 -0
- package/dist/utils/paths.d.ts +0 -1
- package/dist/utils/paths.js +1 -1
- package/dist/utils/temp-dir.test.d.ts +4 -0
- package/dist/utils/temp-dir.test.js +20 -0
- package/package.json +8 -6
- package/dist/memory/__tests__/embedder.test.js +0 -14
- package/dist/memory/types.js +0 -9
- package/dist/tools/handoffs.d.ts +0 -22
- package/dist/tools/memory.d.ts +0 -16
- /package/dist/{memory/__tests__/chunker.test.d.ts → cli/commands/cli-digest-pending.test.d.ts} +0 -0
- /package/dist/{memory/__tests__/embedder.test.d.ts → cli/commands/cli-memory.test.d.ts} +0 -0
- /package/dist/{kit-server.d.ts → entrypoints/server.d.ts} +0 -0
- /package/dist/{tools → mcp}/agent.d.ts +0 -0
- /package/dist/{tools → mcp}/integration.d.ts +0 -0
- /package/dist/{memory/__tests__/indexer.test.d.ts → services/digest/digest-background.test.d.ts} +0 -0
- /package/dist/{memory/__tests__/store.test.d.ts → services/digest/digest-init.test.d.ts} +0 -0
- /package/dist/{tools/__tests__/memory.test.d.ts → services/digest/digest-llama-local.test.d.ts} +0 -0
- /package/dist/{utils → services/integration}/credentials.d.ts +0 -0
- /package/dist/{utils/parser.d.ts → services/integration/parser/adf.d.ts} +0 -0
- /package/dist/{memory → services/memory}/store.d.ts +0 -0
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* Configuration utilities
|
|
3
3
|
* Shared configuration constants and loaders
|
|
4
4
|
*/
|
|
5
|
-
import {
|
|
5
|
+
import type { MemoryConfig } from '../../services/memory/types.js';
|
|
6
|
+
import type { ConversationDigestSettings } from '../../services/digest/types.js';
|
|
6
7
|
/**
|
|
7
8
|
* Default file extensions - expanded to support more languages
|
|
8
9
|
* Used by kit_get_project_context and other tools
|
|
@@ -21,15 +22,9 @@ export { MemoryConfig };
|
|
|
21
22
|
* Merge DEFAULT_MEMORY_CONFIG with settings.memory; resolve wikiDir when not explicitly set.
|
|
22
23
|
*/
|
|
23
24
|
export declare function resolveMemoryConfig(settings: ProjectSettings, workspaceRoot: string): MemoryConfig;
|
|
24
|
-
/**
|
|
25
|
-
* Get file extensions - from settings.json or defaults
|
|
26
|
-
* Allows project-specific customization
|
|
27
|
-
*/
|
|
28
|
-
export declare function getFileExtensions(projectDir: string): string[];
|
|
29
|
-
/**
|
|
30
|
-
* Load project settings from .gemini/settings.json
|
|
31
|
-
*/
|
|
32
25
|
export declare function loadProjectSettings(projectDir: string): ProjectSettings;
|
|
26
|
+
export declare function writeProjectSettings(workspaceRoot: string, patch: Partial<ProjectSettings>): void;
|
|
27
|
+
export declare function resolveConversationDigestConfig(settings: ProjectSettings): ConversationDigestSettings | undefined;
|
|
33
28
|
/**
|
|
34
29
|
* Sensitive files that agents are FORBIDDEN from reading/writing.
|
|
35
30
|
*/
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import * as fs from 'fs';
|
|
6
6
|
import * as path from 'path';
|
|
7
|
-
import { DEFAULT_MEMORY_CONFIG } from '
|
|
7
|
+
import { DEFAULT_MEMORY_CONFIG } from '../../services/memory/constants.js';
|
|
8
|
+
import { atomicWriteJsonFile } from '../../utils/files.js';
|
|
8
9
|
/**
|
|
9
10
|
* Default file extensions - expanded to support more languages
|
|
10
11
|
* Used by kit_get_project_context and other tools
|
|
@@ -47,20 +48,6 @@ export function resolveMemoryConfig(settings, workspaceRoot) {
|
|
|
47
48
|
: path.join(workspaceRoot, '.agent-kit', 'wiki'),
|
|
48
49
|
};
|
|
49
50
|
}
|
|
50
|
-
/**
|
|
51
|
-
* Get file extensions - from settings.json or defaults
|
|
52
|
-
* Allows project-specific customization
|
|
53
|
-
*/
|
|
54
|
-
export function getFileExtensions(projectDir) {
|
|
55
|
-
const settings = loadProjectSettings(projectDir);
|
|
56
|
-
if (settings.fileExtensions && Array.isArray(settings.fileExtensions)) {
|
|
57
|
-
return settings.fileExtensions;
|
|
58
|
-
}
|
|
59
|
-
return DEFAULT_EXTENSIONS;
|
|
60
|
-
}
|
|
61
|
-
/**
|
|
62
|
-
* Load project settings from .gemini/settings.json
|
|
63
|
-
*/
|
|
64
51
|
export function loadProjectSettings(projectDir) {
|
|
65
52
|
const settingsPath = path.join(projectDir, '.agent-kit', 'settings.json');
|
|
66
53
|
if (fs.existsSync(settingsPath)) {
|
|
@@ -73,6 +60,23 @@ export function loadProjectSettings(projectDir) {
|
|
|
73
60
|
}
|
|
74
61
|
return {};
|
|
75
62
|
}
|
|
63
|
+
export function writeProjectSettings(workspaceRoot, patch) {
|
|
64
|
+
const settingsPath = path.join(workspaceRoot, '.agent-kit', 'settings.json');
|
|
65
|
+
const current = loadProjectSettings(workspaceRoot);
|
|
66
|
+
atomicWriteJsonFile(settingsPath, { ...current, ...patch });
|
|
67
|
+
}
|
|
68
|
+
export function resolveConversationDigestConfig(settings) {
|
|
69
|
+
const digest = settings.memory?.conversationDigest;
|
|
70
|
+
if (!digest?.initialized || typeof digest.modelId !== 'string' || typeof digest.initializedAt !== 'string') {
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
enabled: digest.enabled !== false,
|
|
75
|
+
initialized: true,
|
|
76
|
+
modelId: digest.modelId,
|
|
77
|
+
initializedAt: digest.initializedAt,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
76
80
|
/**
|
|
77
81
|
* Sensitive files that agents are FORBIDDEN from reading/writing.
|
|
78
82
|
*/
|
|
@@ -108,11 +112,4 @@ export const FORBIDDEN_PATTERNS = [
|
|
|
108
112
|
/**
|
|
109
113
|
* Directory name segments that are always blocked.
|
|
110
114
|
*/
|
|
111
|
-
export const FORBIDDEN_DIRS = [
|
|
112
|
-
'.git',
|
|
113
|
-
'.ssh',
|
|
114
|
-
'.aws',
|
|
115
|
-
'.kube',
|
|
116
|
-
'.gnupg',
|
|
117
|
-
'.docker',
|
|
118
|
-
];
|
|
115
|
+
export const FORBIDDEN_DIRS = ['.git', '.ssh', '.aws', '.kube', '.gnupg', '.docker'];
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export declare const HANDOFF_TYPES: readonly ["brainstorm", "clarification", "plan", "ticket", "research", "scenario", "investigation"];
|
|
2
|
+
type HandoffType = (typeof HANDOFF_TYPES)[number];
|
|
3
|
+
type CanonicalHandoffType = 'brainstorm' | 'clarification' | 'plan' | 'ticket' | 'research' | 'scenario' | 'investigation';
|
|
4
|
+
interface SavedHandoffLocation {
|
|
5
|
+
featureSlug: string;
|
|
6
|
+
canonicalType: CanonicalHandoffType;
|
|
7
|
+
filePath: string;
|
|
8
|
+
relativePath: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function resolveHandoffPath(input: {
|
|
11
|
+
workspaceRoot: string;
|
|
12
|
+
type: HandoffType;
|
|
13
|
+
slug: string;
|
|
14
|
+
content: string;
|
|
15
|
+
}): SavedHandoffLocation;
|
|
16
|
+
export {};
|
|
@@ -12,7 +12,7 @@ const TICKET_ID_PATTERN = /\b[A-Z][A-Z0-9]+-\d+\b/;
|
|
|
12
12
|
function findTicketId(value) {
|
|
13
13
|
return value.match(TICKET_ID_PATTERN)?.[0].toLowerCase() ?? null;
|
|
14
14
|
}
|
|
15
|
-
|
|
15
|
+
function sanitizeFeatureSlug(value) {
|
|
16
16
|
return value
|
|
17
17
|
.replace(/[^a-zA-Z0-9-_]/g, '-')
|
|
18
18
|
.replace(/-+/g, '-')
|
|
@@ -26,13 +26,13 @@ function contentSlugCandidate(content) {
|
|
|
26
26
|
.find((line) => line.length > 0);
|
|
27
27
|
return heading ?? content.replace(/\s+/g, ' ').trim().slice(0, 80);
|
|
28
28
|
}
|
|
29
|
-
|
|
29
|
+
function normalizeHandoffType(type) {
|
|
30
30
|
if (!HANDOFF_TYPES.includes(type)) {
|
|
31
31
|
throw new Error(`Unsupported handoff type: ${type}`);
|
|
32
32
|
}
|
|
33
33
|
return type;
|
|
34
34
|
}
|
|
35
|
-
|
|
35
|
+
function deriveFeatureSlug(input) {
|
|
36
36
|
const requestedTicketSlug = findTicketId(input.requestedSlug);
|
|
37
37
|
if (requestedTicketSlug)
|
|
38
38
|
return requestedTicketSlug;
|
|
@@ -42,8 +42,7 @@ export function deriveFeatureSlug(input) {
|
|
|
42
42
|
const contentTicketSlug = findTicketId(input.content);
|
|
43
43
|
if (contentTicketSlug)
|
|
44
44
|
return contentTicketSlug;
|
|
45
|
-
return
|
|
46
|
-
'untitled-handoff');
|
|
45
|
+
return sanitizeFeatureSlug(contentSlugCandidate(input.content)) || 'untitled-handoff';
|
|
47
46
|
}
|
|
48
47
|
export function resolveHandoffPath(input) {
|
|
49
48
|
const canonicalType = normalizeHandoffType(input.type);
|
|
@@ -18,26 +18,7 @@ export declare function sanitize(input: string): string;
|
|
|
18
18
|
* Uses stricter path.sep check to prevent prefix matching flaws.
|
|
19
19
|
*/
|
|
20
20
|
export declare function validatePath(filePath: string, baseDir?: string): string;
|
|
21
|
-
/**
|
|
22
|
-
* Safe git command execution using execFileSync
|
|
23
|
-
* Includes stderr in error message for better debugging
|
|
24
|
-
*
|
|
25
|
-
* @param timeout Default from GEMINI_KIT_GIT_TIMEOUT env var or 30s
|
|
26
|
-
*/
|
|
27
|
-
export declare function safeGit(args: string[], options?: {
|
|
28
|
-
timeout?: number;
|
|
29
|
-
cwd?: string;
|
|
30
|
-
}): string;
|
|
31
|
-
/**
|
|
32
|
-
* Check if a command exists (cross-platform)
|
|
33
|
-
* Uses 'where' on Windows, 'which' on macOS/Linux
|
|
34
|
-
*/
|
|
35
21
|
export declare function commandExists(cmd: string): boolean;
|
|
36
|
-
/**
|
|
37
|
-
* Async file finder - non-blocking for large repos
|
|
38
|
-
* Uses queue-based approach to prevent stack overflow on deep directories
|
|
39
|
-
*/
|
|
40
|
-
export declare function findFilesAsync(dir: string, extensions: string[], maxFiles: number, excludeDirs?: string[]): Promise<string[]>;
|
|
41
22
|
/**
|
|
42
23
|
* Sanitize output text by redacting sensitive tokens (API keys, etc.)
|
|
43
24
|
*/
|
|
@@ -3,10 +3,9 @@
|
|
|
3
3
|
* Exported utilities for safe command execution
|
|
4
4
|
*/
|
|
5
5
|
import { execFileSync } from 'child_process';
|
|
6
|
-
import * as fs from 'fs';
|
|
7
6
|
import * as path from 'path';
|
|
8
|
-
import { getWorkspaceRoot } from '
|
|
9
|
-
import { FORBIDDEN_DIRS, FORBIDDEN_FILES, FORBIDDEN_PATTERNS } from '
|
|
7
|
+
import { getWorkspaceRoot } from '../../utils/utils.js';
|
|
8
|
+
import { FORBIDDEN_DIRS, FORBIDDEN_FILES, FORBIDDEN_PATTERNS } from '../config/index.js';
|
|
10
9
|
/**
|
|
11
10
|
* Sanitize string for safe use with execFileSync
|
|
12
11
|
* Only removes dangerous shell operators - safe chars like !?#* are allowed
|
|
@@ -52,42 +51,6 @@ export function validatePath(filePath, baseDir = process.cwd()) {
|
|
|
52
51
|
}
|
|
53
52
|
return resolved;
|
|
54
53
|
}
|
|
55
|
-
/**
|
|
56
|
-
* Extract stderr from error for better logging
|
|
57
|
-
*/
|
|
58
|
-
function extractStderr(error) {
|
|
59
|
-
if (error && typeof error === 'object' && 'stderr' in error) {
|
|
60
|
-
return String(error.stderr);
|
|
61
|
-
}
|
|
62
|
-
return '';
|
|
63
|
-
}
|
|
64
|
-
// Configurable timeouts via environment variables
|
|
65
|
-
const GIT_TIMEOUT = parseInt(process.env.GEMINI_KIT_GIT_TIMEOUT || '30000', 10);
|
|
66
|
-
/**
|
|
67
|
-
* Safe git command execution using execFileSync
|
|
68
|
-
* Includes stderr in error message for better debugging
|
|
69
|
-
*
|
|
70
|
-
* @param timeout Default from GEMINI_KIT_GIT_TIMEOUT env var or 30s
|
|
71
|
-
*/
|
|
72
|
-
export function safeGit(args, options) {
|
|
73
|
-
try {
|
|
74
|
-
return execFileSync('git', args, {
|
|
75
|
-
encoding: 'utf8',
|
|
76
|
-
timeout: options?.timeout || GIT_TIMEOUT,
|
|
77
|
-
cwd: options?.cwd || getWorkspaceRoot(),
|
|
78
|
-
maxBuffer: 10 * 1024 * 1024, // 10MB
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
catch (error) {
|
|
82
|
-
const stderr = extractStderr(error);
|
|
83
|
-
const baseMsg = error instanceof Error ? error.message : String(error);
|
|
84
|
-
throw new Error(`Git command failed: ${baseMsg}${stderr ? `\nDetails: ${stderr}` : ''}`);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
/**
|
|
88
|
-
* Check if a command exists (cross-platform)
|
|
89
|
-
* Uses 'where' on Windows, 'which' on macOS/Linux
|
|
90
|
-
*/
|
|
91
54
|
export function commandExists(cmd) {
|
|
92
55
|
try {
|
|
93
56
|
const checkCmd = process.platform === 'win32' ? 'where' : 'which';
|
|
@@ -103,46 +66,6 @@ export function commandExists(cmd) {
|
|
|
103
66
|
return false;
|
|
104
67
|
}
|
|
105
68
|
}
|
|
106
|
-
/**
|
|
107
|
-
* Async file finder - non-blocking for large repos
|
|
108
|
-
* Uses queue-based approach to prevent stack overflow on deep directories
|
|
109
|
-
*/
|
|
110
|
-
export async function findFilesAsync(dir, extensions, maxFiles, excludeDirs = ['node_modules', '.git', 'dist', 'build', 'coverage']) {
|
|
111
|
-
const results = [];
|
|
112
|
-
// Use queue instead of recursion to prevent stack overflow
|
|
113
|
-
const queue = [
|
|
114
|
-
{ fullPath: dir, relativePath: '' },
|
|
115
|
-
];
|
|
116
|
-
while (queue.length > 0 && results.length < maxFiles) {
|
|
117
|
-
const current = queue.shift();
|
|
118
|
-
let entries;
|
|
119
|
-
try {
|
|
120
|
-
entries = await fs.promises.readdir(current.fullPath, { withFileTypes: true });
|
|
121
|
-
}
|
|
122
|
-
catch {
|
|
123
|
-
continue; // Skip directories we can't read
|
|
124
|
-
}
|
|
125
|
-
for (const entry of entries) {
|
|
126
|
-
if (results.length >= maxFiles)
|
|
127
|
-
break;
|
|
128
|
-
const entryFullPath = path.join(current.fullPath, entry.name);
|
|
129
|
-
const entryRelPath = current.relativePath
|
|
130
|
-
? path.join(current.relativePath, entry.name)
|
|
131
|
-
: entry.name;
|
|
132
|
-
if (entry.isDirectory()) {
|
|
133
|
-
if (!excludeDirs.includes(entry.name)) {
|
|
134
|
-
queue.push({ fullPath: entryFullPath, relativePath: entryRelPath });
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
else if (entry.isFile()) {
|
|
138
|
-
if (extensions.some((ext) => entry.name.endsWith(ext))) {
|
|
139
|
-
results.push(entryRelPath);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
return results;
|
|
145
|
-
}
|
|
146
69
|
/**
|
|
147
70
|
* Sanitize output text by redacting sensitive tokens (API keys, etc.)
|
|
148
71
|
*/
|
|
@@ -152,7 +75,7 @@ export function sanitizeOutput(text) {
|
|
|
152
75
|
/SK-[A-Za-z0-9]{40,}/g, // OpenAI keys
|
|
153
76
|
/AKIA[0-9A-Z]{16}/g, // AWS access keys
|
|
154
77
|
/xox[baprs]-[A-Za-z0-9-]{10,}/g, // Slack tokens
|
|
155
|
-
/[Aa]tlassian[_
|
|
78
|
+
/[Aa]tlassian[_-]?[Aa][Pp][Ii][_-]?[Tt]oken[^\s]*\s*[:=]\s*\S+/g, // Atlassian token patterns
|
|
156
79
|
];
|
|
157
80
|
let sanitized = text;
|
|
158
81
|
for (const pattern of SECRET_PATTERNS) {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { runAgentKitCli } from '../cli/index.js';
|
|
3
|
+
try {
|
|
4
|
+
const exitCode = await runAgentKitCli(process.argv.slice(2), process.env);
|
|
5
|
+
process.exit(exitCode);
|
|
6
|
+
}
|
|
7
|
+
catch (err) {
|
|
8
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
9
|
+
process.stderr.write('[agent-kit] CLI error: ' + message + '\n');
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Agent-Kit MCP Server
|
|
4
|
+
* Provides custom tools for agent orchestration
|
|
5
|
+
*
|
|
6
|
+
* Modular architecture - tools split into separate modules
|
|
7
|
+
*/
|
|
8
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
9
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
10
|
+
import { registerCoreTools } from '../mcp/core.js';
|
|
11
|
+
import { registerAgentTools } from '../mcp/agent.js';
|
|
12
|
+
import { registerIntegrationTools } from '../mcp/integration.js';
|
|
13
|
+
import { loadProjectSettings } from '../core/config/index.js';
|
|
14
|
+
import { registerMemoryTools } from '../mcp/memory.js';
|
|
15
|
+
import { getWorkspaceRoot } from '../utils/utils.js';
|
|
16
|
+
const server = new McpServer({
|
|
17
|
+
name: 'kit-agents',
|
|
18
|
+
version: '1.0.0',
|
|
19
|
+
});
|
|
20
|
+
// ═══════════════════════════════════════════════════════════════
|
|
21
|
+
// REGISTER MODULAR TOOLS
|
|
22
|
+
// ═══════════════════════════════════════════════════════════════
|
|
23
|
+
registerIntegrationTools(server);
|
|
24
|
+
registerCoreTools(server);
|
|
25
|
+
registerAgentTools(server);
|
|
26
|
+
const workspaceRoot = getWorkspaceRoot();
|
|
27
|
+
const settings = loadProjectSettings(workspaceRoot);
|
|
28
|
+
const memoryIndexer = registerMemoryTools(server, settings, workspaceRoot);
|
|
29
|
+
// ═══════════════════════════════════════════════════════════════
|
|
30
|
+
// START SERVER
|
|
31
|
+
// ═══════════════════════════════════════════════════════════════
|
|
32
|
+
const transport = new StdioServerTransport();
|
|
33
|
+
await server.connect(transport);
|
|
34
|
+
// Fire-and-forget startup indexing — must run after server.connect
|
|
35
|
+
if (memoryIndexer) {
|
|
36
|
+
void memoryIndexer.startupIndex();
|
|
37
|
+
}
|
|
@@ -8,7 +8,7 @@ import * as fs from 'fs';
|
|
|
8
8
|
import * as path from 'path';
|
|
9
9
|
import { z } from 'zod';
|
|
10
10
|
import { getWorkspaceRoot, mcpJson, mcpText } from '../utils/utils.js';
|
|
11
|
-
import { commandExists, sanitizeOutput, validatePath } from '
|
|
11
|
+
import { commandExists, sanitizeOutput, validatePath } from '../core/security/index.js';
|
|
12
12
|
// Configurable timeout via environment variable (default: 5 minutes)
|
|
13
13
|
const AGENT_TIMEOUT = parseInt(process.env.KIT_AGENT_TIMEOUT || '300000', 10);
|
|
14
14
|
const jobRegistry = new Map();
|
|
@@ -24,16 +24,7 @@ function agentArgs(agent, prompt, workspaceRoot) {
|
|
|
24
24
|
case 'claude':
|
|
25
25
|
return ['--dangerously-skip-permissions', '-p', prompt];
|
|
26
26
|
case 'codex':
|
|
27
|
-
return [
|
|
28
|
-
'--ask-for-approval',
|
|
29
|
-
'never',
|
|
30
|
-
'--sandbox',
|
|
31
|
-
'workspace-write',
|
|
32
|
-
'--cd',
|
|
33
|
-
workspaceRoot,
|
|
34
|
-
'exec',
|
|
35
|
-
prompt,
|
|
36
|
-
];
|
|
27
|
+
return ['--ask-for-approval', 'never', '--sandbox', 'workspace-write', '--cd', workspaceRoot, 'exec', prompt];
|
|
37
28
|
}
|
|
38
29
|
}
|
|
39
30
|
function taskSlug(task) {
|
|
@@ -59,9 +50,7 @@ function initAgentLog(workspaceRoot, slug, agent) {
|
|
|
59
50
|
}
|
|
60
51
|
export function registerAgentTools(server) {
|
|
61
52
|
server.tool('kit_trigger_agent', '!Important: Trigger only when user explicitly asks to delegate a task to an external agent CLI (gemini, claude, or codex). The task can be a direct message or a path to a handoff file (.agent-kit/handoffs/feature-slug/plan.md).', {
|
|
62
|
-
agent: z
|
|
63
|
-
.enum(['gemini', 'claude', 'codex'])
|
|
64
|
-
.describe('The agent CLI to invoke: "gemini", "claude", or "codex"'),
|
|
53
|
+
agent: z.enum(['gemini', 'claude', 'codex']).describe('The agent CLI to invoke: "gemini", "claude", or "codex"'),
|
|
65
54
|
task: z
|
|
66
55
|
.string()
|
|
67
56
|
.describe('Task message or path to a handoff file (e.g., ".agent-kit/handoffs/feature-slug/plan.md")'),
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Core Tools - Extension info and handoff persistence
|
|
3
3
|
*/
|
|
4
4
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
5
|
-
import { DEFAULT_EXTENSIONS } from '
|
|
5
|
+
import { DEFAULT_EXTENSIONS } from '../core/config/index.js';
|
|
6
6
|
/**
|
|
7
7
|
* Register core tools with MCP server
|
|
8
8
|
*/
|
|
@@ -5,8 +5,8 @@ import * as fs from 'fs';
|
|
|
5
5
|
import * as path from 'path';
|
|
6
6
|
import { z } from 'zod';
|
|
7
7
|
import { getWorkspaceRoot, mcpText } from '../utils/utils.js';
|
|
8
|
-
import { DEFAULT_EXTENSIONS } from '
|
|
9
|
-
import { HANDOFF_TYPES, resolveHandoffPath } from '
|
|
8
|
+
import { DEFAULT_EXTENSIONS } from '../core/config/index.js';
|
|
9
|
+
import { HANDOFF_TYPES, resolveHandoffPath } from '../core/handoffs/index.js';
|
|
10
10
|
/**
|
|
11
11
|
* Register core tools with MCP server
|
|
12
12
|
*/
|
|
@@ -17,9 +17,7 @@ export function registerCoreTools(server) {
|
|
|
17
17
|
// Returns the saved file path to use in next-step instructions
|
|
18
18
|
// ═══════════════════════════════════════════════════════════════
|
|
19
19
|
server.tool('kit_save_handoff', `Save a handoff artifact to .agent-kit/handoffs/. Returns the saved file path to use in next-step instructions. Do NOT append version numbers (v2, v3, etc.) to the slug.`, {
|
|
20
|
-
type: z
|
|
21
|
-
.enum(HANDOFF_TYPES)
|
|
22
|
-
.describe('Handoff type'),
|
|
20
|
+
type: z.enum(HANDOFF_TYPES).describe('Handoff type'),
|
|
23
21
|
content: z.string().describe('Full markdown content to save'),
|
|
24
22
|
slug: z
|
|
25
23
|
.string()
|
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { writeFileSync } from 'fs';
|
|
6
6
|
import { z } from 'zod';
|
|
7
|
-
import { getCredential } from '../
|
|
7
|
+
import { getCredential } from '../services/integration/credentials.js';
|
|
8
8
|
import { mcpText } from '../utils/utils.js';
|
|
9
|
-
import { adfToMarkdown } from '../
|
|
10
|
-
import { sanitize } from '
|
|
9
|
+
import { adfToMarkdown } from '../services/integration/parser/adf.js';
|
|
10
|
+
import { sanitize } from '../core/security/index.js';
|
|
11
11
|
// Zod schema for Bitbucket PR REST API response
|
|
12
12
|
const BitbucketPrSchema = z.object({
|
|
13
13
|
id: z.number(),
|
|
@@ -81,11 +81,7 @@ export function registerIntegrationTools(server) {
|
|
|
81
81
|
.optional()
|
|
82
82
|
.describe('Bitbucket workspace slug (required for numeric ID if BITBUCKET_DEFAULT_WORKSPACE not set)'),
|
|
83
83
|
repoSlug: z.string().optional().describe('Bitbucket repo slug (required for numeric ID)'),
|
|
84
|
-
includeDiff: z
|
|
85
|
-
.boolean()
|
|
86
|
-
.optional()
|
|
87
|
-
.default(true)
|
|
88
|
-
.describe('Include unified diff in response'),
|
|
84
|
+
includeDiff: z.boolean().optional().default(true).describe('Include unified diff in response'),
|
|
89
85
|
}, async ({ input, workspace, repoSlug, includeDiff }) => {
|
|
90
86
|
try {
|
|
91
87
|
let ws;
|
|
@@ -180,9 +176,7 @@ ${pr.description || 'No description'}`;
|
|
|
180
176
|
**Type:** ${ticket.fields.issuetype?.name || 'Unknown'}
|
|
181
177
|
|
|
182
178
|
### Description
|
|
183
|
-
${typeof ticket.fields.description === 'string'
|
|
184
|
-
? ticket.fields.description
|
|
185
|
-
: adfToMarkdown(ticket.fields.description)}
|
|
179
|
+
${typeof ticket.fields.description === 'string' ? ticket.fields.description : adfToMarkdown(ticket.fields.description)}
|
|
186
180
|
|
|
187
181
|
### Labels
|
|
188
182
|
${ticket.fields.labels?.join(', ') || 'None'}`;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { MemoryIndexer } from '../services/memory/indexer.js';
|
|
3
|
+
import { MemoryStore } from '../services/memory/store.js';
|
|
4
|
+
import type { MemoryConfig } from '../services/memory/types.js';
|
|
5
|
+
import { type ProjectSettings } from '../core/config/index.js';
|
|
6
|
+
/**
|
|
7
|
+
* Initializes the memory subsystem and registers all memory tools.
|
|
8
|
+
* Returns the MemoryIndexer so the caller can fire startupIndex() after server.connect().
|
|
9
|
+
* Returns null when memory is disabled (settings.memory.enabled !== true).
|
|
10
|
+
*/
|
|
11
|
+
export declare function registerMemoryTools(server: McpServer, settings: ProjectSettings, workspaceRoot: string, overrides?: {
|
|
12
|
+
indexer?: MemoryIndexer;
|
|
13
|
+
store?: MemoryStore;
|
|
14
|
+
config?: MemoryConfig;
|
|
15
|
+
}): MemoryIndexer | null;
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import * as path from 'path';
|
|
2
2
|
import { z } from 'zod';
|
|
3
|
-
import { Embedder } from '../memory/embedder.js';
|
|
4
|
-
import { MemoryIndexer } from '../memory/indexer.js';
|
|
5
|
-
import { MemoryStore } from '../memory/store.js';
|
|
3
|
+
import { Embedder } from '../services/memory/embedder.js';
|
|
4
|
+
import { MemoryIndexer } from '../services/memory/indexer.js';
|
|
5
|
+
import { MemoryStore } from '../services/memory/store.js';
|
|
6
|
+
import { initializeConversationDigestModel } from '../services/digest/processor.js';
|
|
7
|
+
import { DEFAULT_DIGEST_MODEL_ID } from '../services/digest/constants.js';
|
|
6
8
|
import { mcpJson, mcpText } from '../utils/utils.js';
|
|
7
|
-
import { resolveMemoryConfig } from '
|
|
9
|
+
import { loadProjectSettings, resolveConversationDigestConfig, resolveMemoryConfig, } from '../core/config/index.js';
|
|
8
10
|
/**
|
|
9
11
|
* Registers tool handlers onto an already-constructed indexer/store pair.
|
|
10
|
-
* Exported for unit testing with mocks.
|
|
11
12
|
*/
|
|
12
|
-
|
|
13
|
+
function registerMemoryToolHandlers(server, indexer, store, config, workspaceRoot = process.cwd()) {
|
|
13
14
|
server.tool('kit_memory_search', 'Search persistent memory for context relevant to the current question. Returns semantically relevant results using hybrid dense+BM25+RRF search.', {
|
|
14
15
|
query: z.string().min(1).describe('Search query'),
|
|
15
16
|
top_k: z.number().int().positive().optional().describe('Number of results to return'),
|
|
@@ -57,19 +58,46 @@ export function registerMemoryToolHandlers(server, indexer, store, config) {
|
|
|
57
58
|
return mcpJson({ saved: false, error: message });
|
|
58
59
|
}
|
|
59
60
|
});
|
|
61
|
+
server.tool('kit_memory_digest_init', 'Initialize or toggle local conversation digesting. Downloads/cache-loads the pinned local model only when explicitly invoked.', {
|
|
62
|
+
model_id: z.string().optional().describe('Digest model id'),
|
|
63
|
+
enabled: z.boolean().optional().describe('Enable or disable conversation digesting'),
|
|
64
|
+
}, async ({ model_id, enabled }) => {
|
|
65
|
+
try {
|
|
66
|
+
const existingConfig = resolveConversationDigestConfig(loadProjectSettings(workspaceRoot));
|
|
67
|
+
const result = await initializeConversationDigestModel({
|
|
68
|
+
workspaceRoot,
|
|
69
|
+
modelId: model_id ?? existingConfig?.modelId ?? DEFAULT_DIGEST_MODEL_ID,
|
|
70
|
+
allowDownload: true,
|
|
71
|
+
enabled,
|
|
72
|
+
});
|
|
73
|
+
return mcpJson({
|
|
74
|
+
initialized: result.initialized,
|
|
75
|
+
model_id: result.modelId,
|
|
76
|
+
initialized_at: result.initializedAt,
|
|
77
|
+
error: result.error,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
82
|
+
return mcpJson({ initialized: false, error: message });
|
|
83
|
+
}
|
|
84
|
+
});
|
|
60
85
|
}
|
|
61
86
|
/**
|
|
62
87
|
* Initializes the memory subsystem and registers all memory tools.
|
|
63
88
|
* Returns the MemoryIndexer so the caller can fire startupIndex() after server.connect().
|
|
64
89
|
* Returns null when memory is disabled (settings.memory.enabled !== true).
|
|
65
90
|
*/
|
|
66
|
-
export function registerMemoryTools(server, settings, workspaceRoot) {
|
|
91
|
+
export function registerMemoryTools(server, settings, workspaceRoot, overrides) {
|
|
67
92
|
if (settings.memory?.enabled !== true)
|
|
68
93
|
return null;
|
|
69
|
-
const config = resolveMemoryConfig(settings, workspaceRoot);
|
|
70
|
-
const store = new MemoryStore(path.join(config.wikiDir, 'index.db'), config);
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
94
|
+
const config = overrides?.config ?? resolveMemoryConfig(settings, workspaceRoot);
|
|
95
|
+
const store = overrides?.store ?? new MemoryStore(path.join(config.wikiDir, 'index.db'), config);
|
|
96
|
+
const indexer = overrides?.indexer ??
|
|
97
|
+
(() => {
|
|
98
|
+
const embedder = new Embedder(config.embeddingModel);
|
|
99
|
+
return new MemoryIndexer(store, embedder, config);
|
|
100
|
+
})();
|
|
101
|
+
registerMemoryToolHandlers(server, indexer, store, config, workspaceRoot);
|
|
74
102
|
return indexer;
|
|
75
103
|
}
|