@kjerneverk/agentic 1.0.5-dev.0

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 (58) hide show
  1. package/.kodrdriv-test-cache.json +6 -0
  2. package/LICENSE +18 -0
  3. package/README.md +167 -0
  4. package/SECURITY.md +94 -0
  5. package/dist/index.d.ts +770 -0
  6. package/dist/index.js +1822 -0
  7. package/dist/index.js.map +1 -0
  8. package/output/kodrdriv/260110-1207-commit-message.md +1 -0
  9. package/output/kodrdriv/260110-1208-commit-message.md +1 -0
  10. package/output/kodrdriv/260110-1210-release-notes.md +27 -0
  11. package/output/kodrdriv/260110-1232-commit-message.md +1 -0
  12. package/output/kodrdriv/260110-1234-release-notes.md +46 -0
  13. package/output/kodrdriv/260110-1308-release-notes.md +20 -0
  14. package/output/kodrdriv/260112-2155-commit-message.md +1 -0
  15. package/output/kodrdriv/260112-2156-release-notes.md +33 -0
  16. package/output/kodrdriv/260112-2340-commit-message.md +1 -0
  17. package/output/kodrdriv/260113-0017-commit-message.md +1 -0
  18. package/output/kodrdriv/260113-0017-release-notes.md +53 -0
  19. package/output/kodrdriv/260115-0616-commit-message.md +4 -0
  20. package/output/kodrdriv/260115-0739-commit-message.md +1 -0
  21. package/output/kodrdriv/260115-0746-commit-message.md +1 -0
  22. package/output/kodrdriv/260115-0747-release-notes.md +91 -0
  23. package/output/kodrdriv/260126-0851-commit-message.md +1 -0
  24. package/output/kodrdriv/260128-0900-commit-message.md +1 -0
  25. package/output/kodrdriv/260128-0900-release-notes.md +29 -0
  26. package/output/kodrdriv/260128-0905-commit-message.md +1 -0
  27. package/output/kodrdriv/260128-0913-commit-message.md +1 -0
  28. package/output/kodrdriv/260128-0913-release-notes.md +23 -0
  29. package/output/kodrdriv/260128-0919-commit-message.md +1 -0
  30. package/output/kodrdriv/260128-0919-release-notes.md +21 -0
  31. package/output/kodrdriv/260130-1642-commit-message.md +1 -0
  32. package/output/kodrdriv/260130-1642-release-notes.md +29 -0
  33. package/output/kodrdriv/RELEASE_NOTES.md +27 -0
  34. package/output/kodrdriv/RELEASE_TITLE.md +1 -0
  35. package/output/kodrdriv/agentic-reflection-commit-2026-01-10T19-18-11-913Z.md +187 -0
  36. package/output/kodrdriv/agentic-reflection-commit-2026-01-10T20-03-27-409Z.md +121 -0
  37. package/output/kodrdriv/agentic-reflection-commit-2026-01-10T20-07-27-813Z.md +114 -0
  38. package/output/kodrdriv/agentic-reflection-commit-2026-01-10T20-08-06-279Z.md +97 -0
  39. package/output/kodrdriv/agentic-reflection-commit-2026-01-10T20-32-25-791Z.md +114 -0
  40. package/output/kodrdriv/agentic-reflection-commit-2026-01-13T05-55-30-733Z.md +97 -0
  41. package/output/kodrdriv/agentic-reflection-commit-2026-01-13T07-40-28-878Z.md +97 -0
  42. package/output/kodrdriv/agentic-reflection-commit-2026-01-13T08-17-22-230Z.md +114 -0
  43. package/output/kodrdriv/agentic-reflection-commit-2026-01-14T14-55-10-122Z.md +177 -0
  44. package/output/kodrdriv/agentic-reflection-commit-2026-01-15T14-16-05-605Z.md +100 -0
  45. package/output/kodrdriv/agentic-reflection-commit-2026-01-15T15-39-32-200Z.md +97 -0
  46. package/output/kodrdriv/agentic-reflection-commit-2026-01-15T15-46-32-840Z.md +114 -0
  47. package/output/kodrdriv/agentic-reflection-commit-2026-01-26T16-51-00-446Z.md +152 -0
  48. package/output/kodrdriv/agentic-reflection-commit-2026-01-31T00-38-59-868Z.md +236 -0
  49. package/output/kodrdriv/agentic-reflection-commit-2026-01-31T00-42-31-993Z.md +152 -0
  50. package/output/kodrdriv/agentic-reflection-release-2026-01-10T20-10-49-531Z.md +329 -0
  51. package/output/kodrdriv/agentic-reflection-release-2026-01-10T20-34-32-038Z.md +319 -0
  52. package/output/kodrdriv/agentic-reflection-release-2026-01-10T21-08-36-314Z.md +168 -0
  53. package/output/kodrdriv/agentic-reflection-release-2026-01-13T05-56-04-802Z.md +264 -0
  54. package/output/kodrdriv/agentic-reflection-release-2026-01-13T08-17-56-556Z.md +255 -0
  55. package/output/kodrdriv/agentic-reflection-release-2026-01-15T15-47-32-509Z.md +460 -0
  56. package/output/kodrdriv/agentic-reflection-release-2026-01-31T00-42-59-683Z.md +400 -0
  57. package/package.json +59 -0
  58. package/package.json~ +58 -0
