@inneranimalmedia/agentsam-sdk 2.6.9 → 2.6.10

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 (176) hide show
  1. package/README.md +18 -0
  2. package/apps/cad-creator/shared/cad/package.json +1 -1
  3. package/apps/client-cms-editor/backend/package.json +3 -3
  4. package/apps/client-cms-editor/frontend/package.json +4 -4
  5. package/apps/client-cms-editor/package-lock.json +8 -8
  6. package/apps/client-cms-editor/package.json +1 -1
  7. package/apps/client-cms-editor/shared/cms/package.json +2 -2
  8. package/docs/architecture/HOOKS-MODELS-SELF-HOSTING-PLAN.md +199 -0
  9. package/examples/hyperspace-demo/index.html +16 -0
  10. package/examples/hyperspace-demo/package.json +21 -0
  11. package/examples/hyperspace-demo/src/main.tsx +160 -0
  12. package/examples/hyperspace-demo/tsconfig.json +9 -0
  13. package/examples/hyperspace-demo/vite.config.ts +24 -0
  14. package/migrations/runtime/0005_agentsam_hooks.sql +87 -0
  15. package/package.json +23 -7
  16. package/packages/agentsam-brand/package.json +1 -1
  17. package/packages/agentsam-brand/src/core/v2/compile.js +1 -1
  18. package/packages/agentsam-brand/src/core/v2/schema.js +1 -1
  19. package/packages/agentsam-contracts/package.json +1 -1
  20. package/packages/agentsam-contracts/src/errors.ts +19 -10
  21. package/packages/agentsam-errors/package.json +11 -3
  22. package/packages/agentsam-errors/protocol/agentsam.error.v2.schema.json +821 -0
  23. package/packages/agentsam-errors/protocol/error-catalog.json +3512 -0
  24. package/packages/agentsam-errors/src/envelope.js +63 -11
  25. package/packages/agentsam-errors/src/error.js +6 -0
  26. package/packages/agentsam-errors/src/fingerprint.js +9 -1
  27. package/packages/agentsam-errors/src/normalize.js +51 -6
  28. package/packages/agentsam-errors/src/recovery.js +9 -35
  29. package/packages/agentsam-errors/src/transport.js +6 -1
  30. package/packages/agentsam-errors/src/vocabulary.js +515 -126
  31. package/packages/agentsam-errors/test/catalog-v2.test.mjs +106 -0
  32. package/packages/agentsam-errors/types/index.d.ts +157 -0
  33. package/packages/agentsam-goap/package.json +2 -2
  34. package/packages/agentsam-hooks/README.md +206 -0
  35. package/packages/agentsam-hooks/bin/agentsam-hooks.mjs +64 -0
  36. package/packages/agentsam-hooks/bindings/go/command.go +55 -0
  37. package/packages/agentsam-hooks/bindings/go/errors_generated.go +402 -0
  38. package/packages/agentsam-hooks/bindings/go/go.mod +3 -0
  39. package/packages/agentsam-hooks/bindings/go/hooks.go +285 -0
  40. package/packages/agentsam-hooks/bindings/go/hooks_test.go +48 -0
  41. package/packages/agentsam-hooks/bindings/rust/Cargo.toml +11 -0
  42. package/packages/agentsam-hooks/bindings/rust/src/errors_generated.rs +396 -0
  43. package/packages/agentsam-hooks/bindings/rust/src/lib.rs +397 -0
  44. package/packages/agentsam-hooks/package.json +69 -0
  45. package/packages/agentsam-hooks/schema/manifest.json +54 -0
  46. package/packages/agentsam-hooks/scripts/clean-protocol.mjs +17 -0
  47. package/packages/agentsam-hooks/scripts/sync-protocol.mjs +27 -0
  48. package/packages/agentsam-hooks/src/adapters/agents.js +82 -0
  49. package/packages/agentsam-hooks/src/adapters/callback.js +4 -0
  50. package/packages/agentsam-hooks/src/adapters/capability.js +122 -0
  51. package/packages/agentsam-hooks/src/adapters/command.js +104 -0
  52. package/packages/agentsam-hooks/src/adapters/composite.js +31 -0
  53. package/packages/agentsam-hooks/src/adapters/http.js +58 -0
  54. package/packages/agentsam-hooks/src/adapters/lsp.js +116 -0
  55. package/packages/agentsam-hooks/src/adapters/mcp.js +76 -0
  56. package/packages/agentsam-hooks/src/adapters/provider.js +129 -0
  57. package/packages/agentsam-hooks/src/config.js +143 -0
  58. package/packages/agentsam-hooks/src/contracts.js +284 -0
  59. package/packages/agentsam-hooks/src/errors-generated.js +2732 -0
  60. package/packages/agentsam-hooks/src/index.d.ts +193 -0
  61. package/packages/agentsam-hooks/src/index.js +14 -0
  62. package/packages/agentsam-hooks/src/policy.js +34 -0
  63. package/packages/agentsam-hooks/src/runtime.js +312 -0
  64. package/packages/agentsam-hooks/src/store.js +264 -0
  65. package/packages/agentsam-hooks/test/adapters.test.mjs +216 -0
  66. package/packages/agentsam-hooks/test/runtime.test.mjs +258 -0
  67. package/packages/agentsam-knowledge/package.json +1 -1
  68. package/packages/agentsam-nav/dist/Nav.d.ts +43 -0
  69. package/packages/agentsam-nav/dist/NavCollapsible.d.ts +10 -0
  70. package/packages/agentsam-nav/dist/NavContent.d.ts +2 -0
  71. package/packages/agentsam-nav/dist/NavFooter.d.ts +2 -0
  72. package/packages/agentsam-nav/dist/NavGroup.d.ts +3 -0
  73. package/packages/agentsam-nav/dist/NavHeader.d.ts +2 -0
  74. package/packages/agentsam-nav/dist/NavMenu.d.ts +22 -0
  75. package/packages/agentsam-nav/dist/NavProvider.d.ts +35 -0
  76. package/packages/agentsam-nav/dist/NavTrigger.d.ts +2 -0
  77. package/packages/agentsam-nav/dist/Surfaces.d.ts +41 -0
  78. package/packages/agentsam-nav/dist/browser.d.ts +11 -0
  79. package/packages/agentsam-nav/dist/browser.global.js +135 -0
  80. package/packages/agentsam-nav/dist/browser.js +7290 -0
  81. package/packages/agentsam-nav/dist/contracts.d.ts +76 -0
  82. package/packages/agentsam-nav/dist/index.d.ts +11 -0
  83. package/packages/agentsam-nav/dist/index.js +7291 -0
  84. package/packages/agentsam-nav/dist/legacy.css +83 -0
  85. package/packages/agentsam-nav/dist/shell.css +113 -0
  86. package/packages/agentsam-nav/dist/theme.css +113 -0
  87. package/packages/agentsam-nav/dist/utils.d.ts +1 -0
  88. package/packages/agentsam-nav/package.json +1 -1
  89. package/packages/agentsam-queue-control/package.json +1 -1
  90. package/packages/agentsam-repository/package.json +1 -1
  91. package/packages/agentsam-repository/src/goap.js.bak-20260922011251 +670 -0
  92. package/packages/agentsam-vault/package.json +1 -1
  93. package/packages/agentsam-workbench/package.json +3 -3
  94. package/packages/connectors/cloudflare/package.json +1 -1
  95. package/packages/heuristic-theme/package.json +1 -1
  96. package/packages/identity/package.json +2 -2
  97. package/packages/providers/completeful/package.json +1 -1
  98. package/packages/theme-church-site/package.json +2 -2
  99. package/packages/theme-companions-site/package.json +2 -2
  100. package/packages/theme-floors-site/package.json +2 -2
  101. package/packages/theme-fuelnfree-site/package.json +2 -2
  102. package/packages/theme-handyman-site/package.json +2 -2
  103. package/packages/theme-insurance-site/package.json +2 -2
  104. package/packages/theme-scenes/package.json +1 -1
  105. package/packages/theme-shinshu-site/package.json +2 -2
  106. package/packages/work-graph/package.json +1 -1
  107. package/protocol/apps/agentsam.app.v1.schema.json +1 -1
  108. package/protocol/apps/agentsam.host-install.v1.schema.json +1 -1
  109. package/protocol/assets/asset-record.v1.schema.json +1 -1
  110. package/protocol/brand/brandpack.v1.schema.json +1 -1
  111. package/protocol/capabilities/capability-manifest.schema.json +1 -1
  112. package/protocol/capabilities/knowledge-search-input.schema.json +1 -0
  113. package/protocol/credentials/issue.v1.schema.json +1 -1
  114. package/protocol/database/agentsam.schema-pack.v1.schema.json +1 -1
  115. package/protocol/database/connection.v1.schema.json +1 -1
  116. package/protocol/embeddings/embedding-profile.v1.schema.json +1 -1
  117. package/protocol/errors/README.md +36 -0
  118. package/protocol/errors/agentsam.error.v2.schema.json +821 -0
  119. package/protocol/errors/error-catalog.json +3506 -158
  120. package/protocol/errors/error-envelope.schema.json +195 -52
  121. package/protocol/errors/recovery.v1.schema.json +1 -1
  122. package/protocol/features/agentsam.feature.v1.schema.json +1 -1
  123. package/protocol/hooks/README.md +18 -0
  124. package/protocol/hooks/agentsam.hook.output.v1.schema.json +27 -0
  125. package/protocol/hooks/agentsam.hook.receipt.v1.schema.json +61 -0
  126. package/protocol/hooks/agentsam.hook.v1.schema.json +35 -0
  127. package/protocol/hooks/agentsam.hooks.config.v1.schema.json +78 -0
  128. package/protocol/local-engine/benchmark.v1.schema.json +1 -1
  129. package/protocol/local-engine/engine.v1.schema.json +1 -1
  130. package/protocol/models/model-inventory-v2.schema.json +1 -1
  131. package/protocol/models/provider-model-snapshot.v1.schema.json +22 -0
  132. package/protocol/rpc/v1/errors.proto +1 -0
  133. package/protocol/runtime/agentsam.runtime.v1.schema.json +1 -1
  134. package/protocol/runtime/workspace-fs.v1.schema.json +1 -1
  135. package/protocol/sam/activity.v1.schema.json +1 -1
  136. package/protocol/sam/answer.v1.schema.json +1 -1
  137. package/protocol/sam/calibration.v1.schema.json +1 -1
  138. package/protocol/sam/decision-receipt.v1.schema.json +1 -1
  139. package/protocol/sam/evaluation.v1.schema.json +1 -1
  140. package/protocol/sam/operation.schema.json +1 -1
  141. package/protocol/sam/outcome.v1.schema.json +1 -1
  142. package/protocol/sam/question.v1.schema.json +1 -1
  143. package/protocol/sam/result.schema.json +1 -1
  144. package/protocol/sam/state.v1.schema.json +1 -1
  145. package/protocol/scoring/scorecard.v1.schema.json +1 -1
  146. package/protocol/skills/agentsam.interaction.v1.schema.json +1 -1
  147. package/protocol/skills/agentsam.skill.v1.schema.json +1 -1
  148. package/protocol/theme-refinery/product-metadata.schema.json +1 -1
  149. package/protocol/theme-refinery/reconciliation-receipt.schema.json +1 -1
  150. package/protocol/ui/icon.v1.schema.json +1 -1
  151. package/python/README.md +20 -0
  152. package/python/agentsam_sdk/errors/__init__.py +4 -0
  153. package/python/agentsam_sdk/errors/vocabulary.py +2730 -0
  154. package/python/agentsam_sdk/hooks/__init__.py +32 -0
  155. package/python/agentsam_sdk/hooks/adapters.py +132 -0
  156. package/python/agentsam_sdk/hooks/contracts.py +121 -0
  157. package/python/agentsam_sdk/hooks/runtime.py +253 -0
  158. package/python/tests/test_hooks.py +115 -0
  159. package/src/agent/responses-runner.js +145 -1
  160. package/src/cli/command-catalog.js +1 -0
  161. package/src/cli.js +9 -0
  162. package/src/commands/cheat-sheet.js +1 -0
  163. package/src/commands/hooks.js +260 -0
  164. package/src/commands/models.js +1 -1
  165. package/src/commands/shell.js +114 -9
  166. package/src/errors/diagnostic.js +9 -9
  167. package/src/index.js +32 -0
  168. package/src/knowledge/service/grpc-codec.js +2 -0
  169. package/src/lib/slash-commands.js +1 -0
  170. package/src/local/runtime-store.js +46 -0
  171. package/src/models/catalog.js +0 -49
  172. package/src/models/inventory-core.js +69 -0
  173. package/src/plugins/index.js +8 -1
  174. package/src/plugins/registry.js +56 -0
  175. package/src/rpc/generated/errors_pb.js +49 -1
  176. package/src/tools/runtime.js +13 -5
