@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.
- package/README.md +18 -0
- package/apps/cad-creator/shared/cad/package.json +1 -1
- package/apps/client-cms-editor/backend/package.json +3 -3
- package/apps/client-cms-editor/frontend/package.json +4 -4
- package/apps/client-cms-editor/package-lock.json +8 -8
- package/apps/client-cms-editor/package.json +1 -1
- package/apps/client-cms-editor/shared/cms/package.json +2 -2
- package/docs/architecture/HOOKS-MODELS-SELF-HOSTING-PLAN.md +199 -0
- package/examples/hyperspace-demo/index.html +16 -0
- package/examples/hyperspace-demo/package.json +21 -0
- package/examples/hyperspace-demo/src/main.tsx +160 -0
- package/examples/hyperspace-demo/tsconfig.json +9 -0
- package/examples/hyperspace-demo/vite.config.ts +24 -0
- package/migrations/runtime/0005_agentsam_hooks.sql +87 -0
- package/package.json +23 -7
- package/packages/agentsam-brand/package.json +1 -1
- package/packages/agentsam-brand/src/core/v2/compile.js +1 -1
- package/packages/agentsam-brand/src/core/v2/schema.js +1 -1
- package/packages/agentsam-contracts/package.json +1 -1
- package/packages/agentsam-contracts/src/errors.ts +19 -10
- package/packages/agentsam-errors/package.json +11 -3
- package/packages/agentsam-errors/protocol/agentsam.error.v2.schema.json +821 -0
- package/packages/agentsam-errors/protocol/error-catalog.json +3512 -0
- package/packages/agentsam-errors/src/envelope.js +63 -11
- package/packages/agentsam-errors/src/error.js +6 -0
- package/packages/agentsam-errors/src/fingerprint.js +9 -1
- package/packages/agentsam-errors/src/normalize.js +51 -6
- package/packages/agentsam-errors/src/recovery.js +9 -35
- package/packages/agentsam-errors/src/transport.js +6 -1
- package/packages/agentsam-errors/src/vocabulary.js +515 -126
- package/packages/agentsam-errors/test/catalog-v2.test.mjs +106 -0
- package/packages/agentsam-errors/types/index.d.ts +157 -0
- package/packages/agentsam-goap/package.json +2 -2
- package/packages/agentsam-hooks/README.md +206 -0
- package/packages/agentsam-hooks/bin/agentsam-hooks.mjs +64 -0
- package/packages/agentsam-hooks/bindings/go/command.go +55 -0
- package/packages/agentsam-hooks/bindings/go/errors_generated.go +402 -0
- package/packages/agentsam-hooks/bindings/go/go.mod +3 -0
- package/packages/agentsam-hooks/bindings/go/hooks.go +285 -0
- package/packages/agentsam-hooks/bindings/go/hooks_test.go +48 -0
- package/packages/agentsam-hooks/bindings/rust/Cargo.toml +11 -0
- package/packages/agentsam-hooks/bindings/rust/src/errors_generated.rs +396 -0
- package/packages/agentsam-hooks/bindings/rust/src/lib.rs +397 -0
- package/packages/agentsam-hooks/package.json +69 -0
- package/packages/agentsam-hooks/schema/manifest.json +54 -0
- package/packages/agentsam-hooks/scripts/clean-protocol.mjs +17 -0
- package/packages/agentsam-hooks/scripts/sync-protocol.mjs +27 -0
- package/packages/agentsam-hooks/src/adapters/agents.js +82 -0
- package/packages/agentsam-hooks/src/adapters/callback.js +4 -0
- package/packages/agentsam-hooks/src/adapters/capability.js +122 -0
- package/packages/agentsam-hooks/src/adapters/command.js +104 -0
- package/packages/agentsam-hooks/src/adapters/composite.js +31 -0
- package/packages/agentsam-hooks/src/adapters/http.js +58 -0
- package/packages/agentsam-hooks/src/adapters/lsp.js +116 -0
- package/packages/agentsam-hooks/src/adapters/mcp.js +76 -0
- package/packages/agentsam-hooks/src/adapters/provider.js +129 -0
- package/packages/agentsam-hooks/src/config.js +143 -0
- package/packages/agentsam-hooks/src/contracts.js +284 -0
- package/packages/agentsam-hooks/src/errors-generated.js +2732 -0
- package/packages/agentsam-hooks/src/index.d.ts +193 -0
- package/packages/agentsam-hooks/src/index.js +14 -0
- package/packages/agentsam-hooks/src/policy.js +34 -0
- package/packages/agentsam-hooks/src/runtime.js +312 -0
- package/packages/agentsam-hooks/src/store.js +264 -0
- package/packages/agentsam-hooks/test/adapters.test.mjs +216 -0
- package/packages/agentsam-hooks/test/runtime.test.mjs +258 -0
- package/packages/agentsam-knowledge/package.json +1 -1
- package/packages/agentsam-nav/dist/Nav.d.ts +43 -0
- package/packages/agentsam-nav/dist/NavCollapsible.d.ts +10 -0
- package/packages/agentsam-nav/dist/NavContent.d.ts +2 -0
- package/packages/agentsam-nav/dist/NavFooter.d.ts +2 -0
- package/packages/agentsam-nav/dist/NavGroup.d.ts +3 -0
- package/packages/agentsam-nav/dist/NavHeader.d.ts +2 -0
- package/packages/agentsam-nav/dist/NavMenu.d.ts +22 -0
- package/packages/agentsam-nav/dist/NavProvider.d.ts +35 -0
- package/packages/agentsam-nav/dist/NavTrigger.d.ts +2 -0
- package/packages/agentsam-nav/dist/Surfaces.d.ts +41 -0
- package/packages/agentsam-nav/dist/browser.d.ts +11 -0
- package/packages/agentsam-nav/dist/browser.global.js +135 -0
- package/packages/agentsam-nav/dist/browser.js +7290 -0
- package/packages/agentsam-nav/dist/contracts.d.ts +76 -0
- package/packages/agentsam-nav/dist/index.d.ts +11 -0
- package/packages/agentsam-nav/dist/index.js +7291 -0
- package/packages/agentsam-nav/dist/legacy.css +83 -0
- package/packages/agentsam-nav/dist/shell.css +113 -0
- package/packages/agentsam-nav/dist/theme.css +113 -0
- package/packages/agentsam-nav/dist/utils.d.ts +1 -0
- package/packages/agentsam-nav/package.json +1 -1
- package/packages/agentsam-queue-control/package.json +1 -1
- package/packages/agentsam-repository/package.json +1 -1
- package/packages/agentsam-repository/src/goap.js.bak-20260922011251 +670 -0
- package/packages/agentsam-vault/package.json +1 -1
- package/packages/agentsam-workbench/package.json +3 -3
- package/packages/connectors/cloudflare/package.json +1 -1
- package/packages/heuristic-theme/package.json +1 -1
- package/packages/identity/package.json +2 -2
- package/packages/providers/completeful/package.json +1 -1
- package/packages/theme-church-site/package.json +2 -2
- package/packages/theme-companions-site/package.json +2 -2
- package/packages/theme-floors-site/package.json +2 -2
- package/packages/theme-fuelnfree-site/package.json +2 -2
- package/packages/theme-handyman-site/package.json +2 -2
- package/packages/theme-insurance-site/package.json +2 -2
- package/packages/theme-scenes/package.json +1 -1
- package/packages/theme-shinshu-site/package.json +2 -2
- package/packages/work-graph/package.json +1 -1
- package/protocol/apps/agentsam.app.v1.schema.json +1 -1
- package/protocol/apps/agentsam.host-install.v1.schema.json +1 -1
- package/protocol/assets/asset-record.v1.schema.json +1 -1
- package/protocol/brand/brandpack.v1.schema.json +1 -1
- package/protocol/capabilities/capability-manifest.schema.json +1 -1
- package/protocol/capabilities/knowledge-search-input.schema.json +1 -0
- package/protocol/credentials/issue.v1.schema.json +1 -1
- package/protocol/database/agentsam.schema-pack.v1.schema.json +1 -1
- package/protocol/database/connection.v1.schema.json +1 -1
- package/protocol/embeddings/embedding-profile.v1.schema.json +1 -1
- package/protocol/errors/README.md +36 -0
- package/protocol/errors/agentsam.error.v2.schema.json +821 -0
- package/protocol/errors/error-catalog.json +3506 -158
- package/protocol/errors/error-envelope.schema.json +195 -52
- package/protocol/errors/recovery.v1.schema.json +1 -1
- package/protocol/features/agentsam.feature.v1.schema.json +1 -1
- package/protocol/hooks/README.md +18 -0
- package/protocol/hooks/agentsam.hook.output.v1.schema.json +27 -0
- package/protocol/hooks/agentsam.hook.receipt.v1.schema.json +61 -0
- package/protocol/hooks/agentsam.hook.v1.schema.json +35 -0
- package/protocol/hooks/agentsam.hooks.config.v1.schema.json +78 -0
- package/protocol/local-engine/benchmark.v1.schema.json +1 -1
- package/protocol/local-engine/engine.v1.schema.json +1 -1
- package/protocol/models/model-inventory-v2.schema.json +1 -1
- package/protocol/models/provider-model-snapshot.v1.schema.json +22 -0
- package/protocol/rpc/v1/errors.proto +1 -0
- package/protocol/runtime/agentsam.runtime.v1.schema.json +1 -1
- package/protocol/runtime/workspace-fs.v1.schema.json +1 -1
- package/protocol/sam/activity.v1.schema.json +1 -1
- package/protocol/sam/answer.v1.schema.json +1 -1
- package/protocol/sam/calibration.v1.schema.json +1 -1
- package/protocol/sam/decision-receipt.v1.schema.json +1 -1
- package/protocol/sam/evaluation.v1.schema.json +1 -1
- package/protocol/sam/operation.schema.json +1 -1
- package/protocol/sam/outcome.v1.schema.json +1 -1
- package/protocol/sam/question.v1.schema.json +1 -1
- package/protocol/sam/result.schema.json +1 -1
- package/protocol/sam/state.v1.schema.json +1 -1
- package/protocol/scoring/scorecard.v1.schema.json +1 -1
- package/protocol/skills/agentsam.interaction.v1.schema.json +1 -1
- package/protocol/skills/agentsam.skill.v1.schema.json +1 -1
- package/protocol/theme-refinery/product-metadata.schema.json +1 -1
- package/protocol/theme-refinery/reconciliation-receipt.schema.json +1 -1
- package/protocol/ui/icon.v1.schema.json +1 -1
- package/python/README.md +20 -0
- package/python/agentsam_sdk/errors/__init__.py +4 -0
- package/python/agentsam_sdk/errors/vocabulary.py +2730 -0
- package/python/agentsam_sdk/hooks/__init__.py +32 -0
- package/python/agentsam_sdk/hooks/adapters.py +132 -0
- package/python/agentsam_sdk/hooks/contracts.py +121 -0
- package/python/agentsam_sdk/hooks/runtime.py +253 -0
- package/python/tests/test_hooks.py +115 -0
- package/src/agent/responses-runner.js +145 -1
- package/src/cli/command-catalog.js +1 -0
- package/src/cli.js +9 -0
- package/src/commands/cheat-sheet.js +1 -0
- package/src/commands/hooks.js +260 -0
- package/src/commands/models.js +1 -1
- package/src/commands/shell.js +114 -9
- package/src/errors/diagnostic.js +9 -9
- package/src/index.js +32 -0
- package/src/knowledge/service/grpc-codec.js +2 -0
- package/src/lib/slash-commands.js +1 -0
- package/src/local/runtime-store.js +46 -0
- package/src/models/catalog.js +0 -49
- package/src/models/inventory-core.js +69 -0
- package/src/plugins/index.js +8 -1
- package/src/plugins/registry.js +56 -0
- package/src/rpc/generated/errors_pb.js +49 -1
- package/src/tools/runtime.js +13 -5
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import http from 'node:http';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import test from 'node:test';
|
|
7
|
+
import {
|
|
8
|
+
HOOK_PROTOCOL_SCHEMA,
|
|
9
|
+
AgentSamHooks,
|
|
10
|
+
HookPermissionError,
|
|
11
|
+
createCallbackHookAdapter,
|
|
12
|
+
createCommandHookAdapter,
|
|
13
|
+
createHookRuntime,
|
|
14
|
+
createHookedCapabilityAdapter,
|
|
15
|
+
createHookedProviderAdapter,
|
|
16
|
+
createHttpHookAdapter,
|
|
17
|
+
normalizeHookEvent,
|
|
18
|
+
normalizeHookOutput,
|
|
19
|
+
} from '../src/index.js';
|
|
20
|
+
|
|
21
|
+
const cwd = process.cwd();
|
|
22
|
+
|
|
23
|
+
test('contract accepts ergonomic event/output aliases but emits canonical fields', () => {
|
|
24
|
+
assert.equal(HOOK_PROTOCOL_SCHEMA, 'agentsam.hook.v1');
|
|
25
|
+
assert.equal(new AgentSamHooks().list().length, 0);
|
|
26
|
+
assert.equal(normalizeHookEvent('onPreToolUse'), 'pre_tool_use');
|
|
27
|
+
assert.deepEqual(normalizeHookOutput('session_start', { modifiedConfig: { auto_compact: false } }), { modified_config: { auto_compact: false } });
|
|
28
|
+
assert.deepEqual(normalizeHookOutput('pre_tool_use', {
|
|
29
|
+
permissionDecision: 'allow',
|
|
30
|
+
modifiedArgs: { timeout: 30 },
|
|
31
|
+
additionalContext: 'bounded context',
|
|
32
|
+
}), {
|
|
33
|
+
permission_decision: 'allow',
|
|
34
|
+
modified_args: { timeout: 30 },
|
|
35
|
+
additional_context: 'bounded context',
|
|
36
|
+
});
|
|
37
|
+
assert.throws(() => normalizeHookOutput('post_tool_use', { permission_decision: 'deny' }), /not_supported/);
|
|
38
|
+
assert.throws(() => normalizeHookOutput('pre_tool_use', { permissionDecison: 'allow' }), /unsupported_hook_output_field/);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('runtime composes modifications in priority order and receipts exclude values', async () => {
|
|
42
|
+
const observed = [];
|
|
43
|
+
const hooks = createHookRuntime({
|
|
44
|
+
onReceipt: (receipt) => observed.push(receipt),
|
|
45
|
+
hooks: {
|
|
46
|
+
pre_tool_use: [
|
|
47
|
+
{
|
|
48
|
+
id: 'second', priority: 20,
|
|
49
|
+
handler: ({ input }) => ({
|
|
50
|
+
permission_decision: 'allow',
|
|
51
|
+
modified_args: { ...input.tool_args, second: true },
|
|
52
|
+
additional_context: 'second context',
|
|
53
|
+
}),
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
id: 'first', priority: 10,
|
|
57
|
+
handler: ({ input }) => ({
|
|
58
|
+
permission_decision: 'allow',
|
|
59
|
+
modified_args: { ...input.tool_args, secret: 'not-in-receipt' },
|
|
60
|
+
additional_context: 'first context',
|
|
61
|
+
}),
|
|
62
|
+
},
|
|
63
|
+
],
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
const result = await hooks.dispatch('pre_tool_use', {
|
|
67
|
+
tool_name: 'example.read', tool_args: { value: 1 },
|
|
68
|
+
}, { session_id: 'session-test' }, { cwd });
|
|
69
|
+
assert.deepEqual(result.input.tool_args, { value: 1, secret: 'not-in-receipt', second: true });
|
|
70
|
+
assert.equal(result.output.additional_context, 'first context\n\nsecond context');
|
|
71
|
+
assert.deepEqual(result.receipts.map((row) => row.hook_id), ['first', 'second']);
|
|
72
|
+
assert.equal(JSON.stringify(observed).includes('not-in-receipt'), false);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test('session-start config patches compose without discarding prior or base keys', async () => {
|
|
76
|
+
const hooks = createHookRuntime({ hooks: {
|
|
77
|
+
session_start: [
|
|
78
|
+
{ id: 'model', priority: 10, handler: () => ({ modified_config: { model_key: 'xai:grok-4' } }) },
|
|
79
|
+
{ id: 'effort', priority: 20, handler: () => ({ modified_config: { reasoning_effort: 'high' } }) },
|
|
80
|
+
],
|
|
81
|
+
} });
|
|
82
|
+
const result = await hooks.dispatch('session_start', { config: { auto_compact: true } });
|
|
83
|
+
assert.deepEqual(result.input.config, {
|
|
84
|
+
auto_compact: true,
|
|
85
|
+
model_key: 'xai:grok-4',
|
|
86
|
+
reasoning_effort: 'high',
|
|
87
|
+
});
|
|
88
|
+
assert.deepEqual(result.output.modified_config, {
|
|
89
|
+
model_key: 'xai:grok-4',
|
|
90
|
+
reasoning_effort: 'high',
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test('pre hooks fail closed by default while observers fail open', async () => {
|
|
95
|
+
const pre = createHookRuntime({ hooks: { pre_tool_use: { id: 'policy', handler: () => { throw new Error('offline api_key=do-not-record'); } } } });
|
|
96
|
+
const denied = await pre.dispatch('pre_tool_use', { tool_name: 'x', tool_args: {} });
|
|
97
|
+
assert.equal(denied.output.permission_decision, 'deny');
|
|
98
|
+
assert.match(denied.output.permission_decision_reason, /failed closed/);
|
|
99
|
+
assert.doesNotMatch(JSON.stringify(denied), /do-not-record/);
|
|
100
|
+
assert.deepEqual({
|
|
101
|
+
code: denied.receipts[0].error.error_code,
|
|
102
|
+
reason: denied.receipts[0].error.reason,
|
|
103
|
+
domain: denied.receipts[0].error.domain,
|
|
104
|
+
behavior: denied.receipts[0].error.failure_behavior,
|
|
105
|
+
sideEffect: denied.receipts[0].error.side_effect_state,
|
|
106
|
+
retryable: denied.receipts[0].error.retryable,
|
|
107
|
+
}, {
|
|
108
|
+
code: 'INTERNAL', reason: 'hook_handler_failed', domain: 'hook', behavior: 'fail_closed',
|
|
109
|
+
sideEffect: 'not_started', retryable: false,
|
|
110
|
+
});
|
|
111
|
+
assert.match(denied.receipts[0].error.fingerprint, /^err_[a-f0-9]{16}$/);
|
|
112
|
+
assert.equal(denied.receipts[0].native_evidence.message.includes('do-not-record'), false);
|
|
113
|
+
|
|
114
|
+
const post = createHookRuntime({ hooks: { post_tool_use: { id: 'audit', handler: () => { throw new Error('hook_http_timeout:1000'); } } } });
|
|
115
|
+
const allowed = await post.dispatch('post_tool_use', { tool_name: 'x', tool_result: { ok: true } });
|
|
116
|
+
assert.deepEqual(allowed.input.tool_result, { ok: true });
|
|
117
|
+
assert.equal(allowed.errors.length, 1);
|
|
118
|
+
assert.equal(allowed.receipts[0].error.reason, 'hook_http_timeout');
|
|
119
|
+
assert.equal(allowed.receipts[0].error.error_code, 'DEADLINE_EXCEEDED');
|
|
120
|
+
assert.equal(allowed.receipts[0].error.failure_behavior, 'fail_open');
|
|
121
|
+
assert.equal(allowed.receipts[0].error.side_effect_state, 'confirmed_applied');
|
|
122
|
+
assert.equal(allowed.receipts[0].error.retryable, false);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test('callback adapter exposes input and invocation without changing wire envelope', async () => {
|
|
126
|
+
const handler = createCallbackHookAdapter((input, invocation, envelope) => ({
|
|
127
|
+
permission_decision: input.tool_name === 'read' ? 'allow' : 'deny',
|
|
128
|
+
metadata: { session: invocation.session_id, schema: envelope.schema },
|
|
129
|
+
}));
|
|
130
|
+
const hooks = createHookRuntime({ hooks: { pre_tool_use: handler } });
|
|
131
|
+
const result = await hooks.dispatch('pre_tool_use', { tool_name: 'read', tool_args: {} }, { session_id: 's1' });
|
|
132
|
+
assert.equal(result.output.permission_decision, 'allow');
|
|
133
|
+
assert.deepEqual(result.output.metadata, { session: 's1', schema: 'agentsam.hook.v1' });
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test('command adapter uses JSON stdin/stdout without a shell', async () => {
|
|
137
|
+
const script = [
|
|
138
|
+
"let data='';",
|
|
139
|
+
"process.stdin.on('data', c => data += c);",
|
|
140
|
+
"process.stdin.on('end', () => { const row=JSON.parse(data); console.error('diagnostic'); console.log(JSON.stringify({permission_decision: row.input.tool_name === 'safe' ? 'allow' : 'deny'})); });",
|
|
141
|
+
].join('');
|
|
142
|
+
const adapter = createCommandHookAdapter({ command: process.execPath, args: ['-e', script], cwd, timeout_ms: 2_000 });
|
|
143
|
+
const hooks = createHookRuntime({ hooks: { pre_tool_use: { id: 'external', handler: adapter } } });
|
|
144
|
+
const result = await hooks.dispatch('pre_tool_use', { tool_name: 'safe', tool_args: {} }, {}, { cwd });
|
|
145
|
+
assert.equal(result.output.permission_decision, 'allow');
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test('HTTP adapter exchanges the canonical envelope with a portable service', async (t) => {
|
|
149
|
+
const server = http.createServer((request, response) => {
|
|
150
|
+
let body = '';
|
|
151
|
+
request.on('data', chunk => { body += chunk; });
|
|
152
|
+
request.on('end', () => {
|
|
153
|
+
const envelope = JSON.parse(body);
|
|
154
|
+
response.setHeader('content-type', 'application/json');
|
|
155
|
+
response.end(JSON.stringify({
|
|
156
|
+
permission_decision: envelope.schema === 'agentsam.hook.v1' ? 'allow' : 'deny',
|
|
157
|
+
}));
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
await new Promise(resolve => server.listen(0, '127.0.0.1', resolve));
|
|
161
|
+
t.after(() => server.close());
|
|
162
|
+
const address = server.address();
|
|
163
|
+
const handler = createHttpHookAdapter({ url: `http://127.0.0.1:${address.port}/hook`, timeout_ms: 2_000 });
|
|
164
|
+
const hooks = createHookRuntime({ hooks: { pre_tool_use: handler } });
|
|
165
|
+
const result = await hooks.dispatch('pre_tool_use', { tool_name: 'read', tool_args: {} });
|
|
166
|
+
assert.equal(result.output.permission_decision, 'allow');
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
test('capability wrapper enforces ask, argument modification, post transformation, and bounded retry', async () => {
|
|
170
|
+
let attempts = 0;
|
|
171
|
+
const hooks = createHookRuntime({ hooks: {
|
|
172
|
+
pre_tool_use: ({ input }) => ({
|
|
173
|
+
permission_decision: 'ask',
|
|
174
|
+
modified_args: { ...input.tool_args, approved: true },
|
|
175
|
+
}),
|
|
176
|
+
post_tool_use: ({ input }) => ({ modified_result: { ...input.tool_result, transformed: true } }),
|
|
177
|
+
error_occurred: ({ input }) => input.error_context === 'tool_execution'
|
|
178
|
+
? { error_handling: 'retry', retry_count: 1 }
|
|
179
|
+
: null,
|
|
180
|
+
} });
|
|
181
|
+
const base = {
|
|
182
|
+
toolDescriptors: () => [{ name: 'example.write', input_schema: { type: 'object' } }],
|
|
183
|
+
async invoke(_id, input) {
|
|
184
|
+
attempts += 1;
|
|
185
|
+
if (attempts === 1) throw new Error('temporary');
|
|
186
|
+
return { input };
|
|
187
|
+
},
|
|
188
|
+
};
|
|
189
|
+
const adapter = createHookedCapabilityAdapter(base, {
|
|
190
|
+
hookRuntime: hooks,
|
|
191
|
+
requestPermission: async () => true,
|
|
192
|
+
maxRetries: 1,
|
|
193
|
+
});
|
|
194
|
+
const result = await adapter.invoke('example.write', { value: 1 });
|
|
195
|
+
assert.equal(attempts, 2);
|
|
196
|
+
assert.deepEqual(result, { input: { value: 1, approved: true }, transformed: true });
|
|
197
|
+
|
|
198
|
+
const noApproval = createHookedCapabilityAdapter(base, { hookRuntime: hooks, requestPermission: async () => false });
|
|
199
|
+
await assert.rejects(noApproval.invoke('example.write', {}), HookPermissionError);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
test('provider wrapper modifies a provider-neutral request and result', async () => {
|
|
203
|
+
const hooks = createHookRuntime({ hooks: {
|
|
204
|
+
pre_model_use: ({ input }) => ({
|
|
205
|
+
permission_decision: 'allow',
|
|
206
|
+
modified_request: { ...input.request, model: 'portable-model', temperature: 0 },
|
|
207
|
+
}),
|
|
208
|
+
post_model_use: ({ input }) => ({ modified_result: { ...input.model_result, output_text: 'redacted' } }),
|
|
209
|
+
} });
|
|
210
|
+
let request;
|
|
211
|
+
const provider = createHookedProviderAdapter({
|
|
212
|
+
provider: 'fixture',
|
|
213
|
+
async create(value) { request = value; return { output_text: 'raw', tool_calls: [] }; },
|
|
214
|
+
async continueWithToolOutputs(value) { return this.create(value); },
|
|
215
|
+
}, { hookRuntime: hooks });
|
|
216
|
+
const result = await provider.create({ model: 'initial', input: 'hello' });
|
|
217
|
+
assert.equal(request.model, 'portable-model');
|
|
218
|
+
assert.equal(request.temperature, 0);
|
|
219
|
+
assert.equal(result.output_text, 'redacted');
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
test('post-hook failures never replay a completed side effect or paid model request', async () => {
|
|
223
|
+
const receipts = [];
|
|
224
|
+
const hooks = createHookRuntime({ onReceipt: (receipt) => receipts.push(receipt), hooks: {
|
|
225
|
+
post_tool_use: { id: 'tool-post', failure_mode: 'error', handler: () => { throw new Error('observer failed'); } },
|
|
226
|
+
post_model_use: { id: 'model-post', failure_mode: 'error', handler: () => { throw new Error('observer failed'); } },
|
|
227
|
+
error_occurred: () => ({ error_handling: 'retry', retry_count: 3 }),
|
|
228
|
+
} });
|
|
229
|
+
let toolCalls = 0;
|
|
230
|
+
const capability = createHookedCapabilityAdapter({
|
|
231
|
+
toolDescriptors: () => [{ name: 'write' }],
|
|
232
|
+
async invoke() { toolCalls += 1; return { ok: true }; },
|
|
233
|
+
}, { hookRuntime: hooks });
|
|
234
|
+
await assert.rejects(capability.invoke('write'), /hook_execution_failed:tool-post/);
|
|
235
|
+
assert.equal(toolCalls, 1);
|
|
236
|
+
|
|
237
|
+
let modelCalls = 0;
|
|
238
|
+
const provider = createHookedProviderAdapter({
|
|
239
|
+
provider: 'fixture',
|
|
240
|
+
async create() { modelCalls += 1; return { output_text: 'paid result', tool_calls: [] }; },
|
|
241
|
+
async continueWithToolOutputs() { throw new Error('unused'); },
|
|
242
|
+
}, { hookRuntime: hooks });
|
|
243
|
+
await assert.rejects(provider.create({ model: 'fixture' }), /hook_execution_failed:model-post/);
|
|
244
|
+
assert.equal(modelCalls, 1);
|
|
245
|
+
const failures = receipts.filter((receipt) => receipt.status === 'failed');
|
|
246
|
+
assert.equal(failures.length, 2);
|
|
247
|
+
for (const receipt of failures) {
|
|
248
|
+
assert.equal(receipt.error.failure_behavior, 'no_replay');
|
|
249
|
+
assert.equal(receipt.error.side_effect_state, 'confirmed_applied');
|
|
250
|
+
assert.equal(receipt.error.retryable, false);
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
test('runtime rejects duplicate hook ids', () => {
|
|
255
|
+
const runtime = createHookRuntime();
|
|
256
|
+
runtime.register('session_start', { id: 'same', handler: () => null });
|
|
257
|
+
assert.throws(() => runtime.register('session_start', { id: 'same', handler: () => null }), /duplicate_hook_id/);
|
|
258
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { type HTMLAttributes } from 'react';
|
|
2
|
+
import { NavCollapsible, NavCollapsibleContent, NavCollapsibleTrigger } from './NavCollapsible.js';
|
|
3
|
+
import { NavContent } from './NavContent.js';
|
|
4
|
+
import { NavFooter } from './NavFooter.js';
|
|
5
|
+
import { NavGroup, NavGroupLabel } from './NavGroup.js';
|
|
6
|
+
import { NavHeader } from './NavHeader.js';
|
|
7
|
+
import { NavMenu, NavMenuButton, NavMenuChevron, NavMenuItem, NavMenuSub, NavMenuSubButton } from './NavMenu.js';
|
|
8
|
+
import { NavProvider } from './NavProvider.js';
|
|
9
|
+
import { NavTrigger } from './NavTrigger.js';
|
|
10
|
+
import { NavScope, NavSidenav, NavTopbar, NavTopbarLogo, NavTopbarSpacer, NavProjectContext, NavAccountSwitcher, NavFilesResources, NavPinnedSummary, NavConversationActions, NavShareButton, NavOverflowMenu, NavModeSwitcher } from './Surfaces.js';
|
|
11
|
+
declare function NavRoot({ className, children, ...props }: HTMLAttributes<HTMLElement>): import("react").JSX.Element;
|
|
12
|
+
export declare const Nav: typeof NavRoot & {
|
|
13
|
+
Scope: typeof NavScope;
|
|
14
|
+
Sidenav: typeof NavSidenav;
|
|
15
|
+
Topbar: typeof NavTopbar;
|
|
16
|
+
TopbarLogo: typeof NavTopbarLogo;
|
|
17
|
+
TopbarSpacer: typeof NavTopbarSpacer;
|
|
18
|
+
ProjectContext: typeof NavProjectContext;
|
|
19
|
+
AccountSwitcher: typeof NavAccountSwitcher;
|
|
20
|
+
FilesResources: typeof NavFilesResources;
|
|
21
|
+
PinnedSummary: typeof NavPinnedSummary;
|
|
22
|
+
ConversationActions: typeof NavConversationActions;
|
|
23
|
+
ShareButton: typeof NavShareButton;
|
|
24
|
+
OverflowMenu: typeof NavOverflowMenu;
|
|
25
|
+
ModeSwitcher: typeof NavModeSwitcher;
|
|
26
|
+
Provider: typeof NavProvider;
|
|
27
|
+
Header: typeof NavHeader;
|
|
28
|
+
Content: typeof NavContent;
|
|
29
|
+
Footer: typeof NavFooter;
|
|
30
|
+
Group: typeof NavGroup;
|
|
31
|
+
GroupLabel: typeof NavGroupLabel;
|
|
32
|
+
Menu: typeof NavMenu;
|
|
33
|
+
MenuItem: typeof NavMenuItem;
|
|
34
|
+
MenuButton: typeof NavMenuButton;
|
|
35
|
+
MenuSub: typeof NavMenuSub;
|
|
36
|
+
MenuSubButton: typeof NavMenuSubButton;
|
|
37
|
+
MenuChevron: typeof NavMenuChevron;
|
|
38
|
+
Collapsible: typeof NavCollapsible;
|
|
39
|
+
CollapsibleTrigger: typeof NavCollapsibleTrigger;
|
|
40
|
+
CollapsibleContent: typeof NavCollapsibleContent;
|
|
41
|
+
Trigger: typeof NavTrigger;
|
|
42
|
+
};
|
|
43
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type ButtonHTMLAttributes, type HTMLAttributes, type ReactElement, type ReactNode } from 'react';
|
|
2
|
+
export declare function NavCollapsible({ children, defaultOpen }: {
|
|
3
|
+
children: ReactNode;
|
|
4
|
+
defaultOpen?: boolean;
|
|
5
|
+
}): import("react").JSX.Element;
|
|
6
|
+
export declare function NavCollapsibleTrigger({ render, children, className, ...props }: ButtonHTMLAttributes<HTMLButtonElement> & {
|
|
7
|
+
render?: ReactElement;
|
|
8
|
+
children?: ReactNode;
|
|
9
|
+
}): import("react").JSX.Element;
|
|
10
|
+
export declare function NavCollapsibleContent({ className, ...props }: HTMLAttributes<HTMLDivElement>): import("react").JSX.Element;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { HTMLAttributes } from 'react';
|
|
2
|
+
export declare function NavGroup({ className, ...props }: HTMLAttributes<HTMLElement>): import("react").JSX.Element;
|
|
3
|
+
export declare function NavGroupLabel({ className, ...props }: HTMLAttributes<HTMLParagraphElement>): import("react").JSX.Element;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ButtonHTMLAttributes, HTMLAttributes, ReactNode } from 'react';
|
|
2
|
+
export declare function NavMenu({ className, ...props }: HTMLAttributes<HTMLUListElement>): import("react").JSX.Element;
|
|
3
|
+
export declare function NavMenuItem({ className, ...props }: HTMLAttributes<HTMLLIElement>): import("react").JSX.Element;
|
|
4
|
+
export type NavMenuButtonProps = ButtonHTMLAttributes<HTMLButtonElement> & {
|
|
5
|
+
icon?: unknown;
|
|
6
|
+
active?: boolean;
|
|
7
|
+
itemId?: string;
|
|
8
|
+
children: ReactNode;
|
|
9
|
+
};
|
|
10
|
+
export declare function NavMenuButton({ icon: Icon, active, itemId, className, children, ...props }: NavMenuButtonProps): import("react").JSX.Element;
|
|
11
|
+
export declare function NavMenuSub({ className, ...props }: HTMLAttributes<HTMLUListElement>): import("react").JSX.Element;
|
|
12
|
+
export type NavMenuSubButtonProps = {
|
|
13
|
+
href?: string;
|
|
14
|
+
active?: boolean;
|
|
15
|
+
className?: string;
|
|
16
|
+
children?: unknown;
|
|
17
|
+
onClick?: (event: React.MouseEvent<HTMLAnchorElement>) => void;
|
|
18
|
+
};
|
|
19
|
+
export declare function NavMenuSubButton({ active, className, children, ...props }: NavMenuSubButtonProps): import("react").JSX.Element;
|
|
20
|
+
export declare function NavMenuChevron({ className }: {
|
|
21
|
+
className?: string;
|
|
22
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type PropsWithChildren } from 'react';
|
|
2
|
+
import type { NavTheme, NavValue, NavTokens } from './contracts.js';
|
|
3
|
+
export type NavContextValue = {
|
|
4
|
+
data: NavValue;
|
|
5
|
+
tokens: NavTokens;
|
|
6
|
+
theme: NavTheme;
|
|
7
|
+
accentColor: string;
|
|
8
|
+
menu: string | null;
|
|
9
|
+
setMenu: (menu: string | null) => void;
|
|
10
|
+
open: boolean;
|
|
11
|
+
isMobile: boolean;
|
|
12
|
+
isPeeking: boolean;
|
|
13
|
+
peekable: boolean;
|
|
14
|
+
resizable: boolean;
|
|
15
|
+
width: number;
|
|
16
|
+
mobileBreakpoint: number;
|
|
17
|
+
setOpen: (open: boolean) => void;
|
|
18
|
+
toggle: () => void;
|
|
19
|
+
close: () => void;
|
|
20
|
+
setPeeking: (peeking: boolean) => void;
|
|
21
|
+
setWidth: (width: number) => void;
|
|
22
|
+
scrollToItem: (itemId: string) => void;
|
|
23
|
+
};
|
|
24
|
+
export type NavProviderProps = PropsWithChildren<{
|
|
25
|
+
value?: NavValue;
|
|
26
|
+
tokens?: NavTokens;
|
|
27
|
+
theme?: NavTheme;
|
|
28
|
+
accentColor?: string;
|
|
29
|
+
defaultOpen?: boolean;
|
|
30
|
+
mobileBreakpoint?: number;
|
|
31
|
+
peekable?: boolean;
|
|
32
|
+
resizable?: boolean;
|
|
33
|
+
}>;
|
|
34
|
+
export declare function NavProvider({ children, value: data, tokens, theme, accentColor, defaultOpen, mobileBreakpoint, peekable, resizable, }: NavProviderProps): import("react").JSX.Element;
|
|
35
|
+
export declare function useNav(): NavContextValue;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { type CSSProperties, type HTMLAttributes, type ReactNode } from 'react';
|
|
2
|
+
import type { NavAccount, NavAction } from './contracts.js';
|
|
3
|
+
export declare function useNavStyle(): CSSProperties;
|
|
4
|
+
export declare function NavScope({ className, style, ...props }: HTMLAttributes<HTMLDivElement>): import("react").JSX.Element;
|
|
5
|
+
/** The portal carries its own theme, so standalone surfaces never depend on body CSS. */
|
|
6
|
+
export declare function NavOverflowMenu({ label, actions, children, id: suppliedId, align }: {
|
|
7
|
+
label?: string;
|
|
8
|
+
actions?: NavAction[];
|
|
9
|
+
children?: ReactNode;
|
|
10
|
+
id?: string;
|
|
11
|
+
align?: 'start' | 'center' | 'end';
|
|
12
|
+
}): import("react").JSX.Element;
|
|
13
|
+
export declare function NavTopbarLogo({ variant, onClick, toggle }: {
|
|
14
|
+
variant?: 'mark' | 'expanded';
|
|
15
|
+
onClick?: () => void;
|
|
16
|
+
toggle?: boolean;
|
|
17
|
+
}): import("react").JSX.Element;
|
|
18
|
+
export declare function NavProjectContext(): import("react").JSX.Element;
|
|
19
|
+
export declare function NavTopbarSpacer(): import("react").JSX.Element;
|
|
20
|
+
export declare function NavFilesResources(): import("react").JSX.Element;
|
|
21
|
+
export declare function NavPinnedSummary(): import("react").JSX.Element | null;
|
|
22
|
+
export declare function NavConversationActions(): import("react").JSX.Element;
|
|
23
|
+
export declare function NavShareButton(): import("react").JSX.Element | null;
|
|
24
|
+
export declare function NavAccountSwitcher({ accounts, activeAccountId, onAccountChange, expanded }: {
|
|
25
|
+
accounts?: NavAccount[];
|
|
26
|
+
activeAccountId?: string;
|
|
27
|
+
onAccountChange?: (id: string) => void;
|
|
28
|
+
expanded?: boolean;
|
|
29
|
+
}): import("react").JSX.Element;
|
|
30
|
+
export declare function NavModeSwitcher(): import("react").JSX.Element;
|
|
31
|
+
export declare function NavTopbar({ children, rightSlot, modeSlot, contextSlot, startup, className, showNavigationTrigger, showAccount, }: {
|
|
32
|
+
children?: ReactNode;
|
|
33
|
+
rightSlot?: ReactNode;
|
|
34
|
+
modeSlot?: ReactNode;
|
|
35
|
+
contextSlot?: ReactNode;
|
|
36
|
+
startup?: boolean;
|
|
37
|
+
className?: string;
|
|
38
|
+
showNavigationTrigger?: boolean;
|
|
39
|
+
showAccount?: boolean;
|
|
40
|
+
}): import("react").JSX.Element;
|
|
41
|
+
export declare function NavSidenav(): import("react").JSX.Element;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { NavProviderProps } from './NavProvider.js';
|
|
2
|
+
export interface MountNavOptions extends Omit<NavProviderProps, 'children'> {
|
|
3
|
+
sidenav?: HTMLElement;
|
|
4
|
+
topbar?: HTMLElement;
|
|
5
|
+
startup?: boolean;
|
|
6
|
+
}
|
|
7
|
+
/** Plain HTML integration. Both portals are descendants of the same provider. */
|
|
8
|
+
export declare function mountNav(options: MountNavOptions): {
|
|
9
|
+
update(next: Partial<MountNavOptions>): void;
|
|
10
|
+
unmount(): void;
|
|
11
|
+
};
|