@inneranimalmedia/agentsam-sdk 2.6.8 → 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/README.md +25 -0
- package/apps/cad-creator/shared/cad/package.json +1 -1
- package/apps/client-cms-editor/backend/package.json +5 -7
- package/apps/client-cms-editor/frontend/package.json +5 -7
- package/apps/client-cms-editor/frontend/tsconfig.json +27 -8
- 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 +3 -5
- package/docs/LOCAL_ENGINE_COMPUTE_AUDIT_PLAN.md +299 -0
- 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 +37 -10
- 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 +71 -20
- package/packages/agentsam-contracts/scripts/build.mjs +18 -0
- package/packages/agentsam-contracts/src/errors.ts +19 -10
- package/packages/agentsam-contracts/test/contracts.test.mjs +4 -1
- package/packages/agentsam-contracts/test/repository.test.mjs +4 -1
- package/packages/agentsam-contracts/tsconfig.build.json +10 -0
- package/packages/agentsam-errors/package.json +13 -4
- 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 +3 -2
- package/packages/agentsam-nav/package.json +1 -1
- package/packages/agentsam-queue-control/package.json +1 -1
- package/packages/agentsam-rapid-rust/src/main.rs +1 -0
- package/packages/agentsam-repository/package.json +3 -2
- package/packages/agentsam-vault/package.json +8 -5
- package/packages/agentsam-workbench/package.json +45 -18
- package/packages/connectors/cloudflare/package.json +1 -1
- package/packages/heuristic-theme/package.json +1 -1
- package/packages/identity/package.json +3 -2
- package/packages/identity/tests/lane1-packed-consumer.test.mjs +9 -1
- package/packages/providers/completeful/package.json +1 -1
- package/packages/theme-church-site/README.md +2 -2
- package/packages/theme-church-site/package.json +2 -3
- package/packages/theme-church-site/src/index.js +1 -1
- package/packages/theme-church-site/test/theme.test.mjs +1 -1
- package/packages/theme-companions-site/README.md +2 -2
- package/packages/theme-companions-site/package.json +2 -3
- package/packages/theme-companions-site/src/index.js +1 -1
- package/packages/theme-companions-site/test/theme.test.mjs +1 -1
- package/packages/theme-floors-site/README.md +2 -2
- package/packages/theme-floors-site/package.json +2 -3
- package/packages/theme-floors-site/src/index.js +1 -1
- package/packages/theme-floors-site/test/theme.test.mjs +1 -1
- package/packages/theme-fuelnfree-site/README.md +2 -2
- package/packages/theme-fuelnfree-site/package.json +2 -3
- package/packages/theme-fuelnfree-site/src/index.js +1 -1
- package/packages/theme-fuelnfree-site/test/theme.test.mjs +1 -1
- package/packages/theme-handyman-site/README.md +2 -2
- package/packages/theme-handyman-site/package.json +2 -3
- package/packages/theme-handyman-site/src/index.js +1 -1
- package/packages/theme-handyman-site/test/theme.test.mjs +1 -1
- package/packages/theme-insurance-site/README.md +2 -2
- package/packages/theme-insurance-site/package.json +2 -3
- package/packages/theme-insurance-site/src/index.js +1 -1
- package/packages/theme-insurance-site/test/theme.test.mjs +1 -1
- package/packages/theme-scenes/package.json +1 -1
- package/packages/theme-shinshu-site/README.md +2 -2
- package/packages/theme-shinshu-site/package.json +2 -3
- package/packages/theme-shinshu-site/src/index.js +1 -1
- package/packages/theme-shinshu-site/test/theme.test.mjs +1 -1
- package/packages/work-graph/package.json +3 -2
- 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 +14 -0
- package/protocol/local-engine/engine.v1.schema.json +17 -0
- 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 +2 -0
- package/src/cli.js +19 -0
- package/src/commands/app.js +10 -0
- package/src/commands/cheat-sheet.js +1 -0
- package/src/commands/engine.js +10 -0
- package/src/commands/hooks.js +260 -0
- package/src/commands/machine.js +71 -38
- package/src/commands/models.js +1 -1
- package/src/commands/package.js +10 -3
- package/src/commands/runtime.js +25 -10
- package/src/commands/setup.js +30 -6
- package/src/commands/shell.js +114 -9
- package/src/commands/terminal.js +20 -8
- package/src/errors/contract.js +4 -4
- package/src/errors/diagnostic.js +10 -10
- package/src/errors/index.js +1 -1
- package/src/index.js +52 -0
- package/src/indexing/ingest/materials.js +237 -30
- package/src/knowledge/service/grpc-codec.js +2 -0
- package/src/lib/setup/runtime.js +4 -2
- package/src/lib/slash-commands.js +1 -0
- package/src/local/runtime-store.js +46 -0
- package/src/local-engine/assets.js +37 -0
- package/src/local-engine/audit.js +133 -0
- package/src/local-engine/benchmark.js +26 -0
- package/src/local-engine/contracts.js +101 -0
- package/src/local-engine/engines.js +142 -0
- package/src/local-engine/index.js +18 -0
- package/src/local-engine/runtime.js +39 -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
package/src/errors/diagnostic.js
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
normalizeError,
|
|
7
7
|
redactErrorValue,
|
|
8
8
|
redactString,
|
|
9
|
-
} from '
|
|
9
|
+
} from '@inneranimalmedia/agentsam-errors';
|
|
10
10
|
|
|
11
11
|
const DEFAULT_MAX_DETAIL_CHARS = 12_000;
|
|
12
12
|
function clean(value) { return value == null ? '' : String(value).trim(); }
|
|
@@ -181,16 +181,16 @@ export function diagnosticFromError(error, fallback = {}) {
|
|
|
181
181
|
if (error?.diagnostic) return error.diagnostic;
|
|
182
182
|
const envelope = error?.envelope || (error?.ok === false && error?.schema_version ? error : null);
|
|
183
183
|
if (envelope) return legacyDiagnosticFromEnvelope(envelope, fallback);
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
retry_strategy: fallback.retry_strategy || 'inspect_error',
|
|
184
|
+
const canonical = normalizeError(error, {
|
|
185
|
+
reason: fallback.reason,
|
|
186
|
+
domain: fallback.domain || 'runtime',
|
|
187
|
+
stage: fallback.stage,
|
|
188
|
+
feature: fallback.feature,
|
|
189
|
+
failure_behavior: fallback.failure_behavior,
|
|
190
|
+
side_effect_state: fallback.side_effect_state,
|
|
191
|
+
source: { kind: 'runtime', name: fallback.source || 'agentsam-sdk' },
|
|
193
192
|
});
|
|
193
|
+
return legacyDiagnosticFromEnvelope(canonical, fallback);
|
|
194
194
|
}
|
|
195
195
|
|
|
196
196
|
export function renderDiagnosticError(error) {
|
package/src/errors/index.js
CHANGED
package/src/index.js
CHANGED
|
@@ -25,6 +25,37 @@ export {
|
|
|
25
25
|
suggestCliCommands,
|
|
26
26
|
} from './cli/command-catalog.js';
|
|
27
27
|
export { routeIntent } from './lib/router.js';
|
|
28
|
+
export {
|
|
29
|
+
HOOK_PROTOCOL_SCHEMA,
|
|
30
|
+
HOOK_CONFIG_SCHEMA,
|
|
31
|
+
HOOK_RECEIPT_SCHEMA,
|
|
32
|
+
HOOK_EVENTS,
|
|
33
|
+
PERMISSION_DECISIONS,
|
|
34
|
+
ERROR_HANDLING_DECISIONS,
|
|
35
|
+
HOOK_FAILURE_MODES,
|
|
36
|
+
AgentSamHooks,
|
|
37
|
+
HookExecutionError,
|
|
38
|
+
HookPermissionError,
|
|
39
|
+
normalizeHookEvent,
|
|
40
|
+
normalizeHookOutput,
|
|
41
|
+
createHookEnvelope,
|
|
42
|
+
createHookRuntime,
|
|
43
|
+
createHookRuntimeFromConfig,
|
|
44
|
+
createHookStore,
|
|
45
|
+
registerStoredHooks,
|
|
46
|
+
ensureHookRuntime,
|
|
47
|
+
findHookConfig,
|
|
48
|
+
loadHookConfig,
|
|
49
|
+
createCallbackHookAdapter,
|
|
50
|
+
createCommandHookAdapter,
|
|
51
|
+
createHttpHookAdapter,
|
|
52
|
+
createHookedProviderAdapter,
|
|
53
|
+
createHookedCapabilityAdapter,
|
|
54
|
+
createCompositeCapabilityAdapter,
|
|
55
|
+
createMcpCapabilityAdapter,
|
|
56
|
+
createLspCapabilityAdapter,
|
|
57
|
+
createAgentCapabilityAdapter,
|
|
58
|
+
} from '../packages/agentsam-hooks/src/index.js';
|
|
28
59
|
export { searchToolCards, toToolCard, hydrateToolSchemas } from './tools/index.js';
|
|
29
60
|
export {
|
|
30
61
|
AGENTSAM_PLUGIN_SCHEMA_VERSION,
|
|
@@ -36,6 +67,7 @@ export {
|
|
|
36
67
|
listPlugins,
|
|
37
68
|
listPluginTools,
|
|
38
69
|
recordPluginHealthCheck,
|
|
70
|
+
recordAiPluginModelDiscovery,
|
|
39
71
|
recordToolCall,
|
|
40
72
|
createPluginRuntime,
|
|
41
73
|
createPluginCapabilityAdapter,
|
|
@@ -77,6 +109,26 @@ export {
|
|
|
77
109
|
terminalExec,
|
|
78
110
|
} from './capabilities/index.js';
|
|
79
111
|
export { getPreset, listPresets, resolvePreset, getAddon, listAddons } from './presets/index.js';
|
|
112
|
+
export {
|
|
113
|
+
LOCAL_ENGINE_SCHEMA,
|
|
114
|
+
LOCAL_ENGINE_BENCHMARK_SCHEMA,
|
|
115
|
+
ENGINE_CAPABILITIES,
|
|
116
|
+
LocalEngineRegistry,
|
|
117
|
+
createLocalEngineRegistry,
|
|
118
|
+
createOllamaEngine,
|
|
119
|
+
createMlxLmEngine,
|
|
120
|
+
createLlamaCppEngine,
|
|
121
|
+
auditLocalCompute,
|
|
122
|
+
auditHost,
|
|
123
|
+
auditAppleHost,
|
|
124
|
+
auditTools,
|
|
125
|
+
auditAssets,
|
|
126
|
+
adoptAsset,
|
|
127
|
+
readAssetManifest,
|
|
128
|
+
assetManifestPath,
|
|
129
|
+
benchmarkEngine,
|
|
130
|
+
validateStructuredOutput,
|
|
131
|
+
} from './local-engine/index.js';
|
|
80
132
|
export {
|
|
81
133
|
COMPANY_REPOSITORY_GRAPH_SCHEMA_VERSION,
|
|
82
134
|
REPOSITORY_STATUSES,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Material intake for codebaseindex.ingest — paths pasted/dropped into the CLI.
|
|
3
|
-
*
|
|
3
|
+
* Archives are inspected first and selectively mined instead of blindly unpacked.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import fs from 'node:fs';
|
|
@@ -12,6 +12,20 @@ import { isCodeSourceExtension, isDocumentSourceExtension } from '../../../packa
|
|
|
12
12
|
const ARCHIVE_EXT = new Set(['.zip', '.tar', '.tgz', '.gz', '.tar.gz', '.tbz2', '.tar.bz2']);
|
|
13
13
|
const IMAGE_EXT = new Set(['.png', '.jpg', '.jpeg', '.gif', '.webp', '.svg', '.ico', '.bmp', '.avif']);
|
|
14
14
|
const MODEL_3D_EXT = new Set(['.glb', '.gltf', '.obj', '.fbx', '.stl', '.usdz']);
|
|
15
|
+
const VIDEO_EXT = new Set(['.mp4', '.mov', '.m4v', '.webm', '.mkv', '.avi', '.mpeg', '.mpg']);
|
|
16
|
+
const AUDIO_EXT = new Set(['.mp3', '.wav', '.m4a', '.aac', '.ogg', '.flac']);
|
|
17
|
+
const TEXT_BASENAMES = new Set([
|
|
18
|
+
'readme', 'license', 'licence', 'dockerfile', 'makefile', 'procfile', 'gemfile', 'rakefile',
|
|
19
|
+
'cargo.lock', 'package-lock.json', 'pnpm-lock.yaml', 'yarn.lock', '.gitignore', '.npmrc', '.editorconfig',
|
|
20
|
+
]);
|
|
21
|
+
const DEFAULT_ARCHIVE_POLICY = Object.freeze({
|
|
22
|
+
maxEntries: 10_000,
|
|
23
|
+
maxFiles: 2_000,
|
|
24
|
+
maxEntryBytes: 8 * 1024 * 1024,
|
|
25
|
+
maxTotalBytes: 128 * 1024 * 1024,
|
|
26
|
+
includeAssets: false,
|
|
27
|
+
});
|
|
28
|
+
|
|
15
29
|
function clean(value) {
|
|
16
30
|
return value == null ? '' : String(value).trim();
|
|
17
31
|
}
|
|
@@ -43,9 +57,7 @@ export function parsePastedPaths(text) {
|
|
|
43
57
|
return [...new Set(paths)];
|
|
44
58
|
}
|
|
45
59
|
|
|
46
|
-
/**
|
|
47
|
-
* @param {string} filePath
|
|
48
|
-
*/
|
|
60
|
+
/** @param {string} filePath */
|
|
49
61
|
export function classifyMaterial(filePath) {
|
|
50
62
|
const resolved = path.resolve(filePath);
|
|
51
63
|
const ext = extOf(resolved);
|
|
@@ -79,37 +91,237 @@ function hashFile(filePath) {
|
|
|
79
91
|
|
|
80
92
|
function hasBin(name) {
|
|
81
93
|
const result = spawnSync(name, ['--help'], { stdio: 'ignore' });
|
|
82
|
-
// Some tools exit non-zero on --help; presence of spawn failure matters more.
|
|
83
94
|
return result.error == null;
|
|
84
95
|
}
|
|
85
96
|
|
|
97
|
+
function archiveFormat(archivePath) {
|
|
98
|
+
const lower = archivePath.toLowerCase();
|
|
99
|
+
if (lower.endsWith('.zip')) return 'zip';
|
|
100
|
+
if (lower.endsWith('.tar') || lower.endsWith('.tar.gz') || lower.endsWith('.tgz')
|
|
101
|
+
|| lower.endsWith('.tar.bz2') || lower.endsWith('.tbz2')) return 'tar';
|
|
102
|
+
if (lower.endsWith('.gz')) return 'gzip';
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function archiveEntryKind(entryPath) {
|
|
107
|
+
const normalized = String(entryPath || '').replace(/\\/g, '/');
|
|
108
|
+
const ext = path.posix.extname(normalized.toLowerCase());
|
|
109
|
+
const base = path.posix.basename(normalized).toLowerCase();
|
|
110
|
+
if (IMAGE_EXT.has(ext)) return 'image';
|
|
111
|
+
if (MODEL_3D_EXT.has(ext)) return 'model3d';
|
|
112
|
+
if (VIDEO_EXT.has(ext)) return 'video';
|
|
113
|
+
if (AUDIO_EXT.has(ext)) return 'audio';
|
|
114
|
+
if (isDocumentSourceExtension(ext) || TEXT_BASENAMES.has(base)) return 'document';
|
|
115
|
+
if (isCodeSourceExtension(ext)) return 'code';
|
|
116
|
+
return 'file';
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function isSafeArchivePath(entryPath) {
|
|
120
|
+
const raw = String(entryPath || '').replace(/\\/g, '/');
|
|
121
|
+
if (!raw || raw.includes('\0') || raw.startsWith('/') || /^[a-zA-Z]:\//.test(raw)) return false;
|
|
122
|
+
const parts = raw.split('/').filter((part) => part && part !== '.');
|
|
123
|
+
return !parts.includes('..');
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function shouldMineArchiveEntry(entry, policy) {
|
|
127
|
+
if (!entry.safe) return { selected: false, reason: 'unsafe_path' };
|
|
128
|
+
if (entry.link) return { selected: false, reason: 'link_entry' };
|
|
129
|
+
if (entry.directory) return { selected: false, reason: 'directory' };
|
|
130
|
+
if (Number.isFinite(entry.bytes) && entry.bytes > policy.maxEntryBytes) {
|
|
131
|
+
return { selected: false, reason: 'entry_too_large' };
|
|
132
|
+
}
|
|
133
|
+
if (entry.kind === 'code' || entry.kind === 'document') return { selected: true, reason: 'indexable_text' };
|
|
134
|
+
if (policy.includeAssets && ['image', 'model3d'].includes(entry.kind)) return { selected: true, reason: 'requested_asset' };
|
|
135
|
+
return { selected: false, reason: ['image', 'model3d', 'video', 'audio'].includes(entry.kind) ? 'asset_inventory_only' : 'non_indexable' };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function commandText(command, args, maxBuffer = 16 * 1024 * 1024) {
|
|
139
|
+
return execFileSync(command, args, {
|
|
140
|
+
encoding: 'utf8',
|
|
141
|
+
maxBuffer,
|
|
142
|
+
env: { ...process.env, LC_ALL: 'C' },
|
|
143
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function zipSizeMap(archivePath) {
|
|
148
|
+
if (!hasBin('zipinfo')) return new Map();
|
|
149
|
+
const map = new Map();
|
|
150
|
+
const lines = commandText('zipinfo', ['-l', archivePath], 32 * 1024 * 1024).split(/\r?\n/);
|
|
151
|
+
for (const line of lines) {
|
|
152
|
+
const match = line.match(/^(\S+)\s+\S+\s+\S+\s+(\d+)\s+\S+\s+\d+\s+\S+\s+\S+\s+\S+\s+(.*)$/);
|
|
153
|
+
if (!match) continue;
|
|
154
|
+
map.set(match[3], { bytes: Number(match[2]), link: match[1].startsWith('l') });
|
|
155
|
+
}
|
|
156
|
+
return map;
|
|
157
|
+
}
|
|
158
|
+
|
|
86
159
|
/**
|
|
87
|
-
*
|
|
160
|
+
* Inspect an archive without expanding it to disk.
|
|
161
|
+
* @param {string} archivePath
|
|
162
|
+
* @param {Partial<typeof DEFAULT_ARCHIVE_POLICY>} [options]
|
|
163
|
+
*/
|
|
164
|
+
export function inspectArchive(archivePath, options = {}) {
|
|
165
|
+
const resolved = path.resolve(archivePath);
|
|
166
|
+
const policy = { ...DEFAULT_ARCHIVE_POLICY, ...options };
|
|
167
|
+
const format = archiveFormat(resolved);
|
|
168
|
+
if (!format) throw new Error(`unsupported_archive:${path.basename(resolved)}`);
|
|
169
|
+
if (!fs.existsSync(resolved)) throw new Error(`archive_missing:${resolved}`);
|
|
170
|
+
|
|
171
|
+
let names = [];
|
|
172
|
+
let zipMeta = new Map();
|
|
173
|
+
let tarLinks = new Set();
|
|
174
|
+
if (format === 'zip') {
|
|
175
|
+
if (!hasBin('unzip')) throw new Error('unzip_unavailable: install unzip to inspect .zip materials');
|
|
176
|
+
names = commandText('unzip', ['-Z1', resolved], 32 * 1024 * 1024).split(/\r?\n/).filter(Boolean);
|
|
177
|
+
zipMeta = zipSizeMap(resolved);
|
|
178
|
+
} else if (format === 'tar') {
|
|
179
|
+
if (!hasBin('tar')) throw new Error('tar_unavailable: install tar to inspect tar materials');
|
|
180
|
+
names = commandText('tar', ['-tf', resolved], 32 * 1024 * 1024).split(/\r?\n/).filter(Boolean);
|
|
181
|
+
const verbose = commandText('tar', ['-tvf', resolved], 32 * 1024 * 1024).split(/\r?\n/);
|
|
182
|
+
for (const line of verbose) {
|
|
183
|
+
if (!(line.startsWith('l') || line.startsWith('h'))) continue;
|
|
184
|
+
const marker = line.includes(' -> ') ? line.split(' -> ')[0] : line;
|
|
185
|
+
const name = names.find((candidate) => marker.endsWith(candidate));
|
|
186
|
+
if (name) tarLinks.add(name);
|
|
187
|
+
}
|
|
188
|
+
} else {
|
|
189
|
+
if (!hasBin('gzip')) throw new Error('gzip_unavailable: install gzip to inspect .gz materials');
|
|
190
|
+
names = [path.basename(resolved, '.gz') || 'payload'];
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (names.length > policy.maxEntries) {
|
|
194
|
+
const error = new Error(`archive_entry_limit_exceeded:${names.length}>${policy.maxEntries}`);
|
|
195
|
+
error.code = 'AGENTSAM_ARCHIVE_ENTRY_LIMIT';
|
|
196
|
+
throw error;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const entries = names.map((name) => {
|
|
200
|
+
const meta = zipMeta.get(name) || {};
|
|
201
|
+
const directory = name.endsWith('/');
|
|
202
|
+
const entry = {
|
|
203
|
+
path: name,
|
|
204
|
+
bytes: Number.isFinite(meta.bytes) ? meta.bytes : null,
|
|
205
|
+
kind: directory ? 'directory' : archiveEntryKind(name),
|
|
206
|
+
directory,
|
|
207
|
+
link: Boolean(meta.link || tarLinks.has(name)),
|
|
208
|
+
safe: isSafeArchivePath(name),
|
|
209
|
+
};
|
|
210
|
+
return { ...entry, ...shouldMineArchiveEntry(entry, policy) };
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
const unsafe = entries.filter((entry) => !entry.safe);
|
|
214
|
+
if (unsafe.length) {
|
|
215
|
+
const error = new Error(`archive_unsafe_paths:${unsafe.slice(0, 5).map((entry) => entry.path).join(',')}`);
|
|
216
|
+
error.code = 'AGENTSAM_ARCHIVE_UNSAFE_PATH';
|
|
217
|
+
throw error;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const selected = entries.filter((entry) => entry.selected);
|
|
221
|
+
const selectedKnownBytes = selected.reduce((sum, entry) => sum + (entry.bytes || 0), 0);
|
|
222
|
+
const byKind = {};
|
|
223
|
+
for (const entry of entries) byKind[entry.kind] = (byKind[entry.kind] || 0) + 1;
|
|
224
|
+
|
|
225
|
+
return {
|
|
226
|
+
schema: 'agentsam.archive.inspect.v1',
|
|
227
|
+
archive: resolved,
|
|
228
|
+
format,
|
|
229
|
+
compressed_bytes: fs.statSync(resolved).size,
|
|
230
|
+
entries_total: entries.length,
|
|
231
|
+
selected_total: selected.length,
|
|
232
|
+
selected_known_bytes: selectedKnownBytes,
|
|
233
|
+
by_kind: byKind,
|
|
234
|
+
policy,
|
|
235
|
+
entries,
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function readArchiveEntry(archivePath, format, entry, maxBytes) {
|
|
240
|
+
const maxBuffer = Math.max(1024 * 1024, maxBytes + 1);
|
|
241
|
+
if (format === 'zip') {
|
|
242
|
+
return execFileSync('unzip', ['-p', archivePath, entry], { encoding: null, maxBuffer, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
243
|
+
}
|
|
244
|
+
if (format === 'tar') {
|
|
245
|
+
return execFileSync('tar', ['-xOf', archivePath, entry], { encoding: null, maxBuffer, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
246
|
+
}
|
|
247
|
+
return execFileSync('gzip', ['-dc', archivePath], { encoding: null, maxBuffer, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Selectively mine indexable content from an archive into destDir.
|
|
252
|
+
* Large/binary/media members remain represented in the receipt but are not copied.
|
|
88
253
|
* @param {string} archivePath
|
|
89
254
|
* @param {string} destDir
|
|
255
|
+
* @param {Partial<typeof DEFAULT_ARCHIVE_POLICY>} [options]
|
|
90
256
|
*/
|
|
91
|
-
export function extractArchive(archivePath, destDir) {
|
|
257
|
+
export function extractArchive(archivePath, destDir, options = {}) {
|
|
258
|
+
const policy = { ...DEFAULT_ARCHIVE_POLICY, ...options };
|
|
259
|
+
const inspection = inspectArchive(archivePath, policy);
|
|
260
|
+
fs.rmSync(destDir, { recursive: true, force: true });
|
|
92
261
|
fs.mkdirSync(destDir, { recursive: true });
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
262
|
+
|
|
263
|
+
const selected = inspection.entries.filter((entry) => entry.selected);
|
|
264
|
+
const mined = [];
|
|
265
|
+
const skipped = inspection.entries.filter((entry) => !entry.selected).map((entry) => ({
|
|
266
|
+
path: entry.path, kind: entry.kind, bytes: entry.bytes, reason: entry.reason,
|
|
267
|
+
}));
|
|
268
|
+
let totalBytes = 0;
|
|
269
|
+
|
|
270
|
+
for (const entry of selected) {
|
|
271
|
+
if (mined.length >= policy.maxFiles) {
|
|
272
|
+
skipped.push({ path: entry.path, kind: entry.kind, bytes: entry.bytes, reason: 'file_limit' });
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
if (Number.isFinite(entry.bytes) && totalBytes + entry.bytes > policy.maxTotalBytes) {
|
|
276
|
+
skipped.push({ path: entry.path, kind: entry.kind, bytes: entry.bytes, reason: 'total_byte_limit' });
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
let payload;
|
|
281
|
+
try {
|
|
282
|
+
payload = readArchiveEntry(inspection.archive, inspection.format, entry.path, policy.maxEntryBytes);
|
|
283
|
+
} catch (cause) {
|
|
284
|
+
skipped.push({ path: entry.path, kind: entry.kind, bytes: entry.bytes, reason: 'extract_error', error: String(cause?.message || cause).slice(0, 240) });
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
if (payload.length > policy.maxEntryBytes || totalBytes + payload.length > policy.maxTotalBytes) {
|
|
288
|
+
skipped.push({ path: entry.path, kind: entry.kind, bytes: payload.length, reason: payload.length > policy.maxEntryBytes ? 'entry_too_large' : 'total_byte_limit' });
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const normalized = entry.path.replace(/\\/g, '/').replace(/^\.\//, '');
|
|
293
|
+
const output = path.resolve(destDir, ...normalized.split('/'));
|
|
294
|
+
const root = path.resolve(destDir);
|
|
295
|
+
if (!(output === root || output.startsWith(`${root}${path.sep}`))) {
|
|
296
|
+
throw new Error(`archive_unsafe_output:${entry.path}`);
|
|
297
|
+
}
|
|
298
|
+
fs.mkdirSync(path.dirname(output), { recursive: true });
|
|
299
|
+
fs.writeFileSync(output, payload);
|
|
300
|
+
totalBytes += payload.length;
|
|
301
|
+
mined.push({ path: entry.path, kind: entry.kind, bytes: payload.length });
|
|
104
302
|
}
|
|
105
|
-
|
|
303
|
+
|
|
304
|
+
return {
|
|
305
|
+
schema: 'agentsam.archive.mine.v1',
|
|
306
|
+
format: inspection.format,
|
|
307
|
+
dest: destDir,
|
|
308
|
+
entries_total: inspection.entries_total,
|
|
309
|
+
mined_files: mined.length,
|
|
310
|
+
mined_bytes: totalBytes,
|
|
311
|
+
skipped_files: skipped.length,
|
|
312
|
+
by_kind: inspection.by_kind,
|
|
313
|
+
policy,
|
|
314
|
+
mined,
|
|
315
|
+
skipped: skipped.slice(0, 200),
|
|
316
|
+
skipped_truncated: skipped.length > 200,
|
|
317
|
+
};
|
|
106
318
|
}
|
|
107
319
|
|
|
108
320
|
/**
|
|
109
321
|
* Stage pasted/dropped materials under `.agentsam/ingest/<id>/`.
|
|
110
|
-
* Archives are
|
|
322
|
+
* Archives are inspected + selectively mined; files/dirs are copied or linked by relative include paths.
|
|
111
323
|
*
|
|
112
|
-
* @param {{ root: string, materials: string[], ingestId?: string }} opts
|
|
324
|
+
* @param {{ root: string, materials: string[], ingestId?: string, archivePolicy?: object }} opts
|
|
113
325
|
*/
|
|
114
326
|
export function stageMaterials(opts) {
|
|
115
327
|
const root = path.resolve(opts.root);
|
|
@@ -117,11 +329,8 @@ export function stageMaterials(opts) {
|
|
|
117
329
|
const stageRoot = path.join(root, '.agentsam', 'ingest', ingestId);
|
|
118
330
|
fs.mkdirSync(stageRoot, { recursive: true });
|
|
119
331
|
|
|
120
|
-
/** @type {object[]} */
|
|
121
332
|
const items = [];
|
|
122
|
-
/** @type {string[]} */
|
|
123
333
|
const include = [];
|
|
124
|
-
/** @type {object[]} */
|
|
125
334
|
const assets = [];
|
|
126
335
|
|
|
127
336
|
for (const raw of opts.materials) {
|
|
@@ -133,11 +342,10 @@ export function stageMaterials(opts) {
|
|
|
133
342
|
|
|
134
343
|
if (classified.kind === 'archive') {
|
|
135
344
|
const dest = path.join(stageRoot, 'archives', path.basename(classified.path, classified.ext) || 'archive');
|
|
136
|
-
|
|
137
|
-
const extracted = extractArchive(classified.path, dest);
|
|
345
|
+
const extracted = extractArchive(classified.path, dest, opts.archivePolicy || {});
|
|
138
346
|
const rel = path.relative(root, dest);
|
|
139
|
-
include.push(rel);
|
|
140
|
-
items.push({ ...classified, status: '
|
|
347
|
+
if (extracted.mined_files > 0) include.push(rel);
|
|
348
|
+
items.push({ ...classified, status: 'mined', stage: dest, extract: extracted });
|
|
141
349
|
continue;
|
|
142
350
|
}
|
|
143
351
|
|
|
@@ -158,7 +366,6 @@ export function stageMaterials(opts) {
|
|
|
158
366
|
continue;
|
|
159
367
|
}
|
|
160
368
|
|
|
161
|
-
// Single file
|
|
162
369
|
const destDir = path.join(stageRoot, classified.kind === 'image' ? 'images'
|
|
163
370
|
: classified.kind === 'model3d' ? 'models3d'
|
|
164
371
|
: classified.kind === 'document' ? 'documents'
|
|
@@ -180,7 +387,7 @@ export function stageMaterials(opts) {
|
|
|
180
387
|
|
|
181
388
|
const uniqueInclude = [...new Set(include.map((p) => p.replace(/\\/g, '/').replace(/\/$/, '') || '.'))].sort();
|
|
182
389
|
const manifest = {
|
|
183
|
-
schema: 'agentsam.ingest.materials.
|
|
390
|
+
schema: 'agentsam.ingest.materials.v2',
|
|
184
391
|
ingest_id: ingestId,
|
|
185
392
|
staged_at: new Date().toISOString(),
|
|
186
393
|
stage_root: path.relative(root, stageRoot),
|
|
@@ -77,6 +77,7 @@ export function encodeErrorDetail(value) {
|
|
|
77
77
|
detail.setDomain(envelope.domain);
|
|
78
78
|
if (envelope.tool) detail.setTool(envelope.tool);
|
|
79
79
|
if (envelope.stage) detail.setStage(envelope.stage);
|
|
80
|
+
if (envelope.native_stage) detail.setNativeStage(envelope.native_stage);
|
|
80
81
|
detail.setRemediationAction(envelope.remediation.action);
|
|
81
82
|
if (envelope.remediation.message) detail.setRemediationMessage(envelope.remediation.message);
|
|
82
83
|
detail.setFingerprint(envelope.fingerprint);
|
|
@@ -115,6 +116,7 @@ export function decodeErrorDetail(detail) {
|
|
|
115
116
|
domain: detail.hasDomain?.() ? detail.getDomain() : 'runtime',
|
|
116
117
|
tool: detail.hasTool?.() ? detail.getTool() : null,
|
|
117
118
|
stage: detail.hasStage?.() ? detail.getStage() : null,
|
|
119
|
+
native_stage: detail.hasNativeStage?.() ? detail.getNativeStage() : null,
|
|
118
120
|
remediation: {
|
|
119
121
|
action: detail.hasRemediationAction?.() ? detail.getRemediationAction() : undefined,
|
|
120
122
|
message: detail.hasRemediationMessage?.() ? detail.getRemediationMessage() : null,
|
package/src/lib/setup/runtime.js
CHANGED
|
@@ -380,14 +380,16 @@ export function renderRuntimePlan(plan) {
|
|
|
380
380
|
lines.push(` missing tools: ${c.constraints.missing_tools.join(', ')}`);
|
|
381
381
|
}
|
|
382
382
|
} else if (c.goap.action_ids.length) {
|
|
383
|
-
lines.push(` plan: ${c.goap.action_ids.join(' → ')}`);
|
|
383
|
+
lines.push(` target plan: ${c.goap.action_ids.join(' → ')}`);
|
|
384
384
|
}
|
|
385
385
|
}
|
|
386
386
|
lines.push('');
|
|
387
387
|
if (plan.recommended) {
|
|
388
388
|
lines.push(` Recommended ${plan.recommended.profile.label} (${plan.recommended.profile.id})`);
|
|
389
389
|
lines.push(` Next agentsam setup runtime --profile ${plan.recommended.profile.id} --yes`);
|
|
390
|
-
lines.push(
|
|
390
|
+
lines.push(plan.facts.agentsamd.installed
|
|
391
|
+
? ' agentsam runtime status'
|
|
392
|
+
: ' agentsam runtime install');
|
|
391
393
|
}
|
|
392
394
|
lines.push('');
|
|
393
395
|
return `${lines.join('\n')}\n`;
|
|
@@ -12,6 +12,7 @@ export const SLASH_COMMANDS = [
|
|
|
12
12
|
{ cmd: '/context', description: 'Show model context economics; add repo for Git bridge context', lane: 'context' },
|
|
13
13
|
{ cmd: '/status', description: 'Honest project awareness: account, models, terminal, live deploy', lane: 'local' },
|
|
14
14
|
{ cmd: '/models', description: 'Probe providers and provider-verified known models', lane: 'model' },
|
|
15
|
+
{ cmd: '/hooks', description: 'Inspect lifecycle hook sources, order, and execution receipts', lane: 'agent' },
|
|
15
16
|
{ cmd: '/providers', description: 'Configure and verify machine provider credentials', lane: 'model' },
|
|
16
17
|
{ cmd: '/credentials', description: 'Audit credentials, drift, and remediation (never prints secrets)', lane: 'identity' },
|
|
17
18
|
{ cmd: '/cheat-sheet', description: 'Organized Agent Sam command groups', lane: 'local' },
|
|
@@ -4,6 +4,7 @@ import { createLocalSqliteDatabase } from './sqlite.js';
|
|
|
4
4
|
import { applyRuntimeMigrations } from './migrations.js';
|
|
5
5
|
import { findCliProjectRoot } from '../lib/cli-preferences.js';
|
|
6
6
|
import { getLocalDatabasePath, tryReadProjectConfig } from '../lib/project-config.js';
|
|
7
|
+
import { createHookStore } from '../../packages/agentsam-hooks/src/store.js';
|
|
7
8
|
|
|
8
9
|
function clean(value) { return value == null ? '' : String(value).trim(); }
|
|
9
10
|
function hash(value) { return createHash('sha256').update(String(value || '')).digest('hex'); }
|
|
@@ -187,3 +188,48 @@ export async function pruneExpiredRuntimeState(value = {}) {
|
|
|
187
188
|
return { digests: Number(digests.changes || 0), compactions: Number(compactions.changes || 0), approvals_expired: Number(approvals.changes || 0) };
|
|
188
189
|
});
|
|
189
190
|
}
|
|
191
|
+
|
|
192
|
+
function hookStore(db, value = {}) {
|
|
193
|
+
return createHookStore(db, { ownerId: clean(value.owner_id || value.account_id) || 'local' });
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export async function listRuntimeHooks(value = {}) {
|
|
197
|
+
return withStore(value.projectRoot || value.cwd, (db) => hookStore(db, value).listHooks({
|
|
198
|
+
activeOnly: value.activeOnly,
|
|
199
|
+
event_type: value.event_type,
|
|
200
|
+
context: value.context,
|
|
201
|
+
}));
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export async function upsertRuntimeHook(value = {}) {
|
|
205
|
+
return withStore(value.projectRoot || value.cwd, (db) => hookStore(db, value).upsertHook(value));
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export async function setRuntimeHookActive(value = {}) {
|
|
209
|
+
return withStore(value.projectRoot || value.cwd, (db) => hookStore(db, value).setHookActive(value.hook_id || value.hook_key, value.active));
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export async function removeRuntimeHook(value = {}) {
|
|
213
|
+
return withStore(value.projectRoot || value.cwd, (db) => hookStore(db, value).removeHook(value.hook_id || value.hook_key));
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export async function recordRuntimeHookReceipt(value = {}) {
|
|
217
|
+
if (!value.receipt) return null;
|
|
218
|
+
return withStore(value.projectRoot || value.cwd, (db) => hookStore(db, value).recordExecution(value.receipt, {
|
|
219
|
+
source_kind: value.source_kind,
|
|
220
|
+
hook_id: value.hook_id,
|
|
221
|
+
invocation_id: value.invocation_id,
|
|
222
|
+
agent_run_id: value.agent_run_id,
|
|
223
|
+
session_id: value.session_id,
|
|
224
|
+
conversation_id: value.conversation_id,
|
|
225
|
+
context: value.context,
|
|
226
|
+
}));
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export async function listRuntimeHookExecutions(value = {}) {
|
|
230
|
+
return withStore(value.projectRoot || value.cwd, (db) => hookStore(db, value).listExecutions({
|
|
231
|
+
hook_key: value.hook_key,
|
|
232
|
+
status: value.status,
|
|
233
|
+
limit: value.limit,
|
|
234
|
+
}));
|
|
235
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import crypto from 'node:crypto';
|
|
5
|
+
|
|
6
|
+
export const ASSET_MANIFEST_SCHEMA = 'agentsam.local-model-assets.v1';
|
|
7
|
+
export function agentsamHome(env = process.env) {
|
|
8
|
+
const configured = String(env.AGENTSAM_HOME || '').trim();
|
|
9
|
+
return configured ? path.resolve(configured.replace(/^~(?=\/|$)/, os.homedir())) : path.join(os.homedir(), '.agentsam');
|
|
10
|
+
}
|
|
11
|
+
export function assetManifestPath(env = process.env) { return path.join(agentsamHome(env), 'models', 'manifest.v1.json'); }
|
|
12
|
+
function safeStat(file) { try { return fs.statSync(file); } catch { return null; } }
|
|
13
|
+
function fileId(stat) { return stat ? `${stat.dev}:${stat.ino}` : null; }
|
|
14
|
+
export function readAssetManifest(env = process.env) {
|
|
15
|
+
const file = assetManifestPath(env);
|
|
16
|
+
if (!fs.existsSync(file)) return { schema_version: ASSET_MANIFEST_SCHEMA, generated_at: null, assets: [] };
|
|
17
|
+
try { const parsed = JSON.parse(fs.readFileSync(file, 'utf8')); return parsed?.schema_version === ASSET_MANIFEST_SCHEMA ? parsed : { schema_version: ASSET_MANIFEST_SCHEMA, generated_at: null, assets: [] }; } catch { return { schema_version: ASSET_MANIFEST_SCHEMA, generated_at: null, assets: [] }; }
|
|
18
|
+
}
|
|
19
|
+
function writeManifest(manifest, env = process.env) {
|
|
20
|
+
const file = assetManifestPath(env); fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
21
|
+
const temp = `${file}.${process.pid}.tmp`; fs.writeFileSync(temp, JSON.stringify(manifest, null, 2) + '\n', { mode: 0o600 }); fs.renameSync(temp, file); return file;
|
|
22
|
+
}
|
|
23
|
+
export function adoptAsset(file, metadata = {}, options = {}) {
|
|
24
|
+
const absolute = path.resolve(file); const stat = safeStat(absolute);
|
|
25
|
+
if (!stat || !stat.isFile()) throw new Error(`model asset is not a regular file: ${absolute}`);
|
|
26
|
+
const env = options.env || process.env; const manifest = readAssetManifest(env);
|
|
27
|
+
const asset = { id: metadata.id || crypto.createHash('sha256').update(`${absolute}:${stat.size}:${stat.mtimeMs}`).digest('hex').slice(0, 16), engine_id: metadata.engine_id || null, model: metadata.model || path.basename(absolute), format: metadata.format || path.extname(absolute).slice(1) || 'unknown', canonical_path: absolute, reference: 'external', size_bytes: stat.size, file_id: fileId(stat), digest: metadata.digest || null, seen_at: new Date().toISOString() };
|
|
28
|
+
const assets = manifest.assets.filter((row) => row.canonical_path !== absolute); assets.push(asset);
|
|
29
|
+
const next = { ...manifest, schema_version: ASSET_MANIFEST_SCHEMA, generated_at: new Date().toISOString(), assets }; const manifestPath = writeManifest(next, env);
|
|
30
|
+
return { asset, manifest_path: manifestPath, copied: false };
|
|
31
|
+
}
|
|
32
|
+
export function auditAssets(options = {}) {
|
|
33
|
+
const env = options.env || process.env; const manifest = readAssetManifest(env);
|
|
34
|
+
const assets = manifest.assets.map((asset) => { const stat = safeStat(asset.canonical_path); return { ...asset, exists: Boolean(stat), current_size_bytes: stat?.size || null, current_file_id: fileId(stat), stale: !stat || stat.size !== asset.size_bytes }; });
|
|
35
|
+
const byFile = new Map(); for (const asset of assets) if (asset.file_id) byFile.set(asset.file_id, [...(byFile.get(asset.file_id) || []), asset.id]);
|
|
36
|
+
return { schema_version: ASSET_MANIFEST_SCHEMA, generated_at: new Date().toISOString(), root: path.join(agentsamHome(env), 'models'), assets, duplicates: [...byFile.entries()].filter(([, ids]) => ids.length > 1).map(([file_id, ids]) => ({ file_id, asset_ids: ids })) };
|
|
37
|
+
}
|