@inneranimalmedia/agentsam-sdk 2.4.1 → 2.6.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 (233) hide show
  1. package/AGENTSAM.md +55 -0
  2. package/README.md +17 -12
  3. package/bin/agentsam +2 -0
  4. package/docs/AGENTSAM_ASTRA_OPENAI_INTEGRATION.md +1363 -0
  5. package/docs/AGENTSAM_WORKBENCH.md +30 -0
  6. package/docs/AUTH_IDENTITY_CONTRACT.md +52 -0
  7. package/docs/CAPABILITIES.md +5 -3
  8. package/docs/CLI_SHELL.md +167 -80
  9. package/docs/CMS_STUDIO.md +101 -0
  10. package/docs/CONTEXT.md +170 -0
  11. package/docs/LOCAL_OLLAMA.md +58 -0
  12. package/docs/MERKLE.md +19 -19
  13. package/docs/PORTABLE_CONTEXT.md +4 -3
  14. package/docs/PROJECT_CONFIG.md +72 -0
  15. package/docs/RELEASES.md +35 -2
  16. package/docs/REPOSITORY_INTELLIGENCE.md +1 -1
  17. package/docs/REPOSITORY_KNOWLEDGE.md +114 -0
  18. package/docs/SDK_WORKER.md +86 -0
  19. package/docs/SECURITY.md +59 -22
  20. package/docs/client-cms-editor.md +15 -0
  21. package/docs/local-studio/WORKMODE_DONOR_NOTES.md +485 -0
  22. package/package.json +35 -10
  23. package/packages/connectors/cloudflare/package.json +10 -0
  24. package/packages/connectors/cloudflare/src/index.js +127 -0
  25. package/packages/connectors/cloudflare/src/owner.js +76 -0
  26. package/packages/connectors/cloudflare/src/routes.js +223 -0
  27. package/packages/connectors/cloudflare/src/vault.js +80 -0
  28. package/packages/connectors/cloudflare/tests/connector.test.mjs +44 -0
  29. package/packages/identity/package.json +10 -1
  30. package/packages/identity/src/contracts/auth-config.js +109 -0
  31. package/packages/identity/src/index.js +1 -0
  32. package/packages/identity/src/oauth/README.md +2 -2
  33. package/packages/identity/src/oauth/credentials.js +11 -4
  34. package/packages/identity/src/oauth/iam-platform.js +3 -3
  35. package/packages/identity/src/providers/iam/index.js +7 -7
  36. package/packages/identity/src/providers/iam/oauth.js +6 -4
  37. package/packages/identity/src/providers/iam/profile.js +5 -5
  38. package/packages/identity/tests/auth-config.test.mjs +61 -0
  39. package/packages/identity/tests/oauth-credentials.test.mjs +13 -2
  40. package/protocol/FILEMETA_V1.md +95 -0
  41. package/protocol/INSPECT_VIEWS_V1.md +40 -0
  42. package/protocol/MERKLE_PERSISTENCE_V1.md +50 -0
  43. package/protocol/MERKLE_V1.md +3 -1
  44. package/protocol/README.md +1 -0
  45. package/protocol/capabilities/cloudflare-cpu-audit-input.schema.json +19 -0
  46. package/protocol/capabilities/cloudflare-cpu-profile-input.schema.json +13 -0
  47. package/protocol/capabilities/cloudflare-wrangler-native-input.schema.json +19 -0
  48. package/protocol/capabilities/manifest.json +79 -3
  49. package/protocol/capabilities/repository-snapshot.schema.json +1 -0
  50. package/protocol/context/context-budget.schema.json +36 -0
  51. package/protocol/context/context-item.schema.json +19 -0
  52. package/protocol/context/resolved-context-pack.schema.json +47 -0
  53. package/protocol/context/result-policy.schema.json +17 -0
  54. package/protocol/knowledge/context-pack.schema.json +33 -0
  55. package/protocol/knowledge/retrieval-query.schema.json +68 -13
  56. package/protocol/models/README.md +373 -0
  57. package/protocol/models/model-inventory-v2.schema.json +212 -0
  58. package/python/README.md +15 -7
  59. package/python/agentsam_sdk/cli.py +0 -21
  60. package/python/agentsam_sdk/tui/README.md +17 -12
  61. package/python/agentsam_sdk/tui/bootstrap.py +2 -2
  62. package/python/agentsam_sdk/tui/demo.py +25 -10
  63. package/python/agentsam_sdk/tui/onboarding.py +208 -0
  64. package/python/tests/test_tui_cli.py +7 -6
  65. package/skills/README.md +21 -0
  66. package/skills/agentsam-app-fundamentals/SKILL.md +165 -0
  67. package/skills/agentsam-app-fundamentals/references/graphs-contracts-ast-merkle.md +89 -0
  68. package/skills/agentsam-app-fundamentals/references/trust-credentials-and-destinations.md +99 -0
  69. package/skills/agentsam-cloudflare-workers/SKILL.md +53 -0
  70. package/skills/agentsam-cloudflare-workers/references/cpu-profiling.md +16 -0
  71. package/skills/agentsam-cloudflare-workers/references/errors-and-observability.md +29 -0
  72. package/skills/agentsam-cloudflare-workers/references/wrangler-native-map.md +28 -0
  73. package/skills/agentsam-jr-dev/SKILL.md +232 -0
  74. package/skills/agentsam-jr-dev/references/real-application-logic.md +156 -0
  75. package/skills/agentsam-jr-dev/references/web-application-fundamentals.md +240 -0
  76. package/skills/agentsam-progression-guard/SKILL.md +197 -0
  77. package/skills/agentsam-progression-guard/references/checkpoint-chain.md +111 -0
  78. package/skills/agentsam-progression-guard/references/hooks-operational-io.md +96 -0
  79. package/skills/catalog.json +71 -0
  80. package/src/agent/capability-adapter.js +25 -13
  81. package/src/agent/index.js +1 -0
  82. package/src/agent/responses-runner.js +325 -0
  83. package/src/capabilities/index.js +7 -0
  84. package/src/capabilities/repository-snapshot-view.js +238 -0
  85. package/src/capabilities/repository-snapshot.js +28 -14
  86. package/src/cli.js +198 -76
  87. package/src/cloudflare/cpu-profile.js +115 -0
  88. package/src/cloudflare/index.js +14 -0
  89. package/src/cloudflare/wrangler.js +132 -0
  90. package/src/commands/account-auth.js +47 -0
  91. package/src/commands/cloudflare.js +58 -0
  92. package/src/commands/connections.js +93 -0
  93. package/src/commands/context-economics.js +114 -0
  94. package/src/commands/context.js +14 -2
  95. package/src/commands/db.js +4 -7
  96. package/src/commands/deploy.js +54 -32
  97. package/src/commands/eval.js +63 -0
  98. package/src/commands/interactive.js +18 -0
  99. package/src/commands/knowledge.js +27 -4
  100. package/src/commands/merkle-persist.js +118 -0
  101. package/src/commands/merkle.js +32 -17
  102. package/src/commands/models.js +152 -0
  103. package/src/commands/ollama.js +259 -0
  104. package/src/commands/preferences.js +144 -0
  105. package/src/commands/product.js +86 -16
  106. package/src/commands/resume.js +67 -0
  107. package/src/commands/security.js +8 -6
  108. package/src/commands/shell.js +405 -100
  109. package/src/commands/skills.js +66 -0
  110. package/src/commands/start-local.js +1 -1
  111. package/src/commands/tunnel.js +5 -4
  112. package/src/commands/whoami.js +86 -0
  113. package/src/context/budget.js +120 -0
  114. package/src/context/compact.js +28 -0
  115. package/src/context/index.js +7 -0
  116. package/src/context/rehydrate.js +35 -0
  117. package/src/context/resolve.js +116 -0
  118. package/src/context/result-policy.js +66 -0
  119. package/src/errors/diagnostic.js +160 -0
  120. package/src/errors/index.js +9 -0
  121. package/src/eval/context.js +191 -0
  122. package/src/eval/index.js +1 -0
  123. package/src/index.js +70 -0
  124. package/src/indexing/execution-boundary.js +144 -0
  125. package/src/indexing/index.js +8 -0
  126. package/src/indexing/provider.js +41 -0
  127. package/src/knowledge/config.js +2 -2
  128. package/src/knowledge/context-pack.js +12 -1
  129. package/src/knowledge/contracts.js +9 -4
  130. package/src/knowledge/engine.js +7 -3
  131. package/src/knowledge/service/server.js +1 -1
  132. package/src/lib/account-session.js +98 -0
  133. package/src/lib/agent-instructions.js +73 -0
  134. package/src/lib/auth.js +4 -0
  135. package/src/lib/bridge-client.js +7 -5
  136. package/src/lib/cli-preferences.js +78 -0
  137. package/src/lib/core-client.js +8 -8
  138. package/src/lib/deploy/git-guard.js +69 -0
  139. package/src/lib/deploy/health.js +57 -0
  140. package/src/lib/deploy/local-studio.js +283 -0
  141. package/src/lib/deploy/secret-scan.js +65 -0
  142. package/src/lib/deploy-receipt/index.js +5 -2
  143. package/src/lib/detect-context.js +6 -5
  144. package/src/lib/execution-approvals.js +59 -0
  145. package/src/lib/identity-scaffold.js +1 -1
  146. package/src/lib/local-scaffold.js +35 -33
  147. package/src/lib/local-sessions.js +127 -0
  148. package/src/lib/local-status.js +9 -17
  149. package/src/lib/merkle/cloudflare-persistence.js +321 -0
  150. package/src/lib/merkle/filemeta.js +43 -0
  151. package/src/lib/merkle/git-ignore.js +24 -0
  152. package/src/lib/merkle/hash.js +1 -0
  153. package/src/lib/merkle/index.js +22 -0
  154. package/src/lib/merkle/persistence.js +72 -0
  155. package/src/lib/merkle/semantic.js +359 -0
  156. package/src/lib/merkle/snapshot.js +9 -3
  157. package/src/lib/merkle/tree.js +11 -6
  158. package/src/lib/project-config.js +227 -0
  159. package/src/lib/project-rules.js +68 -0
  160. package/src/lib/provider-credentials.js +83 -0
  161. package/src/lib/save-sdk-token.js +1 -1
  162. package/src/lib/scaffold/templates/worker-api/index.js +101 -20
  163. package/src/lib/scaffold/wizards/worker-api.js +27 -11
  164. package/src/lib/slash-commands.js +22 -15
  165. package/src/lib/tools.js +11 -5
  166. package/src/models/catalog.js +135 -0
  167. package/src/models/index.js +7 -0
  168. package/src/providers/index.js +5 -0
  169. package/src/providers/openai-responses.js +275 -0
  170. package/src/security/index.js +1 -0
  171. package/src/security/inventory.js +4 -1
  172. package/src/security/process.js +35 -9
  173. package/src/security/render.js +27 -5
  174. package/src/security/scan.js +24 -9
  175. package/src/security/trust-boundary.js +24 -0
  176. package/src/skills/index.js +64 -0
  177. package/src/telemetry/contracts.js +203 -0
  178. package/src/telemetry/events.js +48 -0
  179. package/src/telemetry/index.js +8 -0
  180. package/src/tools/hydrate.js +35 -0
  181. package/src/tools/index.js +2 -0
  182. package/src/tools/search.js +70 -0
  183. package/src/ui/ansi.js +1 -1
  184. package/src/ui/boot.js +54 -0
  185. package/src/ui/merkle/render.js +1 -0
  186. package/src/ui/runtime-activity.js +192 -0
  187. package/src/ui/theme.js +19 -18
  188. package/test/account-session.test.mjs +36 -0
  189. package/test/app-building-skills.test.mjs +61 -0
  190. package/test/apps-scaffold-contract.test.mjs +56 -0
  191. package/test/capabilities.test.mjs +53 -4
  192. package/test/cli-preferences.test.mjs +46 -0
  193. package/test/cloudflare-connector.test.mjs +96 -0
  194. package/test/cloudflare-runtime.test.mjs +75 -0
  195. package/test/context.test.mjs +144 -0
  196. package/test/deploy-health-scan.test.mjs +67 -0
  197. package/test/error-diagnostics.test.mjs +59 -0
  198. package/test/eval-context.test.mjs +37 -0
  199. package/test/execution-approvals.test.mjs +27 -0
  200. package/test/indexing-provider.test.mjs +29 -0
  201. package/test/jr-dev-skill.test.mjs +26 -0
  202. package/test/knowledge-context-pack.test.mjs +19 -0
  203. package/test/knowledge.test.mjs +1 -1
  204. package/test/local-sessions.test.mjs +42 -0
  205. package/test/local-studio-deploy.test.mjs +83 -0
  206. package/test/merkle-persistence.test.mjs +91 -0
  207. package/test/merkle.test.mjs +77 -3
  208. package/test/model-catalog.test.mjs +43 -0
  209. package/test/models.test.mjs +51 -0
  210. package/test/npm10-lock.test.mjs +29 -0
  211. package/test/ollama.test.mjs +94 -0
  212. package/test/openai-responses.test.mjs +95 -0
  213. package/test/project-config.test.mjs +81 -0
  214. package/test/project-rules.test.mjs +44 -0
  215. package/test/provider-credentials.test.mjs +52 -0
  216. package/test/rehydrate.test.mjs +25 -0
  217. package/test/release-hygiene.test.mjs +34 -0
  218. package/test/repository-snapshot-view.test.mjs +112 -0
  219. package/test/responses-runner.test.mjs +148 -0
  220. package/test/runtime-activity.test.mjs +98 -0
  221. package/test/sdk-worker-contract.test.mjs +68 -0
  222. package/test/security.test.mjs +46 -0
  223. package/test/shell.test.mjs +55 -21
  224. package/test/skills.test.mjs +22 -0
  225. package/test/smoke.mjs +5 -2
  226. package/test/telemetry.test.mjs +79 -0
  227. package/test/theme-portability.test.mjs +14 -0
  228. package/test/tools-search.test.mjs +40 -0
  229. package/test/whoami-resume.test.mjs +56 -0
  230. package/examples/agentsam-tui-ansi.mjs +0 -149
  231. package/src/commands/tui.js +0 -120
  232. package/src/ui/splash-xterm.js +0 -290
  233. package/src/ui/splash.js +0 -426