@@ -0,0 +1,460 @@
1
+ # Agentic Workflow - Self-Reflection Report
2
+
3
+ Generated: 2026-01-15T15:47:32.509Z
4
+
5
+ ## Execution Summary
6
+
7
+ - **Iterations**: 3
8
+ - **Tool Calls**: 19
9
+ - **Unique Tools**: 8
10
+
11
+ ## Tool Effectiveness Analysis
12
+
13
+ | Tool | Calls | Success | Failures | Success Rate | Avg Duration |
14
+ |------|-------|---------|----------|--------------|--------------|
15
+ | get_file_content | 11 | 11 | 0 | 100.0% | 0ms |
16
+ | search_codebase | 2 | 2 | 0 | 100.0% | 10ms |
17
+ | get_breaking_changes | 1 | 1 | 0 | 100.0% | 43ms |
18
+ | get_release_stats | 1 | 1 | 0 | 100.0% | 38ms |
19
+ | analyze_commit_patterns | 1 | 1 | 0 | 100.0% | 9ms |
20
+ | get_related_tests | 1 | 1 | 0 | 100.0% | 1ms |
21
+ | get_file_dependencies | 1 | 1 | 0 | 100.0% | 53ms |
22
+ | group_files_by_concern | 1 | 1 | 0 | 100.0% | 0ms |
23
+
24
+ ### Tool Performance Insights
25
+
26
+ **Most Frequently Used:**
27
+ - get_breaking_changes: 1 calls
28
+ - get_release_stats: 1 calls
29
+ - analyze_commit_patterns: 1 calls
30
+
31
+ ## Recommendations for Improvement
32
+
33
+ - **Underutilized Tools**: Many tools were called only once. Consider whether all tools are necessary or if the agent needs better guidance on when to use them.
34
+
35
+ ## Detailed Execution Timeline
36
+
37
+ | Time | Iteration | Tool | Result | Duration |
38
+ |------|-----------|------|--------|----------|
39
+ | 7:46:43 AM | 1 | get_breaking_changes | ✅ Success | 43ms |
40
+ | 7:46:43 AM | 1 | get_release_stats | ✅ Success | 38ms |
41
+ | 7:46:43 AM | 1 | analyze_commit_patterns | ✅ Success | 9ms |
42
+ | 7:46:43 AM | 1 | search_codebase | ✅ Success | 10ms |
43
+ | 7:46:43 AM | 1 | search_codebase | ✅ Success | 9ms |
44
+ | 7:46:43 AM | 1 | get_file_content | ✅ Success | 0ms |
45
+ | 7:46:43 AM | 1 | get_file_content | ✅ Success | 0ms |
46
+ | 7:46:43 AM | 1 | get_file_content | ✅ Success | 1ms |
47
+ | 7:46:43 AM | 1 | get_file_content | ✅ Success | 0ms |
48
+ | 7:46:43 AM | 1 | get_file_content | ✅ Success | 0ms |
49
+ | 7:46:43 AM | 1 | get_file_content | ✅ Success | 0ms |
50
+ | 7:46:43 AM | 1 | get_related_tests | ✅ Success | 1ms |
51
+ | 7:46:44 AM | 1 | get_file_dependencies | ✅ Success | 53ms |
52
+ | 7:46:44 AM | 1 | group_files_by_concern | ✅ Success | 0ms |
53
+ | 7:46:57 AM | 2 | get_file_content | ✅ Success | 0ms |
54
+ | 7:46:57 AM | 2 | get_file_content | ✅ Success | 0ms |
55
+ | 7:46:57 AM | 2 | get_file_content | ✅ Success | 0ms |
56
+ | 7:46:57 AM | 2 | get_file_content | ✅ Success | 1ms |
57
+ | 7:46:57 AM | 2 | get_file_content | ✅ Success | 0ms |
58
+
59
+ ## Conversation History
60
+
61
+ <details>
62
+ <summary>Click to expand full agentic interaction</summary>
63
+
64
+ ```json
65
+ [
66
+ {
67
+ "role": "system",
68
+ "content": "You are a professional software engineer writing release notes for your team and users.\n\n## Available Tools\n\n### General\n\n**get_file_history**\nGet git commit history for one or more files to understand their evolution and past changes\n\nParameters:\n- `filePaths` (required): Array of file paths to get history for\n- `limit`: Maximum number of commits to return (default: 10)\n- `format`: Output format: \"summary\" for brief or \"detailed\" for full messages\n\n**When to use:** Get git commit history for one or more files to understand their evolution and past changes\n\n---\n\n**get_file_content**\nGet the complete current content of a file to understand context around changes. Returns a message if the file does not exist (e.g., if it was deleted).\n\nParameters:\n- `filePath` (required): Path to the file\n- `includeLineNumbers`: Include line numbers in output (default: false)\n\n**When to use:** Get the complete current content of a file to understand context around changes. Returns a message if the file does not exist (e.g., if it was deleted).\n\n---\n\n**search_codebase**\nSearch for code patterns, function names, or text across the codebase using git grep\n\nParameters:\n- `query` (required): Search pattern (can be plain text or regex)\n- `fileTypes`: Limit search to specific file extensions (e.g., [\"ts\", \"js\"])\n- `contextLines`: Number of context lines to show around matches (default: 2)\n\n**When to use:** Search for code patterns, function names, or text across the codebase using git grep\n\n---\n\n**get_related_tests**\nFind test files related to production files to understand what the code is supposed to do\n\nParameters:\n- `filePaths` (required): Production file paths\n\n**When to use:** Find test files related to production files to understand what the code is supposed to do\n\n---\n\n**get_file_dependencies**\nFind which files import or depend on the changed files to assess change impact\n\nParameters:\n- `filePaths` (required): Files to analyze dependencies for\n\n**When to use:** Find which files import or depend on the changed files to assess change impact\n\n---\n\n**analyze_diff_section**\nGet expanded context around specific lines in a file to better understand changes\n\nParameters:\n- `filePath` (required): File containing the section to analyze\n- `startLine` (required): Starting line number\n- `endLine` (required): Ending line number\n- `contextLines`: Number of additional context lines to show before and after (default: 10)\n\n**When to use:** Get expanded context around specific lines in a file to better understand changes\n\n---\n\n**get_recent_commits**\nGet recent commits that modified the same files to understand recent work in this area\n\nParameters:\n- `filePaths` (required): Files to check for recent commits\n- `since`: Time period to look back (e.g., \"1 week ago\", \"2 days ago\")\n- `limit`: Maximum number of commits to return (default: 5)\n\n**When to use:** Get recent commits that modified the same files to understand recent work in this area\n\n---\n\n**group_files_by_concern**\nAnalyze changed files and suggest logical groupings that might represent separate concerns\n\nParameters:\n- `filePaths` (required): All changed files to analyze\n\n**When to use:** Analyze changed files and suggest logical groupings that might represent separate concerns\n\n---\n\n### Release Analysis\n\n**get_tag_history** _(cheap)_\nGet the history of previous release tags to understand release patterns and versioning\n\nParameters:\n- `limit`: Number of recent tags to retrieve (default: 10)\n- `pattern`: Filter tags by pattern (e.g., \"v*\" for version tags)\n\n**When to use:** Get the history of previous release tags to understand release patterns and versioning\n\n**Examples:**\n- Check recent version tags: `get_tag_history({\"limit\":5,\"pattern\":\"v*\"})`\n\n---\n\n**compare_previous_release** _(moderate)_\nCompare this release with a previous release to understand what changed between versions\n\nParameters:\n- `previousTag` (required): Previous release tag to compare against\n- `currentRef`: Current reference (default: HEAD)\n- `statsOnly`: Return only statistics, not full diff (default: true)\n\n**When to use:** Compare this release with a previous release to understand what changed between versions\n\n**Examples:**\n- Compare with last release: `compare_previous_release({\"previousTag\":\"v1.0.0\",\"currentRef\":\"HEAD\"})`\n\n---\n\n**get_release_stats** _(moderate)_\nGet comprehensive statistics about the release including contributors, file changes, and commit patterns\n\nParameters:\n- `fromRef` (required): Starting reference for the release range\n- `toRef`: Ending reference for the release range (default: HEAD)\n\n**When to use:** Get comprehensive statistics about the release including contributors, file changes, and commit patterns\n\n**Examples:**\n- Get release overview: `get_release_stats({\"fromRef\":\"v1.0.0\",\"toRef\":\"HEAD\"})`\n\n---\n\n**get_breaking_changes** _(expensive)_\nSearch for potential breaking changes by looking for specific patterns in commits and diffs\n\nParameters:\n- `fromRef` (required): Starting reference for the release range\n- `toRef`: Ending reference for the release range (default: HEAD)\n\n**When to use:** Search for potential breaking changes by looking for specific patterns in commits and diffs\n\n**Examples:**\n- Check for breaking changes: `get_breaking_changes({\"fromRef\":\"v1.0.0\",\"toRef\":\"HEAD\"})`\n\n---\n\n**analyze_commit_patterns** _(cheap)_\nAnalyze commit messages to identify patterns and themes in the release\n\nParameters:\n- `fromRef` (required): Starting reference for the release range\n- `toRef`: Ending reference for the release range (default: HEAD)\n\n**When to use:** Analyze commit messages to identify patterns and themes in the release\n\n**Examples:**\n- Find commit themes: `analyze_commit_patterns({\"fromRef\":\"v1.0.0\",\"toRef\":\"HEAD\"})`\n\n---\n\n\n\n## Your Task\n\nWrite release notes that clearly explain what's in this release and why it matters. Your notes should help users and developers understand what changed without needing to read every commit.\n\nFocus on:\n- What problems does this release solve?\n- What new capabilities does it add?\n- What's the impact on users and developers?\n- Are there breaking changes or important considerations?\n\nUse the available tools to investigate the changes. The more you understand, the better your notes will be.\n\n**Important**: If additional context is provided (from context files or other sources), use your judgment:\n- If the context is relevant to this specific package/release, incorporate it appropriately\n- If the context describes changes in other packages or unrelated work, ignore it\n- Don't fabricate connections between this package and unrelated context\n- Be honest about what changed - only mention what actually happened in this release\n- Context is supplemental information, not a requirement to include\n\n## Investigation Approach\n\nUse tools based on what you need to know:\n\n**Context:**\n- get_tag_history - understand release patterns\n- get_release_stats - quantify scope\n- compare_previous_release - see how this compares\n\n**Understanding:**\n- group_files_by_concern - identify themes\n- analyze_commit_patterns - detect patterns\n- get_file_content - see full context\n- analyze_diff_section - expand unclear changes\n- get_file_history - understand evolution\n\n**Impact:**\n- get_file_dependencies - assess reach\n- search_codebase - find usage patterns\n- get_related_tests - understand behavior changes\n- get_breaking_changes - identify breaking changes (always use)\n\n## Writing Style\n\nWrite naturally and directly:\n- Use plain language that users can understand\n- Be specific about what changed and why\n- Avoid marketing speak and buzzwords\n- No emojis or excessive punctuation\n- No phrases like \"we're excited to announce\"\n- No meta-commentary about the release itself\n- Focus on facts and implications, not enthusiasm\n\nStructure your notes logically:\n- Start with the most important changes\n- Group related changes together\n- Explain breaking changes clearly\n- Include practical examples when helpful\n\n## Output Format\n\nWhen ready, format your response as JSON:\n\nRELEASE_NOTES:\n{\n \"title\": \"Clear, factual title describing the main changes\",\n \"body\": \"Detailed release notes in Markdown format\"\n}\n\nOutput only the JSON. No conversational remarks or follow-up offers."
69
+ },
70
+ {
71
+ "role": "user",
72
+ "content": "I need comprehensive release notes for changes from main to HEAD.\n\n## Commit Log\ncommit eb25beadf6b0d44a8dd75b0cc8bf7331ffa5ba0b\nAuthor: Tim O'Brien <tobrien@discursive.com>\nDate: Thu Jan 15 07:46:32 2026 -0800\n\n chore(release): bump version to 1.0.0\n\ncommit dbbad0e6d05b776e6239a018293366c458294b78\nAuthor: Tim O'Brien <tobrien@discursive.com>\nDate: Thu Jan 15 07:39:32 2026 -0800\n\n chore(git-hooks): run build in precommit script\n\ncommit 41c0890ac6e9ad78d158bac9c7e69873bc95709c\nAuthor: Tim O'Brien <tobrien@discursive.com>\nDate: Thu Jan 15 06:16:05 2026 -0800\n\n chore(release): reset package version to 1.0.0-dev.0\n \n - Add `_versionReset` marker in package.json\n - Add v1.0.0 marker comment to README\n\n\n## Diff Summary\ndiff --git a/README.md b/README.md\nindex f6a5308..6d6889d 100644\n--- a/README.md\n+++ b/README.md\n@@ -163,3 +163,5 @@ interface DynamicContentItem {\n ## License\n \n Apache-2.0\n+\n+<!-- v1.0.0 -->\ndiff --git a/SECURITY.md b/SECURITY.md\nnew file mode 100644\nindex 0000000..2fd4bf2\n--- /dev/null\n+++ b/SECURITY.md\n@@ -0,0 +1,94 @@\n+# Security Policy\n+\n+## Supported Versions\n+\n+| Version | Supported |\n+| ------- | ------------------ |\n+| 0.x.x | :white_check_mark: |\n+\n+## Reporting a Vulnerability\n+\n+If you discover a security vulnerability, please report it responsibly.\n+\n+**Do not open a public GitHub issue for security vulnerabilities.**\n+\n+## Security Features\n+\n+The agentic package includes security features for tool execution:\n+\n+### Tool Validation (ToolGuard)\n+\n+```typescript\n+import { ToolGuard } from '@theunwalked/agentic';\n+import { z } from 'zod';\n+\n+const guard = new ToolGuard({\n+ enabled: true,\n+ validateParams: true,\n+ detectPrototypePollution: true,\n+});\n+\n+// Register tool schemas\n+guard.registerSchema('read_file', z.object({\n+ path: z.string().max(1000),\n+ encoding: z.enum(['utf8', 'base64']).optional(),\n+}));\n+\n+// Validate before execution\n+const result = guard.validate('read_file', params);\n+if (!result.valid) {\n+ // Handle validation failure\n+}\n+```\n+\n+### Tool Sandboxing (ToolSandbox)\n+\n+```typescript\n+import { ToolSandbox } from '@theunwalked/agentic';\n+\n+const sandbox = new ToolSandbox({\n+ enabled: true,\n+ maxExecutionTime: 30000,\n+ maxConcurrent: 5,\n+ maxOutputSize: 1024 * 1024, // 1MB\n+});\n+\n+// Execute with sandboxing\n+const result = await sandbox.execute(\n+ 'my_tool',\n+ async (signal) => {\n+ // Tool implementation\n+ // Check signal.aborted for cancellation\n+ }\n+);\n+```\n+\n+### Security Features\n+\n+- **Parameter Validation**: Zod schema validation for all tool parameters\n+- **Prototype Pollution Prevention**: Blocks `__proto__`, `constructor`, `prototype`\n+- **Execution Timeouts**: Configurable timeouts for tool execution\n+- **Concurrency Limits**: Limit concurrent tool executions\n+- **Output Size Limits**: Prevent memory exhaustion from large outputs\n+- **Allow/Deny Lists**: Control which tools can be executed\n+- **Silent Logging**: Library logging disabled by default\n+\n+## Environment Variables\n+\n+```bash\n+# Enable agentic logging\n+AGENTIC_LOGGING=true\n+\n+# Or use DEBUG pattern\n+DEBUG=*agentic*\n+```\n+\n+## Security Checklist\n+\n+- [ ] Define Zod schemas for all tool parameters\n+- [ ] Enable prototype pollution detection\n+- [ ] Set appropriate execution timeouts\n+- [ ] Configure concurrency limits\n+- [ ] Use allow/deny lists for tools in production\n+- [ ] Monitor tool execution metrics\n+\ndiff --git a/package.json b/package.json\nindex 54cdb28..1691d15 100644\n--- a/package.json\n+++ b/package.json\n@@ -1,6 +1,7 @@\n {\n \"name\": \"@riotprompt/agentic\",\n- \"version\": \"0.0.5\",\n+ \"version\": \"1.0.0\",\n+ \"_versionReset\": \"2026-01-15\",\n \"description\": \"Shared agentic framework for multi-turn AI conversations and tool orchestration\",\n \"type\": \"module\",\n \"main\": \"./dist/index.js\",\n@@ -20,7 +21,7 @@\n \"test\": \"vitest run --coverage\",\n \"test:coverage\": \"vitest run --coverage\",\n \"lint\": \"eslint src\",\n- \"precommit\": \"npm run lint && npm run test\",\n+ \"precommit\": \"npm run build && npm run lint && npm run test\",\n \"prepublishOnly\": \"npm run clean && npm run build\"\n },\n \"keywords\": [\n@@ -39,6 +40,7 @@\n \"url\": \"https://github.com/kjerneverk/agentic\"\n },\n \"dependencies\": {\n+ \"@fjell/logging\": \"^4.4.68\",\n \"zod\": \"^4.3.5\"\n },\n \"devDependencies\": {\ndiff --git a/src/context-manager.ts b/src/context-manager.ts\nindex 4f487cb..7aa5979 100644\n--- a/src/context-manager.ts\n+++ b/src/context-manager.ts\n@@ -251,11 +251,12 @@ export class ContextManager {\n }\n \n private hashContent(content: string): string {\n+ // Use 32 characters (128 bits) for sufficient collision resistance\n return crypto\n .createHash('sha256')\n .update(content)\n .digest('hex')\n- .substring(0, 16);\n+ .substring(0, 32);\n }\n \n private normalizeContent(content: string): string {\ndiff --git a/src/index.ts b/src/index.ts\nindex a7ac357..fc7c436 100644\n--- a/src/index.ts\n+++ b/src/index.ts\n@@ -52,6 +52,22 @@ export type {\n } from './tools';\n export { ToolRegistry } from './tools';\n \n+// Tool Security\n+export type {\n+ ToolSecurityConfig,\n+ ToolValidationResult,\n+ ToolGuardEvents,\n+} from './tool-guard';\n+export { ToolGuard } from './tool-guard';\n+\n+// Tool Sandboxing\n+export type {\n+ SandboxedContext,\n+ SandboxOptions,\n+ ToolSandboxEvents,\n+} from './tool-sandbox';\n+export { ToolSandbox, createSecureTool } from './tool-sandbox';\n+\n // Context Manager\n export type { ContextStats } from './context-manager';\n export { ContextManager } from './context-manager';\ndiff --git a/src/logger.ts b/src/logger.ts\n... [CHANGE OMITTED: File too large (6160 bytes > 5000 limit)] ...\n\ndiff --git a/src/tool-guard.ts b/src/tool-guard.ts\n... [CHANGE OMITTED: File too large (8608 bytes > 5000 limit)] ...\n\ndiff --git a/src/tool-sandbox.ts b/src/tool-sandbox.ts\n... [CHANGE OMITTED: File too large (10547 bytes > 5000 limit)] ...\n\ndiff --git a/src/tools.ts b/src/tools.ts\nindex 7bf98bc..f048d07 100644\n--- a/src/tools.ts\n+++ b/src/tools.ts\n@@ -4,8 +4,10 @@\n * Manages tool definitions and execution for agentic workflows.\n */\n \n-import { z } from 'zod';\n+import { z, ZodSchema } from 'zod';\n import { DEFAULT_LOGGER, wrapLogger, type Logger } from './logger';\n+import { ToolGuard } from './tool-guard';\n+import { ToolSandbox } from './tool-sandbox';\n \n // ===== TYPE DEFINITIONS =====\n \n@@ -62,6 +64,8 @@ export interface Tool {\n category?: string;\n cost?: ToolCost;\n examples?: ToolExample[];\n+ /** Optional Zod schema for parameter validation */\n+ schema?: ZodSchema<any>;\n }\n \n /**\n@@ -123,6 +127,7 @@ export interface ToolDefinition {\n category?: string;\n cost?: ToolCost;\n examples?: ToolExample[];\n+ schema?: ZodSchema<any>;\n }\n \n // ===== VALIDATION SCHEMAS =====\n@@ -169,6 +174,8 @@ export class ToolRegistry {\n string,\n { calls: number; failures: number; totalDuration: number }\n >;\n+ private toolGuard?: ToolGuard;\n+ private toolSandbox?: ToolSandbox;\n \n private constructor(context: ToolContext = {}, logger?: Logger) {\n this.tools = new Map();\n@@ -186,6 +193,38 @@ export class ToolRegistry {\n return new ToolRegistry(context, logger);\n }\n \n+ /**\n+ * Configure security guard for tool execution\n+ */\n+ withSecurity(guard: ToolGuard): this {\n+ this.toolGuard = guard;\n+ this.logger.debug('Security guard configured');\n+ return this;\n+ }\n+\n+ /**\n+ * Get the configured security guard\n+ */\n+ getSecurityGuard(): ToolGuard | undefined {\n+ return this.toolGuard;\n+ }\n+\n+ /**\n+ * Configure sandbox for tool execution\n+ */\n+ withSandbox(sandbox: ToolSandbox): this {\n+ this.toolSandbox = sandbox;\n+ this.logger.debug('Sandbox configured');\n+ return this;\n+ }\n+\n+ /**\n+ * Get the configured sandbox\n+ */\n+ getSandbox(): ToolSandbox | undefined {\n+ return this.toolSandbox;\n+ }\n+\n /**\n * Register a single tool\n */\n@@ -262,6 +301,24 @@ export class ToolRegistry {\n throw new Error(`Tool \"${name}\" not found`);\n }\n \n+ // Security check: is tool allowed?\n+ if (this.toolGuard && !this.toolGuard.isToolAllowed(name)) {\n+ throw new Error(`Tool \"${name}\" is not allowed`);\n+ }\n+\n+ // Security check: validate parameters if schema exists\n+ if (this.toolGuard && tool.schema) {\n+ const validation = this.toolGuard.validateParams(\n+ name,\n+ params,\n+ tool.schema\n+ );\n+ if (!validation.success) {\n+ throw new Error(`Tool \"${name}\": ${validation.error}`);\n+ }\n+ params = validation.data;\n+ }\n+\n this.logger.debug('Executing tool', { name, params });\n \n const startTime = Date.now();\n@@ -269,7 +326,18 @@ export class ToolRegistry {\n stats.calls++;\n \n try {\n- const result = await tool.execute(params, this.context);\n+ let result;\n+ \n+ // Execute with sandbox if configured\n+ if (this.toolSandbox) {\n+ result = await this.toolSandbox.execute(\n+ tool,\n+ params,\n+ this.context\n+ );\n+ } else {\n+ result = await tool.execute(params, this.context);\n+ }\n \n const duration = Date.now() - startTime;\n stats.totalDuration += duration;\n@@ -286,6 +354,23 @@ export class ToolRegistry {\n }\n }\n \n+ /**\n+ * Safely parse and execute a tool from JSON arguments\n+ */\n+ async executeFromJSON(name: string, jsonArgs: string): Promise<any> {\n+ if (this.toolGuard) {\n+ const parseResult = this.toolGuard.parseToolArguments(name, jsonArgs);\n+ if (!parseResult.success) {\n+ throw new Error(`Tool \"${name}\": ${parseResult.error}`);\n+ }\n+ return this.execute(name, parseResult.data);\n+ }\n+\n+ // No guard, parse directly\n+ const params = JSON.parse(jsonArgs);\n+ return this.execute(name, params);\n+ }\n+\n /**\n * Execute multiple tools in sequence\n */\n@@ -352,6 +437,7 @@ export class ToolRegistry {\n category: tool.category,\n cost: tool.cost,\n examples: tool.examples,\n+ schema: tool.schema,\n }));\n }\n \ndiff --git a/tests/tool-guard.test.ts b/tests/tool-guard.test.ts\n... [CHANGE OMITTED: File too large (13112 bytes > 5000 limit)] ...\n\ndiff --git a/tests/tool-sandbox.test.ts b/tests/tool-sandbox.test.ts\n... [CHANGE OMITTED: File too large (17444 bytes > 5000 limit)] ...\n\ndiff --git a/vite.config.ts b/vite.config.ts\nindex f9a5289..4548d8f 100644\n--- a/vite.config.ts\n+++ b/vite.config.ts\n@@ -2,18 +2,18 @@ import { defineConfig } from \"vite\";\n import dts from \"vite-plugin-dts\";\n \n export default defineConfig({\n- build: {\n- lib: {\n- entry: \"src/index.ts\",\n- name: \"agentic\",\n- fileName: () => \"index.js\",\n- formats: [\"es\"],\n+ build: {\n+ lib: {\n+ entry: \"src/index.ts\",\n+ name: \"agentic\",\n+ fileName: () => \"index.js\",\n+ formats: [\"es\"],\n+ },\n+ rollupOptions: {\n+ external: [\"zod\", \"execution\"],\n+ },\n+ sourcemap: true,\n+ minify: false,\n },\n- rollupOptions: {\n- external: [\"zod\", \"execution\"],\n- },\n- sourcemap: true,\n- minify: false,\n- },\n- plugins: [dts({ rollupTypes: true })],\n+ plugins: [dts({ rollupTypes: true })],\n });\ndiff --git a/vitest.config.ts b/vitest.config.ts\nindex c48ee66..101993b 100644\n--- a/vitest.config.ts\n+++ b/vitest.config.ts\n@@ -1,22 +1,22 @@\n import { defineConfig } from \"vitest/config\";\n \n export default defineConfig({\n- test: {\n- include: [\"tests/**/*.test.ts\"],\n- globals: true,\n- coverage: {\n- provider: \"v8\",\n- reporter: [\"text\", \"json\", \"html\"],\n- include: [\"src/**/*.ts\"],\n- exclude: [\n- \"src/**/*.test.ts\",\n- \"node_modules/\",\n- \"dist/**\",\n- \"vitest.config.ts\",\n- \"vite.config.ts\",\n- \"eslint.config.mjs\",\n- ],\n+ test: {\n+ include: [\"tests/**/*.test.ts\"],\n+ globals: true,\n+ coverage: {\n+ provider: \"v8\",\n+ reporter: [\"text\", \"json\", \"html\"],\n+ include: [\"src/**/*.ts\"],\n+ exclude: [\n+ \"src/**/*.test.ts\",\n+ \"node_modules/\",\n+ \"dist/**\",\n+ \"vitest.config.ts\",\n+ \"vite.config.ts\",\n+ \"eslint.config.mjs\",\n+ ],\n+ },\n },\n- },\n });\n \n\n\n[SUMMARY: 5 files omitted due to size limits. Original diff: 67333 bytes, processed diff: 12083 bytes]\n\nAnalyze these changes and write clear release notes. Consider:\n- What's the main story of this release?\n- What problems does it solve?\n- What's the impact on users and developers?\n- Are there breaking changes?\n\nIf context information is provided, use it only if relevant to this specific package.\nDon't force connections that don't exist - if context describes changes in other packages\nor unrelated features, simply ignore it and focus on what actually changed in this release.\n\nInvestigate as needed to write accurate, helpful release notes."
73
+ },
74
+ {
75
+ "role": "assistant",
76
+ "content": null,
77
+ "tool_calls": [
78
+ {
79
+ "id": "call_7U8ZxuzGnRtCWfqmTKglsI9A",
80
+ "type": "function",
81
+ "function": {
82
+ "name": "get_breaking_changes",
83
+ "arguments": "{\"fromRef\": \"main\", \"toRef\": \"HEAD\"}"
84
+ }
85
+ },
86
+ {
87
+ "id": "call_YdiZmtxBqprG5bJVxDqKHO0e",
88
+ "type": "function",
89
+ "function": {
90
+ "name": "get_release_stats",
91
+ "arguments": "{\"fromRef\": \"main\", \"toRef\": \"HEAD\"}"
92
+ }
93
+ },
94
+ {
95
+ "id": "call_GKNJ8eC2rCH5fbsBATUjr6aN",
96
+ "type": "function",
97
+ "function": {
98
+ "name": "analyze_commit_patterns",
99
+ "arguments": "{\"fromRef\": \"main\", \"toRef\": \"HEAD\"}"
100
+ }
101
+ },
102
+ {
103
+ "id": "call_fMyR5rWbSsDmL7xLG5fknPwS",
104
+ "type": "function",
105
+ "function": {
106
+ "name": "search_codebase",
107
+ "arguments": "{\"query\": \"class ToolGuard\", \"fileTypes\": [\"ts\"], \"contextLines\": 2}"
108
+ }
109
+ },
110
+ {
111
+ "id": "call_C8ZcFoEI95Et1NtAraSL7WQS",
112
+ "type": "function",
113
+ "function": {
114
+ "name": "search_codebase",
115
+ "arguments": "{\"query\": \"class ToolSandbox\", \"fileTypes\": [\"ts\"], \"contextLines\": 2}"
116
+ }
117
+ },
118
+ {
119
+ "id": "call_R9O4lrg86YuQrVtwjG2UpBTF",
120
+ "type": "function",
121
+ "function": {
122
+ "name": "get_file_content",
123
+ "arguments": "{\"filePath\": \"src/tool-guard.ts\", \"includeLineNumbers\": false}"
124
+ }
125
+ },
126
+ {
127
+ "id": "call_ptsRXMEm0Cmf7H0INmceM3J4",
128
+ "type": "function",
129
+ "function": {
130
+ "name": "get_file_content",
131
+ "arguments": "{\"filePath\": \"src/tool-sandbox.ts\", \"includeLineNumbers\": false}"
132
+ }
133
+ },
134
+ {
135
+ "id": "call_vuqwIWtbqlsd30RYw2BGAwMz",
136
+ "type": "function",
137
+ "function": {
138
+ "name": "get_file_content",
139
+ "arguments": "{\"filePath\": \"src/logger.ts\", \"includeLineNumbers\": false}"
140
+ }
141
+ },
142
+ {
143
+ "id": "call_4NK83uGAPCOP1hPxuntOXF0d",
144
+ "type": "function",
145
+ "function": {
146
+ "name": "get_file_content",
147
+ "arguments": "{\"filePath\": \"package.json\", \"includeLineNumbers\": false}"
148
+ }
149
+ },
150
+ {
151
+ "id": "call_uj6n0pQxkrnhcBWWap9xBCbU",
152
+ "type": "function",
153
+ "function": {
154
+ "name": "get_file_content",
155
+ "arguments": "{\"filePath\": \"README.md\", \"includeLineNumbers\": false}"
156
+ }
157
+ },
158
+ {
159
+ "id": "call_blEuBa556HPPTF9wGZzWi8Zj",
160
+ "type": "function",
161
+ "function": {
162
+ "name": "get_file_content",
163
+ "arguments": "{\"filePath\": \"SECURITY.md\", \"includeLineNumbers\": false}"
164
+ }
165
+ },
166
+ {
167
+ "id": "call_zIL5lQ638puFeD53NYX0mEgz",
168
+ "type": "function",
169
+ "function": {
170
+ "name": "get_related_tests",
171
+ "arguments": "{\"filePaths\": [\"src/tool-guard.ts\", \"src/tool-sandbox.ts\", \"src/tools.ts\", \"src/context-manager.ts\"]}"
172
+ }
173
+ },
174
+ {
175
+ "id": "call_W2DH5A3J25BszBcb0A0NY0QV",
176
+ "type": "function",
177
+ "function": {
178
+ "name": "get_file_dependencies",
179
+ "arguments": "{\"filePaths\": [\"src/tools.ts\", \"src/tool-guard.ts\", \"src/tool-sandbox.ts\", \"src/context-manager.ts\", \"src/logger.ts\"]}"
180
+ }
181
+ },
182
+ {
183
+ "id": "call_XEOdgJJgF6XnAWRoMrtunn75",
184
+ "type": "function",
185
+ "function": {
186
+ "name": "group_files_by_concern",
187
+ "arguments": "{\"filePaths\": [\"README.md\", \"SECURITY.md\", \"package.json\", \"src/context-manager.ts\", \"src/index.ts\", \"src/logger.ts\", \"src/tool-guard.ts\", \"src/tool-sandbox.ts\", \"src/tools.ts\", \"tests/tool-guard.test.ts\", \"tests/tool-sandbox.test.ts\", \"vite.config.ts\", \"vitest.config.ts\"]}"
188
+ }
189
+ }
190
+ ]
191
+ },
192
+ {
193
+ "role": "tool",
194
+ "tool_call_id": "call_7U8ZxuzGnRtCWfqmTKglsI9A",
195
+ "content": "\nRemoved exports (potential breaking):\n-export const DEFAULT_LOGGER: Logger = {\n\n\n\nChanged function/type signatures (review for breaking changes):\n+function createSilentLogger(name: string): Logger {\n+const isLoggingEnabled = (): boolean => {\n+function createLoggerFromFjell(fjellLogger: ReturnType<typeof LibLogger.get>, name: string): Logger {\n+export interface ToolSecurityConfig {\n+export interface ToolValidationResult<T> {\n+export interface ToolGuardEvents {\n+ onPrototypePollution?: (toolName: string) => void;\n+ typeof parsed !== 'object' ||\n+ private hasPrototypePollution(obj: unknown, depth: number = 0): boolean {\n+ if (typeof obj !== 'object' || obj === null) return false;\n+ if (typeof (obj as Record<string, unknown>)[key] === 'object') {\n+export interface SandboxedContext extends ToolContext {\n+export interface SandboxOptions {\n+export interface ToolSandboxEvents {\n+ const timeoutId = setTimeout(() => {\n+ const abortHandler = () => {\n+ if (typeof value === 'string') return value.length * 2; // UTF-16\n+ if (typeof value === 'number') return 8;\n+ if (typeof value === 'boolean') return 4;\n+export function createSecureTool(",
196
+ "name": "get_breaking_changes"
197
+ },
198
+ {
199
+ "role": "tool",
200
+ "tool_call_id": "call_YdiZmtxBqprG5bJVxDqKHO0e",
201
+ "content": "Total commits: 9\n\nContributors:\n 9\tTim O'Brien\n\n\nFile changes: 13 files changed, 1797 insertions(+), 62 deletions(-)\n\nTop 10 most changed files:\n vitest.config.ts | 32 +--\n vite.config.ts | 26 +--\n tests/tool-sandbox.test.ts | 478 +++++++++++++++++++++++++++++++++++++++++++++\n tests/tool-guard.test.ts | 358 +++++++++++++++++++++++++++++++++\n src/tools.ts | 90 ++++++++-\n src/tool-sandbox.ts | 336 +++++++++++++++++++++++++++++++\n src/tool-guard.ts | 293 +++++++++++++++++++++++++++\n src/logger.ts | 125 +++++++++---\n src/index.ts | 16 ++\n src/context-manager.ts | 3 +-\n",
202
+ "name": "get_release_stats"
203
+ },
204
+ {
205
+ "role": "tool",
206
+ "tool_call_id": "call_GKNJ8eC2rCH5fbsBATUjr6aN",
207
+ "content": "Commit types:\n chore: 5\n fix: 1\n docs: 1\n refactor: 1\n feat: 1\n\nTop keywords in commits:\n chore: 5\n security: 3\n release: 2\n version: 2\n config: 2\n execution: 2\n hooks: 1\n build: 1\n precommit: 1\n script: 1\n reset: 1\n package: 1\n reformat: 1\n vitest: 1\n files: 1",
208
+ "name": "analyze_commit_patterns"
209
+ },
210
+ {
211
+ "role": "tool",
212
+ "tool_call_id": "call_fMyR5rWbSsDmL7xLG5fknPwS",
213
+ "content": "src/tool-guard.ts-72- * ```\nsrc/tool-guard.ts-73- */\nsrc/tool-guard.ts:74:export class ToolGuard {\nsrc/tool-guard.ts-75- private config: ToolSecurityConfig;\nsrc/tool-guard.ts-76- private logger: Logger;\n",
214
+ "name": "search_codebase"
215
+ },
216
+ {
217
+ "role": "tool",
218
+ "tool_call_id": "call_C8ZcFoEI95Et1NtAraSL7WQS",
219
+ "content": "src/tool-sandbox.ts-76- * ```\nsrc/tool-sandbox.ts-77- */\nsrc/tool-sandbox.ts:78:export class ToolSandbox {\nsrc/tool-sandbox.ts-79- private config: ToolSecurityConfig;\nsrc/tool-sandbox.ts-80- private logger: Logger;\n",
220
+ "name": "search_codebase"
221
+ },
222
+ {
223
+ "role": "tool",
224
+ "tool_call_id": "call_R9O4lrg86YuQrVtwjG2UpBTF",
225
+ "content": "/**\n * Agentic Package - Tool Guard\n *\n * Security layer for tool parameter validation and access control.\n */\n\nimport { ZodSchema, ZodError } from 'zod';\nimport { DEFAULT_LOGGER, wrapLogger, type Logger } from './logger';\n\n/**\n * Configuration for tool security\n */\nexport interface ToolSecurityConfig {\n enabled: boolean;\n validateParams: boolean;\n sandboxExecution: boolean;\n maxExecutionTime: number;\n maxConcurrentCalls: number;\n deniedTools: string[];\n allowedTools?: string[];\n}\n\n/**\n * Result of tool parameter validation\n */\nexport interface ToolValidationResult<T> {\n success: boolean;\n data?: T;\n error?: string;\n violations?: string[];\n}\n\n/**\n * Event handlers for security events\n */\nexport interface ToolGuardEvents {\n onValidationFailed?: (toolName: string, message: string) => void;\n onExecutionBlocked?: (toolName: string, reason: string) => void;\n onPrototypePollution?: (toolName: string) => void;\n}\n\nconst DEFAULT_CONFIG: ToolSecurityConfig = {\n enabled: true,\n validateParams: true,\n sandboxExecution: false,\n maxExecutionTime: 30000,\n maxConcurrentCalls: 10,\n deniedTools: [],\n};\n\n/**\n * ToolGuard provides security validation for tool execution.\n *\n * Features:\n * - Schema-based parameter validation with Zod\n * - Prototype pollution detection\n * - Tool allow/deny lists\n * - Event callbacks for security monitoring\n *\n * @example\n * ```typescript\n * const guard = new ToolGuard({ deniedTools: ['dangerous_tool'] });\n *\n * // Validate parameters\n * const schema = z.object({ path: z.string() });\n * const result = guard.validateParams('read_file', { path: '/test' }, schema);\n *\n * // Check if tool is allowed\n * if (guard.isToolAllowed('my_tool')) {\n * // execute tool\n * }\n * ```\n */\nexport class ToolGuard {\n private config: ToolSecurityConfig;\n private logger: Logger;\n private events: ToolGuardEvents;\n\n constructor(\n config: Partial<ToolSecurityConfig> = {},\n logger?: Logger,\n events: ToolGuardEvents = {}\n ) {\n this.config = { ...DEFAULT_CONFIG, ...config };\n this.logger = wrapLogger(logger || DEFAULT_LOGGER, 'ToolGuard');\n this.events = events;\n }\n\n /**\n * Validate tool parameters against a Zod schema\n */\n validateParams<T>(\n toolName: string,\n params: unknown,\n schema: ZodSchema<T>\n ): ToolValidationResult<T> {\n if (!this.config.enabled || !this.config.validateParams) {\n // Bypass validation if disabled\n return { success: true, data: params as T };\n }\n\n try {\n const data = schema.parse(params);\n return { success: true, data };\n } catch (error) {\n if (error instanceof ZodError) {\n // Zod 4 uses 'issues' instead of 'errors'\n const issues = error.issues || [];\n const violations = issues.map(\n (e) => `${e.path.join('.')}: ${e.message}`\n );\n const message = `Schema validation failed: ${violations.join('; ')}`;\n\n this.logger.warn(`Tool validation failed for \"${toolName}\"`, {\n violations,\n });\n this.events.onValidationFailed?.(toolName, message);\n\n return {\n success: false,\n error: 'Parameter validation failed',\n violations,\n };\n }\n throw error;\n }\n }\n\n /**\n * Safely parse JSON tool arguments\n */\n parseToolArguments(\n toolName: string,\n jsonString: string\n ): ToolValidationResult<Record<string, unknown>> {\n if (!this.config.enabled) {\n return { success: true, data: JSON.parse(jsonString) };\n }\n\n try {\n // First, try to parse as JSON\n const parsed = JSON.parse(jsonString);\n\n // Check for prototype pollution attempts\n if (this.hasPrototypePollution(parsed)) {\n this.logger.error(\n `Prototype pollution attempt detected for tool \"${toolName}\"`\n );\n this.events.onPrototypePollution?.(toolName);\n return {\n success: false,\n error: 'Invalid tool arguments: potentially malicious content detected',\n };\n }\n\n // Ensure it's an object\n if (\n typeof parsed !== 'object' ||\n parsed === null ||\n Array.isArray(parsed)\n ) {\n return {\n success: false,\n error: 'Tool arguments must be a JSON object',\n };\n }\n\n return { success: true, data: parsed };\n } catch (error) {\n const errorMsg =\n error instanceof Error ? error.message : 'Unknown error';\n this.logger.warn(`JSON parsing failed for tool \"${toolName}\"`, {\n error: errorMsg,\n });\n this.events.onValidationFailed?.(\n toolName,\n `JSON parsing failed: ${errorMsg}`\n );\n return {\n success: false,\n error: 'Invalid JSON in tool arguments',\n };\n }\n }\n\n /**\n * Check if a tool is allowed to execute\n */\n isToolAllowed(toolName: string): boolean {\n if (!this.config.enabled) return true;\n\n // Check deny list\n if (this.config.deniedTools.includes(toolName)) {\n this.logger.warn(`Tool \"${toolName}\" blocked by deny list`);\n this.events.onExecutionBlocked?.(toolName, 'Tool is in deny list');\n return false;\n }\n\n // Check allow list if specified\n if (\n this.config.allowedTools &&\n !this.config.allowedTools.includes(toolName)\n ) {\n this.logger.warn(`Tool \"${toolName}\" not in allow list`);\n this.events.onExecutionBlocked?.(\n toolName,\n 'Tool is not in allow list'\n );\n return false;\n }\n\n return true;\n }\n\n /**\n * Get current configuration\n */\n getConfig(): Readonly<ToolSecurityConfig> {\n return { ...this.config };\n }\n\n /**\n * Update configuration\n */\n updateConfig(config: Partial<ToolSecurityConfig>): void {\n this.config = { ...this.config, ...config };\n }\n\n /**\n * Add a tool to the deny list\n */\n denyTool(toolName: string): void {\n if (!this.config.deniedTools.includes(toolName)) {\n this.config.deniedTools.push(toolName);\n }\n }\n\n /**\n * Remove a tool from the deny list\n */\n allowTool(toolName: string): void {\n const index = this.config.deniedTools.indexOf(toolName);\n if (index !== -1) {\n this.config.deniedTools.splice(index, 1);\n }\n }\n\n /**\n * Check if security is enabled\n */\n isEnabled(): boolean {\n return this.config.enabled;\n }\n\n /**\n * Enable or disable security\n */\n setEnabled(enabled: boolean): void {\n this.config.enabled = enabled;\n }\n\n /**\n * Detect prototype pollution attempts\n */\n private hasPrototypePollution(obj: unknown, depth: number = 0): boolean {\n if (depth > 10) return false; // Prevent stack overflow\n if (typeof obj !== 'object' || obj === null) return false;\n\n const dangerousKeys = ['__proto__', 'constructor', 'prototype'];\n\n for (const key of Object.keys(obj)) {\n if (dangerousKeys.includes(key)) {\n return true;\n }\n\n if (typeof (obj as Record<string, unknown>)[key] === 'object') {\n if (\n this.hasPrototypePollution(\n (obj as Record<string, unknown>)[key],\n depth + 1\n )\n ) {\n return true;\n }\n }\n }\n\n return false;\n }\n}\n\nexport default ToolGuard;\n\n",
226
+ "name": "get_file_content"
227
+ },
228
+ {
229
+ "role": "tool",
230
+ "tool_call_id": "call_ptsRXMEm0Cmf7H0INmceM3J4",
231
+ "content": "/**\n * Agentic Package - Tool Sandbox\n *\n * Capability-based sandboxing for tool execution with timeout,\n * concurrency limits, and output size restrictions.\n */\n\nimport { ToolContext, Tool } from './tools';\nimport { ToolSecurityConfig, ToolGuard } from './tool-guard';\nimport { DEFAULT_LOGGER, wrapLogger, type Logger } from './logger';\n\n/**\n * Extended context with sandbox restrictions\n */\nexport interface SandboxedContext extends ToolContext {\n readonly sandbox: {\n allowedOperations: Set<string>;\n maxOutputSize: number;\n executionId: string;\n };\n}\n\n/**\n * Options for sandbox execution\n */\nexport interface SandboxOptions {\n maxExecutionTime: number;\n maxOutputSize: number;\n allowedOperations?: string[];\n onBeforeExecution?: (\n tool: Tool,\n params: unknown\n ) => void | Promise<void>;\n onAfterExecution?: (\n tool: Tool,\n result: unknown,\n error?: Error\n ) => void | Promise<void>;\n}\n\n/**\n * Event handlers for sandbox events\n */\nexport interface ToolSandboxEvents {\n onConcurrencyExceeded?: (toolName: string, activeCount: number) => void;\n onTimeout?: (toolName: string, timeoutMs: number) => void;\n onOutputSizeExceeded?: (\n toolName: string,\n size: number,\n maxSize: number\n ) => void;\n onCancelled?: (executionId: string) => void;\n}\n\n/**\n * ToolSandbox provides capability-based sandboxing for tool execution.\n *\n * Features:\n * - Execution timeout enforcement\n * - Concurrent execution limits\n * - Output size limits\n * - Pre/post execution hooks\n * - Cancellation support\n *\n * Note: This provides capability-based restrictions, not true process isolation.\n * For maximum security, consider running untrusted tools in separate processes.\n *\n * @example\n * ```typescript\n * const sandbox = new ToolSandbox({\n * maxExecutionTime: 5000,\n * maxConcurrentCalls: 5,\n * });\n *\n * const result = await sandbox.execute(tool, params, context);\n * ```\n */\nexport class ToolSandbox {\n private config: ToolSecurityConfig;\n private logger: Logger;\n private events: ToolSandboxEvents;\n private activeExecutions: Map<string, AbortController> = new Map();\n private executionCount: number = 0;\n\n constructor(\n config: Partial<ToolSecurityConfig> = {},\n logger?: Logger,\n events: ToolSandboxEvents = {}\n ) {\n this.config = {\n enabled: true,\n validateParams: true,\n sandboxExecution: true,\n maxExecutionTime: 30000,\n maxConcurrentCalls: 10,\n deniedTools: [],\n ...config,\n };\n this.logger = wrapLogger(logger || DEFAULT_LOGGER, 'ToolSandbox');\n this.events = events;\n }\n\n /**\n * Execute a tool with sandbox restrictions\n */\n async execute<T>(\n tool: Tool,\n params: unknown,\n baseContext: ToolContext,\n options: Partial<SandboxOptions> = {}\n ): Promise<T> {\n if (!this.config.enabled || !this.config.sandboxExecution) {\n // Bypass sandboxing if disabled\n return tool.execute(params, baseContext);\n }\n\n // Check concurrent execution limit\n if (this.activeExecutions.size >= this.config.maxConcurrentCalls) {\n this.logger.warn('Max concurrent executions reached', {\n toolName: tool.name,\n activeCount: this.activeExecutions.size,\n limit: this.config.maxConcurrentCalls,\n });\n this.events.onConcurrencyExceeded?.(\n tool.name,\n this.activeExecutions.size\n );\n throw new Error('Too many concurrent tool executions');\n }\n\n const executionId = `exec-${++this.executionCount}-${Date.now()}`;\n const controller = new AbortController();\n this.activeExecutions.set(executionId, controller);\n\n const sandboxedContext: SandboxedContext = {\n ...baseContext,\n sandbox: {\n allowedOperations: new Set(options.allowedOperations || []),\n maxOutputSize: options.maxOutputSize || 1024 * 1024, // 1MB default\n executionId,\n },\n };\n\n const timeout = options.maxExecutionTime || this.config.maxExecutionTime;\n\n try {\n // Pre-execution hook\n await options.onBeforeExecution?.(tool, params);\n\n // Execute with timeout\n const result = await this.executeWithTimeout(\n () => tool.execute(params, sandboxedContext),\n timeout,\n controller.signal,\n tool.name\n );\n\n // Check output size\n const outputSize = this.estimateSize(result);\n if (outputSize > sandboxedContext.sandbox.maxOutputSize) {\n this.logger.warn('Tool output exceeded max size', {\n toolName: tool.name,\n outputSize,\n maxSize: sandboxedContext.sandbox.maxOutputSize,\n });\n this.events.onOutputSizeExceeded?.(\n tool.name,\n outputSize,\n sandboxedContext.sandbox.maxOutputSize\n );\n throw new Error('Tool output exceeded maximum size limit');\n }\n\n // Post-execution hook\n await options.onAfterExecution?.(tool, result);\n\n return result as T;\n } catch (error) {\n await options.onAfterExecution?.(tool, undefined, error as Error);\n throw error;\n } finally {\n this.activeExecutions.delete(executionId);\n }\n }\n\n /**\n * Cancel all active executions\n */\n cancelAll(): void {\n for (const [id, controller] of this.activeExecutions) {\n controller.abort();\n this.logger.info('Execution cancelled', { executionId: id });\n this.events.onCancelled?.(id);\n }\n this.activeExecutions.clear();\n }\n\n /**\n * Cancel a specific execution by ID\n */\n cancel(executionId: string): boolean {\n const controller = this.activeExecutions.get(executionId);\n if (controller) {\n controller.abort();\n this.activeExecutions.delete(executionId);\n this.logger.info('Execution cancelled', { executionId });\n this.events.onCancelled?.(executionId);\n return true;\n }\n return false;\n }\n\n /**\n * Get active execution count\n */\n getActiveCount(): number {\n return this.activeExecutions.size;\n }\n\n /**\n * Get list of active execution IDs\n */\n getActiveExecutionIds(): string[] {\n return Array.from(this.activeExecutions.keys());\n }\n\n /**\n * Check if sandbox is enabled\n */\n isEnabled(): boolean {\n return this.config.enabled && this.config.sandboxExecution;\n }\n\n /**\n * Get current configuration\n */\n getConfig(): Readonly<ToolSecurityConfig> {\n return { ...this.config };\n }\n\n /**\n * Update configuration\n */\n updateConfig(config: Partial<ToolSecurityConfig>): void {\n this.config = { ...this.config, ...config };\n }\n\n private async executeWithTimeout<T>(\n fn: () => Promise<T>,\n timeoutMs: number,\n signal: AbortSignal,\n toolName: string\n ): Promise<T> {\n return new Promise<T>((resolve, reject) => {\n const timeoutId = setTimeout(() => {\n this.logger.warn('Tool execution timed out', {\n toolName,\n timeoutMs,\n });\n this.events.onTimeout?.(toolName, timeoutMs);\n reject(\n new Error(`Tool execution timed out after ${timeoutMs}ms`)\n );\n }, timeoutMs);\n\n const abortHandler = () => {\n clearTimeout(timeoutId);\n reject(new Error('Tool execution was cancelled'));\n };\n signal.addEventListener('abort', abortHandler, { once: true });\n\n fn()\n .then((result) => {\n clearTimeout(timeoutId);\n signal.removeEventListener('abort', abortHandler);\n resolve(result);\n })\n .catch((error) => {\n clearTimeout(timeoutId);\n signal.removeEventListener('abort', abortHandler);\n reject(error);\n });\n });\n }\n\n private estimateSize(value: unknown): number {\n if (value === null || value === undefined) return 0;\n if (typeof value === 'string') return value.length * 2; // UTF-16\n if (typeof value === 'number') return 8;\n if (typeof value === 'boolean') return 4;\n if (Buffer.isBuffer(value)) return value.length;\n try {\n return JSON.stringify(value).length * 2;\n } catch {\n return 0;\n }\n }\n}\n\n/**\n * Create a security-wrapped version of a tool\n */\nexport function createSecureTool(\n tool: Tool,\n sandbox: ToolSandbox,\n guard: ToolGuard\n): Tool {\n return {\n ...tool,\n execute: async (params, context) => {\n // Check if tool is allowed\n if (!guard.isToolAllowed(tool.name)) {\n throw new Error(`Tool \"${tool.name}\" is not allowed`);\n }\n\n // Validate params if schema exists\n if (tool.schema) {\n const validation = guard.validateParams(\n tool.name,\n params,\n tool.schema\n );\n if (!validation.success) {\n throw new Error(`Validation failed: ${validation.error}`);\n }\n params = validation.data;\n }\n\n // Execute in sandbox\n return sandbox.execute(tool, params, context || {});\n },\n };\n}\n\nexport default ToolSandbox;\n\n",
232
+ "name": "get_file_content"
233
+ },
234
+ {
235
+ "role": "tool",
236
+ "tool_call_id": "call_vuqwIWtbqlsd30RYw2BGAwMz",
237
+ "content": "/**\n * Agentic Package - Logger\n *\n * Shared logging infrastructure backed by @fjell/logging for\n * comprehensive sensitive data masking and structured logging.\n */\n\nimport Logging from '@fjell/logging';\n\nexport const LIBRARY_NAME = 'agentic';\n\n// Get the library-level logger from Fjell\nconst LibLogger = Logging.getLogger('@theunwalked/agentic');\n\n/**\n * Create a silent logger with the given name\n */\nfunction createSilentLogger(name: string): Logger {\n return {\n name,\n debug: () => {},\n info: () => {},\n warn: () => {},\n error: () => {},\n verbose: () => {},\n silly: () => {},\n get: (...components: string[]) => createSilentLogger(`${name}:${components.join(':')}`),\n };\n}\n\n/**\n * Silent logger that discards all output\n * Use this as default to prevent accidental information disclosure\n */\nexport const SILENT_LOGGER: Logger = createSilentLogger('silent');\n\n/**\n * Check if logging is explicitly enabled via environment variable\n */\nconst isLoggingEnabled = (): boolean => {\n return process.env.AGENTIC_LOGGING === 'true' ||\n process.env.DEBUG?.includes('agentic') ||\n process.env.NODE_ENV === 'development';\n};\n\nexport interface Logger {\n name: string;\n debug: (message: string, ...args: any[]) => void;\n info: (message: string, ...args: any[]) => void;\n warn: (message: string, ...args: any[]) => void;\n error: (message: string, ...args: any[]) => void;\n verbose: (message: string, ...args: any[]) => void;\n silly: (message: string, ...args: any[]) => void;\n /** Get a child logger for a component */\n get?: (...components: string[]) => Logger;\n}\n\n/**\n * Create a Logger from a Fjell logger instance\n */\nfunction createLoggerFromFjell(fjellLogger: ReturnType<typeof LibLogger.get>, name: string): Logger {\n return {\n name,\n debug: (message: string, ...args: any[]) => fjellLogger.debug(message, ...args),\n info: (message: string, ...args: any[]) => fjellLogger.info(message, ...args),\n warn: (message: string, ...args: any[]) => fjellLogger.warning(message, ...args),\n error: (message: string, ...args: any[]) => fjellLogger.error(message, ...args),\n verbose: (message: string, ...args: any[]) => fjellLogger.debug(message, ...args),\n silly: (message: string, ...args: any[]) => fjellLogger.debug(message, ...args),\n get: (...components: string[]) => {\n const childLogger = fjellLogger.get(...components);\n return createLoggerFromFjell(childLogger, `${name}:${components.join(':')}`);\n },\n };\n}\n\n/**\n * Fjell-backed logger with sensitive data masking\n * \n * Features:\n * - Automatic sensitive data masking (API keys, passwords, etc.)\n * - Circular reference protection\n * - Hierarchical component logging\n */\nconst FJELL_LOGGER: Logger = {\n name: 'fjell',\n debug: (message: string, ...args: any[]) => LibLogger.debug(message, ...args),\n info: (message: string, ...args: any[]) => LibLogger.info(message, ...args),\n warn: (message: string, ...args: any[]) => LibLogger.warning(message, ...args),\n error: (message: string, ...args: any[]) => LibLogger.error(message, ...args),\n verbose: (message: string, ...args: any[]) => LibLogger.debug(message, ...args),\n silly: (message: string, ...args: any[]) => LibLogger.debug(message, ...args),\n get: (...components: string[]) => {\n const childLogger = LibLogger.get(...components);\n return createLoggerFromFjell(childLogger, components.join(':'));\n },\n};\n\n/**\n * Default logger - silent by default to prevent information disclosure\n * \n * Enable logging by setting one of:\n * - AGENTIC_LOGGING=true\n * - DEBUG=*agentic*\n * - NODE_ENV=development\n */\nexport const DEFAULT_LOGGER: Logger = isLoggingEnabled() ? FJELL_LOGGER : SILENT_LOGGER;\n\nexport function wrapLogger(toWrap: Logger, name?: string): Logger {\n const requiredMethods: (keyof Logger)[] = [\n 'debug',\n 'info',\n 'warn',\n 'error',\n 'verbose',\n 'silly',\n ];\n const missingMethods = requiredMethods.filter(\n (method) => typeof toWrap[method] !== 'function'\n );\n\n if (missingMethods.length > 0) {\n throw new Error(\n `Logger is missing required methods: ${missingMethods.join(', ')}`\n );\n }\n\n const log = (level: keyof Logger, message: string, ...args: any[]) => {\n message = `[${LIBRARY_NAME}] ${name ? `[${name}]` : ''}: ${message}`;\n\n if (level === 'debug') toWrap.debug(message, ...args);\n else if (level === 'info') toWrap.info(message, ...args);\n else if (level === 'warn') toWrap.warn(message, ...args);\n else if (level === 'error') toWrap.error(message, ...args);\n else if (level === 'verbose') toWrap.verbose(message, ...args);\n else if (level === 'silly') toWrap.silly(message, ...args);\n };\n\n return {\n name: name || 'wrapped',\n debug: (message: string, ...args: any[]) => log('debug', message, ...args),\n info: (message: string, ...args: any[]) => log('info', message, ...args),\n warn: (message: string, ...args: any[]) => log('warn', message, ...args),\n error: (message: string, ...args: any[]) => log('error', message, ...args),\n verbose: (message: string, ...args: any[]) =>\n log('verbose', message, ...args),\n silly: (message: string, ...args: any[]) => log('silly', message, ...args),\n get: (...components: string[]) => wrapLogger(toWrap, name ? `${name}:${components.join(':')}` : components.join(':')),\n };\n}\n",
238
+ "name": "get_file_content"
239
+ },
240
+ {
241
+ "role": "tool",
242
+ "tool_call_id": "call_4NK83uGAPCOP1hPxuntOXF0d",
243
+ "content": "{\n \"name\": \"@riotprompt/agentic\",\n \"version\": \"1.0.0\",\n \"_versionReset\": \"2026-01-15\",\n \"description\": \"Shared agentic framework for multi-turn AI conversations and tool orchestration\",\n \"type\": \"module\",\n \"main\": \"./dist/index.js\",\n \"types\": \"./dist/index.d.ts\",\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/index.js\"\n }\n },\n \"engines\": {\n \"node\": \">=24.0.0\"\n },\n \"scripts\": {\n \"clean\": \"rm -rf dist\",\n \"build\": \"vite build\",\n \"test\": \"vitest run --coverage\",\n \"test:coverage\": \"vitest run --coverage\",\n \"lint\": \"eslint src\",\n \"precommit\": \"npm run build && npm run lint && npm run test\",\n \"prepublishOnly\": \"npm run clean && npm run build\"\n },\n \"keywords\": [\n \"agentic\",\n \"ai\",\n \"llm\",\n \"conversation\",\n \"tools\",\n \"multi-turn\",\n \"orchestration\"\n ],\n \"author\": \"Tim O'Brien <tobrien@discursive.com>\",\n \"license\": \"Apache-2.0\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/kjerneverk/agentic\"\n },\n \"dependencies\": {\n \"@fjell/logging\": \"^4.4.68\",\n \"zod\": \"^4.3.5\"\n },\n \"devDependencies\": {\n \"@types/node\": \"^25.0.6\",\n \"@typescript-eslint/eslint-plugin\": \"^8.33.1\",\n \"@typescript-eslint/parser\": \"^8.33.1\",\n \"@vitest/coverage-v8\": \"^4.0.16\",\n \"eslint\": \"^9.28.0\",\n \"globals\": \"^17.0.0\",\n \"typescript\": \"^5.8.3\",\n \"vite\": \"^7.0.4\",\n \"vite-plugin-dts\": \"^4.5.4\",\n \"vitest\": \"^4.0.16\"\n }\n}\n",
244
+ "name": "get_file_content"
245
+ },
246
+ {
247
+ "role": "tool",
248
+ "tool_call_id": "call_uj6n0pQxkrnhcBWWap9xBCbU",
249
+ "content": "# agentic\n\nComponents for building agentic AI workflows - tool registry, context management, and conversation state tracking.\n\n## Installation\n\n```bash\nnpm install agentic\n```\n\n## Features\n\n- **ToolRegistry** - Register, execute, and manage tools with usage tracking\n- **ContextManager** - Track and deduplicate injected context\n- **OpenAI/Anthropic Format** - Export tools in provider-specific formats\n\n## Usage\n\n### Tool Registry\n\n```typescript\nimport { ToolRegistry, type Tool } from 'agentic';\n\n// Create registry\nconst registry = ToolRegistry.create({\n workingDirectory: process.cwd(),\n});\n\n// Register a tool\nregistry.register({\n name: 'read_file',\n description: 'Read contents of a file',\n parameters: {\n type: 'object',\n properties: {\n path: { type: 'string', description: 'File path to read' },\n },\n required: ['path'],\n },\n execute: async ({ path }) => {\n const fs = await import('fs/promises');\n return await fs.readFile(path, 'utf-8');\n },\n category: 'filesystem',\n cost: 'cheap',\n});\n\n// Execute a tool\nconst content = await registry.execute('read_file', { path: './README.md' });\n\n// Export for OpenAI\nconst openAITools = registry.toOpenAIFormat();\n\n// Export for Anthropic\nconst anthropicTools = registry.toAnthropicFormat();\n\n// Get usage statistics\nconst stats = registry.getUsageStats();\nconsole.log(stats.get('read_file')); // { calls: 5, failures: 0, successRate: 1 }\n```\n\n### Context Manager\n\n```typescript\nimport { ContextManager } from 'agentic';\n\nconst context = new ContextManager();\n\n// Track context with deduplication\ncontext.track({\n id: 'file:main.ts',\n content: fileContent,\n title: 'Main File',\n category: 'source-code',\n priority: 'high',\n}, 5); // position in conversation\n\n// Check for duplicates\nif (context.hasContext('file:main.ts')) {\n console.log('Already provided');\n}\n\n// Query by category\nconst sourceFiles = context.getByCategory('source-code');\n\n// Get statistics\nconst stats = context.getStats();\nconsole.log(stats.totalItems);\nconsole.log(stats.byCategory);\n```\n\n### Tool Definition\n\n```typescript\ninterface Tool {\n name: string;\n description: string;\n parameters: {\n type: 'object';\n properties: Record<string, ToolParameter>;\n required?: string[];\n };\n execute: (params: any, context?: ToolContext) => Promise<any>;\n category?: string;\n cost?: 'cheap' | 'moderate' | 'expensive';\n examples?: ToolExample[];\n}\n```\n\n### Context Item\n\n```typescript\ninterface DynamicContentItem {\n content: string;\n title?: string;\n weight?: number;\n id?: string;\n category?: string;\n source?: string;\n priority?: 'high' | 'medium' | 'low';\n timestamp?: Date;\n}\n```\n\n## API Reference\n\n### ToolRegistry\n\n| Method | Description |\n|--------|-------------|\n| `create(context?)` | Create a new registry |\n| `register(tool)` | Register a tool |\n| `registerAll(tools)` | Register multiple tools |\n| `execute(name, params)` | Execute a tool |\n| `executeBatch(calls)` | Execute multiple tools |\n| `toOpenAIFormat()` | Export for OpenAI |\n| `toAnthropicFormat()` | Export for Anthropic |\n| `getUsageStats()` | Get usage statistics |\n| `getMostUsed(limit)` | Get most used tools |\n| `getByCategory(category)` | Filter by category |\n\n### ContextManager\n\n| Method | Description |\n|--------|-------------|\n| `track(item, position)` | Track a context item |\n| `hasContext(id)` | Check if ID exists |\n| `hasContentHash(content)` | Check by content hash |\n| `hasSimilarContent(content)` | Fuzzy content match |\n| `getByCategory(category)` | Filter by category |\n| `getByPriority(priority)` | Filter by priority |\n| `getStats()` | Get statistics |\n| `remove(id)` | Remove by ID |\n| `clear()` | Clear all |\n\n## Related Packages\n\n- `execution` - Core provider interfaces\n- `execution-openai` - OpenAI provider\n- `execution-anthropic` - Anthropic provider\n- `execution-gemini` - Gemini provider\n\n## License\n\nApache-2.0\n\n<!-- v1.0.0 -->\n",
250
+ "name": "get_file_content"
251
+ },
252
+ {
253
+ "role": "tool",
254
+ "tool_call_id": "call_blEuBa556HPPTF9wGZzWi8Zj",
255
+ "content": "# Security Policy\n\n## Supported Versions\n\n| Version | Supported |\n| ------- | ------------------ |\n| 0.x.x | :white_check_mark: |\n\n## Reporting a Vulnerability\n\nIf you discover a security vulnerability, please report it responsibly.\n\n**Do not open a public GitHub issue for security vulnerabilities.**\n\n## Security Features\n\nThe agentic package includes security features for tool execution:\n\n### Tool Validation (ToolGuard)\n\n```typescript\nimport { ToolGuard } from '@theunwalked/agentic';\nimport { z } from 'zod';\n\nconst guard = new ToolGuard({\n enabled: true,\n validateParams: true,\n detectPrototypePollution: true,\n});\n\n// Register tool schemas\nguard.registerSchema('read_file', z.object({\n path: z.string().max(1000),\n encoding: z.enum(['utf8', 'base64']).optional(),\n}));\n\n// Validate before execution\nconst result = guard.validate('read_file', params);\nif (!result.valid) {\n // Handle validation failure\n}\n```\n\n### Tool Sandboxing (ToolSandbox)\n\n```typescript\nimport { ToolSandbox } from '@theunwalked/agentic';\n\nconst sandbox = new ToolSandbox({\n enabled: true,\n maxExecutionTime: 30000,\n maxConcurrent: 5,\n maxOutputSize: 1024 * 1024, // 1MB\n});\n\n// Execute with sandboxing\nconst result = await sandbox.execute(\n 'my_tool',\n async (signal) => {\n // Tool implementation\n // Check signal.aborted for cancellation\n }\n);\n```\n\n### Security Features\n\n- **Parameter Validation**: Zod schema validation for all tool parameters\n- **Prototype Pollution Prevention**: Blocks `__proto__`, `constructor`, `prototype`\n- **Execution Timeouts**: Configurable timeouts for tool execution\n- **Concurrency Limits**: Limit concurrent tool executions\n- **Output Size Limits**: Prevent memory exhaustion from large outputs\n- **Allow/Deny Lists**: Control which tools can be executed\n- **Silent Logging**: Library logging disabled by default\n\n## Environment Variables\n\n```bash\n# Enable agentic logging\nAGENTIC_LOGGING=true\n\n# Or use DEBUG pattern\nDEBUG=*agentic*\n```\n\n## Security Checklist\n\n- [ ] Define Zod schemas for all tool parameters\n- [ ] Enable prototype pollution detection\n- [ ] Set appropriate execution timeouts\n- [ ] Configure concurrency limits\n- [ ] Use allow/deny lists for tools in production\n- [ ] Monitor tool execution metrics\n\n",
256
+ "name": "get_file_content"
257
+ },
258
+ {
259
+ "role": "tool",
260
+ "tool_call_id": "call_zIL5lQ638puFeD53NYX0mEgz",
261
+ "content": "Found related test files:\nsrc/tool-guard.ts\nsrc/tool-sandbox.ts\nsrc/tools.ts\nsrc/context-manager.ts",
262
+ "name": "get_related_tests"
263
+ },
264
+ {
265
+ "role": "tool",
266
+ "tool_call_id": "call_W2DH5A3J25BszBcb0A0NY0QV",
267
+ "content": "No files found that import the specified files",
268
+ "name": "get_file_dependencies"
269
+ },
270
+ {
271
+ "role": "tool",
272
+ "tool_call_id": "call_XEOdgJJgF6XnAWRoMrtunn75",
273
+ "content": "documentation (2 files):\n - README.md\n - SECURITY.md\n\ndependencies (1 files):\n - package.json\n\nsource:src (6 files):\n - src/context-manager.ts\n - src/index.ts\n - src/logger.ts\n - src/tool-guard.ts\n - src/tool-sandbox.ts\n - src/tools.ts\n\ntests (2 files):\n - tests/tool-guard.test.ts\n - tests/tool-sandbox.test.ts\n\nother (2 files):\n - vite.config.ts\n - vitest.config.ts\n\nSuggestion: These 5 groups represent different concerns in the release.",
274
+ "name": "group_files_by_concern"
275
+ },
276
+ {
277
+ "role": "assistant",
278
+ "content": null,
279
+ "tool_calls": [
280
+ {
281
+ "id": "call_jOknf5Ny1AolaNdFpq8wCs9v",
282
+ "type": "function",
283
+ "function": {
284
+ "name": "get_file_content",
285
+ "arguments": "{\"filePath\": \"src/tools.ts\", \"includeLineNumbers\": true}"
286
+ }
287
+ },
288
+ {
289
+ "id": "call_xF42grOUaWq5nk8KGv4ipWZN",
290
+ "type": "function",
291
+ "function": {
292
+ "name": "get_file_content",
293
+ "arguments": "{\"filePath\": \"src/context-manager.ts\", \"includeLineNumbers\": true}"
294
+ }
295
+ },
296
+ {
297
+ "id": "call_QmCpAfTVz0IOk4muM0mrG7Wz",
298
+ "type": "function",
299
+ "function": {
300
+ "name": "get_file_content",
301
+ "arguments": "{\"filePath\": \"src/index.ts\", \"includeLineNumbers\": true}"
302
+ }
303
+ },
304
+ {
305
+ "id": "call_hUMvADzeQeCnLDxPgQo2Qsuq",
306
+ "type": "function",
307
+ "function": {
308
+ "name": "get_file_content",
309
+ "arguments": "{\"filePath\": \"tests/tool-guard.test.ts\", \"includeLineNumbers\": false}"
310
+ }
311
+ },
312
+ {
313
+ "id": "call_0JmXjZT8Hw9Ttbedf3njKemd",
314
+ "type": "function",
315
+ "function": {
316
+ "name": "get_file_content",
317
+ "arguments": "{\"filePath\": \"tests/tool-sandbox.test.ts\", \"includeLineNumbers\": false}"
318
+ }
319
+ }
320
+ ]
321
+ },
322
+ {
323
+ "role": "tool",
324
+ "tool_call_id": "call_jOknf5Ny1AolaNdFpq8wCs9v",
325
+ "content": "1: /**\n2: * Agentic Package - Tool Registry\n3: *\n4: * Manages tool definitions and execution for agentic workflows.\n5: */\n6: \n7: import { z, ZodSchema } from 'zod';\n8: import { DEFAULT_LOGGER, wrapLogger, type Logger } from './logger';\n9: import { ToolGuard } from './tool-guard';\n10: import { ToolSandbox } from './tool-sandbox';\n11: \n12: // ===== TYPE DEFINITIONS =====\n13: \n14: /**\n15: * Parameter definition for a tool\n16: */\n17: export interface ToolParameter {\n18: type: 'string' | 'number' | 'boolean' | 'array' | 'object';\n19: description: string;\n20: items?: ToolParameter;\n21: properties?: Record<string, ToolParameter>;\n22: required?: string[];\n23: enum?: string[];\n24: default?: any;\n25: }\n26: \n27: /**\n28: * Context provided to tool execution\n29: */\n30: export interface ToolContext {\n31: workingDirectory?: string;\n32: storage?: any;\n33: logger?: Logger;\n34: conversationState?: any;\n35: [key: string]: any;\n36: }\n37: \n38: /**\n39: * Example usage of a tool\n40: */\n41: export interface ToolExample {\n42: scenario: string;\n43: params: any;\n44: expectedResult: string;\n45: }\n46: \n47: /**\n48: * Cost hint for tool execution\n49: */\n50: export type ToolCost = 'cheap' | 'moderate' | 'expensive';\n51: \n52: /**\n53: * Tool definition\n54: */\n55: export interface Tool {\n56: name: string;\n57: description: string;\n58: parameters: {\n59: type: 'object';\n60: properties: Record<string, ToolParameter>;\n61: required?: string[];\n62: };\n63: execute: (params: any, context?: ToolContext) => Promise<any>;\n64: category?: string;\n65: cost?: ToolCost;\n66: examples?: ToolExample[];\n67: /** Optional Zod schema for parameter validation */\n68: schema?: ZodSchema<any>;\n69: }\n70: \n71: /**\n72: * OpenAI-compatible tool format\n73: */\n74: export interface OpenAITool {\n75: type: 'function';\n76: function: {\n77: name: string;\n78: description: string;\n79: parameters: {\n80: type: 'object';\n81: properties: Record<string, any>;\n82: required?: string[];\n83: };\n84: };\n85: }\n86: \n87: /**\n88: * Anthropic-compatible tool format\n89: */\n90: export interface AnthropicTool {\n91: name: string;\n92: description: string;\n93: input_schema: {\n94: type: 'object';\n95: properties: Record<string, any>;\n96: required?: string[];\n97: };\n98: }\n99: \n100: /**\n101: * Tool execution result\n102: */\n103: export interface ToolExecutionResult {\n104: success: boolean;\n105: result?: any;\n106: error?: string;\n107: duration?: number;\n108: }\n109: \n110: /**\n111: * Usage statistics for a tool\n112: */\n113: export interface ToolUsageStats {\n114: calls: number;\n115: failures: number;\n116: successRate: number;\n117: averageDuration?: number;\n118: }\n119: \n120: /**\n121: * Tool definition for export\n122: */\n123: export interface ToolDefinition {\n124: name: string;\n125: description: string;\n126: parameters: Tool['parameters'];\n127: category?: string;\n128: cost?: ToolCost;\n129: examples?: ToolExample[];\n130: schema?: ZodSchema<any>;\n131: }\n132: \n133: // ===== VALIDATION SCHEMAS =====\n134: \n135: const ToolSchema = z\n136: .object({\n137: name: z.string().min(1),\n138: description: z.string().min(1),\n139: parameters: z\n140: .object({\n141: type: z.literal('object'),\n142: properties: z.record(z.string(), z.any()).default({}),\n143: required: z.array(z.string()).optional(),\n144: })\n145: .passthrough(),\n146: execute: z.custom<(params: any, context?: any) => Promise<any>>(\n147: (val) => typeof val === 'function',\n148: { message: 'execute must be a function' }\n149: ),\n150: category: z.string().optional(),\n151: cost: z.enum(['cheap', 'moderate', 'expensive']).optional(),\n152: examples: z\n153: .array(\n154: z.object({\n155: scenario: z.string(),\n156: params: z.any(),\n157: expectedResult: z.string(),\n158: })\n159: )\n160: .optional(),\n161: })\n162: .passthrough();\n163: \n164: // ===== TOOL REGISTRY =====\n165: \n166: /**\n167: * ToolRegistry manages tool definitions and execution.\n168: */\n169: export class ToolRegistry {\n170: private tools: Map<string, Tool>;\n171: private context: ToolContext;\n172: private logger: Logger;\n173: private usageStats: Map<\n174: string,\n175: { calls: number; failures: number; totalDuration: number }\n176: >;\n177: private toolGuard?: ToolGuard;\n178: private toolSandbox?: ToolSandbox;\n179: \n180: private constructor(context: ToolContext = {}, logger?: Logger) {\n181: this.tools = new Map();\n182: this.context = context;\n183: this.logger = wrapLogger(logger || DEFAULT_LOGGER, 'ToolRegistry');\n184: this.usageStats = new Map();\n185: \n186: this.logger.debug('Created ToolRegistry');\n187: }\n188: \n189: /**\n190: * Create a new ToolRegistry instance\n191: */\n192: static create(context?: ToolContext, logger?: Logger): ToolRegistry {\n193: return new ToolRegistry(context, logger);\n194: }\n195: \n196: /**\n197: * Configure security guard for tool execution\n198: */\n199: withSecurity(guard: ToolGuard): this {\n200: this.toolGuard = guard;\n201: this.logger.debug('Security guard configured');\n202: return this;\n203: }\n204: \n205: /**\n206: * Get the configured security guard\n207: */\n208: getSecurityGuard(): ToolGuard | undefined {\n209: return this.toolGuard;\n210: }\n211: \n212: /**\n213: * Configure sandbox for tool execution\n214: */\n215: withSandbox(sandbox: ToolSandbox): this {\n216: this.toolSandbox = sandbox;\n217: this.logger.debug('Sandbox configured');\n218: return this;\n219: }\n220: \n221: /**\n222: * Get the configured sandbox\n223: */\n224: getSandbox(): ToolSandbox | undefined {\n225: return this.toolSandbox;\n226: }\n227: \n228: /**\n229: * Register a single tool\n230: */\n231: register(tool: Tool): void {\n232: try {\n233: ToolSchema.parse(tool);\n234: } catch (error) {\n235: throw new Error(`Invalid tool definition for \"${tool.name}\": ${error}`);\n236: }\n237: \n238: if (this.tools.has(tool.name)) {\n239: this.logger.warn(`Tool \"${tool.name}\" already registered, overwriting`);\n240: }\n241: \n242: this.tools.set(tool.name, tool);\n243: this.usageStats.set(tool.name, { calls: 0, failures: 0, totalDuration: 0 });\n244: \n245: this.logger.debug('Registered tool', {\n246: name: tool.name,\n247: category: tool.category,\n248: });\n249: }\n250: \n251: /**\n252: * Register multiple tools at once\n253: */\n254: registerAll(tools: Tool[]): void {\n255: this.logger.debug('Registering multiple tools', { count: tools.length });\n256: tools.forEach((tool) => this.register(tool));\n257: }\n258: \n259: /**\n260: * Get a tool by name\n261: */\n262: get(name: string): Tool | undefined {\n263: return this.tools.get(name);\n264: }\n265: \n266: /**\n267: * Get all registered tools\n268: */\n269: getAll(): Tool[] {\n270: return Array.from(this.tools.values());\n271: }\n272: \n273: /**\n274: * Get tools by category\n275: */\n276: getByCategory(category: string): Tool[] {\n277: return this.getAll().filter((tool) => tool.category === category);\n278: }\n279: \n280: /**\n281: * Check if a tool is registered\n282: */\n283: has(name: string): boolean {\n284: return this.tools.has(name);\n285: }\n286: \n287: /**\n288: * Get number of registered tools\n289: */\n290: count(): number {\n291: return this.tools.size;\n292: }\n293: \n294: /**\n295: * Execute a tool by name\n296: */\n297: async execute(name: string, params: any): Promise<any> {\n298: const tool = this.tools.get(name);\n299: \n300: if (!tool) {\n301: throw new Error(`Tool \"${name}\" not found`);\n302: }\n303: \n304: // Security check: is tool allowed?\n305: if (this.toolGuard && !this.toolGuard.isToolAllowed(name)) {\n306: throw new Error(`Tool \"${name}\" is not allowed`);\n307: }\n308: \n309: // Security check: validate parameters if schema exists\n310: if (this.toolGuard && tool.schema) {\n311: const validation = this.toolGuard.validateParams(\n312: name,\n313: params,\n314: tool.schema\n315: );\n316: if (!validation.success) {\n317: throw new Error(`Tool \"${name}\": ${validation.error}`);\n318: }\n319: params = validation.data;\n320: }\n321: \n322: this.logger.debug('Executing tool', { name, params });\n323: \n324: const startTime = Date.now();\n325: const stats = this.usageStats.get(name)!;\n326: stats.calls++;\n327: \n328: try {\n329: let result;\n330: \n331: // Execute with sandbox if configured\n332: if (this.toolSandbox) {\n333: result = await this.toolSandbox.execute(\n334: tool,\n335: params,\n336: this.context\n337: );\n338: } else {\n339: result = await tool.execute(params, this.context);\n340: }\n341: \n342: const duration = Date.now() - startTime;\n343: stats.totalDuration += duration;\n344: \n345: this.logger.debug('Tool execution succeeded', { name, duration });\n346: \n347: return result;\n348: } catch (error) {\n349: stats.failures++;\n350: \n351: this.logger.error('Tool execution failed', { name, error });\n352: \n353: throw error;\n354: }\n355: }\n356: \n357: /**\n358: * Safely parse and execute a tool from JSON arguments\n359: */\n360: async executeFromJSON(name: string, jsonArgs: string): Promise<any> {\n361: if (this.toolGuard) {\n362: const parseResult = this.toolGuard.parseToolArguments(name, jsonArgs);\n363: if (!parseResult.success) {\n364: throw new Error(`Tool \"${name}\": ${parseResult.error}`);\n365: }\n366: return this.execute(name, parseResult.data);\n367: }\n368: \n369: // No guard, parse directly\n370: const params = JSON.parse(jsonArgs);\n371: return this.execute(name, params);\n372: }\n373: \n374: /**\n375: * Execute multiple tools in sequence\n376: */\n377: async executeBatch(\n378: calls: Array<{ name: string; params: any }>\n379: ): Promise<any[]> {\n380: this.logger.debug('Executing batch', { count: calls.length });\n381: \n382: const results: any[] = [];\n383: \n384: for (const call of calls) {\n385: try {\n386: const result = await this.execute(call.name, call.params);\n387: results.push(result);\n388: } catch (error) {\n389: results.push({ error: String(error) });\n390: }\n391: }\n392: \n393: return results;\n394: }\n395: \n396: /**\n397: * Export tools in OpenAI format\n398: */\n399: toOpenAIFormat(): OpenAITool[] {\n400: return this.getAll().map((tool) => ({\n401: type: 'function' as const,\n402: function: {\n403: name: tool.name,\n404: description: tool.description,\n405: parameters: {\n406: type: 'object' as const,\n407: properties: tool.parameters.properties,\n408: required: tool.parameters.required,\n409: },\n410: },\n411: }));\n412: }\n413: \n414: /**\n415: * Export tools in Anthropic format\n416: */\n417: toAnthropicFormat(): AnthropicTool[] {\n418: return this.getAll().map((tool) => ({\n419: name: tool.name,\n420: description: tool.description,\n421: input_schema: {\n422: type: 'object' as const,\n423: properties: tool.parameters.properties,\n424: required: tool.parameters.required,\n425: },\n426: }));\n427: }\n428: \n429: /**\n430: * Get tool definitions (without execute function)\n431: */\n432: getDefinitions(): ToolDefinition[] {\n433: return this.getAll().map((tool) => ({\n434: name: tool.name,\n435: description: tool.description,\n436: parameters: tool.parameters,\n437: category: tool.category,\n438: cost: tool.cost,\n439: examples: tool.examples,\n440: schema: tool.schema,\n441: }));\n442: }\n443: \n444: /**\n445: * Get usage statistics for all tools\n446: */\n447: getUsageStats(): Map<string, ToolUsageStats> {\n448: const stats = new Map<string, ToolUsageStats>();\n449: \n450: this.usageStats.forEach((rawStats, name) => {\n451: stats.set(name, {\n452: calls: rawStats.calls,\n453: failures: rawStats.failures,\n454: successRate:\n455: rawStats.calls > 0\n456: ? (rawStats.calls - rawStats.failures) / rawStats.calls\n457: : 0,\n458: averageDuration:\n459: rawStats.calls > 0\n460: ? rawStats.totalDuration / rawStats.calls\n461: : undefined,\n462: });\n463: });\n464: \n465: return stats;\n466: }\n467: \n468: /**\n469: * Get most frequently used tools\n470: */\n471: getMostUsed(limit: number = 5): Tool[] {\n472: const sorted = Array.from(this.usageStats.entries())\n473: .sort((a, b) => b[1].calls - a[1].calls)\n474: .slice(0, limit)\n475: .map(([name]) => this.tools.get(name)!)\n476: .filter((tool) => tool !== undefined);\n477: \n478: return sorted;\n479: }\n480: \n481: /**\n482: * Get list of all categories\n483: */\n484: getCategories(): string[] {\n485: const categories = new Set<string>();\n486: \n487: this.getAll().forEach((tool) => {\n488: if (tool.category) {\n489: categories.add(tool.category);\n490: }\n491: });\n492: \n493: return Array.from(categories).sort();\n494: }\n495: \n496: /**\n497: * Update execution context\n498: */\n499: updateContext(context: Partial<ToolContext>): void {\n500: this.context = { ...this.context, ...context };\n501: this.logger.debug('Updated context', { keys: Object.keys(context) });\n502: }\n503: \n504: /**\n505: * Get current context\n506: */\n507: getContext(): ToolContext {\n508: return { ...this.context };\n509: }\n510: \n511: /**\n512: * Clear all tools\n513: */\n514: clear(): void {\n515: this.logger.debug('Clearing all tools');\n516: this.tools.clear();\n517: this.usageStats.clear();\n518: }\n519: \n520: /**\n521: * Unregister a specific tool\n522: */\n523: unregister(name: string): boolean {\n524: if (this.tools.has(name)) {\n525: this.tools.delete(name);\n526: this.usageStats.delete(name);\n527: this.logger.debug('Unregistered tool', { name });\n528: return true;\n529: }\n530: \n531: return false;\n532: }\n533: \n534: /**\n535: * Reset usage statistics\n536: */\n537: resetStats(): void {\n538: this.logger.debug('Resetting usage statistics');\n539: \n540: this.usageStats.forEach((stats) => {\n541: stats.calls = 0;\n542: stats.failures = 0;\n543: stats.totalDuration = 0;\n544: });\n545: }\n546: }\n547: \n548: export default ToolRegistry;\n549: \n550: ",
326
+ "name": "get_file_content"
327
+ },
328
+ {
329
+ "role": "tool",
330
+ "tool_call_id": "call_xF42grOUaWq5nk8KGv4ipWZN",
331
+ "content": "1: /**\n2: * Agentic Package - Context Manager\n3: *\n4: * Tracks and manages dynamically injected context in conversations.\n5: */\n6: \n7: import crypto from 'crypto';\n8: import { DEFAULT_LOGGER, wrapLogger, type Logger } from './logger';\n9: import type { DynamicContentItem, TrackedContextItem } from './types';\n10: \n11: /**\n12: * Context statistics\n13: */\n14: export interface ContextStats {\n15: totalItems: number;\n16: byCategory: Map<string, number>;\n17: byPriority: Map<string, number>;\n18: bySource: Map<string, number>;\n19: oldestTimestamp?: Date;\n20: newestTimestamp?: Date;\n21: }\n22: \n23: /**\n24: * ContextManager tracks and manages dynamically injected context.\n25: */\n26: export class ContextManager {\n27: private items: Map<string, TrackedContextItem>;\n28: private hashes: Set<string>;\n29: private logger: Logger;\n30: \n31: constructor(logger?: Logger) {\n32: this.items = new Map();\n33: this.hashes = new Set();\n34: this.logger = wrapLogger(logger || DEFAULT_LOGGER, 'ContextManager');\n35: }\n36: \n37: /**\n38: * Track a context item\n39: */\n40: track(item: DynamicContentItem, position: number): void {\n41: const hash = this.hashContent(item.content);\n42: \n43: if (!item.id && this.hashes.has(hash)) {\n44: this.logger.debug('Skipping duplicate context item by hash', { hash });\n45: return;\n46: }\n47: \n48: const id = item.id || this.generateId();\n49: \n50: const trackedItem: TrackedContextItem = {\n51: ...item,\n52: id,\n53: hash,\n54: position,\n55: injectedAt: new Date(),\n56: timestamp: item.timestamp || new Date(),\n57: priority: item.priority || 'medium',\n58: };\n59: \n60: this.items.set(id, trackedItem);\n61: this.hashes.add(hash);\n62: \n63: this.logger.debug('Tracked context item', {\n64: id,\n65: category: item.category,\n66: position,\n67: });\n68: }\n69: \n70: /**\n71: * Check if context with given ID exists\n72: */\n73: hasContext(id: string): boolean {\n74: return this.items.has(id);\n75: }\n76: \n77: /**\n78: * Check if content with given hash exists\n79: */\n80: hasContentHash(content: string): boolean {\n81: const hash = this.hashContent(content);\n82: return this.hashes.has(hash);\n83: }\n84: \n85: /**\n86: * Check if similar content exists (fuzzy match)\n87: */\n88: hasSimilarContent(\n89: content: string,\n90: similarityThreshold: number = 0.9\n91: ): boolean {\n92: const MAX_ITEMS_WARNING = 1000;\n93: if (this.items.size > MAX_ITEMS_WARNING) {\n94: this.logger.warn(\n95: 'Large number of context items, similarity check may be slow',\n96: {\n97: count: this.items.size,\n98: threshold: MAX_ITEMS_WARNING,\n99: }\n100: );\n101: }\n102: \n103: const normalized = this.normalizeContent(content);\n104: \n105: for (const item of this.items.values()) {\n106: const itemNormalized = this.normalizeContent(item.content || '');\n107: \n108: if (normalized === itemNormalized) {\n109: return true;\n110: }\n111: \n112: const longer =\n113: normalized.length > itemNormalized.length\n114: ? normalized\n115: : itemNormalized;\n116: const shorter =\n117: normalized.length <= itemNormalized.length\n118: ? normalized\n119: : itemNormalized;\n120: \n121: const lengthRatio = shorter.length / longer.length;\n122: \n123: if (lengthRatio >= similarityThreshold) {\n124: if (longer.includes(shorter)) {\n125: return true;\n126: }\n127: }\n128: }\n129: \n130: return false;\n131: }\n132: \n133: /**\n134: * Get context item by ID\n135: */\n136: get(id: string): TrackedContextItem | undefined {\n137: return this.items.get(id);\n138: }\n139: \n140: /**\n141: * Get all tracked context items\n142: */\n143: getAll(): TrackedContextItem[] {\n144: return Array.from(this.items.values());\n145: }\n146: \n147: /**\n148: * Get context items by category\n149: */\n150: getByCategory(category: string): TrackedContextItem[] {\n151: return this.getAll().filter((item) => item.category === category);\n152: }\n153: \n154: /**\n155: * Get context items by priority\n156: */\n157: getByPriority(priority: 'high' | 'medium' | 'low'): TrackedContextItem[] {\n158: return this.getAll().filter((item) => item.priority === priority);\n159: }\n160: \n161: /**\n162: * Get context items by source\n163: */\n164: getBySource(source: string): TrackedContextItem[] {\n165: return this.getAll().filter((item) => item.source === source);\n166: }\n167: \n168: /**\n169: * Get all categories\n170: */\n171: getCategories(): string[] {\n172: const categories = new Set<string>();\n173: this.items.forEach((item) => {\n174: if (item.category) {\n175: categories.add(item.category);\n176: }\n177: });\n178: return Array.from(categories).sort();\n179: }\n180: \n181: /**\n182: * Get context statistics\n183: */\n184: getStats(): ContextStats {\n185: const byCategory = new Map<string, number>();\n186: const byPriority = new Map<string, number>();\n187: const bySource = new Map<string, number>();\n188: let oldestTimestamp: Date | undefined;\n189: let newestTimestamp: Date | undefined;\n190: \n191: this.items.forEach((item) => {\n192: if (item.category) {\n193: byCategory.set(\n194: item.category,\n195: (byCategory.get(item.category) || 0) + 1\n196: );\n197: }\n198: \n199: const priority = item.priority || 'medium';\n200: byPriority.set(priority, (byPriority.get(priority) || 0) + 1);\n201: \n202: if (item.source) {\n203: bySource.set(item.source, (bySource.get(item.source) || 0) + 1);\n204: }\n205: \n206: if (item.timestamp) {\n207: if (!oldestTimestamp || item.timestamp < oldestTimestamp) {\n208: oldestTimestamp = item.timestamp;\n209: }\n210: if (!newestTimestamp || item.timestamp > newestTimestamp) {\n211: newestTimestamp = item.timestamp;\n212: }\n213: }\n214: });\n215: \n216: return {\n217: totalItems: this.items.size,\n218: byCategory,\n219: byPriority,\n220: bySource,\n221: oldestTimestamp,\n222: newestTimestamp,\n223: };\n224: }\n225: \n226: /**\n227: * Remove context item by ID\n228: */\n229: remove(id: string): boolean {\n230: const item = this.items.get(id);\n231: if (item) {\n232: this.items.delete(id);\n233: this.hashes.delete(item.hash);\n234: this.logger.debug('Removed context item', { id });\n235: return true;\n236: }\n237: return false;\n238: }\n239: \n240: /**\n241: * Clear all tracked context\n242: */\n243: clear(): void {\n244: this.items.clear();\n245: this.hashes.clear();\n246: this.logger.debug('Cleared all context');\n247: }\n248: \n249: private generateId(): string {\n250: return `ctx-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;\n251: }\n252: \n253: private hashContent(content: string): string {\n254: // Use 32 characters (128 bits) for sufficient collision resistance\n255: return crypto\n256: .createHash('sha256')\n257: .update(content)\n258: .digest('hex')\n259: .substring(0, 32);\n260: }\n261: \n262: private normalizeContent(content: string): string {\n263: return content.replace(/\\s+/g, ' ').trim().toLowerCase();\n264: }\n265: }\n266: \n267: export default ContextManager;\n268: \n269: ",
332
+ "name": "get_file_content"
333
+ },
334
+ {
335
+ "role": "tool",
336
+ "tool_call_id": "call_QmCpAfTVz0IOk4muM0mrG7Wz",
337
+ "content": "1: /**\n2: * Agentic Package\n3: *\n4: * Components for building agentic AI workflows:\n5: * - Tool registry and execution\n6: * - Context management\n7: * - Conversation state tracking\n8: *\n9: * @packageDocumentation\n10: */\n11: \n12: // Types\n13: export type {\n14: ConversationMessage,\n15: ToolCall,\n16: Model,\n17: ToolUsagePolicy,\n18: IterationAction,\n19: ToolCallAction,\n20: InjectOptions,\n21: DynamicContentItem,\n22: TrackedContextItem,\n23: LLMClient,\n24: StrategyState,\n25: Insight,\n26: ToolResult,\n27: PhaseResult,\n28: TokenUsage,\n29: CompressionStats,\n30: CompressionStrategy,\n31: TokenBudgetConfig,\n32: LogFormat,\n33: LogConfig,\n34: } from './types';\n35: \n36: // Logger\n37: export type { Logger } from './logger';\n38: export { DEFAULT_LOGGER, wrapLogger, LIBRARY_NAME } from './logger';\n39: \n40: // Tool Registry\n41: export type {\n42: ToolParameter,\n43: ToolContext,\n44: ToolExample,\n45: ToolCost,\n46: Tool,\n47: OpenAITool,\n48: AnthropicTool,\n49: ToolExecutionResult,\n50: ToolUsageStats,\n51: ToolDefinition,\n52: } from './tools';\n53: export { ToolRegistry } from './tools';\n54: \n55: // Tool Security\n56: export type {\n57: ToolSecurityConfig,\n58: ToolValidationResult,\n59: ToolGuardEvents,\n60: } from './tool-guard';\n61: export { ToolGuard } from './tool-guard';\n62: \n63: // Tool Sandboxing\n64: export type {\n65: SandboxedContext,\n66: SandboxOptions,\n67: ToolSandboxEvents,\n68: } from './tool-sandbox';\n69: export { ToolSandbox, createSecureTool } from './tool-sandbox';\n70: \n71: // Context Manager\n72: export type { ContextStats } from './context-manager';\n73: export { ContextManager } from './context-manager';\n74: \n75: /**\n76: * Package version\n77: */\n78: export const VERSION = '0.0.1';\n79: ",
338
+ "name": "get_file_content"
339
+ },
340
+ {
341
+ "role": "tool",
342
+ "tool_call_id": "call_hUMvADzeQeCnLDxPgQo2Qsuq",
343
+ "content": "import { describe, it, expect, beforeEach, vi } from 'vitest';\nimport { z } from 'zod';\nimport { ToolGuard, ToolGuardEvents } from '../src/tool-guard';\nimport { ToolRegistry, Tool } from '../src/tools';\n\ndescribe('ToolGuard', () => {\n let guard: ToolGuard;\n let events: {\n validationFailed: { toolName: string; message: string }[];\n executionBlocked: { toolName: string; reason: string }[];\n prototypePollution: string[];\n };\n\n beforeEach(() => {\n events = {\n validationFailed: [],\n executionBlocked: [],\n prototypePollution: [],\n };\n\n const eventHandlers: ToolGuardEvents = {\n onValidationFailed: (toolName, message) =>\n events.validationFailed.push({ toolName, message }),\n onExecutionBlocked: (toolName, reason) =>\n events.executionBlocked.push({ toolName, reason }),\n onPrototypePollution: (toolName) =>\n events.prototypePollution.push(toolName),\n };\n\n guard = new ToolGuard({}, undefined, eventHandlers);\n });\n\n describe('validateParams', () => {\n it('should validate params against schema', () => {\n const schema = z.object({\n path: z.string(),\n recursive: z.boolean().optional(),\n });\n\n const result = guard.validateParams('read_file', { path: '/test' }, schema);\n expect(result.success).toBe(true);\n expect(result.data).toEqual({ path: '/test' });\n });\n\n it('should reject invalid params', () => {\n const schema = z.object({ path: z.string() });\n\n const result = guard.validateParams('read_file', { path: 123 }, schema);\n expect(result.success).toBe(false);\n expect(result.error).toBe('Parameter validation failed');\n expect(result.violations).toBeDefined();\n expect(result.violations!.length).toBeGreaterThan(0);\n });\n\n it('should call onValidationFailed event', () => {\n const schema = z.object({ path: z.string() });\n\n guard.validateParams('read_file', { path: 123 }, schema);\n\n expect(events.validationFailed).toHaveLength(1);\n expect(events.validationFailed[0].toolName).toBe('read_file');\n });\n\n it('should bypass validation when disabled', () => {\n const disabledGuard = new ToolGuard({ validateParams: false });\n const schema = z.object({ path: z.string() });\n\n const result = disabledGuard.validateParams(\n 'read_file',\n { path: 123 },\n schema\n );\n expect(result.success).toBe(true);\n expect(result.data).toEqual({ path: 123 });\n });\n\n it('should bypass validation when guard is disabled', () => {\n const disabledGuard = new ToolGuard({ enabled: false });\n const schema = z.object({ path: z.string() });\n\n const result = disabledGuard.validateParams(\n 'read_file',\n { path: 123 },\n schema\n );\n expect(result.success).toBe(true);\n });\n\n it('should handle complex schemas', () => {\n const schema = z.object({\n files: z.array(z.string()),\n options: z.object({\n recursive: z.boolean(),\n maxDepth: z.number().optional(),\n }),\n });\n\n const result = guard.validateParams('process_files', {\n files: ['a.txt', 'b.txt'],\n options: { recursive: true },\n }, schema);\n\n expect(result.success).toBe(true);\n });\n });\n\n describe('parseToolArguments', () => {\n it('should parse valid JSON', () => {\n const result = guard.parseToolArguments(\n 'test',\n '{\"path\": \"/test\", \"recursive\": true}'\n );\n\n expect(result.success).toBe(true);\n expect(result.data).toEqual({ path: '/test', recursive: true });\n });\n\n it('should reject invalid JSON', () => {\n const result = guard.parseToolArguments('test', 'not valid json');\n\n expect(result.success).toBe(false);\n expect(result.error).toBe('Invalid JSON in tool arguments');\n });\n\n it('should detect prototype pollution - __proto__', () => {\n const result = guard.parseToolArguments(\n 'test',\n '{\"__proto__\": {\"polluted\": true}}'\n );\n\n expect(result.success).toBe(false);\n expect(result.error).toContain('malicious content');\n expect(events.prototypePollution).toContain('test');\n });\n\n it('should detect prototype pollution - constructor', () => {\n const result = guard.parseToolArguments(\n 'test',\n '{\"constructor\": {\"prototype\": {\"polluted\": true}}}'\n );\n\n expect(result.success).toBe(false);\n });\n\n it('should detect nested prototype pollution', () => {\n const result = guard.parseToolArguments(\n 'test',\n '{\"a\": {\"b\": {\"__proto__\": {\"polluted\": true}}}}'\n );\n\n expect(result.success).toBe(false);\n });\n\n it('should reject non-object JSON', () => {\n const result = guard.parseToolArguments('test', '\"just a string\"');\n expect(result.success).toBe(false);\n expect(result.error).toBe('Tool arguments must be a JSON object');\n });\n\n it('should reject array JSON', () => {\n const result = guard.parseToolArguments('test', '[1, 2, 3]');\n expect(result.success).toBe(false);\n expect(result.error).toBe('Tool arguments must be a JSON object');\n });\n\n it('should reject null JSON', () => {\n const result = guard.parseToolArguments('test', 'null');\n expect(result.success).toBe(false);\n });\n\n it('should bypass checks when disabled', () => {\n const disabledGuard = new ToolGuard({ enabled: false });\n const result = disabledGuard.parseToolArguments(\n 'test',\n '{\"__proto__\": {\"polluted\": true}}'\n );\n\n expect(result.success).toBe(true);\n });\n });\n\n describe('isToolAllowed', () => {\n it('should allow tools not in deny list', () => {\n const guardWithDeny = new ToolGuard({ deniedTools: ['dangerous_tool'] });\n\n expect(guardWithDeny.isToolAllowed('safe_tool')).toBe(true);\n expect(guardWithDeny.isToolAllowed('dangerous_tool')).toBe(false);\n });\n\n it('should enforce allow list when specified', () => {\n const guardWithAllow = new ToolGuard({\n allowedTools: ['read_file', 'write_file'],\n });\n\n expect(guardWithAllow.isToolAllowed('read_file')).toBe(true);\n expect(guardWithAllow.isToolAllowed('delete_file')).toBe(false);\n });\n\n it('should call onExecutionBlocked for denied tools', () => {\n guard = new ToolGuard(\n { deniedTools: ['blocked_tool'] },\n undefined,\n {\n onExecutionBlocked: (toolName, reason) =>\n events.executionBlocked.push({ toolName, reason }),\n }\n );\n\n guard.isToolAllowed('blocked_tool');\n\n expect(events.executionBlocked).toHaveLength(1);\n expect(events.executionBlocked[0].toolName).toBe('blocked_tool');\n });\n\n it('should allow all tools when disabled', () => {\n const disabledGuard = new ToolGuard({ enabled: false, deniedTools: ['blocked'] });\n\n expect(disabledGuard.isToolAllowed('blocked')).toBe(true);\n });\n });\n\n describe('configuration', () => {\n it('should return config copy', () => {\n const config = guard.getConfig();\n expect(config.enabled).toBe(true);\n expect(config.validateParams).toBe(true);\n });\n\n it('should update config', () => {\n guard.updateConfig({ maxExecutionTime: 60000 });\n expect(guard.getConfig().maxExecutionTime).toBe(60000);\n });\n\n it('should add tool to deny list', () => {\n guard.denyTool('new_blocked_tool');\n expect(guard.isToolAllowed('new_blocked_tool')).toBe(false);\n });\n\n it('should remove tool from deny list', () => {\n guard = new ToolGuard({ deniedTools: ['blocked'] });\n guard.allowTool('blocked');\n expect(guard.isToolAllowed('blocked')).toBe(true);\n });\n\n it('should check enabled status', () => {\n expect(guard.isEnabled()).toBe(true);\n guard.setEnabled(false);\n expect(guard.isEnabled()).toBe(false);\n });\n });\n});\n\ndescribe('ToolRegistry with ToolGuard', () => {\n let registry: ToolRegistry;\n let guard: ToolGuard;\n\n const createTestTool = (name: string, schema?: z.ZodSchema<any>): Tool => ({\n name,\n description: `Test tool ${name}`,\n parameters: {\n type: 'object',\n properties: {\n input: { type: 'string', description: 'Input value' },\n },\n },\n execute: async (params) => ({ result: params.input }),\n schema,\n });\n\n beforeEach(() => {\n guard = new ToolGuard({ deniedTools: ['blocked_tool'] });\n registry = ToolRegistry.create().withSecurity(guard);\n });\n\n it('should configure security guard', () => {\n expect(registry.getSecurityGuard()).toBe(guard);\n });\n\n it('should block denied tools', async () => {\n registry.register(createTestTool('blocked_tool'));\n\n await expect(\n registry.execute('blocked_tool', { input: 'test' })\n ).rejects.toThrow('Tool \"blocked_tool\" is not allowed');\n });\n\n it('should allow non-denied tools', async () => {\n registry.register(createTestTool('allowed_tool'));\n\n const result = await registry.execute('allowed_tool', { input: 'test' });\n expect(result).toEqual({ result: 'test' });\n });\n\n it('should validate params with schema', async () => {\n const schema = z.object({ input: z.string() });\n registry.register(createTestTool('validated_tool', schema));\n\n // Valid params\n const result = await registry.execute('validated_tool', { input: 'test' });\n expect(result).toEqual({ result: 'test' });\n\n // Invalid params\n await expect(\n registry.execute('validated_tool', { input: 123 })\n ).rejects.toThrow('Parameter validation failed');\n });\n\n it('should work without guard', async () => {\n const noGuardRegistry = ToolRegistry.create();\n noGuardRegistry.register(createTestTool('test_tool'));\n\n const result = await noGuardRegistry.execute('test_tool', { input: 'test' });\n expect(result).toEqual({ result: 'test' });\n });\n\n describe('executeFromJSON', () => {\n it('should parse and execute from JSON', async () => {\n registry.register(createTestTool('json_tool'));\n\n const result = await registry.executeFromJSON(\n 'json_tool',\n '{\"input\": \"test\"}'\n );\n expect(result).toEqual({ result: 'test' });\n });\n\n it('should reject malicious JSON', async () => {\n registry.register(createTestTool('json_tool'));\n\n await expect(\n registry.executeFromJSON(\n 'json_tool',\n '{\"__proto__\": {\"polluted\": true}}'\n )\n ).rejects.toThrow('malicious content');\n });\n\n it('should reject invalid JSON', async () => {\n registry.register(createTestTool('json_tool'));\n\n await expect(\n registry.executeFromJSON('json_tool', 'not json')\n ).rejects.toThrow('Invalid JSON');\n });\n\n it('should work without guard', async () => {\n const noGuardRegistry = ToolRegistry.create();\n noGuardRegistry.register(createTestTool('json_tool'));\n\n const result = await noGuardRegistry.executeFromJSON(\n 'json_tool',\n '{\"input\": \"test\"}'\n );\n expect(result).toEqual({ result: 'test' });\n });\n });\n});\n\n",
344
+ "name": "get_file_content"
345
+ },
346
+ {
347
+ "role": "tool",
348
+ "tool_call_id": "call_0JmXjZT8Hw9Ttbedf3njKemd",
349
+ "content": "import { describe, it, expect, beforeEach, vi } from 'vitest';\nimport { z } from 'zod';\nimport {\n ToolSandbox,\n ToolSandboxEvents,\n createSecureTool,\n} from '../src/tool-sandbox';\nimport { ToolGuard } from '../src/tool-guard';\nimport { ToolRegistry, Tool } from '../src/tools';\n\ndescribe('ToolSandbox', () => {\n let sandbox: ToolSandbox;\n let events: {\n concurrencyExceeded: { toolName: string; activeCount: number }[];\n timeout: { toolName: string; timeoutMs: number }[];\n outputSizeExceeded: { toolName: string; size: number; maxSize: number }[];\n cancelled: string[];\n };\n\n const createTestTool = (\n name: string,\n executeFn?: (params: any, context?: any) => Promise<any>\n ): Tool => ({\n name,\n description: `Test tool ${name}`,\n parameters: {\n type: 'object',\n properties: {\n input: { type: 'string', description: 'Input value' },\n },\n },\n execute: executeFn || (async (params) => ({ result: params.input })),\n });\n\n beforeEach(() => {\n events = {\n concurrencyExceeded: [],\n timeout: [],\n outputSizeExceeded: [],\n cancelled: [],\n };\n\n const eventHandlers: ToolSandboxEvents = {\n onConcurrencyExceeded: (toolName, activeCount) =>\n events.concurrencyExceeded.push({ toolName, activeCount }),\n onTimeout: (toolName, timeoutMs) =>\n events.timeout.push({ toolName, timeoutMs }),\n onOutputSizeExceeded: (toolName, size, maxSize) =>\n events.outputSizeExceeded.push({ toolName, size, maxSize }),\n onCancelled: (executionId) => events.cancelled.push(executionId),\n };\n\n sandbox = new ToolSandbox({}, undefined, eventHandlers);\n });\n\n describe('basic execution', () => {\n it('should execute a tool successfully', async () => {\n const tool = createTestTool('test_tool');\n const result = await sandbox.execute(tool, { input: 'hello' }, {});\n expect(result).toEqual({ result: 'hello' });\n });\n\n it('should pass context to tool', async () => {\n const tool = createTestTool('context_tool', async (params, context) => ({\n input: params.input,\n workingDir: context?.workingDirectory,\n }));\n\n const result = await sandbox.execute(\n tool,\n { input: 'test' },\n { workingDirectory: '/test' }\n );\n\n expect(result.workingDir).toBe('/test');\n });\n\n it('should provide sandbox context', async () => {\n const tool = createTestTool('sandbox_tool', async (params, context) => ({\n hasSandbox: !!context?.sandbox,\n executionId: context?.sandbox?.executionId,\n }));\n\n const result = await sandbox.execute(tool, {}, {});\n\n expect(result.hasSandbox).toBe(true);\n expect(result.executionId).toMatch(/^exec-\\d+-\\d+$/);\n });\n\n it('should bypass sandbox when disabled', async () => {\n const disabledSandbox = new ToolSandbox({ sandboxExecution: false });\n const tool = createTestTool('test_tool', async (params, context) => ({\n hasSandbox: !!context?.sandbox,\n }));\n\n const result = await disabledSandbox.execute(tool, {}, {});\n expect(result.hasSandbox).toBe(false);\n });\n });\n\n describe('timeout enforcement', () => {\n it('should timeout long-running tools', async () => {\n const timeoutSandbox = new ToolSandbox(\n { maxExecutionTime: 100 },\n undefined,\n {\n onTimeout: (toolName, timeoutMs) =>\n events.timeout.push({ toolName, timeoutMs }),\n }\n );\n\n const slowTool = createTestTool(\n 'slow_tool',\n async () => new Promise((r) => setTimeout(r, 5000))\n );\n\n await expect(\n timeoutSandbox.execute(slowTool, {}, {})\n ).rejects.toThrow('timed out');\n\n expect(events.timeout).toHaveLength(1);\n expect(events.timeout[0].toolName).toBe('slow_tool');\n });\n\n it('should allow custom timeout per execution', async () => {\n const tool = createTestTool(\n 'slow_tool',\n async () => new Promise((r) => setTimeout(r, 200))\n );\n\n // Should succeed with longer timeout\n const result = await sandbox.execute(tool, {}, {}, {\n maxExecutionTime: 500,\n });\n expect(result).toBeUndefined(); // setTimeout resolves with undefined\n });\n });\n\n describe('concurrency limits', () => {\n it('should limit concurrent executions', async () => {\n const limitedSandbox = new ToolSandbox(\n { maxConcurrentCalls: 2 },\n undefined,\n {\n onConcurrencyExceeded: (toolName, activeCount) =>\n events.concurrencyExceeded.push({ toolName, activeCount }),\n }\n );\n\n const slowTool = createTestTool(\n 'slow_tool',\n async () => new Promise((r) => setTimeout(r, 500))\n );\n\n // Start two executions\n const p1 = limitedSandbox.execute(slowTool, {}, {});\n const p2 = limitedSandbox.execute(slowTool, {}, {});\n\n // Third should fail\n await expect(\n limitedSandbox.execute(slowTool, {}, {})\n ).rejects.toThrow('Too many concurrent');\n\n expect(events.concurrencyExceeded).toHaveLength(1);\n\n // Wait for others to complete\n await Promise.all([p1, p2]);\n });\n\n it('should track active execution count', async () => {\n const slowTool = createTestTool(\n 'slow_tool',\n async () => new Promise((r) => setTimeout(r, 100))\n );\n\n expect(sandbox.getActiveCount()).toBe(0);\n\n const p = sandbox.execute(slowTool, {}, {});\n expect(sandbox.getActiveCount()).toBe(1);\n\n await p;\n expect(sandbox.getActiveCount()).toBe(0);\n });\n });\n\n describe('output size limits', () => {\n it('should reject oversized output', async () => {\n const largeTool = createTestTool('large_tool', async () => ({\n data: 'x'.repeat(1024 * 1024 * 2), // 2MB string\n }));\n\n await expect(\n sandbox.execute(largeTool, {}, {}, { maxOutputSize: 1024 * 100 })\n ).rejects.toThrow('exceeded maximum size');\n\n expect(events.outputSizeExceeded).toHaveLength(1);\n });\n\n it('should allow output within limits', async () => {\n const smallTool = createTestTool('small_tool', async () => ({\n data: 'small output',\n }));\n\n const result = await sandbox.execute(smallTool, {}, {});\n expect(result.data).toBe('small output');\n });\n });\n\n describe('execution hooks', () => {\n it('should call onBeforeExecution', async () => {\n const beforeCalls: { tool: string; params: any }[] = [];\n const tool = createTestTool('test_tool');\n\n await sandbox.execute(tool, { input: 'test' }, {}, {\n onBeforeExecution: async (t, params) => {\n beforeCalls.push({ tool: t.name, params });\n },\n });\n\n expect(beforeCalls).toHaveLength(1);\n expect(beforeCalls[0].tool).toBe('test_tool');\n });\n\n it('should call onAfterExecution on success', async () => {\n const afterCalls: { tool: string; result: any; error?: Error }[] = [];\n const tool = createTestTool('test_tool');\n\n await sandbox.execute(tool, { input: 'test' }, {}, {\n onAfterExecution: async (t, result, error) => {\n afterCalls.push({ tool: t.name, result, error });\n },\n });\n\n expect(afterCalls).toHaveLength(1);\n expect(afterCalls[0].result).toEqual({ result: 'test' });\n expect(afterCalls[0].error).toBeUndefined();\n });\n\n it('should call onAfterExecution on error', async () => {\n const afterCalls: { tool: string; result: any; error?: Error }[] = [];\n const failingTool = createTestTool('failing_tool', async () => {\n throw new Error('Tool failed');\n });\n\n await expect(\n sandbox.execute(failingTool, {}, {}, {\n onAfterExecution: async (t, result, error) => {\n afterCalls.push({ tool: t.name, result, error });\n },\n })\n ).rejects.toThrow('Tool failed');\n\n expect(afterCalls).toHaveLength(1);\n expect(afterCalls[0].error?.message).toBe('Tool failed');\n });\n });\n\n describe('cancellation', () => {\n it('should track active execution IDs', async () => {\n const tool = createTestTool('fast_tool');\n \n // Before execution\n expect(sandbox.getActiveExecutionIds()).toHaveLength(0);\n \n // During execution (use a slightly slow tool)\n const slowTool = createTestTool(\n 'slow_tool',\n async () => {\n await new Promise((r) => setTimeout(r, 50));\n return { done: true };\n }\n );\n \n const p = sandbox.execute(slowTool, {}, {});\n expect(sandbox.getActiveExecutionIds()).toHaveLength(1);\n expect(sandbox.getActiveExecutionIds()[0]).toMatch(/^exec-\\d+-\\d+$/);\n \n await p;\n expect(sandbox.getActiveExecutionIds()).toHaveLength(0);\n });\n\n it('should clear active executions on cancelAll', async () => {\n const slowTool = createTestTool(\n 'slow_tool',\n async () => new Promise((r) => setTimeout(r, 100))\n );\n\n // Start execution\n sandbox.execute(slowTool, {}, {}).catch(() => {}); // Ignore rejection\n sandbox.execute(slowTool, {}, {}).catch(() => {}); // Ignore rejection\n\n expect(sandbox.getActiveCount()).toBe(2);\n\n // Cancel all\n sandbox.cancelAll();\n\n // Active count should be 0 immediately after cancelAll\n expect(sandbox.getActiveCount()).toBe(0);\n expect(events.cancelled).toHaveLength(2);\n });\n\n it('should cancel specific execution by ID', async () => {\n const slowTool = createTestTool(\n 'slow_tool',\n async () => new Promise((r) => setTimeout(r, 100))\n );\n\n // Start execution\n sandbox.execute(slowTool, {}, {}).catch(() => {}); // Ignore rejection\n const ids = sandbox.getActiveExecutionIds();\n\n expect(ids).toHaveLength(1);\n\n // Cancel specific\n const cancelled = sandbox.cancel(ids[0]);\n expect(cancelled).toBe(true);\n expect(sandbox.getActiveCount()).toBe(0);\n expect(events.cancelled).toContain(ids[0]);\n });\n\n it('should return false when cancelling non-existent execution', () => {\n const cancelled = sandbox.cancel('non-existent-id');\n expect(cancelled).toBe(false);\n });\n });\n\n describe('configuration', () => {\n it('should check enabled status', () => {\n expect(sandbox.isEnabled()).toBe(true);\n\n const disabledSandbox = new ToolSandbox({\n enabled: false,\n sandboxExecution: true,\n });\n expect(disabledSandbox.isEnabled()).toBe(false);\n });\n\n it('should return config copy', () => {\n const config = sandbox.getConfig();\n expect(config.maxExecutionTime).toBe(30000);\n expect(config.maxConcurrentCalls).toBe(10);\n });\n\n it('should update config', () => {\n sandbox.updateConfig({ maxExecutionTime: 60000 });\n expect(sandbox.getConfig().maxExecutionTime).toBe(60000);\n });\n });\n});\n\ndescribe('createSecureTool', () => {\n it('should wrap tool with security checks', async () => {\n const guard = new ToolGuard({ deniedTools: ['blocked'] });\n const sandbox = new ToolSandbox();\n\n const tool: Tool = {\n name: 'test_tool',\n description: 'Test',\n parameters: { type: 'object', properties: {} },\n execute: async () => ({ success: true }),\n };\n\n const secureTool = createSecureTool(tool, sandbox, guard);\n\n const result = await secureTool.execute({}, {});\n expect(result).toEqual({ success: true });\n });\n\n it('should block denied tools', async () => {\n const guard = new ToolGuard({ deniedTools: ['blocked_tool'] });\n const sandbox = new ToolSandbox();\n\n const tool: Tool = {\n name: 'blocked_tool',\n description: 'Blocked',\n parameters: { type: 'object', properties: {} },\n execute: async () => ({ success: true }),\n };\n\n const secureTool = createSecureTool(tool, sandbox, guard);\n\n await expect(secureTool.execute({}, {})).rejects.toThrow('not allowed');\n });\n\n it('should validate params with schema', async () => {\n const guard = new ToolGuard();\n const sandbox = new ToolSandbox();\n\n const tool: Tool = {\n name: 'validated_tool',\n description: 'Validated',\n parameters: { type: 'object', properties: {} },\n execute: async (params) => ({ input: params.input }),\n schema: z.object({ input: z.string() }),\n };\n\n const secureTool = createSecureTool(tool, sandbox, guard);\n\n // Valid params\n const result = await secureTool.execute({ input: 'test' }, {});\n expect(result.input).toBe('test');\n\n // Invalid params\n await expect(\n secureTool.execute({ input: 123 }, {})\n ).rejects.toThrow('Validation failed');\n });\n});\n\ndescribe('ToolRegistry with ToolSandbox', () => {\n it('should configure sandbox', () => {\n const sandbox = new ToolSandbox();\n const registry = ToolRegistry.create().withSandbox(sandbox);\n\n expect(registry.getSandbox()).toBe(sandbox);\n });\n\n it('should execute tools through sandbox', async () => {\n const sandbox = new ToolSandbox();\n const registry = ToolRegistry.create().withSandbox(sandbox);\n\n const tool: Tool = {\n name: 'sandboxed_tool',\n description: 'Test',\n parameters: { type: 'object', properties: {} },\n execute: async (params, context) => ({\n hasSandbox: !!context?.sandbox,\n }),\n };\n\n registry.register(tool);\n\n const result = await registry.execute('sandboxed_tool', {});\n expect(result.hasSandbox).toBe(true);\n });\n\n it('should enforce sandbox timeout', async () => {\n const sandbox = new ToolSandbox({ maxExecutionTime: 100 });\n const registry = ToolRegistry.create().withSandbox(sandbox);\n\n const slowTool: Tool = {\n name: 'slow_tool',\n description: 'Slow',\n parameters: { type: 'object', properties: {} },\n execute: async () => new Promise((r) => setTimeout(r, 5000)),\n };\n\n registry.register(slowTool);\n\n await expect(registry.execute('slow_tool', {})).rejects.toThrow(\n 'timed out'\n );\n });\n\n it('should work with both guard and sandbox', async () => {\n const guard = new ToolGuard({ deniedTools: ['blocked'] });\n const sandbox = new ToolSandbox();\n const registry = ToolRegistry.create()\n .withSecurity(guard)\n .withSandbox(sandbox);\n\n const tool: Tool = {\n name: 'secure_tool',\n description: 'Secure',\n parameters: { type: 'object', properties: {} },\n execute: async (params, context) => ({\n hasSandbox: !!context?.sandbox,\n }),\n schema: z.object({ input: z.string().optional() }),\n };\n\n registry.register(tool);\n\n const result = await registry.execute('secure_tool', {});\n expect(result.hasSandbox).toBe(true);\n });\n});\n\n",
350
+ "name": "get_file_content"
351
+ },
352
+ {
353
+ "role": "assistant",
354
+ "content": "RELEASE_NOTES:\n{\n \"title\": \"v1.0.0: Tool execution security (ToolGuard/ToolSandbox), safer defaults, and improved tool invocation\",\n \"body\": \"## Overview\\nThis release focuses on making tool execution safer and more controllable in agentic workflows. It introduces opt-in security and sandboxing layers for tools, changes the default logging behavior to reduce accidental information disclosure, and adds safer JSON argument handling for tool calls.\\n\\n## Highlights\\n\\n### Tool execution security (new)\\nYou can now add a security layer to tool execution via **ToolGuard**:\\n\\n- **Allow/deny lists** for tools (`allowedTools`, `deniedTools`)\\n- **Zod-based parameter validation** (when a tool provides a `schema`)\\n- **Safe JSON argument parsing** with **prototype pollution detection** (`__proto__`, `constructor`, `prototype`)\\n- **Security event hooks** for monitoring/telemetry (validation failures, blocked execution, prototype pollution attempts)\\n\\nNew/updated APIs:\\n- `ToolRegistry.withSecurity(guard)`\\n- `ToolRegistry.getSecurityGuard()`\\n- `ToolRegistry.executeFromJSON(name, jsonArgs)` (uses `ToolGuard` parsing when configured)\\n- `Tool` and `ToolDefinition` now support an optional `schema?: ZodSchema<any>`\\n\\nExample:\\n```ts\\nimport { ToolRegistry, ToolGuard } from '@riotprompt/agentic';\\nimport { z } from 'zod';\\n\\nconst registry = ToolRegistry.create();\\nconst guard = new ToolGuard({ deniedTools: ['rm_rf'] });\\n\\nregistry.withSecurity(guard);\\n\\nregistry.register({\\n name: 'read_file',\\n description: 'Read a file',\\n parameters: { type: 'object', properties: { path: { type: 'string', description: 'Path' } } },\\n schema: z.object({ path: z.string().min(1) }),\\n execute: async ({ path }) => path,\\n});\\n\\nawait registry.executeFromJSON('read_file', '{\\\"path\\\":\\\"./README.md\\\"}');\\n```\\n\\n### Tool sandboxing (new)\\nYou can now enforce runtime constraints on tool execution via **ToolSandbox**:\\n\\n- **Timeout enforcement** (`maxExecutionTime`)\\n- **Concurrency limits** (`maxConcurrentCalls`)\\n- **Output size limits** (per-execution `maxOutputSize`)\\n- **Cancellation support** (`cancel(executionId)`, `cancelAll()`)\\n- Tools receive an extended `context.sandbox` object (execution id, limits, allowed operations set)\\n\\nNew/updated APIs:\\n- `ToolRegistry.withSandbox(sandbox)`\\n- `ToolRegistry.getSandbox()`\\n- `ToolSandbox.execute(tool, params, context, options?)`\\n- `createSecureTool(tool, sandbox, guard)` helper to wrap individual tools\\n\\n## Behavior changes / improvements\\n\\n### Logging is now silent by default\\nThe logging system is now backed by `@fjell/logging`, and the library’s **default logger does not emit logs unless explicitly enabled**. This reduces the risk of sensitive data leaking via logs.\\n\\nTo enable logging:\\n- `AGENTIC_LOGGING=true`, or\\n- `DEBUG=*agentic*`, or\\n- `NODE_ENV=development`\\n\\n### Context deduplication hash strengthened\\n`ContextManager` now uses **32 hex characters (128 bits)** of the SHA-256 digest (previously shorter). This reduces the likelihood of collisions when deduplicating context content.\\n\\n## Documentation\\n- Added `SECURITY.md` describing tool execution security concepts and recommended operational practices.\\n- Added a `<!-- v1.0.0 -->` marker comment to `README.md`.\\n\\n## Developer experience\\n- `precommit` now runs `build` before lint and tests: `npm run build && npm run lint && npm run test`.\\n- Build/test config files were reformatted (no functional behavior intended beyond configuration maintenance).\\n\\n## Dependencies\\n- Added runtime dependency: `@fjell/logging`.\\n\\n## Breaking changes / upgrade notes\\n- **Version bump to 1.0.0**: this is a major release.\\n- **Default logging behavior change**: if you relied on logs being emitted by default, you must now explicitly enable logging (see “Logging is now silent by default”).\\n- **Context hashing change**: if you persist or compare `ContextManager` content hashes externally, the hash values will differ going forward.\\n\\n## Full change categories\\n- **Security**: ToolGuard + ToolSandbox, schema validation, safer JSON parsing, runtime limits.\\n- **Core**: ToolRegistry security/sandbox integration and JSON execution helper.\\n- **Operational**: safer default logging with `@fjell/logging`.\\n- **Maintenance**: precommit now builds; docs and config updates.\\n\"\n}"
355
+ }
356
+ ]
357
+ ```
358
+
359
+ </details>
360
+
361
+ ## Generated Release Notes
362
+
363
+ ### Title
364
+ ```
365
+ v1.0.0: Tool execution security (ToolGuard/ToolSandbox), safer defaults, and improved tool invocation
366
+ ```
367
+
368
+ ### Body
369
+ ```markdown
370
+ ## Overview
371
+ This release focuses on making tool execution safer and more controllable in agentic workflows. It introduces opt-in security and sandboxing layers for tools, changes the default logging behavior to reduce accidental information disclosure, and adds safer JSON argument handling for tool calls.
372
+
373
+ ## Highlights
374
+
375
+ ### Tool execution security (new)
376
+ You can now add a security layer to tool execution via **ToolGuard**:
377
+
378
+ - **Allow/deny lists** for tools (`allowedTools`, `deniedTools`)
379
+ - **Zod-based parameter validation** (when a tool provides a `schema`)
380
+ - **Safe JSON argument parsing** with **prototype pollution detection** (`__proto__`, `constructor`, `prototype`)
381
+ - **Security event hooks** for monitoring/telemetry (validation failures, blocked execution, prototype pollution attempts)
382
+
383
+ New/updated APIs:
384
+ - `ToolRegistry.withSecurity(guard)`
385
+ - `ToolRegistry.getSecurityGuard()`
386
+ - `ToolRegistry.executeFromJSON(name, jsonArgs)` (uses `ToolGuard` parsing when configured)
387
+ - `Tool` and `ToolDefinition` now support an optional `schema?: ZodSchema<any>`
388
+
389
+ Example:
390
+ ```ts
391
+ import { ToolRegistry, ToolGuard } from '@riotprompt/agentic';
392
+ import { z } from 'zod';
393
+
394
+ const registry = ToolRegistry.create();
395
+ const guard = new ToolGuard({ deniedTools: ['rm_rf'] });
396
+
397
+ registry.withSecurity(guard);
398
+
399
+ registry.register({
400
+ name: 'read_file',
401
+ description: 'Read a file',
402
+ parameters: { type: 'object', properties: { path: { type: 'string', description: 'Path' } } },
403
+ schema: z.object({ path: z.string().min(1) }),
404
+ execute: async ({ path }) => path,
405
+ });
406
+
407
+ await registry.executeFromJSON('read_file', '{"path":"./README.md"}');
408
+ ```
409
+
410
+ ### Tool sandboxing (new)
411
+ You can now enforce runtime constraints on tool execution via **ToolSandbox**:
412
+
413
+ - **Timeout enforcement** (`maxExecutionTime`)
414
+ - **Concurrency limits** (`maxConcurrentCalls`)
415
+ - **Output size limits** (per-execution `maxOutputSize`)
416
+ - **Cancellation support** (`cancel(executionId)`, `cancelAll()`)
417
+ - Tools receive an extended `context.sandbox` object (execution id, limits, allowed operations set)
418
+
419
+ New/updated APIs:
420
+ - `ToolRegistry.withSandbox(sandbox)`
421
+ - `ToolRegistry.getSandbox()`
422
+ - `ToolSandbox.execute(tool, params, context, options?)`
423
+ - `createSecureTool(tool, sandbox, guard)` helper to wrap individual tools
424
+
425
+ ## Behavior changes / improvements
426
+
427
+ ### Logging is now silent by default
428
+ The logging system is now backed by `@fjell/logging`, and the library’s **default logger does not emit logs unless explicitly enabled**. This reduces the risk of sensitive data leaking via logs.
429
+
430
+ To enable logging:
431
+ - `AGENTIC_LOGGING=true`, or
432
+ - `DEBUG=*agentic*`, or
433
+ - `NODE_ENV=development`
434
+
435
+ ### Context deduplication hash strengthened
436
+ `ContextManager` now uses **32 hex characters (128 bits)** of the SHA-256 digest (previously shorter). This reduces the likelihood of collisions when deduplicating context content.
437
+
438
+ ## Documentation
439
+ - Added `SECURITY.md` describing tool execution security concepts and recommended operational practices.
440
+ - Added a `<!-- v1.0.0 -->` marker comment to `README.md`.
441
+
442
+ ## Developer experience
443
+ - `precommit` now runs `build` before lint and tests: `npm run build && npm run lint && npm run test`.
444
+ - Build/test config files were reformatted (no functional behavior intended beyond configuration maintenance).
445
+
446
+ ## Dependencies
447
+ - Added runtime dependency: `@fjell/logging`.
448
+
449
+ ## Breaking changes / upgrade notes
450
+ - **Version bump to 1.0.0**: this is a major release.
451
+ - **Default logging behavior change**: if you relied on logs being emitted by default, you must now explicitly enable logging (see “Logging is now silent by default”).
452
+ - **Context hashing change**: if you persist or compare `ContextManager` content hashes externally, the hash values will differ going forward.
453
+
454
+ ## Full change categories
455
+ - **Security**: ToolGuard + ToolSandbox, schema validation, safer JSON parsing, runtime limits.
456
+ - **Core**: ToolRegistry security/sandbox integration and JSON execution helper.
457
+ - **Operational**: safer default logging with `@fjell/logging`.
458
+ - **Maintenance**: precommit now builds; docs and config updates.
459
+
460
+ ```