thatgfsj-code 0.3.0 → 0.3.1

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.
Files changed (262) hide show
  1. package/CHANGELOG.md +229 -0
  2. package/DEVELOPMENT.md +286 -0
  3. package/README.md +144 -79
  4. package/dist/agent/core.d.ts +56 -0
  5. package/dist/agent/core.d.ts.map +1 -0
  6. package/dist/agent/core.js +142 -0
  7. package/dist/agent/core.js.map +1 -0
  8. package/dist/agent/index.d.ts +8 -0
  9. package/dist/agent/index.d.ts.map +1 -0
  10. package/dist/agent/index.js +8 -0
  11. package/dist/agent/index.js.map +1 -0
  12. package/dist/agent/intent.d.ts +36 -0
  13. package/dist/agent/intent.d.ts.map +1 -0
  14. package/dist/agent/intent.js +146 -0
  15. package/dist/agent/intent.js.map +1 -0
  16. package/dist/agent/streaming.d.ts +50 -0
  17. package/dist/agent/streaming.d.ts.map +1 -0
  18. package/dist/agent/streaming.js +110 -0
  19. package/dist/agent/streaming.js.map +1 -0
  20. package/dist/core/ai-engine.d.ts +76 -0
  21. package/dist/core/ai-engine.d.ts.map +1 -0
  22. package/dist/core/ai-engine.js +394 -0
  23. package/dist/core/ai-engine.js.map +1 -0
  24. package/dist/core/cli.d.ts +21 -0
  25. package/dist/core/cli.d.ts.map +1 -0
  26. package/dist/core/cli.js +143 -0
  27. package/dist/core/cli.js.map +1 -0
  28. package/dist/core/config.d.ts +55 -0
  29. package/dist/core/config.d.ts.map +1 -0
  30. package/dist/core/config.js +162 -0
  31. package/dist/core/config.js.map +1 -0
  32. package/dist/core/context-compactor.d.ts +54 -0
  33. package/dist/core/context-compactor.d.ts.map +1 -0
  34. package/dist/core/context-compactor.js +197 -0
  35. package/dist/core/context-compactor.js.map +1 -0
  36. package/dist/core/hooks.d.ts +75 -0
  37. package/dist/core/hooks.d.ts.map +1 -0
  38. package/dist/core/hooks.js +146 -0
  39. package/dist/core/hooks.js.map +1 -0
  40. package/dist/core/permissions.d.ts +52 -0
  41. package/dist/core/permissions.d.ts.map +1 -0
  42. package/dist/core/permissions.js +237 -0
  43. package/dist/core/permissions.js.map +1 -0
  44. package/dist/core/session.d.ts +54 -0
  45. package/dist/core/session.d.ts.map +1 -0
  46. package/dist/core/session.js +147 -0
  47. package/dist/core/session.js.map +1 -0
  48. package/dist/core/skills.d.ts +60 -0
  49. package/dist/core/skills.d.ts.map +1 -0
  50. package/dist/core/skills.js +175 -0
  51. package/dist/core/skills.js.map +1 -0
  52. package/dist/core/state.d.ts +60 -0
  53. package/dist/core/state.d.ts.map +1 -0
  54. package/dist/core/state.js +106 -0
  55. package/dist/core/state.js.map +1 -0
  56. package/dist/core/subagent.d.ts +68 -0
  57. package/dist/core/subagent.d.ts.map +1 -0
  58. package/dist/core/subagent.js +142 -0
  59. package/dist/core/subagent.js.map +1 -0
  60. package/dist/core/system-prompt.d.ts +40 -0
  61. package/dist/core/system-prompt.d.ts.map +1 -0
  62. package/dist/core/system-prompt.js +138 -0
  63. package/dist/core/system-prompt.js.map +1 -0
  64. package/dist/core/tool-registry.d.ts +53 -0
  65. package/dist/core/tool-registry.d.ts.map +1 -0
  66. package/dist/core/tool-registry.js +138 -0
  67. package/dist/core/tool-registry.js.map +1 -0
  68. package/dist/core/types.d.ts +206 -0
  69. package/dist/core/types.d.ts.map +1 -0
  70. package/dist/core/types.js +127 -0
  71. package/dist/core/types.js.map +1 -0
  72. package/dist/index.d.ts +7 -0
  73. package/dist/{cmd/index.d.ts.map → index.d.ts.map} +1 -1
  74. package/dist/index.js +472 -0
  75. package/dist/index.js.map +1 -0
  76. package/dist/mcp/client.d.ts +1 -1
  77. package/dist/mcp/client.d.ts.map +1 -1
  78. package/dist/repl/index.d.ts +8 -0
  79. package/dist/repl/index.d.ts.map +1 -0
  80. package/dist/repl/index.js +8 -0
  81. package/dist/repl/index.js.map +1 -0
  82. package/dist/repl/input.d.ts +69 -0
  83. package/dist/repl/input.d.ts.map +1 -0
  84. package/dist/repl/input.js +139 -0
  85. package/dist/repl/input.js.map +1 -0
  86. package/dist/repl/loop.d.ts +184 -0
  87. package/dist/repl/loop.d.ts.map +1 -0
  88. package/dist/repl/loop.js +932 -0
  89. package/dist/repl/loop.js.map +1 -0
  90. package/dist/repl/output.d.ts +105 -0
  91. package/dist/repl/output.d.ts.map +1 -0
  92. package/dist/repl/output.js +199 -0
  93. package/dist/repl/output.js.map +1 -0
  94. package/dist/repl/welcome.d.ts +68 -0
  95. package/dist/repl/welcome.d.ts.map +1 -0
  96. package/dist/repl/welcome.js +254 -0
  97. package/dist/repl/welcome.js.map +1 -0
  98. package/dist/tools/file.d.ts +1 -1
  99. package/dist/tools/file.d.ts.map +1 -1
  100. package/dist/tools/git.d.ts +1 -1
  101. package/dist/tools/git.d.ts.map +1 -1
  102. package/dist/tools/index.d.ts +15 -35
  103. package/dist/tools/index.d.ts.map +1 -1
  104. package/dist/tools/index.js +31 -80
  105. package/dist/tools/index.js.map +1 -1
  106. package/dist/tools/search.d.ts +1 -1
  107. package/dist/tools/search.d.ts.map +1 -1
  108. package/dist/tools/shell.d.ts +11 -4
  109. package/dist/tools/shell.d.ts.map +1 -1
  110. package/dist/tools/shell.js +78 -42
  111. package/dist/tools/shell.js.map +1 -1
  112. package/dist/utils/diff-preview.d.ts +42 -0
  113. package/dist/utils/diff-preview.d.ts.map +1 -0
  114. package/dist/utils/diff-preview.js +174 -0
  115. package/dist/utils/diff-preview.js.map +1 -0
  116. package/dist/utils/index.d.ts +8 -0
  117. package/dist/utils/index.d.ts.map +1 -0
  118. package/dist/utils/index.js +8 -0
  119. package/dist/utils/index.js.map +1 -0
  120. package/dist/utils/memory.d.ts +81 -0
  121. package/dist/utils/memory.d.ts.map +1 -0
  122. package/dist/utils/memory.js +186 -0
  123. package/dist/utils/memory.js.map +1 -0
  124. package/dist/utils/project-context.d.ts +62 -0
  125. package/dist/utils/project-context.d.ts.map +1 -0
  126. package/dist/utils/project-context.js +197 -0
  127. package/dist/utils/project-context.js.map +1 -0
  128. package/docs/API_KEY_GUIDE.md +6 -0
  129. package/docs/FAQ.md +25 -3
  130. package/package.json +41 -9
  131. package/ROADMAP.md +0 -61
  132. package/dist/app/agent.d.ts +0 -31
  133. package/dist/app/agent.d.ts.map +0 -1
  134. package/dist/app/agent.js +0 -106
  135. package/dist/app/agent.js.map +0 -1
  136. package/dist/app/index.d.ts +0 -39
  137. package/dist/app/index.d.ts.map +0 -1
  138. package/dist/app/index.js +0 -80
  139. package/dist/app/index.js.map +0 -1
  140. package/dist/cmd/index.d.ts +0 -12
  141. package/dist/cmd/index.js +0 -85
  142. package/dist/cmd/index.js.map +0 -1
  143. package/dist/config/index.d.ts +0 -39
  144. package/dist/config/index.d.ts.map +0 -1
  145. package/dist/config/index.js +0 -110
  146. package/dist/config/index.js.map +0 -1
  147. package/dist/config/providers.d.ts +0 -23
  148. package/dist/config/providers.d.ts.map +0 -1
  149. package/dist/config/providers.js +0 -152
  150. package/dist/config/providers.js.map +0 -1
  151. package/dist/config/types.d.ts +0 -33
  152. package/dist/config/types.d.ts.map +0 -1
  153. package/dist/config/types.js +0 -5
  154. package/dist/config/types.js.map +0 -1
  155. package/dist/hooks/index.d.ts +0 -31
  156. package/dist/hooks/index.d.ts.map +0 -1
  157. package/dist/hooks/index.js +0 -71
  158. package/dist/hooks/index.js.map +0 -1
  159. package/dist/llm/anthropic.d.ts +0 -17
  160. package/dist/llm/anthropic.d.ts.map +0 -1
  161. package/dist/llm/anthropic.js +0 -126
  162. package/dist/llm/anthropic.js.map +0 -1
  163. package/dist/llm/gemini.d.ts +0 -16
  164. package/dist/llm/gemini.d.ts.map +0 -1
  165. package/dist/llm/gemini.js +0 -107
  166. package/dist/llm/gemini.js.map +0 -1
  167. package/dist/llm/index.d.ts +0 -48
  168. package/dist/llm/index.d.ts.map +0 -1
  169. package/dist/llm/index.js +0 -174
  170. package/dist/llm/index.js.map +0 -1
  171. package/dist/llm/openai.d.ts +0 -17
  172. package/dist/llm/openai.d.ts.map +0 -1
  173. package/dist/llm/openai.js +0 -113
  174. package/dist/llm/openai.js.map +0 -1
  175. package/dist/llm/provider.d.ts +0 -33
  176. package/dist/llm/provider.d.ts.map +0 -1
  177. package/dist/llm/provider.js +0 -6
  178. package/dist/llm/provider.js.map +0 -1
  179. package/dist/prompts/index.d.ts +0 -27
  180. package/dist/prompts/index.d.ts.map +0 -1
  181. package/dist/prompts/index.js +0 -102
  182. package/dist/prompts/index.js.map +0 -1
  183. package/dist/session/compactor.d.ts +0 -48
  184. package/dist/session/compactor.d.ts.map +0 -1
  185. package/dist/session/compactor.js +0 -77
  186. package/dist/session/compactor.js.map +0 -1
  187. package/dist/session/index.d.ts +0 -48
  188. package/dist/session/index.d.ts.map +0 -1
  189. package/dist/session/index.js +0 -69
  190. package/dist/session/index.js.map +0 -1
  191. package/dist/session/message.d.ts +0 -26
  192. package/dist/session/message.d.ts.map +0 -1
  193. package/dist/session/message.js +0 -34
  194. package/dist/session/message.js.map +0 -1
  195. package/dist/tools/types.d.ts +0 -68
  196. package/dist/tools/types.d.ts.map +0 -1
  197. package/dist/tools/types.js +0 -57
  198. package/dist/tools/types.js.map +0 -1
  199. package/dist/tui/index.d.ts +0 -8
  200. package/dist/tui/index.d.ts.map +0 -1
  201. package/dist/tui/index.js +0 -8
  202. package/dist/tui/index.js.map +0 -1
  203. package/dist/tui/input.d.ts +0 -14
  204. package/dist/tui/input.d.ts.map +0 -1
  205. package/dist/tui/input.js +0 -50
  206. package/dist/tui/input.js.map +0 -1
  207. package/dist/tui/output.d.ts +0 -20
  208. package/dist/tui/output.d.ts.map +0 -1
  209. package/dist/tui/output.js +0 -70
  210. package/dist/tui/output.js.map +0 -1
  211. package/dist/tui/repl.d.ts +0 -25
  212. package/dist/tui/repl.d.ts.map +0 -1
  213. package/dist/tui/repl.js +0 -114
  214. package/dist/tui/repl.js.map +0 -1
  215. package/dist/tui/welcome.d.ts +0 -15
  216. package/dist/tui/welcome.d.ts.map +0 -1
  217. package/dist/tui/welcome.js +0 -86
  218. package/dist/tui/welcome.js.map +0 -1
  219. package/dist/types.d.ts +0 -38
  220. package/dist/types.d.ts.map +0 -1
  221. package/dist/types.js +0 -6
  222. package/dist/types.js.map +0 -1
  223. package/dist/utils/diff.d.ts +0 -22
  224. package/dist/utils/diff.d.ts.map +0 -1
  225. package/dist/utils/diff.js +0 -60
  226. package/dist/utils/diff.js.map +0 -1
  227. package/dist/utils/project.d.ts +0 -32
  228. package/dist/utils/project.d.ts.map +0 -1
  229. package/dist/utils/project.js +0 -89
  230. package/dist/utils/project.js.map +0 -1
  231. package/src/app/agent.ts +0 -140
  232. package/src/app/index.ts +0 -101
  233. package/src/cmd/index.ts +0 -96
  234. package/src/config/index.ts +0 -130
  235. package/src/config/providers.ts +0 -160
  236. package/src/config/types.ts +0 -46
  237. package/src/hooks/index.ts +0 -111
  238. package/src/llm/anthropic.ts +0 -146
  239. package/src/llm/gemini.ts +0 -127
  240. package/src/llm/index.ts +0 -209
  241. package/src/llm/openai.ts +0 -132
  242. package/src/llm/provider.ts +0 -38
  243. package/src/mcp/client.ts +0 -330
  244. package/src/prompts/index.ts +0 -123
  245. package/src/session/compactor.ts +0 -103
  246. package/src/session/index.ts +0 -81
  247. package/src/session/message.ts +0 -42
  248. package/src/tools/file.ts +0 -117
  249. package/src/tools/git.ts +0 -132
  250. package/src/tools/index.ts +0 -108
  251. package/src/tools/search.ts +0 -263
  252. package/src/tools/shell.ts +0 -136
  253. package/src/tools/types.ts +0 -122
  254. package/src/tui/index.ts +0 -8
  255. package/src/tui/input.ts +0 -56
  256. package/src/tui/output.ts +0 -83
  257. package/src/tui/repl.ts +0 -131
  258. package/src/tui/welcome.ts +0 -101
  259. package/src/types.ts +0 -42
  260. package/src/utils/diff.ts +0 -71
  261. package/src/utils/project.ts +0 -99
  262. package/tsconfig.json +0 -19