@@ -0,0 +1,193 @@
1
+ export type HookEvent =
2
+ | 'session_start' | 'session_end'
3
+ | 'user_prompt_submitted' | 'user_prompt_transformed'
4
+ | 'pre_model_use' | 'post_model_use'
5
+ | 'pre_tool_use' | 'post_tool_use' | 'post_tool_use_failure'
6
+ | 'error_occurred' | 'agent_stop' | 'subagent_start' | 'subagent_stop';
7
+ export type PermissionDecision = 'allow' | 'deny' | 'ask';
8
+ export type ErrorHandlingDecision = 'retry' | 'skip' | 'abort';
9
+ export type HookFailureMode = 'open' | 'closed' | 'error';
10
+
11
+ export interface HookInvocation {
12
+ session_id?: string;
13
+ run_id?: string;
14
+ turn_id?: string;
15
+ message_id?: string;
16
+ agent_id?: string;
17
+ parent_agent_id?: string;
18
+ source?: string;
19
+ metadata?: Record<string, unknown>;
20
+ }
21
+
22
+ export interface HookEnvelope<TInput extends Record<string, unknown> = Record<string, unknown>> {
23
+ schema: 'agentsam.hook.v1';
24
+ hook: HookEvent;
25
+ timestamp: number;
26
+ cwd: string;
27
+ invocation: HookInvocation;
28
+ input: Readonly<TInput>;
29
+ }
30
+
31
+ export interface HookOutput {
32
+ permission_decision?: PermissionDecision;
33
+ permission_decision_reason?: string;
34
+ modified_args?: Record<string, unknown>;
35
+ modified_request?: Record<string, unknown>;
36
+ modified_result?: unknown;
37
+ modified_prompt?: string;
38
+ modified_transformed_prompt?: string;
39
+ modified_config?: Record<string, unknown>;
40
+ additional_context?: string;
41
+ suppress_output?: boolean;
42
+ error_handling?: ErrorHandlingDecision;
43
+ retry_count?: number;
44
+ user_notification?: string;
45
+ decision?: 'allow' | 'block';
46
+ reason?: string;
47
+ cleanup_actions?: string[];
48
+ session_summary?: string;
49
+ metadata?: Record<string, unknown>;
50
+ }
51
+
52
+ export type HookHandler = (envelope: HookEnvelope) => HookOutput | null | undefined | Promise<HookOutput | null | undefined>;
53
+ export interface HookDefinition {
54
+ id?: string;
55
+ handler: HookHandler;
56
+ matches?: (envelope: HookEnvelope) => boolean | Promise<boolean>;
57
+ priority?: number;
58
+ timeout_ms?: number;
59
+ failure_mode?: HookFailureMode;
60
+ enabled?: boolean;
61
+ metadata?: Record<string, unknown>;
62
+ }
63
+ export interface HookReceipt {
64
+ schema: 'agentsam.hook.receipt.v1';
65
+ hook_id: string;
66
+ hook: HookEvent;
67
+ invocation?: Readonly<HookInvocation>;
68
+ status: 'completed' | 'failed';
69
+ started_at: number;
70
+ completed_at: number;
71
+ duration_ms: number;
72
+ input_keys: readonly string[];
73
+ output_keys: readonly string[];
74
+ error?: {
75
+ error_code: string;
76
+ reason: string;
77
+ domain: string;
78
+ failure_class: string;
79
+ stage: string;
80
+ feature: string;
81
+ failure_behavior: 'fail_closed' | 'fail_open' | 'observe_only' | 'retry_bounded' | 'skip' | 'abort' | 'no_replay';
82
+ retryable: boolean;
83
+ side_effect_state: 'none' | 'not_started' | 'confirmed_not_applied' | 'confirmed_applied' | 'partially_applied' | 'unknown';
84
+ adapter: string | null;
85
+ protocol: string | null;
86
+ transport: string | null;
87
+ fingerprint: string;
88
+ message: string;
89
+ };
90
+ native_evidence?: { code: string | null; exception_type: string | null; message: string };
91
+ }
92
+ export interface HookDispatchResult {
93
+ schema: 'agentsam.hook.dispatch.v1';
94
+ hook: HookEvent;
95
+ input: Readonly<Record<string, unknown>>;
96
+ output: Readonly<HookOutput>;
97
+ receipts: readonly HookReceipt[];
98
+ errors: readonly (NonNullable<HookReceipt['error']> & { hook_id: string; hook: HookEvent; native_evidence?: HookReceipt['native_evidence'] })[];
99
+ }
100
+ export interface HookRuntime {
101
+ register(event: HookEvent | string, definition: HookDefinition | HookHandler): this;
102
+ unregister(event: HookEvent | string, id: string): boolean;
103
+ list(event?: HookEvent | string): readonly Omit<HookDefinition, 'handler' | 'matches'>[];
104
+ dispatch(event: HookEvent | string, input?: Record<string, unknown>, invocation?: HookInvocation, options?: { cwd?: string }): Promise<HookDispatchResult>;
105
+ }
106
+
107
+ export const HOOK_PROTOCOL_SCHEMA: 'agentsam.hook.v1';
108
+ export const HOOK_CONFIG_SCHEMA: 'agentsam.hooks.config.v1';
109
+ export const HOOK_RECEIPT_SCHEMA: 'agentsam.hook.receipt.v1';
110
+ export const HOOK_EVENTS: readonly HookEvent[];
111
+ export const PERMISSION_DECISIONS: readonly PermissionDecision[];
112
+ export const ERROR_HANDLING_DECISIONS: readonly ErrorHandlingDecision[];
113
+ export const STOP_DECISIONS: readonly ('allow' | 'block')[];
114
+ export const HOOK_FAILURE_MODES: readonly HookFailureMode[];
115
+ export function normalizeHookEvent(value: string): HookEvent;
116
+ export function normalizeHookFailureMode(value: string | undefined, hook: HookEvent): HookFailureMode;
117
+ export function createHookInvocation(value?: HookInvocation): Readonly<HookInvocation>;
118
+ export function createHookEnvelope(event: string, input?: Record<string, unknown>, invocation?: HookInvocation, options?: { timestamp?: number; cwd?: string }): HookEnvelope;
119
+ export function normalizeHookOutput(event: string, value: HookOutput | null | undefined): Readonly<HookOutput>;
120
+ export function normalizeHookDefinition(event: string, value: HookDefinition | HookHandler, index?: number): Readonly<HookDefinition & { id: string; hook: HookEvent; timeout_ms: number; failure_mode: HookFailureMode }>;
121
+ export function createHookRuntime(options?: { hooks?: Record<string, HookDefinition | HookHandler | (HookDefinition | HookHandler)[]>; clock?: () => number; onReceipt?: (receipt: HookReceipt) => unknown }): HookRuntime;
122
+ export class AgentSamHooks implements HookRuntime {
123
+ constructor(options?: { hooks?: Record<string, HookDefinition | HookHandler | (HookDefinition | HookHandler)[]>; clock?: () => number; onReceipt?: (receipt: HookReceipt) => unknown });
124
+ register(event: HookEvent | string, definition: HookDefinition | HookHandler): this;
125
+ unregister(event: HookEvent | string, id: string): boolean;
126
+ list(event?: HookEvent | string): readonly Omit<HookDefinition, 'handler' | 'matches'>[];
127
+ dispatch(event: HookEvent | string, input?: Record<string, unknown>, invocation?: HookInvocation, options?: { cwd?: string }): Promise<HookDispatchResult>;
128
+ }
129
+ export function ensureHookRuntime(value?: HookRuntime | Record<string, unknown>, options?: Record<string, unknown>): HookRuntime;
130
+ export function isHookRuntime(value: unknown): value is HookRuntime;
131
+ export class HookExecutionError extends Error { code: string; hook: HookEvent; hook_id: string; }
132
+ export class HookPermissionError extends Error { code: string; kind: string; target: string; decision: string; }
133
+
134
+ export function createCallbackHookAdapter(handler: (input: Record<string, unknown>, invocation: HookInvocation, envelope: HookEnvelope) => unknown): HookHandler;
135
+ export function createCommandHookAdapter(options: { command: string; args?: string[]; cwd?: string; env?: Record<string, string>; inherit_environment?: boolean; timeout_ms?: number; max_output_bytes?: number }): HookHandler;
136
+ export function createHttpHookAdapter(options: { url: string; headers?: Record<string, string>; timeout_ms?: number; max_response_bytes?: number; fetchImpl?: typeof fetch }): HookHandler;
137
+ export function findHookConfig(startDirectory?: string, options?: Record<string, unknown>): string | null;
138
+ export function loadHookConfig(filename?: string, options?: Record<string, unknown>): Record<string, unknown>;
139
+ export function normalizeHookConfig(value: Record<string, unknown>, options?: Record<string, unknown>): Record<string, unknown>;
140
+ export function createConfiguredHookAdapter(definition: Record<string, unknown>, configDirectory: string, options?: Record<string, unknown>): HookHandler;
141
+ export function createHookRuntimeFromConfig(configOrFilename?: string | Record<string, unknown>, options?: Record<string, unknown>): { runtime: HookRuntime; config: Record<string, unknown> };
142
+
143
+ export interface StoredHook {
144
+ id: string;
145
+ owner_id: string;
146
+ hook_key: string;
147
+ event_type: HookEvent;
148
+ source_kind: 'stored' | 'config' | 'code' | 'system';
149
+ scope_type: 'global' | 'account' | 'repository' | 'project' | 'session';
150
+ scope_ref: string | null;
151
+ handler_type: string;
152
+ handler_config: Record<string, unknown>;
153
+ match: Record<string, unknown>;
154
+ failure_mode: HookFailureMode;
155
+ priority: number;
156
+ timeout_ms: number;
157
+ is_active: boolean;
158
+ workflow_id: string | null;
159
+ description: string | null;
160
+ metadata: Record<string, unknown>;
161
+ revision: number;
162
+ }
163
+ export interface HookStore {
164
+ readonly ownerId: string;
165
+ listHooks(query?: Record<string, unknown>): Promise<readonly StoredHook[]>;
166
+ getHook(id: string): Promise<StoredHook | null>;
167
+ upsertHook(value: Record<string, unknown>): Promise<StoredHook>;
168
+ setHookActive(id: string, active: boolean): Promise<boolean>;
169
+ removeHook(id: string): Promise<boolean>;
170
+ recordExecution(receipt: HookReceipt, correlation?: Record<string, unknown>): Promise<string>;
171
+ listExecutions(query?: Record<string, unknown>): Promise<readonly Record<string, unknown>[]>;
172
+ }
173
+ export function createHookStore(db: { prepare(sql: string): unknown }, options?: { ownerId?: string }): HookStore;
174
+ export function matchesHookInput(match?: Record<string, unknown>, input?: Record<string, unknown>): boolean;
175
+ export function registerStoredHooks(runtime: HookRuntime, store: HookStore, options?: Record<string, unknown>): Promise<readonly StoredHook[]>;
176
+
177
+ export interface CapabilityAdapter {
178
+ toolDescriptors(options?: Record<string, unknown>): Array<{ name: string; description?: string; input_schema?: Record<string, unknown>; [key: string]: unknown }>;
179
+ canInvoke?(id: string): boolean;
180
+ invoke(id: string, input?: Record<string, unknown>, context?: Record<string, unknown>): Promise<unknown>;
181
+ }
182
+ export interface ProviderAdapter {
183
+ provider?: string;
184
+ create(params?: Record<string, unknown>): Promise<Record<string, unknown>>;
185
+ continueWithToolOutputs(params?: Record<string, unknown>): Promise<Record<string, unknown>>;
186
+ compact?(params?: Record<string, unknown>): Promise<Record<string, unknown>>;
187
+ }
188
+ export function createHookedCapabilityAdapter(base: CapabilityAdapter, options?: Record<string, unknown>): CapabilityAdapter;
189
+ export function createHookedProviderAdapter(base: ProviderAdapter, options?: Record<string, unknown>): ProviderAdapter;
190
+ export function createCompositeCapabilityAdapter(adapters: CapabilityAdapter[]): CapabilityAdapter;
191
+ export function createMcpCapabilityAdapter(options: Record<string, unknown>): Promise<CapabilityAdapter>;
192
+ export function createLspCapabilityAdapter(options: Record<string, unknown>): CapabilityAdapter;
193
+ export function createAgentCapabilityAdapter(options: Record<string, unknown>): CapabilityAdapter;
@@ -0,0 +1,14 @@
1
+ export * from './contracts.js';
2
+ export * from './runtime.js';
3
+ export * from './policy.js';
4
+ export * from './config.js';
5
+ export * from './store.js';
6
+ export * from './adapters/callback.js';
7
+ export * from './adapters/command.js';
8
+ export * from './adapters/http.js';
9
+ export * from './adapters/provider.js';
10
+ export * from './adapters/capability.js';
11
+ export * from './adapters/composite.js';
12
+ export * from './adapters/mcp.js';
13
+ export * from './adapters/lsp.js';
14
+ export * from './adapters/agents.js';
@@ -0,0 +1,34 @@
1
+ export class HookPermissionError extends Error {
2
+ constructor(kind, name, decision = {}) {
3
+ const reason = decision.permission_decision_reason || `Hook policy ${decision.permission_decision || 'denied'} ${kind} '${name}'`;
4
+ super(reason);
5
+ this.name = 'HookPermissionError';
6
+ this.code = decision.permission_decision === 'ask'
7
+ ? 'AGENTSAM_HOOK_APPROVAL_REQUIRED'
8
+ : 'AGENTSAM_HOOK_PERMISSION_DENIED';
9
+ this.kind = kind;
10
+ this.target = name;
11
+ this.decision = decision.permission_decision || 'deny';
12
+ }
13
+ }
14
+
15
+ export async function resolveHookPermission({ output = {}, kind, name, requestPermission, envelope }) {
16
+ const decision = output.permission_decision;
17
+ if (!decision || decision === 'allow') return true;
18
+ if (decision === 'deny') throw new HookPermissionError(kind, name, output);
19
+ if (typeof requestPermission !== 'function') throw new HookPermissionError(kind, name, output);
20
+ const result = await requestPermission({
21
+ kind,
22
+ name,
23
+ reason: output.permission_decision_reason || null,
24
+ envelope,
25
+ });
26
+ const responseDecision = typeof result === 'string' ? result : (result?.decision || result?.kind);
27
+ const approved = result === true || result?.allowed === true || ['allow', 'approve', 'approve_once', 'approve-once', 'approve_always', 'approve-always'].includes(responseDecision);
28
+ if (!approved) throw new HookPermissionError(kind, name, {
29
+ ...output,
30
+ permission_decision: 'deny',
31
+ permission_decision_reason: result?.reason || output.permission_decision_reason || `Approval was not granted for ${kind} '${name}'`,
32
+ });
33
+ return true;
34
+ }
@@ -0,0 +1,312 @@
1
+ import {
2
+ HOOK_RECEIPT_SCHEMA,
3
+ createHookEnvelope,
4
+ normalizeHookDefinition,
5
+ normalizeHookEvent,
6
+ normalizeHookOutput,
7
+ } from './contracts.js';
8
+ import {
9
+ generatedErrorPolicy,
10
+ normalizeGeneratedErrorReason,
11
+ } from './errors-generated.js';
12
+
13
+ function clone(value) {
14
+ return value == null ? value : structuredClone(value);
15
+ }
16
+
17
+ const SKIPPED_MATCH = Symbol('agentsam_hook_skipped_match');
18
+
19
+ function nowMs(clock) {
20
+ const value = Number(clock());
21
+ if (!Number.isFinite(value) || value < 0) throw new TypeError('hook_clock_must_return_non_negative_number');
22
+ return value;
23
+ }
24
+
25
+ function errorMessage(error) {
26
+ return String(error?.message || error || 'unknown_hook_error')
27
+ .replace(/(authorization\s*[:=]\s*bearer\s+)[^\s,;]+/gi, '$1[REDACTED]')
28
+ .replace(/((?:api[_-]?key|password|secret|token)\s*[:=]\s*)[^\s,;]+/gi, '$1[REDACTED]')
29
+ .slice(0, 512);
30
+ }
31
+
32
+ function fingerprint(fields) {
33
+ const text = fields.map((value) => String(value || '').trim()).join('\u001f');
34
+ let hash = 0xcbf29ce484222325n;
35
+ for (let index = 0; index < text.length; index += 1) {
36
+ hash ^= BigInt(text.charCodeAt(index));
37
+ hash = BigInt.asUintN(64, hash * 0x100000001b3n);
38
+ }
39
+ return `err_${hash.toString(16).padStart(16, '0')}`;
40
+ }
41
+
42
+ function adapterFacts(reason, definition, error) {
43
+ const inferred = ['http', 'mcp', 'lsp', 'command'].find((name) => reason.startsWith(`hook_${name}_`)) || null;
44
+ const adapter = String(error?.adapter || definition.metadata?.adapter_type || inferred || '').trim() || null;
45
+ const protocol = String(error?.protocol || (adapter === 'command' ? 'agentsam.hook.v1' : adapter || '')).trim() || null;
46
+ const transport = String(error?.transport || (adapter === 'command' ? 'process' : '')).trim() || null;
47
+ return { adapter, protocol, transport };
48
+ }
49
+
50
+ function failureBehavior(definition) {
51
+ if (definition.failure_mode === 'closed') return 'fail_closed';
52
+ if (definition.failure_mode === 'open') return 'fail_open';
53
+ if (['post_tool_use', 'post_model_use'].includes(definition.hook)) return 'no_replay';
54
+ return 'abort';
55
+ }
56
+
57
+ function sideEffectState(hook) {
58
+ if (['post_tool_use', 'post_model_use'].includes(hook)) return 'confirmed_applied';
59
+ if (hook === 'post_tool_use_failure') return 'unknown';
60
+ return 'not_started';
61
+ }
62
+
63
+ function canonicalHookFailure(definition, error) {
64
+ let reason = normalizeGeneratedErrorReason(error?.reason, error?.message, error?.code);
65
+ if (/^(mcp|lsp)_/.test(reason)) {
66
+ const hookSpecific = normalizeGeneratedErrorReason(`hook_${reason}`);
67
+ if (hookSpecific !== 'hook_handler_failed') reason = hookSpecific;
68
+ }
69
+ const policy = generatedErrorPolicy(reason);
70
+ const behavior = failureBehavior(definition);
71
+ const sideEffect = sideEffectState(definition.hook);
72
+ const facts = adapterFacts(reason, definition, error);
73
+ const message = errorMessage(error);
74
+ const feature = `hooks.${definition.hook}`;
75
+ const canonical = {
76
+ error_code: policy.code,
77
+ reason,
78
+ domain: policy.domain,
79
+ failure_class: policy.failure_class,
80
+ stage: policy.default_stage,
81
+ feature,
82
+ failure_behavior: behavior,
83
+ retryable: sideEffect === 'confirmed_applied' || behavior === 'fail_closed' ? false : policy.retryable,
84
+ side_effect_state: sideEffect,
85
+ adapter: facts.adapter,
86
+ protocol: facts.protocol,
87
+ transport: facts.transport,
88
+ fingerprint: fingerprint([policy.code, reason, policy.domain, policy.failure_class, policy.default_stage, feature, behavior, sideEffect, facts.adapter, facts.protocol, facts.transport, definition.id]),
89
+ message,
90
+ };
91
+ return Object.freeze({
92
+ error: Object.freeze(canonical),
93
+ native_evidence: Object.freeze({
94
+ code: error?.code ? String(error.code).slice(0, 128) : null,
95
+ exception_type: error?.name ? String(error.name).slice(0, 128) : null,
96
+ message,
97
+ }),
98
+ });
99
+ }
100
+
101
+ function withTimeout(promise, timeoutMs, hookId) {
102
+ let timer;
103
+ const timeout = new Promise((_, reject) => {
104
+ timer = setTimeout(() => {
105
+ const error = new Error(`hook_timeout:${hookId}:${timeoutMs}`);
106
+ error.code = 'AGENTSAM_HOOK_TIMEOUT';
107
+ reject(error);
108
+ }, timeoutMs);
109
+ });
110
+ return Promise.race([promise, timeout]).finally(() => clearTimeout(timer));
111
+ }
112
+
113
+ function receiptFor({ definition, envelope, startedAt, completedAt, status, output, error }) {
114
+ const failure = error ? canonicalHookFailure(definition, error) : null;
115
+ return Object.freeze({
116
+ schema: HOOK_RECEIPT_SCHEMA,
117
+ hook_id: definition.id,
118
+ hook: definition.hook,
119
+ invocation: envelope.invocation,
120
+ status,
121
+ started_at: startedAt,
122
+ completed_at: completedAt,
123
+ duration_ms: Math.max(0, completedAt - startedAt),
124
+ input_keys: Object.freeze(Object.keys(envelope.input).sort()),
125
+ output_keys: Object.freeze(Object.keys(output || {}).sort()),
126
+ ...(failure || {}),
127
+ });
128
+ }
129
+
130
+ function updateWorkingInput(hook, input, output) {
131
+ const next = clone(input);
132
+ if (output.modified_args !== undefined) next.tool_args = clone(output.modified_args);
133
+ if (output.modified_request !== undefined) next.request = clone(output.modified_request);
134
+ if (output.modified_result !== undefined) {
135
+ if (hook === 'post_tool_use') next.tool_result = clone(output.modified_result);
136
+ else next.model_result = clone(output.modified_result);
137
+ }
138
+ if (output.modified_prompt !== undefined) next.prompt = output.modified_prompt;
139
+ if (output.modified_transformed_prompt !== undefined) next.transformed_prompt = output.modified_transformed_prompt;
140
+ if (output.modified_config !== undefined) next.config = {
141
+ ...(next.config && typeof next.config === 'object' && !Array.isArray(next.config) ? next.config : {}),
142
+ ...clone(output.modified_config),
143
+ };
144
+ return next;
145
+ }
146
+
147
+ function mergeOutput(current, output) {
148
+ const next = { ...current };
149
+ for (const [key, value] of Object.entries(output)) {
150
+ if (key === 'additional_context') continue;
151
+ if (key === 'suppress_output') next.suppress_output = next.suppress_output === true || value === true;
152
+ else if (key === 'retry_count') next.retry_count = Math.max(Number(next.retry_count || 0), Number(value || 0));
153
+ else if (key === 'cleanup_actions') next.cleanup_actions = [...(next.cleanup_actions || []), ...value];
154
+ else if (key === 'modified_config') next.modified_config = { ...(next.modified_config || {}), ...clone(value) };
155
+ else next[key] = clone(value);
156
+ }
157
+ return next;
158
+ }
159
+
160
+ function isTerminalDecision(hook, output) {
161
+ if (['pre_tool_use', 'pre_model_use'].includes(hook)) {
162
+ return output.permission_decision === 'deny' || output.permission_decision === 'ask';
163
+ }
164
+ return hook === 'agent_stop' && output.decision === 'block';
165
+ }
166
+
167
+ export class HookExecutionError extends Error {
168
+ constructor(definition, cause) {
169
+ super(`hook_execution_failed:${definition.id}:${errorMessage(cause)}`, { cause });
170
+ this.name = 'HookExecutionError';
171
+ this.code = 'AGENTSAM_HOOK_EXECUTION_FAILED';
172
+ this.reason = normalizeGeneratedErrorReason(cause?.reason, cause?.message, cause?.code);
173
+ this.error_code = generatedErrorPolicy(this.reason).code;
174
+ this.hook = definition.hook;
175
+ this.hook_id = definition.id;
176
+ }
177
+ }
178
+
179
+ export function isHookRuntime(value) {
180
+ return Boolean(value && typeof value.dispatch === 'function' && typeof value.register === 'function');
181
+ }
182
+
183
+ export function createHookRuntime(options = {}) {
184
+ const clock = typeof options.clock === 'function' ? options.clock : Date.now;
185
+ const onReceipt = options.onReceipt;
186
+ const registry = new Map();
187
+ let sequence = 0;
188
+
189
+ function register(event, definition) {
190
+ const hook = normalizeHookEvent(event);
191
+ const normalized = normalizeHookDefinition(hook, definition, sequence++);
192
+ const current = registry.get(hook) || [];
193
+ if (current.some((row) => row.id === normalized.id)) throw new Error(`duplicate_hook_id:${normalized.id}`);
194
+ current.push(normalized);
195
+ current.sort((a, b) => a.priority - b.priority || a.id.localeCompare(b.id));
196
+ registry.set(hook, current);
197
+ return runtime;
198
+ }
199
+
200
+ function unregister(event, id) {
201
+ const hook = normalizeHookEvent(event);
202
+ const before = registry.get(hook) || [];
203
+ const after = before.filter((row) => row.id !== String(id));
204
+ registry.set(hook, after);
205
+ return after.length !== before.length;
206
+ }
207
+
208
+ function list(event) {
209
+ if (event == null) return Object.freeze([...registry.values()].flat().map((row) => Object.freeze({ ...row, handler: undefined, matches: undefined })));
210
+ const hook = normalizeHookEvent(event);
211
+ return Object.freeze((registry.get(hook) || []).map((row) => Object.freeze({ ...row, handler: undefined, matches: undefined })));
212
+ }
213
+
214
+ async function dispatch(event, input = {}, invocation = {}, dispatchOptions = {}) {
215
+ const hook = normalizeHookEvent(event);
216
+ let workingInput = clone(input);
217
+ let combinedOutput = {};
218
+ const contexts = [];
219
+ const receipts = [];
220
+ const errors = [];
221
+
222
+ for (const definition of registry.get(hook) || []) {
223
+ if (!definition.enabled) continue;
224
+ const envelope = createHookEnvelope(hook, workingInput, invocation, {
225
+ timestamp: nowMs(clock),
226
+ cwd: dispatchOptions.cwd,
227
+ });
228
+ const startedAt = nowMs(clock);
229
+ let output;
230
+ let receipt;
231
+ try {
232
+ const raw = await withTimeout(Promise.resolve().then(async () => {
233
+ if (definition.matches && !await definition.matches(envelope)) return SKIPPED_MATCH;
234
+ return definition.handler(envelope);
235
+ }), definition.timeout_ms, definition.id);
236
+ if (raw === SKIPPED_MATCH) continue;
237
+ output = normalizeHookOutput(hook, raw);
238
+ workingInput = updateWorkingInput(hook, workingInput, output);
239
+ combinedOutput = mergeOutput(combinedOutput, output);
240
+ if (output.additional_context) contexts.push(output.additional_context);
241
+ const completedAt = nowMs(clock);
242
+ receipt = receiptFor({ definition, envelope, startedAt, completedAt, status: 'completed', output });
243
+ } catch (error) {
244
+ const completedAt = nowMs(clock);
245
+ receipt = receiptFor({ definition, envelope, startedAt, completedAt, status: 'failed', error });
246
+ errors.push(Object.freeze({ hook_id: definition.id, hook, ...receipt.error, native_evidence: receipt.native_evidence }));
247
+ receipts.push(receipt);
248
+ if (typeof onReceipt === 'function') {
249
+ try { await onReceipt(receipt); } catch { /* Observer failures never replace hook policy. */ }
250
+ }
251
+ if (definition.failure_mode === 'error') throw new HookExecutionError(definition, error);
252
+ if (definition.failure_mode === 'closed') {
253
+ combinedOutput = mergeOutput(combinedOutput, {
254
+ permission_decision: 'deny',
255
+ permission_decision_reason: `Hook '${definition.id}' failed closed: ${errorMessage(error)}`,
256
+ });
257
+ break;
258
+ }
259
+ continue;
260
+ }
261
+
262
+ receipts.push(receipt);
263
+ if (typeof onReceipt === 'function') {
264
+ try { await onReceipt(receipt); } catch { /* Receipts are observable without becoming execution authority. */ }
265
+ }
266
+ if (isTerminalDecision(hook, output)) break;
267
+ }
268
+
269
+ if (contexts.length) combinedOutput.additional_context = contexts.join('\n\n');
270
+ return Object.freeze({
271
+ schema: 'agentsam.hook.dispatch.v1',
272
+ hook,
273
+ input: Object.freeze(workingInput),
274
+ output: Object.freeze(combinedOutput),
275
+ receipts: Object.freeze(receipts),
276
+ errors: Object.freeze(errors),
277
+ });
278
+ }
279
+
280
+ const runtime = Object.freeze({ register, unregister, list, dispatch });
281
+ for (const [event, definitions] of Object.entries(options.hooks || {})) {
282
+ for (const definition of Array.isArray(definitions) ? definitions : [definitions]) register(event, definition);
283
+ }
284
+ return runtime;
285
+ }
286
+
287
+ export class AgentSamHooks {
288
+ #runtime;
289
+
290
+ constructor(options = {}) {
291
+ this.#runtime = createHookRuntime(options);
292
+ }
293
+
294
+ register(event, definition) {
295
+ this.#runtime.register(event, definition);
296
+ return this;
297
+ }
298
+
299
+ unregister(event, id) { return this.#runtime.unregister(event, id); }
300
+ list(event) { return this.#runtime.list(event); }
301
+ dispatch(event, input = {}, invocation = {}, options = {}) {
302
+ return this.#runtime.dispatch(event, input, invocation, options);
303
+ }
304
+ }
305
+
306
+ export function ensureHookRuntime(value, options = {}) {
307
+ if (!value) return createHookRuntime(options);
308
+ if (isHookRuntime(value)) return value;
309
+ if (value.hooks && typeof value === 'object') return createHookRuntime({ ...options, ...value });
310
+ if (typeof value === 'object') return createHookRuntime({ ...options, hooks: value });
311
+ throw new TypeError('invalid_hook_runtime');
312
+ }