@@ -0,0 +1,160 @@
1
+ const DEFAULT_MAX_DETAIL_CHARS = 12_000;
2
+ const SECRET_KEY = /(?:authorization|api[-_]?key|access[-_]?token|refresh[-_]?token|password|secret|cookie|credential)/i;
3
+ const SECRET_VALUE_PATTERNS = [
4
+ /\bBearer\s+[A-Za-z0-9._~+\/-]+=*/gi,
5
+ /\bsk-[A-Za-z0-9_-]{12,}\b/g,
6
+ /\bsdk_[A-Za-z0-9_-]{8,}\b/g,
7
+ /\bgh[pousr]_[A-Za-z0-9]{20,}\b/g,
8
+ ];
9
+
10
+ function clean(value) { return value == null ? '' : String(value).trim(); }
11
+ function bounded(value, maxChars = DEFAULT_MAX_DETAIL_CHARS) {
12
+ const text = String(value ?? '');
13
+ if (text.length <= maxChars) return text;
14
+ return `${text.slice(0, Math.max(0, maxChars - 1))}…`;
15
+ }
16
+
17
+ function redactString(value, maxChars) {
18
+ let text = bounded(value, maxChars);
19
+ for (const pattern of SECRET_VALUE_PATTERNS) text = text.replace(pattern, '[REDACTED]');
20
+ return text;
21
+ }
22
+
23
+ export function redactDiagnosticValue(value, options = {}, depth = 0, key = '') {
24
+ const maxChars = Number.isInteger(options.maxChars) && options.maxChars > 0 ? options.maxChars : DEFAULT_MAX_DETAIL_CHARS;
25
+ if (SECRET_KEY.test(key)) return '[REDACTED]';
26
+ if (depth > 6) return '[TRUNCATED_DEPTH]';
27
+ if (value == null || typeof value === 'number' || typeof value === 'boolean') return value;
28
+ if (typeof value === 'string') return redactString(value, maxChars);
29
+ if (Array.isArray(value)) return value.slice(0, 50).map((item) => redactDiagnosticValue(item, options, depth + 1));
30
+ if (typeof value === 'object') {
31
+ const result = {};
32
+ for (const [childKey, childValue] of Object.entries(value).slice(0, 80)) {
33
+ result[childKey] = redactDiagnosticValue(childValue, options, depth + 1, childKey);
34
+ }
35
+ return result;
36
+ }
37
+ return redactString(String(value), maxChars);
38
+ }
39
+
40
+ function header(headers, name) {
41
+ if (!headers) return '';
42
+ if (typeof headers.get === 'function') return clean(headers.get(name));
43
+ const wanted = name.toLowerCase();
44
+ for (const [key, value] of Object.entries(headers)) if (String(key).toLowerCase() === wanted) return clean(value);
45
+ return '';
46
+ }
47
+
48
+ function retryAfterMs(headers) {
49
+ const value = header(headers, 'retry-after');
50
+ if (!value) return null;
51
+ if (/^\d+(?:\.\d+)?$/.test(value)) return Math.max(0, Math.round(Number(value) * 1000));
52
+ const when = Date.parse(value);
53
+ return Number.isFinite(when) ? Math.max(0, when - Date.now()) : null;
54
+ }
55
+
56
+ const OPENAI_NON_RETRY_CODES = new Set([
57
+ 'credit_balance_exhausted',
58
+ 'organization_spend_limit_exceeded',
59
+ 'project_spend_limit_exceeded',
60
+ 'organization_usage_limit_exceeded',
61
+ ]);
62
+
63
+ export function classifyOpenAIError({ status, type, code, message, param } = {}) {
64
+ const httpStatus = Number(status || 0);
65
+ const normalizedType = clean(type).toLowerCase();
66
+ const normalizedCode = clean(code).toLowerCase();
67
+ const diagnosticText = `${normalizedCode} ${normalizedType} ${clean(param).toLowerCase()} ${clean(message).toLowerCase()}`;
68
+ if (normalizedCode === 'previous_response_not_found') return { category: 'continuation', retriable: true, retry_strategy: 'retry_full_context' };
69
+ if (normalizedCode === 'websocket_connection_limit_reached') return { category: 'connection_lifetime', retriable: true, retry_strategy: 'reconnect' };
70
+ if (OPENAI_NON_RETRY_CODES.has(normalizedCode)) return { category: 'billing_or_quota', retriable: false, retry_strategy: 'operator_action' };
71
+ if (httpStatus === 400 && /service[_ -]?tier/.test(diagnosticText)) return { category: 'service_tier', retriable: false, retry_strategy: 'change_configuration' };
72
+ if (httpStatus === 400) return { category: 'invalid_request', retriable: false, retry_strategy: 'change_request' };
73
+ if (httpStatus === 401) return { category: 'authentication', retriable: false, retry_strategy: 'fix_credentials' };
74
+ if (httpStatus === 403) return { category: 'authorization_or_region', retriable: false, retry_strategy: 'operator_action' };
75
+ if (httpStatus === 429 && normalizedCode === 'slow_down') return { category: 'ramp_rate', retriable: true, retry_strategy: 'retry_after_backoff' };
76
+ if (httpStatus === 429) return { category: 'rate_limit', retriable: true, retry_strategy: 'retry_after_backoff' };
77
+ if (httpStatus === 500) return { category: 'provider_server', retriable: true, retry_strategy: 'retry_backoff' };
78
+ if (httpStatus === 503 || normalizedCode === 'server_is_overloaded') return { category: 'provider_overload', retriable: true, retry_strategy: 'retry_after_backoff' };
79
+ return { category: 'provider_error', retriable: false, retry_strategy: 'inspect_error' };
80
+ }
81
+
82
+ export class AgentSamDiagnosticError extends Error {
83
+ constructor(diagnostic, options = {}) {
84
+ super(clean(diagnostic?.message) || clean(options.message) || 'AgentSam operation failed', options.cause ? { cause: options.cause } : undefined);
85
+ this.name = 'AgentSamDiagnosticError';
86
+ this.diagnostic = Object.freeze({ schema_version: 1, ...diagnostic });
87
+ this.code = diagnostic?.code || diagnostic?.kind || 'agentsam_error';
88
+ this.status = diagnostic?.http_status ?? diagnostic?.status ?? null;
89
+ }
90
+ }
91
+
92
+ export function createOpenAIHttpError({ status, body, headers, requestedServiceTier, rawText } = {}) {
93
+ const payload = body && typeof body === 'object' ? body : {};
94
+ const providerError = payload.error && typeof payload.error === 'object' ? payload.error : payload;
95
+ const type = clean(providerError.type) || null;
96
+ const code = clean(providerError.code) || null;
97
+ const message = clean(providerError.message || payload.message || rawText) || `OpenAI API returned HTTP ${status}`;
98
+ const classification = classifyOpenAIError({ status, type, code, message, param: providerError.param });
99
+ const diagnostic = {
100
+ source: 'openai',
101
+ kind: 'provider_http_error',
102
+ http_status: Number(status || 0) || null,
103
+ type,
104
+ code,
105
+ param: clean(providerError.param) || null,
106
+ message: redactString(message, 4_000),
107
+ category: classification.category,
108
+ retriable: classification.retriable,
109
+ retry_strategy: classification.retry_strategy,
110
+ retry_after_ms: retryAfterMs(headers),
111
+ request_id: header(headers, 'x-request-id') || header(headers, 'openai-request-id') || header(headers, 'request-id') || null,
112
+ ray_id: header(headers, 'cf-ray') || null,
113
+ requested_service_tier: clean(requestedServiceTier) || null,
114
+ details: redactDiagnosticValue(payload, { maxChars: DEFAULT_MAX_DETAIL_CHARS }),
115
+ };
116
+ return new AgentSamDiagnosticError(diagnostic);
117
+ }
118
+
119
+ export function createProcessDiagnosticError({ code, message, command, args, cwd, exitCode, signal, stderr, stdout, cause, retriable = false } = {}) {
120
+ return new AgentSamDiagnosticError({
121
+ source: 'process',
122
+ kind: 'process_error',
123
+ code: clean(code) || 'process_failed',
124
+ message: clean(message) || 'Process failed',
125
+ retriable: Boolean(retriable),
126
+ retry_strategy: retriable ? 'retry_after_inspection' : 'inspect_error',
127
+ command: clean(command) || null,
128
+ args: Array.isArray(args) ? args.slice(0, 64).map((value) => redactString(value, 1_000)) : [],
129
+ cwd: clean(cwd) || null,
130
+ exit_code: Number.isInteger(exitCode) ? exitCode : null,
131
+ signal: clean(signal) || null,
132
+ stderr: redactString(stderr || '', 12_000) || null,
133
+ stdout: redactString(stdout || '', 4_000) || null,
134
+ }, { cause });
135
+ }
136
+
137
+ export function diagnosticFromError(error, fallback = {}) {
138
+ if (error?.diagnostic) return error.diagnostic;
139
+ return Object.freeze({
140
+ schema_version: 1,
141
+ source: fallback.source || 'agentsam',
142
+ kind: fallback.kind || 'runtime_error',
143
+ code: clean(error?.code || fallback.code) || 'runtime_error',
144
+ message: redactString(error?.message || error || fallback.message || 'Unknown error', 4_000),
145
+ http_status: Number(error?.status || fallback.status || 0) || null,
146
+ retriable: Boolean(fallback.retriable),
147
+ retry_strategy: fallback.retry_strategy || 'inspect_error',
148
+ });
149
+ }
150
+
151
+ export function renderDiagnosticError(error) {
152
+ const d = diagnosticFromError(error);
153
+ const identity = [d.source, d.http_status ? `HTTP ${d.http_status}` : '', d.type, d.code].filter(Boolean).join(' · ');
154
+ const lines = [`✗ ${d.message}`, identity ? ` ${identity}` : ''];
155
+ if (d.request_id) lines.push(` request_id: ${d.request_id}`);
156
+ if (d.ray_id) lines.push(` ray_id: ${d.ray_id}`);
157
+ if (d.retry_after_ms != null) lines.push(` retry_after_ms: ${d.retry_after_ms}`);
158
+ if (d.retry_strategy) lines.push(` retry: ${d.retriable ? 'yes' : 'no'} · ${d.retry_strategy}`);
159
+ return lines.filter(Boolean).join('\n');
160
+ }
@@ -0,0 +1,9 @@
1
+ export {
2
+ AgentSamDiagnosticError,
3
+ classifyOpenAIError,
4
+ createOpenAIHttpError,
5
+ createProcessDiagnosticError,
6
+ diagnosticFromError,
7
+ redactDiagnosticValue,
8
+ renderDiagnosticError,
9
+ } from './diagnostic.js';
@@ -0,0 +1,191 @@
1
+ import { assessContextUsage, compactContextItem, createContextBudget, estimateContextTokens, rehydrateContextRef, resolveContext } from '../context/index.js';
2
+ import { calculateModelCost, getModelRecord } from '../models/index.js';
3
+ import { hydrateToolSchemas, searchToolCards } from '../tools/index.js';
4
+
5
+ const STRATEGIES = Object.freeze(['bounded', 'discovery', 'compact']);
6
+
7
+ const BASE_TOOLS = Object.freeze([
8
+ { name: 'repository.snapshot', description: 'Inspect repository identity, tree, packages, and structural evidence.', category: 'repository', input_schema: { type: 'object', properties: { cwd: { type: 'string' } } } },
9
+ { name: 'code.symbols', description: 'Find exact symbols, declarations, imports, and callers in indexed source.', category: 'code', input_schema: { type: 'object', required: ['query'], properties: { query: { type: 'string' } } } },
10
+ { name: 'files.read', description: 'Read a selected file or bounded source range by stable reference.', category: 'files', input_schema: { type: 'object', required: ['ref'], properties: { ref: { type: 'string' } } } },
11
+ { name: 'tests.trace', description: 'Trace a failing test to referenced source symbols and files.', category: 'tests', input_schema: { type: 'object', required: ['test'], properties: { test: { type: 'string' } } } },
12
+ { name: 'context.rehydrate', description: 'Recover previously compacted evidence by stable reference and hash.', category: 'context', input_schema: { type: 'object', required: ['ref'], properties: { ref: { type: 'string' } } } },
13
+ ...Array.from({ length: 20 }, (_, index) => ({ name: `misc.tool.${index}`, description: `Unrelated generic capability ${index}.`, category: 'misc', input_schema: { type: 'object', properties: {} } })),
14
+ ]);
15
+
16
+ const FIXTURES = Object.freeze({
17
+ 'exact-symbol-callers': {
18
+ task: 'Find the exact resolveContext implementation and its budget dependency before changing context selection.',
19
+ required: ['file:src/context/resolve.js', 'file:src/context/budget.js'],
20
+ syntheticBaseTokens: 8_000,
21
+ items: [
22
+ { ref: 'file:src/context/resolve.js', kind: 'file', priority: 100, content: 'resolveContext selects evidence, computes receipt composition, and defers overflow refs.'.repeat(40) },
23
+ { ref: 'file:src/context/budget.js', kind: 'file', priority: 95, content: 'createContextBudget separates model capacity from AgentSam working-set policy.'.repeat(36) },
24
+ { ref: 'file:README.md', kind: 'file', priority: 8, content: 'General product documentation.'.repeat(100) },
25
+ { ref: 'file:apps/local-studio/theme.css', kind: 'file', priority: 1, content: 'Unrelated visual styles.'.repeat(160) },
26
+ ],
27
+ },
28
+ 'two-file-repair': {
29
+ task: 'Repair a shell model-control regression spanning CLI preferences and slash dispatch without loading unrelated apps.',
30
+ required: ['file:src/lib/cli-preferences.js', 'file:src/commands/shell.js'],
31
+ syntheticBaseTokens: 14_000,
32
+ items: [
33
+ { ref: 'file:src/lib/cli-preferences.js', kind: 'file', priority: 100, content: 'CLI preference schema and persistence.'.repeat(90) },
34
+ { ref: 'file:src/commands/shell.js', kind: 'file', priority: 98, content: 'Slash command parsing, runtime cwd, model and context controls.'.repeat(120) },
35
+ { ref: 'file:apps/cad-creator/frontend/app.tsx', kind: 'file', priority: 5, content: 'CAD UI.'.repeat(300) },
36
+ { ref: 'file:docs/identity.md', kind: 'file', priority: 4, content: 'Identity docs.'.repeat(240) },
37
+ ],
38
+ },
39
+ 'config-without-runtime-pollution': {
40
+ task: 'Update portable project defaults while keeping account, run, connection, and execution state out of committed config.',
41
+ required: ['file:.agentsam/config.json', 'file:src/lib/cli-preferences.js'],
42
+ syntheticBaseTokens: 10_000,
43
+ items: [
44
+ { ref: 'file:.agentsam/config.json', kind: 'file', priority: 100, content: 'Portable repository identity and defaults only.'.repeat(90) },
45
+ { ref: 'file:src/lib/cli-preferences.js', kind: 'file', priority: 90, content: 'Gitignored per-machine model, runtime, terminal, and trust preferences.'.repeat(85) },
46
+ { ref: 'memory:run-history', kind: 'memory', priority: 3, content: 'Large hosted run history must not be copied into project config.'.repeat(500), consumed: true },
47
+ ],
48
+ },
49
+ 'trace-failing-test': {
50
+ task: 'Trace a failing shell regression test to the exact implementation and fix only the implicated source.',
51
+ required: ['file:test/shell.test.mjs', 'file:src/commands/shell.js'],
52
+ syntheticBaseTokens: 22_000,
53
+ items: [
54
+ { ref: 'file:test/shell.test.mjs', kind: 'file', priority: 100, content: 'Regression assertions for slash dispatch and runtime cwd.'.repeat(110) },
55
+ { ref: 'file:src/commands/shell.js', kind: 'file', priority: 96, content: 'Command implementation under test.'.repeat(140) },
56
+ { ref: 'file:test/cad.test.mjs', kind: 'file', priority: 2, content: 'Unrelated CAD tests.'.repeat(240) },
57
+ ],
58
+ },
59
+ 'contradictory-paths': {
60
+ task: 'Detect two contradictory model-selection paths and choose one canonical runtime preference path.',
61
+ required: ['file:src/commands/models.js', 'file:src/commands/preferences.js', 'file:src/lib/cli-preferences.js'],
62
+ syntheticBaseTokens: 36_000,
63
+ items: [
64
+ { ref: 'file:src/commands/models.js', kind: 'file', priority: 100, content: 'Provider model discovery and availability proof.'.repeat(105) },
65
+ { ref: 'file:src/commands/preferences.js', kind: 'file', priority: 98, content: 'Interactive model, reasoning, and processing selection.'.repeat(100) },
66
+ { ref: 'file:src/lib/cli-preferences.js', kind: 'file', priority: 96, content: 'Canonical local preference persistence.'.repeat(90) },
67
+ { ref: 'file:legacy/model-picker.js', kind: 'file', priority: 15, content: 'Legacy duplicate picker path that should not become a second authority.'.repeat(200), consumed: true },
68
+ ],
69
+ },
70
+ 'continuation-after-compaction': {
71
+ task: 'Continue a long tool-heavy run by compacting consumed evidence and rehydrating the exact result needed for the next step.',
72
+ required: ['tool:call_previous', 'file:src/context/compact.js'],
73
+ syntheticBaseTokens: 174_000,
74
+ items: [
75
+ { ref: 'tool:call_previous', kind: 'tool_result', priority: 100, content: 'Critical prior tool evidence with stable identity and the exact invariant required later. '.repeat(900), consumed: true },
76
+ { ref: 'file:src/context/compact.js', kind: 'file', priority: 95, content: 'Compaction preserves ref hash and source size while shrinking active context.'.repeat(100) },
77
+ { ref: 'file:unrelated/generated.log', kind: 'file', priority: 1, content: 'Noisy old generated log.'.repeat(500), consumed: true },
78
+ ],
79
+ },
80
+ });
81
+
82
+ function budgetFor(record) {
83
+ const p = record.context_policy;
84
+ return createContextBudget({ windowTokens: record.context_window, targetInputTokens: p.target_input_tokens, compactAtTokens: p.compact_at_tokens, interveneAtTokens: p.intervene_at_tokens, maxNormalInputTokens: p.max_normal_input_tokens, pricingThresholdTokens: p.pricing_threshold_tokens, safetyMarginTokens: p.safety_margin_tokens });
85
+ }
86
+
87
+ function fixture(name) {
88
+ const value = FIXTURES[name];
89
+ if (!value) throw new Error(`unknown context eval fixture: ${name}`);
90
+ return value;
91
+ }
92
+
93
+ async function runStrategy(fx, strategy, record) {
94
+ const budget = budgetFor(record);
95
+ let items = fx.items.map(item => ({ ...item }));
96
+ let compactedChars = 0;
97
+ const sources = new Map(items.map(item => [item.ref, item]));
98
+ const rehydratedRefs = [];
99
+
100
+ if (strategy === 'compact') {
101
+ items = items.map(item => {
102
+ if (!item.consumed) return item;
103
+ const compacted = compactContextItem(item, { maxChars: 4_000 });
104
+ compactedChars += Math.max(0, item.content.length - compacted.content.length);
105
+ return compacted;
106
+ });
107
+ }
108
+
109
+ const tools = strategy === 'bounded' ? { cards: [], receipt: { returned_items: 0, chars: 0 } } : searchToolCards(BASE_TOOLS, fx.task, { maxItems: 8 });
110
+ const hydrated = strategy === 'bounded' ? { tools: [], receipt: { hydrated_tools: 0, schema_chars: 0 } } : hydrateToolSchemas(BASE_TOOLS, tools.cards.map(card => card.tool), { maxTools: 8, maxChars: 40_000 });
111
+ let pack = resolveContext({ objective: fx.task, budget, toolSchemaChars: hydrated.receipt.schema_chars, items });
112
+ const selectedRefs = new Set(pack.items.map(item => item.ref));
113
+
114
+ if (strategy === 'compact') {
115
+ for (const ref of fx.required) {
116
+ const selected = pack.items.find(item => item.ref === ref);
117
+ if (!selected?.compacted) continue;
118
+ const source = sources.get(ref);
119
+ const rehydrated = await rehydrateContextRef(ref, async () => source, { kind: source.kind, maxChars: budget.maxFileCharsPerRead });
120
+ const remaining = pack.items.filter(item => item.ref !== ref);
121
+ remaining.push({ ...rehydrated, priority: source.priority });
122
+ pack = resolveContext({ objective: fx.task, budget, toolSchemaChars: hydrated.receipt.schema_chars, items: remaining });
123
+ selectedRefs.add(ref);
124
+ rehydratedRefs.push(ref);
125
+ }
126
+ }
127
+
128
+ const missingRequired = fx.required.filter(ref => !selectedRefs.has(ref));
129
+ const evidenceTokens = pack.receipt.estimated_input_tokens;
130
+ const activeTokens = fx.syntheticBaseTokens + evidenceTokens;
131
+ const pressure = assessContextUsage(activeTokens, budget);
132
+ const inputCost = calculateModelCost(record, { input_tokens: activeTokens, output_tokens: 0 }, { serviceTier: 'default' });
133
+ return Object.freeze({
134
+ strategy,
135
+ result: missingRequired.length ? 'FAIL' : 'PASS',
136
+ required_evidence: fx.required.length,
137
+ required_found: fx.required.length - missingRequired.length,
138
+ missing_required: Object.freeze(missingRequired),
139
+ sources_considered: pack.receipt.sources_considered,
140
+ sources_selected: pack.receipt.sources_included,
141
+ sources_deferred: pack.receipt.sources_deferred,
142
+ tool_cards: tools.receipt.returned_items,
143
+ tool_card_chars: tools.receipt.chars,
144
+ hydrated_tools: hydrated.receipt.hydrated_tools,
145
+ tool_schema_chars: hydrated.receipt.schema_chars,
146
+ compacted_chars: compactedChars,
147
+ rehydrated_refs: Object.freeze(rehydratedRefs),
148
+ active_context_tokens: activeTokens,
149
+ evidence_tokens: evidenceTokens,
150
+ window_tokens: budget.windowTokens,
151
+ pricing_threshold_tokens: budget.pricingThresholdTokens,
152
+ tokens_until_pricing_threshold: pressure.tokensUntilPricingThreshold,
153
+ pressure: pressure.stage,
154
+ should_compact: pressure.shouldCompact,
155
+ estimated_input_cost_usd: inputCost.total_usd,
156
+ estimate_kind: 'local',
157
+ });
158
+ }
159
+
160
+ function rank(results) {
161
+ return [...results].sort((a, b) => {
162
+ if (a.result !== b.result) return a.result === 'PASS' ? -1 : 1;
163
+ if (a.required_found !== b.required_found) return b.required_found - a.required_found;
164
+ if (a.active_context_tokens !== b.active_context_tokens) return a.active_context_tokens - b.active_context_tokens;
165
+ return a.tool_schema_chars - b.tool_schema_chars;
166
+ });
167
+ }
168
+
169
+ export function listContextEvalFixtures() { return Object.keys(FIXTURES); }
170
+
171
+ export async function evaluateContextFixture(options = {}) {
172
+ const name = options.fixture || 'exact-symbol-callers';
173
+ const fx = fixture(name);
174
+ const record = getModelRecord(options.model || 'gpt-6-astra');
175
+ if (!record) throw new Error(`unknown model: ${options.model}`);
176
+ const strategies = options.strategy && options.strategy !== 'all' ? [options.strategy] : STRATEGIES;
177
+ for (const strategy of strategies) if (!STRATEGIES.includes(strategy)) throw new Error(`unknown context strategy: ${strategy}`);
178
+ const results = [];
179
+ for (const strategy of strategies) results.push(await runStrategy(fx, strategy, record));
180
+ const ranked = rank(results);
181
+ return Object.freeze({
182
+ schema_version: 1,
183
+ fixture: name,
184
+ task: fx.task,
185
+ model: record.provider_model_id,
186
+ live_provider_used: false,
187
+ strategies: Object.freeze(results),
188
+ winner: ranked[0]?.strategy || null,
189
+ scoring: Object.freeze(['correctness', 'required_evidence', 'context_efficiency', 'tool_efficiency']),
190
+ });
191
+ }
@@ -0,0 +1 @@
1
+ export { listContextEvalFixtures, evaluateContextFixture } from './context.js';
package/src/index.js CHANGED
@@ -4,6 +4,7 @@ import pkg from '../package.json' with { type: 'json' };
4
4
 