@@ -0,0 +1,162 @@
1
+ /**
2
+ * Config Manager - Handles configuration loading and saving
3
+ * Supports multiple AI providers: MiniMax, SiliconFlow, OpenAI, Anthropic
4
+ *
5
+ * 0.2.3: Tolerates unknown / legacy provider names (e.g. `custom_openai`
6
+ * from 1.0.4-era configs) — keeps the user's provider and falls back to
7
+ * deriving a sensible baseUrl from `CUSTOM_BASE_URL` / `BASE_URL` env vars
8
+ * or from the provider id itself, instead of silently overwriting the
9
+ * provider field. Also refreshes default model to current SiliconFlow
10
+ * recommendation (Qwen2.5-72B-Instruct instead of 7B).
11
+ */
12
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
13
+ import { join, dirname } from 'path';
14
+ import { homedir } from 'os';
15
+ import { PROVIDERS } from './types.js';
16
+ const DEFAULT_CONFIG = {
17
+ model: 'Qwen/Qwen2.5-72B-Instruct',
18
+ apiKey: '',
19
+ temperature: 0.7,
20
+ maxTokens: 4096,
21
+ provider: 'siliconflow',
22
+ baseUrl: 'https://api.siliconflow.cn/v1'
23
+ };
24
+ export class ConfigManager {
25
+ configPath;
26
+ constructor() {
27
+ const homeDir = homedir();
28
+ const configDir = join(homeDir, '.thatgfsj');
29
+ this.configPath = join(configDir, 'config.json');
30
+ }
31
+ /**
32
+ * Load configuration with environment variable support
33
+ */
34
+ static async load() {
35
+ const manager = new ConfigManager();
36
+ return manager.loadConfig();
37
+ }
38
+ /**
39
+ * Load config from file and merge with environment variables
40
+ */
41
+ loadConfig() {
42
+ let config = { ...DEFAULT_CONFIG };
43
+ try {
44
+ if (existsSync(this.configPath)) {
45
+ const data = readFileSync(this.configPath, 'utf-8');
46
+ const loaded = JSON.parse(data);
47
+ config = { ...config, ...loaded };
48
+ }
49
+ }
50
+ catch (error) {
51
+ console.warn('Failed to load config, using defaults:', error);
52
+ }
53
+ // Resolve provider and get API key from environment
54
+ config = this.resolveProvider(config);
55
+ return config;
56
+ }
57
+ /**
58
+ * Resolve provider and API key from config or environment
59
+ *
60
+ * 0.2.3: tolerate unknown / legacy provider names (e.g. `custom_openai`).
61
+ * - If the provider is in `PROVIDERS`, use its baseUrl + envKeys.
62
+ * - Otherwise keep the user-specified provider id exactly and try to
63
+ * derive a baseUrl from `CUSTOM_BASE_URL` / `BASE_URL` env vars; if
64
+ * neither is set, fall back to the OpenAI-compatible default
65
+ * (https://api.openai.com/v1) so the request still goes through.
66
+ * No silent overwrite of `provider`.
67
+ * - When API key is missing for an unknown provider, we still keep the
68
+ * provider and surface a non-fatal warning.
69
+ */
70
+ resolveProvider(config) {
71
+ const provider = config.provider || 'siliconflow';
72
+ const providerConfig = PROVIDERS[provider];
73
+ if (!providerConfig) {
74
+ // Unknown / legacy / custom provider — keep what the user wrote, but
75
+ // resolve a reasonable baseUrl.
76
+ const envBase = process.env.CUSTOM_BASE_URL || process.env.BASE_URL;
77
+ const baseUrl = config.baseUrl ||
78
+ envBase ||
79
+ // Last-resort: OpenAI-compatible default. The user can override via
80
+ // `gfcode init` or by editing ~/.thatgfsj/config.json.
81
+ 'https://api.openai.com/v1';
82
+ const apiKey = config.apiKey ||
83
+ process.env.OPENAI_API_KEY ||
84
+ process.env.CUSTOM_API_KEY ||
85
+ '';
86
+ // Model: keep user choice, otherwise fall back to env MODEL, otherwise
87
+ // a benign default. No warning spam — this is an expected path.
88
+ const model = config.model || process.env.MODEL || 'gpt-4o-mini';
89
+ return {
90
+ ...config,
91
+ provider: provider,
92
+ baseUrl,
93
+ model,
94
+ apiKey,
95
+ };
96
+ }
97
+ // Known provider
98
+ let model = process.env.MODEL || config.model || providerConfig.defaultModel;
99
+ let apiKey = config.apiKey;
100
+ let baseUrl = config.baseUrl || providerConfig.baseUrl;
101
+ if (provider === 'ollama') {
102
+ apiKey = '';
103
+ baseUrl = process.env.OLLAMA_BASE_URL || baseUrl;
104
+ }
105
+ else {
106
+ for (const envKey of providerConfig.envKeys) {
107
+ if (process.env[envKey]) {
108
+ apiKey = process.env[envKey];
109
+ break;
110
+ }
111
+ }
112
+ }
113
+ return {
114
+ ...config,
115
+ provider,
116
+ baseUrl,
117
+ model,
118
+ apiKey: apiKey || ''
119
+ };
120
+ }
121
+ /**
122
+ * Get AIConfig for AIEngine
123
+ */
124
+ static async getAIConfig() {
125
+ const config = await ConfigManager.load();
126
+ return {
127
+ model: config.model,
128
+ apiKey: config.apiKey,
129
+ temperature: config.temperature,
130
+ maxTokens: config.maxTokens,
131
+ baseUrl: config.baseUrl,
132
+ provider: config.provider
133
+ };
134
+ }
135
+ /**
136
+ * Save configuration
137
+ */
138
+ async save(config) {
139
+ const current = this.loadConfig();
140
+ const merged = { ...current, ...config };
141
+ const dir = dirname(this.configPath);
142
+ if (!existsSync(dir)) {
143
+ mkdirSync(dir, { recursive: true });
144
+ }
145
+ writeFileSync(this.configPath, JSON.stringify(merged, null, 2));
146
+ }
147
+ /**
148
+ * Get config path
149
+ */
150
+ getPath() {
151
+ return this.configPath;
152
+ }
153
+ /**
154
+ * List available providers
155
+ */
156
+ static listProviders() {
157
+ return Object.entries(PROVIDERS)
158
+ .map(([key, val]) => ` ${key}: ${val.name}`)
159
+ .join('\n');
160
+ }
161
+ }
162
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/core/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * S05: Context Compactor - 3-tier progressive compression strategy
3
+ *
4
+ * Tier 1: Truncation - Remove oldest messages beyond hard limit
5
+ * Tier 2: Summarize - Replace old message ranges with summary
6
+ * Tier 3: Selective - Keep only recent N turns around tool executions
7
+ *
8
+ * Core insight: Context window is finite. Three-tier strategy enables "infinite" work.
9
+ */
10
+ import { ChatMessage } from './types.js';
11
+ export interface CompactorConfig {
12
+ /** Hard limit: messages beyond this get removed first */
13
+ hardLimit?: number;
14
+ /** Soft limit: messages beyond this get summarized */
15
+ softLimit?: number;
16
+ /** How many recent turns to always preserve */
17
+ preserveRecent?: number;
18
+ /** Enable summarization (requires AI API) */
19
+ enableSummarize?: boolean;
20
+ }
21
+ export interface CompressionResult {
22
+ originalCount: number;
23
+ compactedCount: number;
24
+ removedCount: number;
25
+ method: 'none' | 'truncation' | 'summarize' | 'selective';
26
+ }
27
+ /**
28
+ * S05: Context Compactor
29
+ */
30
+ export declare class ContextCompactor {
31
+ private config;
32
+ private totalTokensUsed;
33
+ constructor(config?: CompactorConfig);
34
+ /**
35
+ * S05: Compact messages using 3-tier strategy
36
+ * Called at the end of each agent turn
37
+ */
38
+ compact(messages: ChatMessage[]): {
39
+ compacted: ChatMessage[];
40
+ result: CompressionResult;
41
+ };
42
+ private truncate;
43
+ private summarizeStrategy;
44
+ private summarizeTopic;
45
+ private selectiveStrategy;
46
+ isNearLimit(messages: ChatMessage[], limit?: number): boolean;
47
+ estimateTokens(messages: ChatMessage[]): number;
48
+ autoCompact(messages: ChatMessage[]): {
49
+ compacted: ChatMessage[];
50
+ result: CompressionResult;
51
+ };
52
+ }
53
+ export declare function integrateWithSessionManager(SessionManagerClass: any): void;
54
+ //# sourceMappingURL=context-compactor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context-compactor.d.ts","sourceRoot":"","sources":["../../src/core/context-compactor.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAEzC,MAAM,WAAW,eAAe;IAC9B,yDAAyD;IACzD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sDAAsD;IACtD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,+CAA+C;IAC/C,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,6CAA6C;IAC7C,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,MAAM,WAAW,iBAAiB;IAChC,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,GAAG,YAAY,GAAG,WAAW,GAAG,WAAW,CAAC;CAC3D;AAED;;GAEG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,MAAM,CAAkB;IAChC,OAAO,CAAC,eAAe,CAAK;gBAEhB,MAAM,GAAE,eAAoB;IAUxC;;;OAGG;IACH,OAAO,CAAC,QAAQ,EAAE,WAAW,EAAE,GAAG;QAAE,SAAS,EAAE,WAAW,EAAE,CAAC;QAAC,MAAM,EAAE,iBAAiB,CAAA;KAAE;IAqBzF,OAAO,CAAC,QAAQ;IAehB,OAAO,CAAC,iBAAiB;IAuCzB,OAAO,CAAC,cAAc;IAWtB,OAAO,CAAC,iBAAiB;IAiEzB,WAAW,CAAC,QAAQ,EAAE,WAAW,EAAE,EAAE,KAAK,GAAE,MAAe,GAAG,OAAO;IAOrE,cAAc,CAAC,QAAQ,EAAE,WAAW,EAAE,GAAG,MAAM;IAQ/C,WAAW,CAAC,QAAQ,EAAE,WAAW,EAAE,GAAG;QAAE,SAAS,EAAE,WAAW,EAAE,CAAC;QAAC,MAAM,EAAE,iBAAiB,CAAA;KAAE;CAS9F;AAID,wBAAgB,2BAA2B,CAAC,mBAAmB,EAAE,GAAG,QAcnE"}
@@ -0,0 +1,197 @@
1
+ /**
2
+ * S05: Context Compactor - 3-tier progressive compression strategy
3
+ *
4
+ * Tier 1: Truncation - Remove oldest messages beyond hard limit
5
+ * Tier 2: Summarize - Replace old message ranges with summary
6
+ * Tier 3: Selective - Keep only recent N turns around tool executions
7
+ *
8
+ * Core insight: Context window is finite. Three-tier strategy enables "infinite" work.
9
+ */
10
+ /**
11
+ * S05: Context Compactor
12
+ */
13
+ export class ContextCompactor {
14
+ config;
15
+ totalTokensUsed = 0;
16
+ constructor(config = {}) {
17
+ this.config = {
18
+ hardLimit: 50,
19
+ softLimit: 30,
20
+ preserveRecent: 10,
21
+ enableSummarize: false,
22
+ ...config
23
+ };
24
+ }
25
+ /**
26
+ * S05: Compact messages using 3-tier strategy
27
+ * Called at the end of each agent turn
28
+ */
29
+ compact(messages) {
30
+ const count = messages.length;
31
+ // Tier 1: Within hard limit — no compression
32
+ if (count <= (this.config.hardLimit || 50)) {
33
+ return {
34
+ compacted: messages,
35
+ result: { originalCount: count, compactedCount: count, removedCount: 0, method: 'none' }
36
+ };
37
+ }
38
+ // Tier 2: Beyond soft limit — summarize middle messages
39
+ if (count > (this.config.softLimit || 30)) {
40
+ return this.summarizeStrategy(messages);
41
+ }
42
+ // Tier 3: Between soft and hard — selective preservation
43
+ return this.selectiveStrategy(messages);
44
+ }
45
+ // Tier 1: Simple truncation
46
+ truncate(messages, maxMessages) {
47
+ const systemMsg = messages.find(m => m.role === 'system');
48
+ const others = messages.filter(m => m.role !== 'system');
49
+ const toRemove = others.length - maxMessages;
50
+ if (toRemove <= 0 || others.length === 0) {
51
+ return messages;
52
+ }
53
+ // Remove oldest non-system messages
54
+ const keptOthers = others.slice(toRemove);
55
+ return systemMsg ? [systemMsg, ...keptOthers] : keptOthers;
56
+ }
57
+ // Tier 2: Summarize middle messages (stub — needs AI for real summarization)
58
+ summarizeStrategy(messages) {
59
+ const preserved = this.config.preserveRecent || 10;
60
+ const systemMsg = messages.find(m => m.role === 'system');
61
+ const others = messages.filter(m => m.role !== 'system');
62
+ if (others.length <= preserved) {
63
+ return {
64
+ compacted: messages,
65
+ result: { originalCount: messages.length, compactedCount: messages.length, removedCount: 0, method: 'none' }
66
+ };
67
+ }
68
+ // Keep recent N turns
69
+ const recent = others.slice(-preserved);
70
+ // Summarize the middle (this is a placeholder — real impl would call AI)
71
+ const summaryMsg = {
72
+ role: 'system',
73
+ content: '[S05: ' + (others.length - preserved) + ' earlier messages summarized — ' +
74
+ 'conversation had ' + (others.length - preserved) + ' exchanges covering ' +
75
+ this.summarizeTopic(others.slice(0, -preserved)) + ']'
76
+ };
77
+ const compacted = systemMsg
78
+ ? [systemMsg, summaryMsg, ...recent]
79
+ : [summaryMsg, ...recent];
80
+ return {
81
+ compacted,
82
+ result: {
83
+ originalCount: messages.length,
84
+ compactedCount: compacted.length,
85
+ removedCount: messages.length - compacted.length,
86
+ method: 'summarize'
87
+ }
88
+ };
89
+ }
90
+ // Extract simple topic from message content
91
+ summarizeTopic(msgs) {
92
+ const texts = msgs
93
+ .filter(m => m.role === 'user')
94
+ .map(m => m.content)
95
+ .slice(0, 3)
96
+ .join(' ')
97
+ .substring(0, 50);
98
+ return texts || 'various topics';
99
+ }
100
+ // Tier 3: Selective — keep turns around tool executions
101
+ selectiveStrategy(messages) {
102
+ const systemMsg = messages.find(m => m.role === 'system');
103
+ const others = messages.filter(m => m.role !== 'system');
104
+ const max = this.config.softLimit || 30;
105
+ if (others.length <= max) {
106
+ return {
107
+ compacted: messages,
108
+ result: { originalCount: messages.length, compactedCount: messages.length, removedCount: 0, method: 'none' }
109
+ };
110
+ }
111
+ // Find indices of tool-related messages
112
+ const toolIndices = new Set();
113
+ others.forEach((m, i) => {
114
+ if (m.tool_calls || m.role === 'tool' || m.role === 'assistant') {
115
+ toolIndices.add(i);
116
+ }
117
+ });
118
+ // Also add indices around tool messages
119
+ for (const idx of toolIndices) {
120
+ for (let offset = -2; offset <= 2; offset++) {
121
+ toolIndices.add(idx + offset);
122
+ }
123
+ }
124
+ // Keep system message + messages near tool executions + recent messages
125
+ const recentCount = Math.floor(max * 0.3); // 30% recent
126
+ const toolCount = max - recentCount;
127
+ const recent = others.slice(-recentCount);
128
+ const toolMsgs = [];
129
+ for (const idx of [...toolIndices].sort((a, b) => a - b)) {
130
+ if (toolMsgs.length >= toolCount)
131
+ break;
132
+ if (idx >= 0 && idx < others.length - recentCount) {
133
+ toolMsgs.push(others[idx]);
134
+ }
135
+ }
136
+ const compacted = systemMsg
137
+ ? [systemMsg, ...toolMsgs, ...recent]
138
+ : [...toolMsgs, ...recent];
139
+ // Deduplicate while preserving order
140
+ const seen = new Set();
141
+ const deduped = compacted.filter(m => {
142
+ const key = m.role + m.content.substring(0, 30);
143
+ if (seen.has(key))
144
+ return false;
145
+ seen.add(key);
146
+ return true;
147
+ });
148
+ return {
149
+ compacted: deduped,
150
+ result: {
151
+ originalCount: messages.length,
152
+ compactedCount: deduped.length,
153
+ removedCount: messages.length - deduped.length,
154
+ method: 'selective'
155
+ }
156
+ };
157
+ }
158
+ // S05: Check if context is approaching limit
159
+ isNearLimit(messages, limit = 150000) {
160
+ // Rough estimate: 1 message ~= 200 tokens on average
161
+ const estimatedTokens = messages.length * 200;
162
+ return estimatedTokens > limit * 0.8; // 80% of limit
163
+ }
164
+ // S05: Get estimated token count
165
+ estimateTokens(messages) {
166
+ return messages.reduce((sum, m) => {
167
+ // Rough: content chars / 4 tokens
168
+ return sum + Math.ceil(m.content.length / 4) + 10; // +10 for role/overhead
169
+ }, 0);
170
+ }
171
+ // S05: Auto-compact if needed (called from AIEngine or SessionManager)
172
+ autoCompact(messages) {
173
+ if (this.isNearLimit(messages)) {
174
+ return this.compact(messages);
175
+ }
176
+ return {
177
+ compacted: messages,
178
+ result: { originalCount: messages.length, compactedCount: messages.length, removedCount: 0, method: 'none' }
179
+ };
180
+ }
181
+ }
182
+ // ==================== SessionManager Integration ====================
183
+ export function integrateWithSessionManager(SessionManagerClass) {
184
+ const originalTruncate = SessionManagerClass.prototype.truncate;
185
+ SessionManagerClass.prototype.truncate = function (maxMessages, forceCompact = false) {
186
+ if (forceCompact) {
187
+ const compactor = new ContextCompactor({ hardLimit: maxMessages });
188
+ const { compacted, result } = compactor.autoCompact(this.messages);
189
+ if (result.method !== 'none') {
190
+ this.messages = compacted;
191
+ return result;
192
+ }
193
+ }
194
+ return originalTruncate.call(this, maxMessages);
195
+ };
196
+ }
197
+ //# sourceMappingURL=context-compactor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context-compactor.js","sourceRoot":"","sources":["../../src/core/context-compactor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,75 @@
1
+ /**
2
+ * S08: Hooks System
3
+ * Inject custom logic at key event points without modifying agent source code
4
+ *
5
+ * Hook points: before/after tool calls, before/after agent loop, onError, etc.
6
+ * Users can register hooks for: audit logging, auto-formatting, security filtering, notifications
7
+ */
8
+ import { ChatMessage } from './types.js';
9
+ export type HookName = 'beforeToolCall' | 'afterToolCall' | 'beforeAgentLoop' | 'afterAgentLoop' | 'onError' | 'beforeMessage' | 'afterMessage' | 'onCompact';
10
+ export interface HookContext {
11
+ toolName?: string;
12
+ toolParams?: Record<string, any>;
13
+ toolResult?: any;
14
+ messages?: ChatMessage[];
15
+ error?: Error;
16
+ iteration?: number;
17
+ [key: string]: any;
18
+ }
19
+ export type HookFn = (ctx: HookContext) => void | Promise<void>;
20
+ export interface HookRegistration {
21
+ name: HookName;
22
+ fn: HookFn;
23
+ priority?: number;
24
+ once?: boolean;
25
+ }
26
+ export declare class HookManager {
27
+ private hooks;
28
+ constructor();
29
+ /**
30
+ * S08: Register a hook
31
+ * @param name Hook point name
32
+ * @param fn Function to run
33
+ * @param priority Higher priority runs first
34
+ * @param once If true, auto-removed after first run
35
+ */
36
+ register(name: HookName, fn: HookFn, priority?: number, once?: boolean): void;
37
+ /**
38
+ * S08: Unregister a hook by function reference
39
+ */
40
+ unregister(name: HookName, fn: HookFn): void;
41
+ /**
42
+ * S08: Clear all hooks for a given name (or all)
43
+ */
44
+ clear(name?: HookName): void;
45
+ /**
46
+ * S08: Emit a hook — call all registered functions at that point
47
+ * Returns after all sync hooks complete, awaits async ones
48
+ */
49
+ emit(name: HookName, ctx: HookContext): Promise<void>;
50
+ /**
51
+ * S08: Check if any hooks are registered for a given name
52
+ */
53
+ hasHooks(name: HookName): boolean;
54
+ /**
55
+ * S08: Get hook count
56
+ */
57
+ getHookCount(name?: HookName): number;
58
+ }
59
+ /**
60
+ * S08: Audit logging hook — logs all tool calls
61
+ */
62
+ export declare function auditLogHook(ctx: HookContext): void;
63
+ /**
64
+ * S08: Prettier auto-format hook — run prettier after file writes
65
+ */
66
+ export declare function prettierFormatHook(ctx: HookContext): Promise<void>;
67
+ /**
68
+ * S08: Security filter hook — block suspicious patterns
69
+ */
70
+ export declare function securityFilterHook(ctx: HookContext): {
71
+ blocked: boolean;
72
+ reason: string;
73
+ };
74
+ export declare function getHookManager(): HookManager;
75
+ //# sourceMappingURL=hooks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../../src/core/hooks.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,WAAW,EAAY,MAAM,YAAY,CAAC;AAInD,MAAM,MAAM,QAAQ,GAChB,gBAAgB,GAChB,eAAe,GACf,iBAAiB,GACjB,gBAAgB,GAChB,SAAS,GACT,eAAe,GACf,cAAc,GACd,WAAW,CAAC;AAEhB,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACjC,UAAU,CAAC,EAAE,GAAG,CAAC;IACjB,QAAQ,CAAC,EAAE,WAAW,EAAE,CAAC;IACzB,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;AAED,MAAM,MAAM,MAAM,GAAG,CAAC,GAAG,EAAE,WAAW,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAEhE,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,QAAQ,CAAC;IACf,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAID,qBAAa,WAAW;IACtB,OAAO,CAAC,KAAK,CAAgD;;IAa7D;;;;;;OAMG;IACH,QAAQ,CAAC,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,QAAQ,SAAI,EAAE,IAAI,UAAQ,GAAG,IAAI;IAOtE;;OAEG;IACH,UAAU,CAAC,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI;IAK5C;;OAEG;IACH,KAAK,CAAC,IAAI,CAAC,EAAE,QAAQ,GAAG,IAAI;IAU5B;;;OAGG;IACG,IAAI,CAAC,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAiB3D;;OAEG;IACH,QAAQ,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO;IAIjC;;OAEG;IACH,YAAY,CAAC,IAAI,CAAC,EAAE,QAAQ,GAAG,MAAM;CAUtC;AAID;;GAEG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,WAAW,GAAG,IAAI,CAMnD;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CAAC,GAAG,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAWxE;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,WAAW,GAAG;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAiBzF;AAMD,wBAAgB,cAAc,IAAI,WAAW,CAK5C"}
@@ -0,0 +1,146 @@
1
+ /**
2
+ * S08: Hooks System
3
+ * Inject custom logic at key event points without modifying agent source code
4
+ *
5
+ * Hook points: before/after tool calls, before/after agent loop, onError, etc.
6
+ * Users can register hooks for: audit logging, auto-formatting, security filtering, notifications
7
+ */
8
+ // ==================== Hook Manager ====================
9
+ export class HookManager {
10
+ hooks = new Map();
11
+ constructor() {
12
+ // Initialize all hook types
13
+ const allHooks = [
14
+ 'beforeToolCall', 'afterToolCall', 'beforeAgentLoop', 'afterAgentLoop',
15
+ 'onError', 'beforeMessage', 'afterMessage', 'onCompact'
16
+ ];
17
+ for (const h of allHooks) {
18
+ this.hooks.set(h, []);
19
+ }
20
+ }
21
+ /**
22
+ * S08: Register a hook
23
+ * @param name Hook point name
24
+ * @param fn Function to run
25
+ * @param priority Higher priority runs first
26
+ * @param once If true, auto-removed after first run
27
+ */
28
+ register(name, fn, priority = 0, once = false) {
29
+ const regs = this.hooks.get(name) || [];
30
+ regs.push({ name, fn, priority, once });
31
+ regs.sort((a, b) => (b.priority || 0) - (a.priority || 0));
32
+ this.hooks.set(name, regs);
33
+ }
34
+ /**
35
+ * S08: Unregister a hook by function reference
36
+ */
37
+ unregister(name, fn) {
38
+ const regs = this.hooks.get(name) || [];
39
+ this.hooks.set(name, regs.filter(r => r.fn !== fn));
40
+ }
41
+ /**
42
+ * S08: Clear all hooks for a given name (or all)
43
+ */
44
+ clear(name) {
45
+ if (name) {
46
+ this.hooks.set(name, []);
47
+ }
48
+ else {
49
+ for (const [, regs] of this.hooks) {
50
+ regs.length = 0;
51
+ }
52
+ }
53
+ }
54
+ /**
55
+ * S08: Emit a hook — call all registered functions at that point
56
+ * Returns after all sync hooks complete, awaits async ones
57
+ */
58
+ async emit(name, ctx) {
59
+ const regs = this.hooks.get(name) || [];
60
+ for (const reg of regs) {
61
+ try {
62
+ await Promise.resolve(reg.fn(ctx));
63
+ }
64
+ catch (error) {
65
+ console.error(`[Hook ${name}] error:`, error.message);
66
+ }
67
+ // Remove if one-time hook
68
+ if (reg.once) {
69
+ this.unregister(name, reg.fn);
70
+ }
71
+ }
72
+ }
73
+ /**
74
+ * S08: Check if any hooks are registered for a given name
75
+ */
76
+ hasHooks(name) {
77
+ return (this.hooks.get(name)?.length || 0) > 0;
78
+ }
79
+ /**
80
+ * S08: Get hook count
81
+ */
82
+ getHookCount(name) {
83
+ if (name) {
84
+ return this.hooks.get(name)?.length || 0;
85
+ }
86
+ let total = 0;
87
+ for (const regs of this.hooks.values()) {
88
+ total += regs.length;
89
+ }
90
+ return total;
91
+ }
92
+ }
93
+ // ==================== Built-in Hooks ====================
94
+ /**
95
+ * S08: Audit logging hook — logs all tool calls
96
+ */
97
+ export function auditLogHook(ctx) {
98
+ if (ctx.toolName) {
99
+ const ts = new Date().toISOString().split('T')[1].split('.')[0];
100
+ const status = ctx.toolResult?.success ? 'OK' : 'FAIL';
101
+ console.log(`[AUDIT ${ts}] ${status} | tool=${ctx.toolName} | params=${JSON.stringify(ctx.toolParams || {})} | result=${ctx.toolResult?.success ? 'OK' : ctx.toolResult?.error}`);
102
+ }
103
+ }
104
+ /**
105
+ * S08: Prettier auto-format hook — run prettier after file writes
106
+ */
107
+ export async function prettierFormatHook(ctx) {
108
+ if (ctx.toolName === 'file' && ctx.toolParams?.action === 'write') {
109
+ const path = ctx.toolParams.path;
110
+ const ext = path.split('.').pop()?.toLowerCase();
111
+ const formattable = ['js', 'ts', 'jsx', 'tsx', 'json', 'md', 'css', 'html'];
112
+ if (formattable.includes(ext || '')) {
113
+ console.log(`[Hook] Auto-format skipped (prettier not installed) for ${path}`);
114
+ // In real impl: spawn prettier on the file
115
+ }
116
+ }
117
+ }
118
+ /**
119
+ * S08: Security filter hook — block suspicious patterns
120
+ */
121
+ export function securityFilterHook(ctx) {
122
+ if (ctx.toolName === 'shell' && ctx.toolParams?.command) {
123
+ const cmd = ctx.toolParams.command;
124
+ const suspicious = [
125
+ /curl\s+.*\|.*sh/i,
126
+ /wget\s+.*\|.*sh/i,
127
+ /base64\s+-d/i,
128
+ /\.\/[^/]+\s+&&.*rm/i
129
+ ];
130
+ for (const pattern of suspicious) {
131
+ if (pattern.test(cmd)) {
132
+ return { blocked: true, reason: `Suspicious pattern detected: ${pattern}` };
133
+ }
134
+ }
135
+ }
136
+ return { blocked: false, reason: '' };
137
+ }
138
+ // ==================== Singleton ====================
139
+ let globalHookManager = null;
140
+ export function getHookManager() {
141
+ if (!globalHookManager) {
142
+ globalHookManager = new HookManager();
143
+ }
144
+ return globalHookManager;
145
+ }
146
+ //# sourceMappingURL=hooks.js.map