5
5
  export { AgentSam } from './AgentSam.js';
6
6
  export { routeIntent } from './lib/router.js';
7
+ export { searchToolCards, toToolCard, hydrateToolSchemas } from './tools/index.js';
7
8
  export { getToolCatalog } from './lib/tools.js';
8
9
  export { scaffoldProject } from './lib/scaffold.js';
9
10
  export {
@@ -39,6 +40,75 @@ export {
39
40
  repositorySnapshot,
40
41
  } from './capabilities/index.js';
41
42
  export { getPreset, listPresets, resolvePreset, getAddon, listAddons } from './presets/index.js';
43
+ export {
44
+ DEFAULT_CONTEXT_RATIOS,
45
+ DEFAULT_RESULT_POLICY,
46
+ createContextBudget,
47
+ assessContextUsage,
48
+ normalizeResultPolicy,
49
+ resolveContext,
50
+ resolveProjectContext,
51
+ compactConsumedToolResult,
52
+ rehydrateContextRef,
53
+ loadProjectRules,
54
+ compileAgentInstructions,
55
+ AGENT_INSTRUCTION_PRECEDENCE,
56
+ } from './context/index.js';
57
+ export {
58
+ assertRepositoryKnowledgeProvider,
59
+ createRepositoryKnowledgeClient,
60
+ describeRepositoryKnowledgeProvider,
61
+ } from './indexing/index.js';
62
+ export {
63
+ MODEL_CATALOG_SCHEMA,
64
+ MODEL_CATALOG,
65
+ listModelCatalog,
66
+ getModelRecord,
67
+ calculateModelCost,
68
+ } from './models/index.js';
69
+ export {
70
+ AGENT_EVENT_TYPES,
71
+ RUNTIME_RECEIPT_SCHEMA_VERSION,
72
+ createAgentEvent,
73
+ createUsageSnapshot,
74
+ createRunReceipt,
75
+ createUsageReceipt,
76
+ createApprovalReceipt,
77
+ createTerminalJobReceipt,
78
+ } from './telemetry/index.js';
79
+ export {
80
+ createOpenAIResponsesAdapter,
81
+ extractOpenAIOutputText,
82
+ extractOpenAIFunctionCalls,
83
+ } from './providers/index.js';
84
+ export {
85
+ AgentSamDiagnosticError,
86
+ classifyOpenAIError,
87
+ createOpenAIHttpError,
88
+ createProcessDiagnosticError,
89
+ diagnosticFromError,
90
+ redactDiagnosticValue,
91
+ renderDiagnosticError,
92
+ } from './errors/index.js';
93
+ export {
94
+ WRANGLER_NATIVE_COMMANDS,
95
+ WRANGLER_OPERATION_FAMILIES,
96
+ buildWranglerInvocation,
97
+ listWranglerNativeCommands,
98
+ parseWranglerErrorEvidence,
99
+ runWranglerNative,
100
+ summarizeCloudflareCpuProfile,
101
+ summarizeCloudflareCpuProfileFile,
102
+ buildCloudflareCpuAuditPacket,
103
+ runCloudflareCpuAudit,
104
+ } from './cloudflare/index.js';
105
+ export {
106
+ createCapabilityAdapter,
107
+ buildAgentToolSurface,
108
+ capabilityFunctionName,
109
+ runResponsesAgent,
110
+ } from './agent/index.js';
111
+ export { listContextEvalFixtures, evaluateContextFixture } from './eval/index.js';
42
112
 
43
113
  export {
44
114
  createIdentityClient,
@@ -0,0 +1,144 @@
1
+ import { builtinModules } from 'node:module';
2
+
3
+ const NODE_BUILTINS = new Set(builtinModules.flatMap((name) => [name, name.replace(/^node:/, '')]));
4
+ const SERVER_PACKAGES = new Set([
5
+ 'pg', 'postgres', 'mysql', 'mysql2', 'better-sqlite3', 'sqlite3', 'tedious', 'oracledb',
6
+ ]);
7
+ const PUBLIC_ENV_PREFIX = /^(?:VITE_|NEXT_PUBLIC_|PUBLIC_|REACT_APP_)/;
8
+ const SECRETISH_ENV = /(?:SECRET|TOKEN|PASSWORD|PASSWD|PRIVATE|CREDENTIAL|DATABASE_URL|API_KEY|ACCESS_KEY|CLIENT_SECRET|SIGNING_KEY|ENCRYPTION_KEY)/i;
9
+
10
+ function packageRoot(specifier) {
11
+ if (!specifier || specifier.startsWith('.') || specifier.startsWith('/')) return null;
12
+ const clean = specifier.replace(/^node:/, '');
13
+ if (clean.startsWith('@')) return clean.split('/').slice(0, 2).join('/');
14
+ return clean.split('/')[0];
15
+ }
16
+
17
+ function finding(kind, entry, extra = {}) {
18
+ return {
19
+ kind,
20
+ severity: 'high',
21
+ source: entry.path,
22
+ source_hash: entry.hash,
23
+ execution_domain: entry.execution_domain,
24
+ ...extra,
25
+ };
26
+ }
27
+
28
+ export function repairSuggestionForBoundaryFinding(item) {
29
+ const base = { finding: item.kind, automatic: false, evidence: { source: item.source, target: item.target || null } };
30
+ if (item.kind === 'browser-server-import') return {
31
+ ...base,
32
+ action: 'move-server-call-behind-api-boundary',
33
+ summary: 'Keep the browser-facing contract client-safe; move the privileged implementation behind a server/API boundary and call it over HTTP/RPC.',
34
+ };
35
+ if (item.kind === 'browser-server-runtime-import') return {
36
+ ...base,
37
+ action: 'move-server-dependency-out-of-browser-graph',
38
+ summary: `Move ${item.specifier} to server-owned code; expose only a bounded request/response contract to the browser.`,
39
+ };
40
+ if (item.kind === 'browser-private-env-access' || item.kind === 'browser-public-secret-name') return {
41
+ ...base,
42
+ action: 'move-secret-to-server-runtime',
43
+ summary: `Treat ${item.env} as server-owned. Browser code should receive only non-secret derived data from an authenticated server endpoint.`,
44
+ };
45
+ if (item.kind === 'shared-server-env') return {
46
+ ...base,
47
+ action: 'split-shared-contract-from-server-implementation',
48
+ summary: 'Keep shared code deterministic and secret-free; move server environment access into a server adapter and retain only types/schemas/contracts in shared.',
49
+ };
50
+ return { ...base, action: 'review-trust-boundary', summary: 'Review the client/server execution boundary using the attached AST/Merkle evidence.' };
51
+ }
52
+
53
+ export function analyzeExecutionBoundaries(semantic) {
54
+ if (!semantic || !Array.isArray(semantic.entries)) throw new TypeError('agentsam-filemeta semantic metadata is required');
55
+ const byPath = new Map(semantic.entries.map((entry) => [entry.path, entry]));
56
+ const browserRoots = semantic.entries.filter((entry) => entry.type === 'file' && entry.execution_domain === 'browser');
57
+ const reachable = new Map();
58
+ const queue = browserRoots.map((entry) => ({ path: entry.path, root: entry.path }));
59
+ for (const item of queue) reachable.set(item.path, item.root);
60
+ const findings = [];
61
+ const dedupe = new Set();
62
+ const add = (item) => {
63
+ const key = JSON.stringify([item.kind, item.source, item.target || '', item.specifier || '', item.env || '']);
64
+ if (dedupe.has(key)) return;
65
+ dedupe.add(key);
66
+ findings.push({ ...item, repair: repairSuggestionForBoundaryFinding(item) });
67
+ };
68
+
69
+ for (let i = 0; i < queue.length; i++) {
70
+ const current = queue[i];
71
+ const entry = byPath.get(current.path);
72
+ if (!entry) continue;
73
+
74
+ for (const edge of entry.resolved_imports || []) {
75
+ if (!edge.target) continue;
76
+ const target = byPath.get(edge.target);
77
+ if (!target) continue;
78
+ if (target.execution_domain === 'server') {
79
+ add(finding('browser-server-import', entry, {
80
+ target: target.path,
81
+ target_hash: target.hash,
82
+ specifier: edge.specifier,
83
+ browser_root: current.root,
84
+ }));
85
+ continue;
86
+ }
87
+ if (target.execution_domain === 'framework') {
88
+ if (!reachable.has(target.path)) reachable.set(target.path, current.root);
89
+ continue;
90
+ }
91
+ if (!reachable.has(target.path)) {
92
+ reachable.set(target.path, current.root);
93
+ queue.push({ path: target.path, root: current.root });
94
+ }
95
+ }
96
+
97
+ for (const specifier of entry.imports || []) {
98
+ if (specifier.startsWith('.')) continue;
99
+ const root = packageRoot(specifier);
100
+ const normalized = specifier.replace(/^node:/, '');
101
+ if (NODE_BUILTINS.has(normalized) || SERVER_PACKAGES.has(root)) {
102
+ add(finding('browser-server-runtime-import', entry, { specifier, browser_root: current.root }));
103
+ }
104
+ }
105
+
106
+ for (const access of entry.env_accesses || []) {
107
+ const env = access.name;
108
+ const publicName = PUBLIC_ENV_PREFIX.test(env);
109
+ if (publicName && SECRETISH_ENV.test(env)) {
110
+ add(finding('browser-public-secret-name', entry, { env, env_source: access.source, browser_root: current.root }));
111
+ } else if (access.source === 'process.env' && !publicName) {
112
+ add(finding('browser-private-env-access', entry, { env, env_source: access.source, browser_root: current.root }));
113
+ }
114
+ }
115
+ }
116
+
117
+ for (const entry of semantic.entries) {
118
+ if (entry.execution_domain !== 'shared') continue;
119
+ for (const access of entry.env_accesses || []) {
120
+ if (access.source === 'process.env' || SECRETISH_ENV.test(access.name)) {
121
+ add(finding('shared-server-env', entry, { env: access.name, env_source: access.source }));
122
+ }
123
+ }
124
+ }
125
+
126
+ findings.sort((a, b) => [a.source, a.kind, a.target || '', a.specifier || '', a.env || ''].join('\0').localeCompare([b.source, b.kind, b.target || '', b.specifier || '', b.env || ''].join('\0')));
127
+ const astEntries = semantic.entries.filter((entry) => entry.ast?.indexed);
128
+ const parseErrors = astEntries.filter((entry) => Number(entry.ast?.parse_error_count || 0) > 0).map((entry) => entry.path);
129
+ return {
130
+ schema_version: 1,
131
+ analyzer: 'agentsam-execution-boundary',
132
+ metadata_root: semantic.rootHash || null,
133
+ classifier: semantic.classifier || null,
134
+ complete: parseErrors.length === 0,
135
+ status: parseErrors.length ? 'incomplete' : findings.length ? 'action-required' : 'clean',
136
+ ok: parseErrors.length === 0 && findings.length === 0,
137
+ browser_roots: browserRoots.length,
138
+ browser_reachable_files: reachable.size,
139
+ ast_files: astEntries.length,
140
+ parse_errors: parseErrors,
141
+ findings,
142
+ repair_suggestions: findings.map((item) => item.repair),
143
+ };
144
+ }
@@ -0,0 +1,8 @@
1
+ export {
2
+ REPOSITORY_KNOWLEDGE_PROVIDER_METHODS,
3
+ assertRepositoryKnowledgeProvider,
4
+ createRepositoryKnowledgeClient,
5
+ describeRepositoryKnowledgeProvider,
6
+ } from './provider.js';
7
+
8
+ export { analyzeExecutionBoundaries, repairSuggestionForBoundaryFinding } from './execution-boundary.js';
@@ -0,0 +1,41 @@
1
+ export const REPOSITORY_KNOWLEDGE_PROVIDER_METHODS = Object.freeze([
2
+ 'status',
3
+ 'refresh',
4
+ 'findSymbol',
5
+ 'graph',
6
+ 'retrieve',
7
+ 'snapshot',
8
+ ]);
9
+
10
+ export function assertRepositoryKnowledgeProvider(provider) {
11
+ if (!provider || typeof provider !== 'object') throw new TypeError('RepositoryKnowledgeProvider must be an object');
12
+ for (const method of REPOSITORY_KNOWLEDGE_PROVIDER_METHODS) {
13
+ if (typeof provider[method] !== 'function') throw new TypeError(`RepositoryKnowledgeProvider.${method}() is required`);
14
+ }
15
+ return provider;
16
+ }
17
+
18
+ export function createRepositoryKnowledgeClient(provider) {
19
+ const impl = assertRepositoryKnowledgeProvider(provider);
20
+ const call = (method, input = {}) => Promise.resolve(impl[method](input));
21
+ return Object.freeze({
22
+ status: (input) => call('status', input),
23
+ refresh: (input) => call('refresh', input),
24
+ findSymbol: (input) => call('findSymbol', input),
25
+ graph: (input) => call('graph', input),
26
+ retrieve: (input) => call('retrieve', input),
27
+ snapshot: (input) => call('snapshot', input),
28
+ });
29
+ }
30
+
31
+ export function describeRepositoryKnowledgeProvider(capabilities = {}) {
32
+ return Object.freeze({
33
+ structure: capabilities.structure || 'unknown',
34
+ lexical: capabilities.lexical !== false,
35
+ semantic: Boolean(capabilities.semantic),
36
+ graph: Boolean(capabilities.graph),
37
+ history: Boolean(capabilities.history),
38
+ evidence: capabilities.evidence || 'unknown',
39
+ provider: capabilities.provider || 'custom',
40
+ });
41
+ }
@@ -50,9 +50,9 @@ export function validateConfig(input) {
50
50
  if (c.storage.driver === 'postgres' && !/^[A-Z_][A-Z0-9_]*$/.test(c.storage.connection_env || '')) throw new Error('Postgres requires a connection_env name, never a connection string in config.');
51
51
  return c;
52
52
  }
53
- export function defaultConfig({ include = ['.'], exclude = [], scope = 'default', target = 'local', dimensions = 768 } = {}) {
53
+ export function defaultConfig({ include = ['.'], exclude = [], scope = 'default', target = 'local', dimensions = 768, repositoryId = '' } = {}) {
54
54
  if (!['local', 'production'].includes(target)) throw new Error('target must be local or production.');
55
- return validateConfig({ version: 1, repository_id: randomUUID(),
55
+ return validateConfig({ version: 1, repository_id: String(repositoryId || '').trim() || randomUUID(),
56
56
  scope: { name: scope, include, exclude }, chunking: { max_chars: 4000 },
57
57
  embedding: { provider: 'gemini', model: 'gemini-embedding-2', revision: '1', dimensions, parameters: { task: 'code retrieval' } },
58
58
  storage: target === 'local' ? { driver: 'sqlite' } : { driver: 'postgres', connection_env: 'AGENTSAM_DATABASE_URL' } });