create-walle 0.9.11 → 0.9.13
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 +3 -3
- package/package.json +2 -2
- package/template/bin/dev.sh +7 -1
- package/template/bin/setup.js +53 -9
- package/template/bin/sync-images.js +53 -0
- package/template/builder-journal.md +17 -0
- package/template/claude-task-manager/api-prompts.js +98 -13
- package/template/claude-task-manager/api-reviews.js +82 -5
- package/template/claude-task-manager/db.js +32 -5
- package/template/claude-task-manager/docs/session-capture-foundation-design.md +1273 -0
- package/template/claude-task-manager/lib/claude-desktop-sessions.js +696 -0
- package/template/claude-task-manager/lib/coding-agent-models.js +49 -1
- package/template/claude-task-manager/lib/session-capture.js +421 -0
- package/template/claude-task-manager/lib/session-history.js +135 -15
- package/template/claude-task-manager/lib/session-jobs.js +10 -5
- package/template/claude-task-manager/lib/session-stream.js +87 -19
- package/template/claude-task-manager/lib/setup-provider-config.js +115 -0
- package/template/claude-task-manager/lib/walle-ctm-history.js +72 -0
- package/template/claude-task-manager/lib/walle-session-context.js +61 -0
- package/template/claude-task-manager/lib/walle-transcript.js +176 -0
- package/template/claude-task-manager/public/css/setup.css +35 -8
- package/template/claude-task-manager/public/css/walle-session.css +56 -0
- package/template/claude-task-manager/public/css/walle.css +120 -0
- package/template/claude-task-manager/public/index.html +814 -181
- package/template/claude-task-manager/public/js/message-renderer.js +148 -19
- package/template/claude-task-manager/public/js/reviews.js +120 -62
- package/template/claude-task-manager/public/js/setup.js +75 -31
- package/template/claude-task-manager/public/js/stream-view.js +115 -55
- package/template/claude-task-manager/public/js/walle-session.js +84 -2
- package/template/claude-task-manager/public/js/walle.js +308 -54
- package/template/claude-task-manager/server.js +1092 -146
- package/template/claude-task-manager/session-integrity.js +181 -54
- package/template/claude-task-manager/session-utils.js +123 -41
- package/template/claude-task-manager/workers/state-detectors/codex.js +5 -2
- package/template/package.json +1 -1
- package/template/wall-e/adapters/ctm.js +39 -18
- package/template/wall-e/agent-runners/contract.js +17 -0
- package/template/wall-e/agent-runners/index.js +22 -0
- package/template/wall-e/agent-runtime/harness.js +212 -0
- package/template/wall-e/agent-runtime/index.js +8 -0
- package/template/wall-e/agent-runtime/registry.js +67 -0
- package/template/wall-e/agent-runtime/session-store.js +179 -0
- package/template/wall-e/agent-runtime/spawn.js +208 -0
- package/template/wall-e/api-walle.js +174 -7
- package/template/wall-e/brain.js +266 -28
- package/template/wall-e/channels/policy.js +88 -0
- package/template/wall-e/channels/registry.js +15 -1
- package/template/wall-e/channels/reply-dispatcher.js +70 -0
- package/template/wall-e/channels/session-bindings.js +51 -0
- package/template/wall-e/chat/code-review-context.js +29 -0
- package/template/wall-e/chat.js +188 -42
- package/template/wall-e/coding/acp-adapter.js +188 -0
- package/template/wall-e/coding/agent-catalog.js +129 -0
- package/template/wall-e/coding/compaction-service.js +247 -0
- package/template/wall-e/coding/execution-trace.js +3 -0
- package/template/wall-e/coding/instruction-service.js +224 -0
- package/template/wall-e/coding/model-message.js +67 -0
- package/template/wall-e/coding/permission-rules-store.js +111 -0
- package/template/wall-e/coding/permission-service.js +266 -0
- package/template/wall-e/coding/prompt-bundle.js +67 -0
- package/template/wall-e/coding/prompt-runtime.js +243 -0
- package/template/wall-e/coding/provider-transform.js +188 -0
- package/template/wall-e/coding/runtime-mode.js +132 -0
- package/template/wall-e/coding/snapshot-service.js +155 -0
- package/template/wall-e/coding/stream-processor.js +268 -0
- package/template/wall-e/coding/task-tool.js +255 -0
- package/template/wall-e/coding/tool-registry.js +361 -0
- package/template/wall-e/coding/transcript-writer.js +143 -0
- package/template/wall-e/coding/workspace-replay.js +324 -0
- package/template/wall-e/coding-context.js +4 -22
- package/template/wall-e/coding-orchestrator.js +307 -18
- package/template/wall-e/coding-prompts.js +44 -3
- package/template/wall-e/context/context-builder.js +43 -1
- package/template/wall-e/context/topic-matcher.js +1 -1
- package/template/wall-e/eval/agent-runner.js +59 -13
- package/template/wall-e/eval/benchmarks/memory-retrieval.json +155 -57
- package/template/wall-e/eval/benchmarks.js +100 -16
- package/template/wall-e/eval/eval-orchestrator.js +218 -8
- package/template/wall-e/eval/harvester.js +62 -5
- package/template/wall-e/eval/head-to-head.js +23 -2
- package/template/wall-e/eval/humaneval-adapter.js +30 -5
- package/template/wall-e/eval/livecodebench-adapter.js +29 -5
- package/template/wall-e/eval/manifest.js +186 -0
- package/template/wall-e/eval/run-agent-benchmarks.js +66 -2
- package/template/wall-e/eval/session-retrieval-benchmark.js +150 -0
- package/template/wall-e/eval/session-transcripts.js +57 -4
- package/template/wall-e/eval/swebench-adapter.js +109 -3
- package/template/wall-e/evaluation/agent-router.js +53 -1
- package/template/wall-e/evaluation/coding-quorum.js +48 -1
- package/template/wall-e/evaluation/router.js +4 -2
- package/template/wall-e/evaluation/tier-selector.js +11 -1
- package/template/wall-e/extraction/contradiction.js +2 -2
- package/template/wall-e/extraction/indexer.js +2 -1
- package/template/wall-e/extraction/knowledge-extractor.js +2 -2
- package/template/wall-e/hooks/cli.js +92 -0
- package/template/wall-e/hooks/discovery.js +119 -0
- package/template/wall-e/hooks/index.js +7 -0
- package/template/wall-e/hooks/manifest.js +55 -0
- package/template/wall-e/hooks/runtime.js +84 -0
- package/template/wall-e/hooks/session-memory.js +225 -0
- package/template/wall-e/http/auth.js +6 -2
- package/template/wall-e/http/chat-api.js +54 -8
- package/template/wall-e/integrations/claude-plugin/hooks/hooks.json +27 -0
- package/template/wall-e/integrations/claude-plugin/hooks/walle-precompact-hook.sh +5 -0
- package/template/wall-e/integrations/claude-plugin/hooks/walle-stop-hook.sh +5 -0
- package/template/wall-e/integrations/codex-plugin/hooks/walle-hook.sh +7 -0
- package/template/wall-e/integrations/codex-plugin/hooks.json +37 -0
- package/template/wall-e/listening/calendar.js +3 -1
- package/template/wall-e/llm/client.js +64 -10
- package/template/wall-e/llm/google.js +39 -5
- package/template/wall-e/llm/ollama.js +1 -1
- package/template/wall-e/llm/ollama.plugin.json +1 -1
- package/template/wall-e/llm/provider-availability.js +10 -0
- package/template/wall-e/llm/provider-error.js +269 -0
- package/template/wall-e/llm/tool-adapter.js +48 -12
- package/template/wall-e/loops/boot.js +2 -1
- package/template/wall-e/loops/initiative.js +2 -2
- package/template/wall-e/loops/tasks.js +8 -47
- package/template/wall-e/loops/workspace-prompts.js +20 -0
- package/template/wall-e/mcp-server.js +442 -1
- package/template/wall-e/memory/session-ingest-service.js +159 -0
- package/template/wall-e/memory/source-indexer.js +289 -0
- package/template/wall-e/plugins/discovery.js +83 -0
- package/template/wall-e/plugins/manifest-loader.js +50 -10
- package/template/wall-e/plugins/manifest-schema.js +69 -0
- package/template/wall-e/plugins/model-catalog.js +55 -0
- package/template/wall-e/prompts/coding/base.txt +2 -0
- package/template/wall-e/prompts/coding/deepseek.txt +1 -0
- package/template/wall-e/prompts/coding/memory-protocol.md +9 -0
- package/template/wall-e/prompts/coding/plan.txt +1 -0
- package/template/wall-e/runtime/execution-trace.js +220 -0
- package/template/wall-e/security/audit.js +266 -0
- package/template/wall-e/security/ssrf.js +236 -0
- package/template/wall-e/session-files.js +303 -0
- package/template/wall-e/skills/_bundled/slack-backfill/SKILL.md +3 -0
- package/template/wall-e/skills/_bundled/slack-sync/SKILL.md +3 -0
- package/template/wall-e/skills/internal-skill-registry.js +2 -2
- package/template/wall-e/skills/script-skill-runner.js +143 -0
- package/template/wall-e/skills/skill-executor.js +5 -6
- package/template/wall-e/skills/skill-fallback.js +3 -1
- package/template/wall-e/skills/skill-harness-registry.js +7 -8
- package/template/wall-e/skills/skill-planner.js +52 -4
- package/template/wall-e/skills/slack-ingest.js +11 -3
- package/template/wall-e/sources/base.js +90 -0
- package/template/wall-e/sources/builtin.js +33 -0
- package/template/wall-e/sources/claude-code-jsonl.js +78 -0
- package/template/wall-e/sources/codex-jsonl.js +125 -0
- package/template/wall-e/sources/coding-session-utils.js +117 -0
- package/template/wall-e/sources/contract-suite.js +59 -0
- package/template/wall-e/sources/gemini-jsonl.js +85 -0
- package/template/wall-e/sources/index.js +9 -0
- package/template/wall-e/sources/jsonl-utils.js +181 -0
- package/template/wall-e/sources/record-types.js +252 -0
- package/template/wall-e/sources/registry.js +92 -0
- package/template/wall-e/sources/transforms.js +100 -0
- package/template/wall-e/sources/walle-jsonl.js +108 -0
- package/template/wall-e/tools/coding-middleware.js +31 -1
- package/template/wall-e/tools/file-tracker.js +25 -1
- package/template/wall-e/tools/local-tools.js +75 -47
- package/template/wall-e/tools/session-sharing.js +68 -1
- package/template/wall-e/tools/shell-analyzer.js +1 -1
- package/template/wall-e/tools/shell-policy.js +47 -0
- package/template/wall-e/tools/snapshot.js +42 -0
- package/template/wall-e/training/harvester.js +62 -5
- package/template/wall-e/utils/repair.js +253 -1
- package/template/website/index.html +3 -3
- package/template/wall-e/skills/_bundled/slack-mentions/.watched-threads.json +0 -18
package/template/wall-e/brain.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
const Database = require('better-sqlite3');
|
|
3
3
|
const path = require('path');
|
|
4
4
|
const fs = require('fs');
|
|
5
|
+
const crypto = require('crypto');
|
|
5
6
|
const { v4: uuidv4 } = require('uuid');
|
|
6
7
|
const { EventEmitter } = require('events');
|
|
7
8
|
const { createWriteAuditLog } = require('./db/write-audit');
|
|
@@ -33,7 +34,7 @@ let currentDbPath = null;
|
|
|
33
34
|
let _daemonOwned = false; // When true, closeDb() is a no-op (daemon manages lifecycle)
|
|
34
35
|
|
|
35
36
|
// --- Schema versioning via PRAGMA user_version ---
|
|
36
|
-
const SCHEMA_VERSION =
|
|
37
|
+
const SCHEMA_VERSION = 10; // Bump on every migration addition
|
|
37
38
|
|
|
38
39
|
const MIGRATIONS = {
|
|
39
40
|
1: (d) => {
|
|
@@ -297,6 +298,31 @@ const MIGRATIONS = {
|
|
|
297
298
|
d.prepare("CREATE INDEX IF NOT EXISTS idx_agent_runner_eval_runner ON agent_runner_evaluations(runner_id, task_type)").run();
|
|
298
299
|
d.prepare("CREATE INDEX IF NOT EXISTS idx_agent_runner_eval_created ON agent_runner_evaluations(created_at)").run();
|
|
299
300
|
},
|
|
301
|
+
10: (d) => {
|
|
302
|
+
const addCol = (table, col, type) => {
|
|
303
|
+
try { d.prepare(`SELECT ${col} FROM ${table} LIMIT 0`).run(); } catch (_) {
|
|
304
|
+
d.prepare(`ALTER TABLE ${table} ADD COLUMN ${col} ${type}`).run();
|
|
305
|
+
}
|
|
306
|
+
};
|
|
307
|
+
addCol('eval_benchmark_runs', 'sample_id', 'TEXT');
|
|
308
|
+
addCol('eval_benchmark_runs', 'dataset_version', 'TEXT');
|
|
309
|
+
addCol('eval_benchmark_runs', 'dataset_hash', 'TEXT');
|
|
310
|
+
addCol('eval_benchmark_runs', 'prompt_hash', 'TEXT');
|
|
311
|
+
addCol('eval_benchmark_runs', 'system_prompt_hash', 'TEXT');
|
|
312
|
+
addCol('eval_benchmark_runs', 'repo_sha', 'TEXT');
|
|
313
|
+
addCol('eval_benchmark_runs', 'scorer_version', 'TEXT');
|
|
314
|
+
addCol('eval_benchmark_runs', 'evaluator_version', 'TEXT');
|
|
315
|
+
addCol('eval_benchmark_runs', 'scoring_method', 'TEXT');
|
|
316
|
+
addCol('eval_benchmark_runs', 'run_config_json', 'TEXT');
|
|
317
|
+
addCol('eval_benchmark_runs', 'eval_manifest_json', 'TEXT');
|
|
318
|
+
addCol('eval_benchmark_runs', 'artifact_path', 'TEXT');
|
|
319
|
+
addCol('eval_benchmark_runs', 'model_snapshot', 'TEXT');
|
|
320
|
+
addCol('eval_benchmark_runs', 'temperature', 'REAL');
|
|
321
|
+
addCol('eval_benchmark_runs', 'seed', 'TEXT');
|
|
322
|
+
addCol('eval_benchmark_runs', 'trusted', 'INTEGER DEFAULT 0');
|
|
323
|
+
d.prepare("CREATE INDEX IF NOT EXISTS idx_eval_bench_trusted ON eval_benchmark_runs(trusted, provider, model)").run();
|
|
324
|
+
d.prepare("CREATE INDEX IF NOT EXISTS idx_eval_bench_dataset ON eval_benchmark_runs(suite, dataset_version)").run();
|
|
325
|
+
},
|
|
300
326
|
};
|
|
301
327
|
|
|
302
328
|
// Schema invariants — columns/tables that MUST exist after the named migration.
|
|
@@ -312,6 +338,7 @@ const SCHEMA_INVARIANTS = [
|
|
|
312
338
|
{ migration: 7, table: 'chat_messages', column: 'attachments' },
|
|
313
339
|
{ migration: 8, table: 'model_providers', column: 'auth_method' },
|
|
314
340
|
{ migration: 9, table: 'agent_runner_evaluations', column: 'runner_id' },
|
|
341
|
+
{ migration: 10, table: 'eval_benchmark_runs', column: 'dataset_version' },
|
|
315
342
|
];
|
|
316
343
|
|
|
317
344
|
function _columnExists(d, table, column) {
|
|
@@ -393,9 +420,22 @@ function _detectUpgrade(previousSchemaVersion) {
|
|
|
393
420
|
|
|
394
421
|
const storedVersion = getKv('installed_version');
|
|
395
422
|
if (!storedVersion) {
|
|
423
|
+
const existingInstall = previousSchemaVersion > 0 || _hasExistingInstallFootprint();
|
|
396
424
|
setKv('installed_version', pkgVersion);
|
|
397
|
-
|
|
398
|
-
|
|
425
|
+
if (existingInstall) {
|
|
426
|
+
telemetry.track('upgrade', {
|
|
427
|
+
from: 'unknown',
|
|
428
|
+
to: pkgVersion,
|
|
429
|
+
schema_from: previousSchemaVersion,
|
|
430
|
+
schema_to: SCHEMA_VERSION,
|
|
431
|
+
migrations_applied: Math.max(0, SCHEMA_VERSION - previousSchemaVersion),
|
|
432
|
+
first_tracked: true,
|
|
433
|
+
});
|
|
434
|
+
setKv('last_upgrade_at', new Date().toISOString());
|
|
435
|
+
} else {
|
|
436
|
+
setKv('first_installed_at', new Date().toISOString());
|
|
437
|
+
telemetry.track('install', { version: pkgVersion, fresh: true, schema: SCHEMA_VERSION });
|
|
438
|
+
}
|
|
399
439
|
} else if (storedVersion !== pkgVersion) {
|
|
400
440
|
telemetry.track('upgrade', {
|
|
401
441
|
from: storedVersion,
|
|
@@ -420,6 +460,32 @@ function _readVersion() {
|
|
|
420
460
|
} catch { return 'unknown'; }
|
|
421
461
|
}
|
|
422
462
|
|
|
463
|
+
function _hasExistingInstallFootprint() {
|
|
464
|
+
try {
|
|
465
|
+
const d = getDb();
|
|
466
|
+
const hasTable = d.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name = ? LIMIT 1");
|
|
467
|
+
const footprintTables = [
|
|
468
|
+
['memories', 'SELECT count(*) AS c FROM memories'],
|
|
469
|
+
['knowledge', 'SELECT count(*) AS c FROM knowledge'],
|
|
470
|
+
['tasks', 'SELECT count(*) AS c FROM tasks'],
|
|
471
|
+
['skills', 'SELECT count(*) AS c FROM skills'],
|
|
472
|
+
['chat_messages', 'SELECT count(*) AS c FROM chat_messages'],
|
|
473
|
+
['sessions', 'SELECT count(*) AS c FROM sessions'],
|
|
474
|
+
];
|
|
475
|
+
for (const [table, countSql] of footprintTables) {
|
|
476
|
+
if (!hasTable.get(table)) continue;
|
|
477
|
+
if ((d.prepare(countSql).get()?.c || 0) > 0) return true;
|
|
478
|
+
}
|
|
479
|
+
if (hasTable.get('kv_store')) {
|
|
480
|
+
const row = d.prepare(
|
|
481
|
+
"SELECT 1 FROM kv_store WHERE key NOT IN ('installed_version', 'first_installed_at', 'last_upgrade_at') LIMIT 1"
|
|
482
|
+
).get();
|
|
483
|
+
if (row) return true;
|
|
484
|
+
}
|
|
485
|
+
} catch {}
|
|
486
|
+
return false;
|
|
487
|
+
}
|
|
488
|
+
|
|
423
489
|
function getDb() {
|
|
424
490
|
if (db) return db;
|
|
425
491
|
throw new Error('Database not initialized. Call initDb() first.');
|
|
@@ -971,6 +1037,22 @@ function createTables() {
|
|
|
971
1037
|
input_tokens INTEGER,
|
|
972
1038
|
output_tokens INTEGER,
|
|
973
1039
|
gen_tok_per_sec REAL,
|
|
1040
|
+
sample_id TEXT,
|
|
1041
|
+
dataset_version TEXT,
|
|
1042
|
+
dataset_hash TEXT,
|
|
1043
|
+
prompt_hash TEXT,
|
|
1044
|
+
system_prompt_hash TEXT,
|
|
1045
|
+
repo_sha TEXT,
|
|
1046
|
+
scorer_version TEXT,
|
|
1047
|
+
evaluator_version TEXT,
|
|
1048
|
+
scoring_method TEXT,
|
|
1049
|
+
run_config_json TEXT,
|
|
1050
|
+
eval_manifest_json TEXT,
|
|
1051
|
+
artifact_path TEXT,
|
|
1052
|
+
model_snapshot TEXT,
|
|
1053
|
+
temperature REAL,
|
|
1054
|
+
seed TEXT,
|
|
1055
|
+
trusted INTEGER DEFAULT 0,
|
|
974
1056
|
created_at TEXT DEFAULT (datetime('now'))
|
|
975
1057
|
);
|
|
976
1058
|
CREATE INDEX IF NOT EXISTS idx_eval_bench_run ON eval_benchmark_runs(run_id);
|
|
@@ -2889,39 +2971,131 @@ function listModelDefaults() {
|
|
|
2889
2971
|
|
|
2890
2972
|
// -- Benchmark Evaluations --
|
|
2891
2973
|
|
|
2974
|
+
function _benchmarkHash(value) {
|
|
2975
|
+
return crypto.createHash('sha256').update(String(value ?? '')).digest('hex');
|
|
2976
|
+
}
|
|
2977
|
+
|
|
2978
|
+
function _safeJsonString(value) {
|
|
2979
|
+
if (value == null) return null;
|
|
2980
|
+
if (typeof value === 'string') return value;
|
|
2981
|
+
try { return JSON.stringify(value); } catch { return null; }
|
|
2982
|
+
}
|
|
2983
|
+
|
|
2984
|
+
function _inferBenchmarkTrust(entry = {}) {
|
|
2985
|
+
if (entry.trusted !== undefined) return entry.trusted ? 1 : 0;
|
|
2986
|
+
if (entry.error) return 0;
|
|
2987
|
+
if (entry.compositeScore == null && entry.composite_score == null) return 0;
|
|
2988
|
+
const method = String(entry.scoringMethod || entry.scoring_method || '').toLowerCase();
|
|
2989
|
+
if (
|
|
2990
|
+
method.includes('judge') ||
|
|
2991
|
+
method.includes('executable') ||
|
|
2992
|
+
method.includes('official') ||
|
|
2993
|
+
method.includes('agent-rubric') ||
|
|
2994
|
+
method.includes('tests')
|
|
2995
|
+
) return 1;
|
|
2996
|
+
return 0;
|
|
2997
|
+
}
|
|
2998
|
+
|
|
2999
|
+
function _normalizeBenchmarkResult(entry = {}) {
|
|
3000
|
+
const runId = entry.runId || entry.run_id || 'manual';
|
|
3001
|
+
const suite = entry.suite || 'unknown';
|
|
3002
|
+
const benchmarkId = entry.promptId || entry.benchmark_id || entry.sampleId || entry.sample_id || 'unknown';
|
|
3003
|
+
const prompt = entry.prompt || '';
|
|
3004
|
+
const sampleId = entry.sampleId || entry.sample_id || benchmarkId;
|
|
3005
|
+
const datasetVersion = entry.datasetVersion || entry.dataset_version || `${suite}:local-v1`;
|
|
3006
|
+
const datasetHash = entry.datasetHash || entry.dataset_hash || _benchmarkHash(JSON.stringify({
|
|
3007
|
+
suite,
|
|
3008
|
+
datasetVersion,
|
|
3009
|
+
sampleId,
|
|
3010
|
+
prompt,
|
|
3011
|
+
}));
|
|
3012
|
+
const scoringMethod = entry.scoringMethod || entry.scoring_method || null;
|
|
3013
|
+
const scorerVersion = entry.scorerVersion || entry.scorer_version || 'wall-e-eval-v1-legacy';
|
|
3014
|
+
const evaluatorVersion = entry.evaluatorVersion || entry.evaluator_version || 'wall-e-evaluator-v1';
|
|
3015
|
+
const runConfigJson = entry.runConfigJson || entry.run_config_json || _safeJsonString(entry.runConfig);
|
|
3016
|
+
const evalManifestJson = entry.evalManifestJson || entry.eval_manifest_json || _safeJsonString({
|
|
3017
|
+
runId,
|
|
3018
|
+
suite,
|
|
3019
|
+
datasetVersion,
|
|
3020
|
+
datasetHash,
|
|
3021
|
+
sampleId,
|
|
3022
|
+
promptHash: entry.promptHash || entry.prompt_hash || _benchmarkHash(prompt),
|
|
3023
|
+
provider: entry.provider || null,
|
|
3024
|
+
model: entry.model || null,
|
|
3025
|
+
modelSnapshot: entry.modelSnapshot || entry.model_snapshot || entry.model || null,
|
|
3026
|
+
scorerVersion,
|
|
3027
|
+
evaluatorVersion,
|
|
3028
|
+
scoringMethod,
|
|
3029
|
+
repoSha: entry.repoSha || entry.repo_sha || null,
|
|
3030
|
+
runConfig: runConfigJson ? (() => { try { return JSON.parse(runConfigJson); } catch { return null; } })() : null,
|
|
3031
|
+
artifactPath: entry.artifactPath || entry.artifact_path || null,
|
|
3032
|
+
trusted: !!_inferBenchmarkTrust(entry),
|
|
3033
|
+
});
|
|
3034
|
+
|
|
3035
|
+
return {
|
|
3036
|
+
...entry,
|
|
3037
|
+
runId,
|
|
3038
|
+
suite,
|
|
3039
|
+
promptId: benchmarkId,
|
|
3040
|
+
prompt,
|
|
3041
|
+
sampleId,
|
|
3042
|
+
datasetVersion,
|
|
3043
|
+
datasetHash,
|
|
3044
|
+
promptHash: entry.promptHash || entry.prompt_hash || _benchmarkHash(prompt),
|
|
3045
|
+
systemPromptHash: entry.systemPromptHash || entry.system_prompt_hash || null,
|
|
3046
|
+
repoSha: entry.repoSha || entry.repo_sha || null,
|
|
3047
|
+
scorerVersion,
|
|
3048
|
+
evaluatorVersion,
|
|
3049
|
+
scoringMethod,
|
|
3050
|
+
runConfigJson,
|
|
3051
|
+
evalManifestJson,
|
|
3052
|
+
artifactPath: entry.artifactPath || entry.artifact_path || null,
|
|
3053
|
+
modelSnapshot: entry.modelSnapshot || entry.model_snapshot || entry.model || null,
|
|
3054
|
+
temperature: entry.temperature ?? null,
|
|
3055
|
+
seed: entry.seed == null ? null : String(entry.seed),
|
|
3056
|
+
trusted: _inferBenchmarkTrust(entry),
|
|
3057
|
+
};
|
|
3058
|
+
}
|
|
3059
|
+
|
|
2892
3060
|
function isInvalidBenchmarkResult(entry = {}) {
|
|
2893
|
-
const error = typeof entry.error === 'string' ? entry.error.trim() : entry.error;
|
|
2894
3061
|
const provider = typeof entry.provider === 'string' ? entry.provider.trim() : entry.provider;
|
|
2895
3062
|
const model = typeof entry.model === 'string' ? entry.model.trim() : entry.model;
|
|
2896
|
-
return
|
|
3063
|
+
return !provider || !model || provider === 'unknown' || model === 'unknown';
|
|
2897
3064
|
}
|
|
2898
3065
|
|
|
2899
3066
|
function insertBenchmarkResult(entry) {
|
|
2900
|
-
|
|
3067
|
+
const normalized = _normalizeBenchmarkResult(entry);
|
|
3068
|
+
if (isInvalidBenchmarkResult(normalized)) return null;
|
|
2901
3069
|
|
|
2902
|
-
const id =
|
|
3070
|
+
const id = normalized.id || crypto.randomUUID();
|
|
2903
3071
|
getDb().prepare(`
|
|
2904
3072
|
INSERT OR REPLACE INTO eval_benchmark_runs
|
|
2905
3073
|
(id, run_id, suite, benchmark_id, provider, model, prompt, response, trait_score, judge_score, composite_score, latency_ms, cost_estimate, error,
|
|
2906
|
-
cost_dollars, tests_before, tests_after, total_tests, dimensions_json, model_metadata_json, input_tokens, output_tokens, gen_tok_per_sec
|
|
2907
|
-
|
|
3074
|
+
cost_dollars, tests_before, tests_after, total_tests, dimensions_json, model_metadata_json, input_tokens, output_tokens, gen_tok_per_sec,
|
|
3075
|
+
sample_id, dataset_version, dataset_hash, prompt_hash, system_prompt_hash, repo_sha, scorer_version, evaluator_version, scoring_method,
|
|
3076
|
+
run_config_json, eval_manifest_json, artifact_path, model_snapshot, temperature, seed, trusted)
|
|
3077
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
2908
3078
|
`).run(
|
|
2909
|
-
id,
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
3079
|
+
id, normalized.runId, normalized.suite, normalized.promptId,
|
|
3080
|
+
normalized.provider, normalized.model, normalized.prompt,
|
|
3081
|
+
normalized.response || null, normalized.traitScore ?? null, normalized.judgeScore ?? null,
|
|
3082
|
+
normalized.compositeScore ?? null, normalized.latencyMs || null, normalized.costEstimate || null,
|
|
3083
|
+
normalized.error || null,
|
|
3084
|
+
normalized.costDollars ?? null, normalized.testsBefore ?? null, normalized.testsAfter ?? null,
|
|
3085
|
+
normalized.totalTests ?? null, normalized.dimensionsJson || null, normalized.modelMetadataJson || null,
|
|
3086
|
+
normalized.inputTokens ?? null, normalized.outputTokens ?? null, normalized.genTokPerSec ?? null,
|
|
3087
|
+
normalized.sampleId || null, normalized.datasetVersion || null, normalized.datasetHash || null,
|
|
3088
|
+
normalized.promptHash || null, normalized.systemPromptHash || null, normalized.repoSha || null,
|
|
3089
|
+
normalized.scorerVersion || null, normalized.evaluatorVersion || null, normalized.scoringMethod || null,
|
|
3090
|
+
normalized.runConfigJson || null, normalized.evalManifestJson || null, normalized.artifactPath || null,
|
|
3091
|
+
normalized.modelSnapshot || null, normalized.temperature ?? null, normalized.seed ?? null, normalized.trusted ? 1 : 0
|
|
2917
3092
|
);
|
|
2918
3093
|
return id;
|
|
2919
3094
|
}
|
|
2920
3095
|
|
|
2921
3096
|
function purgeInvalidBenchmarkResults({ dryRun = false } = {}) {
|
|
2922
3097
|
const where = `
|
|
2923
|
-
|
|
2924
|
-
OR model IS NULL OR trim(model) = '' OR model = 'unknown'
|
|
3098
|
+
model IS NULL OR trim(model) = '' OR model = 'unknown'
|
|
2925
3099
|
OR provider IS NULL OR trim(provider) = '' OR provider = 'unknown'
|
|
2926
3100
|
`;
|
|
2927
3101
|
const matched = getDb().prepare(`SELECT COUNT(*) AS count FROM eval_benchmark_runs WHERE ${where}`).get();
|
|
@@ -2940,7 +3114,7 @@ function getBenchmarkResults({ suite, days } = {}) {
|
|
|
2940
3114
|
}
|
|
2941
3115
|
|
|
2942
3116
|
function getBenchmarkLeaderboard({ suite, days } = {}) {
|
|
2943
|
-
let where = "model != 'unknown'";
|
|
3117
|
+
let where = "model != 'unknown' AND provider != 'unknown'";
|
|
2944
3118
|
const params = [];
|
|
2945
3119
|
if (suite) { where += ' AND suite = ?'; params.push(suite); }
|
|
2946
3120
|
if (days) { where += " AND created_at >= datetime('now', ?)"; params.push(`-${days} days`); }
|
|
@@ -2948,15 +3122,21 @@ function getBenchmarkLeaderboard({ suite, days } = {}) {
|
|
|
2948
3122
|
SELECT provider, model,
|
|
2949
3123
|
AVG(CASE WHEN error IS NULL THEN composite_score END) AS avg_score,
|
|
2950
3124
|
AVG(composite_score) AS avg_score_all,
|
|
3125
|
+
AVG(CASE WHEN error IS NULL AND trusted = 1 THEN composite_score END) AS trusted_avg_score,
|
|
2951
3126
|
COUNT(*) AS total_evals,
|
|
2952
3127
|
SUM(CASE WHEN error IS NULL THEN 1 ELSE 0 END) AS successful_evals,
|
|
2953
3128
|
SUM(CASE WHEN error IS NOT NULL THEN 1 ELSE 0 END) AS errors,
|
|
3129
|
+
SUM(CASE WHEN error IS NULL AND trusted = 1 THEN 1 ELSE 0 END) AS trusted_evals,
|
|
3130
|
+
COUNT(DISTINCT COALESCE(sample_id, benchmark_id)) AS sample_count,
|
|
2954
3131
|
AVG(CASE WHEN error IS NULL THEN latency_ms END) AS avg_latency_ms,
|
|
2955
|
-
SUM(COALESCE(cost_dollars, cost_estimate, 0)) AS total_cost
|
|
3132
|
+
SUM(COALESCE(cost_dollars, cost_estimate, 0)) AS total_cost,
|
|
3133
|
+
GROUP_CONCAT(DISTINCT dataset_version) AS dataset_versions,
|
|
3134
|
+
GROUP_CONCAT(DISTINCT scorer_version) AS scorer_versions,
|
|
3135
|
+
MAX(created_at) AS last_eval_at
|
|
2956
3136
|
FROM eval_benchmark_runs
|
|
2957
3137
|
WHERE ${where}
|
|
2958
3138
|
GROUP BY provider, model
|
|
2959
|
-
ORDER BY COALESCE(avg_score, 0) DESC, successful_evals DESC
|
|
3139
|
+
ORDER BY COALESCE(trusted_avg_score, avg_score, 0) DESC, trusted_evals DESC, successful_evals DESC
|
|
2960
3140
|
`).all(...params);
|
|
2961
3141
|
}
|
|
2962
3142
|
|
|
@@ -2969,7 +3149,8 @@ function getBenchmarkLeaderboard({ suite, days } = {}) {
|
|
|
2969
3149
|
* efficiency = costEfficiency (single dimension)
|
|
2970
3150
|
*/
|
|
2971
3151
|
function getBenchmarkLeaderboardWithDimensions({ suite, days } = {}) {
|
|
2972
|
-
|
|
3152
|
+
const MIN_TRUSTED_EVALS = 10;
|
|
3153
|
+
let where = "model != 'unknown' AND provider != 'unknown'";
|
|
2973
3154
|
const params = [];
|
|
2974
3155
|
if (suite) { where += ' AND suite = ?'; params.push(suite); }
|
|
2975
3156
|
if (days) { where += " AND created_at >= datetime('now', ?)"; params.push(`-${days} days`); }
|
|
@@ -2977,7 +3158,9 @@ function getBenchmarkLeaderboardWithDimensions({ suite, days } = {}) {
|
|
|
2977
3158
|
const rows = getDb().prepare(`
|
|
2978
3159
|
SELECT provider, model, composite_score, dimensions_json,
|
|
2979
3160
|
latency_ms, cost_estimate, cost_dollars, error, created_at,
|
|
2980
|
-
input_tokens, output_tokens, gen_tok_per_sec
|
|
3161
|
+
input_tokens, output_tokens, gen_tok_per_sec,
|
|
3162
|
+
benchmark_id, sample_id, dataset_version, scorer_version, evaluator_version,
|
|
3163
|
+
scoring_method, repo_sha, model_snapshot, trusted, artifact_path
|
|
2981
3164
|
FROM eval_benchmark_runs
|
|
2982
3165
|
WHERE ${where}
|
|
2983
3166
|
`).all(...params);
|
|
@@ -2999,14 +3182,40 @@ function getBenchmarkLeaderboardWithDimensions({ suite, days } = {}) {
|
|
|
2999
3182
|
'costEfficiency',
|
|
3000
3183
|
];
|
|
3001
3184
|
|
|
3185
|
+
function finiteScores(rowsForStats) {
|
|
3186
|
+
return rowsForStats
|
|
3187
|
+
.map(r => Number(r.composite_score))
|
|
3188
|
+
.filter(n => Number.isFinite(n));
|
|
3189
|
+
}
|
|
3190
|
+
|
|
3191
|
+
function meanAndCi(scores) {
|
|
3192
|
+
if (!scores.length) return { mean: 0, low: null, high: null, stdev: null };
|
|
3193
|
+
const mean = scores.reduce((s, n) => s + n, 0) / scores.length;
|
|
3194
|
+
if (scores.length < 2) return { mean, low: mean, high: mean, stdev: 0 };
|
|
3195
|
+
const variance = scores.reduce((s, n) => s + Math.pow(n - mean, 2), 0) / (scores.length - 1);
|
|
3196
|
+
const stdev = Math.sqrt(variance);
|
|
3197
|
+
const margin = 1.96 * (stdev / Math.sqrt(scores.length));
|
|
3198
|
+
return {
|
|
3199
|
+
mean,
|
|
3200
|
+
low: Math.max(0, mean - margin),
|
|
3201
|
+
high: Math.min(1, mean + margin),
|
|
3202
|
+
stdev,
|
|
3203
|
+
};
|
|
3204
|
+
}
|
|
3205
|
+
|
|
3206
|
+
function uniq(values) {
|
|
3207
|
+
return [...new Set(values.filter(Boolean))];
|
|
3208
|
+
}
|
|
3209
|
+
|
|
3002
3210
|
const results = [];
|
|
3003
3211
|
for (const g of Object.values(groups)) {
|
|
3004
3212
|
const n = g.rows.length;
|
|
3005
3213
|
const successRows = g.rows.filter(r => !r.error);
|
|
3214
|
+
const trustedRows = successRows.filter(r => Number(r.trusted) === 1);
|
|
3006
3215
|
const qualityRows = successRows;
|
|
3007
|
-
const
|
|
3008
|
-
|
|
3009
|
-
|
|
3216
|
+
const scoreStats = meanAndCi(finiteScores(qualityRows));
|
|
3217
|
+
const trustedStats = meanAndCi(finiteScores(trustedRows));
|
|
3218
|
+
const avgComposite = scoreStats.mean;
|
|
3010
3219
|
const avgCompositeAll = g.rows.reduce((s, r) => s + (r.composite_score || 0), 0) / n;
|
|
3011
3220
|
const errors = g.rows.filter(r => r.error).length;
|
|
3012
3221
|
const avgLatency = qualityRows.length > 0
|
|
@@ -3067,15 +3276,35 @@ function getBenchmarkLeaderboardWithDimensions({ suite, days } = {}) {
|
|
|
3067
3276
|
model: g.model,
|
|
3068
3277
|
avg_score: avgComposite,
|
|
3069
3278
|
avg_score_all: avgCompositeAll,
|
|
3279
|
+
score_confidence_low: scoreStats.low,
|
|
3280
|
+
score_confidence_high: scoreStats.high,
|
|
3281
|
+
score_stdev: scoreStats.stdev,
|
|
3282
|
+
trusted_avg_score: trustedRows.length ? trustedStats.mean : null,
|
|
3283
|
+
trusted_score_confidence_low: trustedRows.length ? trustedStats.low : null,
|
|
3284
|
+
trusted_score_confidence_high: trustedRows.length ? trustedStats.high : null,
|
|
3070
3285
|
total_evals: n,
|
|
3071
3286
|
successful_evals: successRows.length,
|
|
3287
|
+
trusted_evals: trustedRows.length,
|
|
3288
|
+
min_trusted_evals: MIN_TRUSTED_EVALS,
|
|
3289
|
+
min_n_met: trustedRows.length >= MIN_TRUSTED_EVALS,
|
|
3290
|
+
trust_status: trustedRows.length >= MIN_TRUSTED_EVALS ? 'trusted' : trustedRows.length > 0 ? 'provisional' : 'legacy',
|
|
3291
|
+
sample_count: uniq(g.rows.map(r => r.sample_id || r.benchmark_id)).length,
|
|
3072
3292
|
dim_evals: dimCount,
|
|
3073
3293
|
errors,
|
|
3074
3294
|
error_rate: n > 0 ? errors / n : 0,
|
|
3295
|
+
success_rate: n > 0 ? successRows.length / n : 0,
|
|
3075
3296
|
avg_latency_ms: avgLatency,
|
|
3076
3297
|
total_cost: totalCost,
|
|
3077
3298
|
dimensions,
|
|
3078
3299
|
categories,
|
|
3300
|
+
dataset_versions: uniq(g.rows.map(r => r.dataset_version)),
|
|
3301
|
+
scorer_versions: uniq(g.rows.map(r => r.scorer_version)),
|
|
3302
|
+
evaluator_versions: uniq(g.rows.map(r => r.evaluator_version)),
|
|
3303
|
+
scoring_methods: uniq(g.rows.map(r => r.scoring_method)),
|
|
3304
|
+
repo_shas: uniq(g.rows.map(r => r.repo_sha)),
|
|
3305
|
+
model_snapshots: uniq(g.rows.map(r => r.model_snapshot)),
|
|
3306
|
+
artifact_count: g.rows.filter(r => !!r.artifact_path).length,
|
|
3307
|
+
last_eval_at: g.rows.reduce((max, r) => !max || r.created_at > max ? r.created_at : max, null),
|
|
3079
3308
|
avg_input_tokens: avgInputTokens,
|
|
3080
3309
|
avg_output_tokens: avgOutputTokens,
|
|
3081
3310
|
avg_tok_per_sec: avgTokPerSec,
|
|
@@ -3084,7 +3313,9 @@ function getBenchmarkLeaderboardWithDimensions({ suite, days } = {}) {
|
|
|
3084
3313
|
});
|
|
3085
3314
|
}
|
|
3086
3315
|
|
|
3087
|
-
results.sort((a, b) =>
|
|
3316
|
+
results.sort((a, b) => (
|
|
3317
|
+
(b.trusted_avg_score ?? b.avg_score ?? 0) - (a.trusted_avg_score ?? a.avg_score ?? 0)
|
|
3318
|
+
));
|
|
3088
3319
|
return results;
|
|
3089
3320
|
}
|
|
3090
3321
|
|
|
@@ -3142,6 +3373,11 @@ function getBenchmarkRuns({ limit: lim = 20 } = {}) {
|
|
|
3142
3373
|
SELECT run_id, suite, COUNT(*) AS total_prompts,
|
|
3143
3374
|
COUNT(DISTINCT provider || '/' || model) AS providers,
|
|
3144
3375
|
AVG(composite_score) AS avg_score,
|
|
3376
|
+
SUM(CASE WHEN error IS NULL THEN 1 ELSE 0 END) AS successful_prompts,
|
|
3377
|
+
SUM(CASE WHEN error IS NOT NULL THEN 1 ELSE 0 END) AS errors,
|
|
3378
|
+
SUM(CASE WHEN error IS NULL AND trusted = 1 THEN 1 ELSE 0 END) AS trusted_prompts,
|
|
3379
|
+
GROUP_CONCAT(DISTINCT dataset_version) AS dataset_versions,
|
|
3380
|
+
GROUP_CONCAT(DISTINCT scorer_version) AS scorer_versions,
|
|
3145
3381
|
MIN(created_at) AS started_at,
|
|
3146
3382
|
MAX(created_at) AS ended_at
|
|
3147
3383
|
FROM eval_benchmark_runs
|
|
@@ -3635,6 +3871,7 @@ module.exports = {
|
|
|
3635
3871
|
setProviderAuthMethod,
|
|
3636
3872
|
getProviderAuthMethod,
|
|
3637
3873
|
VALID_AUTH_METHODS,
|
|
3874
|
+
SCHEMA_VERSION,
|
|
3638
3875
|
deleteModelProvider,
|
|
3639
3876
|
deleteModelRegistryEntry,
|
|
3640
3877
|
deleteModelRegistryByProvider,
|
|
@@ -3702,4 +3939,5 @@ module.exports = {
|
|
|
3702
3939
|
insertMemoryIndex, searchMemoryIndex, getMemoryIndex,
|
|
3703
3940
|
// Memory Lifecycle Hooks
|
|
3704
3941
|
on, off, once,
|
|
3942
|
+
logWrite,
|
|
3705
3943
|
};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const crypto = require('node:crypto');
|
|
4
|
+
|
|
5
|
+
function normalizePrincipal(input = {}) {
|
|
6
|
+
return {
|
|
7
|
+
channel: input.channel || input.channelId || 'unknown',
|
|
8
|
+
userId: String(input.userId || input.senderId || input.from || ''),
|
|
9
|
+
threadId: String(input.threadId || input.conversationId || input.roomId || ''),
|
|
10
|
+
groupId: String(input.groupId || input.roomId || ''),
|
|
11
|
+
isGroup: Boolean(input.isGroup || input.groupId || input.roomId),
|
|
12
|
+
command: input.command || '',
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function makePrincipalKey(principal = {}) {
|
|
17
|
+
const p = normalizePrincipal(principal);
|
|
18
|
+
return [p.channel, p.isGroup ? 'group' : 'dm', p.groupId || p.threadId || p.userId].join(':');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function evaluateDirectMessageAccess(input = {}) {
|
|
22
|
+
const principal = normalizePrincipal(input);
|
|
23
|
+
const policy = input.policy || {};
|
|
24
|
+
if (policy.enabled === false) return decision('block', 'dm-policy-disabled', principal);
|
|
25
|
+
const allowlist = normalizeList(policy.allowUsers || policy.allowlist || []);
|
|
26
|
+
const pairedUsers = new Set(normalizeList(input.pairedUsers || []));
|
|
27
|
+
if (allowlist.length === 0 && pairedUsers.size === 0) return decision('allow', 'dm-open', principal);
|
|
28
|
+
if (allowlist.includes(principal.userId)) return decision('allow', 'dm-user-allowlisted', principal);
|
|
29
|
+
if (pairedUsers.has(principal.userId)) return decision('allow', 'dm-user-paired', principal);
|
|
30
|
+
if (policy.pairingRequired) return decision('pairing_required', 'dm-pairing-required', principal);
|
|
31
|
+
return decision('block', 'dm-user-not-allowlisted', principal);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function evaluateGroupAccess(input = {}) {
|
|
35
|
+
const principal = normalizePrincipal({ ...input, isGroup: true });
|
|
36
|
+
const policy = input.policy || {};
|
|
37
|
+
if (policy.enabled === false) return decision('block', 'group-policy-disabled', principal);
|
|
38
|
+
const allowGroups = normalizeList(policy.allowGroups || policy.groupAllowlist || []);
|
|
39
|
+
const allowUsers = normalizeList(policy.allowUsers || policy.senderAllowlist || []);
|
|
40
|
+
if (allowGroups.length === 0 && allowUsers.length === 0) {
|
|
41
|
+
return decision(policy.defaultOpen === false ? 'block' : 'allow', policy.defaultOpen === false ? 'group-default-closed' : 'group-open', principal);
|
|
42
|
+
}
|
|
43
|
+
if (allowGroups.includes(principal.groupId || principal.threadId)) return decision('allow', 'group-allowlisted', principal);
|
|
44
|
+
if (allowUsers.includes(principal.userId)) return decision('allow', 'group-sender-allowlisted', principal);
|
|
45
|
+
return decision('block', 'group-not-allowlisted', principal);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function createPairingChallenge({ channel, userId, ttlMs = 5 * 60 * 1000, now = Date.now } = {}) {
|
|
49
|
+
const nonce = crypto.randomBytes(8).toString('hex');
|
|
50
|
+
return {
|
|
51
|
+
channel: channel || 'unknown',
|
|
52
|
+
userId: String(userId || ''),
|
|
53
|
+
nonce,
|
|
54
|
+
code: nonce.slice(0, 6).toUpperCase(),
|
|
55
|
+
expiresAt: now() + ttlMs,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function verifyPairingChallenge(challenge, code, { now = Date.now } = {}) {
|
|
60
|
+
if (!challenge) return { ok: false, reason: 'missing-challenge' };
|
|
61
|
+
if (now() > challenge.expiresAt) return { ok: false, reason: 'challenge-expired' };
|
|
62
|
+
if (String(code || '').trim().toUpperCase() !== challenge.code) return { ok: false, reason: 'code-mismatch' };
|
|
63
|
+
return { ok: true, userId: challenge.userId, channel: challenge.channel };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function decision(status, reason, principal) {
|
|
67
|
+
return {
|
|
68
|
+
status,
|
|
69
|
+
allow: status === 'allow',
|
|
70
|
+
reason,
|
|
71
|
+
principal,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function normalizeList(values) {
|
|
76
|
+
return (Array.isArray(values) ? values : [values])
|
|
77
|
+
.map(value => String(value || '').trim())
|
|
78
|
+
.filter(Boolean);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
module.exports = {
|
|
82
|
+
createPairingChallenge,
|
|
83
|
+
evaluateDirectMessageAccess,
|
|
84
|
+
evaluateGroupAccess,
|
|
85
|
+
makePrincipalKey,
|
|
86
|
+
normalizePrincipal,
|
|
87
|
+
verifyPairingChallenge,
|
|
88
|
+
};
|
|
@@ -4,6 +4,7 @@ const path = require('path');
|
|
|
4
4
|
const { Registry } = require('../plugins/registry-base');
|
|
5
5
|
const { loadSiblingManifestsFromDir } = require('../plugins/manifest-loader');
|
|
6
6
|
const { addDiagnostics, addDiagnostic } = require('../plugins/diagnostics');
|
|
7
|
+
const { evaluateDirectMessageAccess, evaluateGroupAccess } = require('./policy');
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Channel registry — replaces hardcoded require list in agent.js:10-12.
|
|
@@ -79,7 +80,18 @@ function instantiate(id, ctorOpts) {
|
|
|
79
80
|
const entry = _registry.get(id);
|
|
80
81
|
if (!entry || typeof entry.ChannelClass !== 'function') return null;
|
|
81
82
|
try {
|
|
82
|
-
|
|
83
|
+
const opts = { ...(ctorOpts || {}) };
|
|
84
|
+
if (!opts.channelPolicy) {
|
|
85
|
+
Object.defineProperty(opts, 'channelPolicy', {
|
|
86
|
+
enumerable: false,
|
|
87
|
+
configurable: true,
|
|
88
|
+
value: {
|
|
89
|
+
evaluateDirectMessageAccess,
|
|
90
|
+
evaluateGroupAccess,
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
return new entry.ChannelClass(opts);
|
|
83
95
|
} catch (err) {
|
|
84
96
|
addDiagnostic({
|
|
85
97
|
level: 'error',
|
|
@@ -229,5 +241,7 @@ module.exports = {
|
|
|
229
241
|
ensureBootstrapped,
|
|
230
242
|
isEnabledByConfig,
|
|
231
243
|
configRequirementsSatisfied,
|
|
244
|
+
evaluateDirectMessageAccess,
|
|
245
|
+
evaluateGroupAccess,
|
|
232
246
|
_resetForTest,
|
|
233
247
|
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
class ReplyDispatcher {
|
|
4
|
+
constructor({ send, now = () => new Date().toISOString() } = {}) {
|
|
5
|
+
if (typeof send !== 'function') throw new Error('ReplyDispatcher requires send function');
|
|
6
|
+
this.send = send;
|
|
7
|
+
this.now = now;
|
|
8
|
+
this.buffer = [];
|
|
9
|
+
this.seenDeliveryIds = new Set();
|
|
10
|
+
this.statuses = [];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async dispatch(reply = {}) {
|
|
14
|
+
const deliveryId = reply.deliveryId || `${reply.channel || 'channel'}:${reply.target || ''}:${hash(reply.message || '')}`;
|
|
15
|
+
if (this.seenDeliveryIds.has(deliveryId)) {
|
|
16
|
+
return this._status(deliveryId, 'skipped', 'duplicate');
|
|
17
|
+
}
|
|
18
|
+
this.seenDeliveryIds.add(deliveryId);
|
|
19
|
+
if (reply.mode === 'buffered') {
|
|
20
|
+
this.buffer.push({ ...reply, deliveryId });
|
|
21
|
+
return this._status(deliveryId, 'buffered', '');
|
|
22
|
+
}
|
|
23
|
+
return this._sendNow({ ...reply, deliveryId });
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async flush() {
|
|
27
|
+
const pending = this.buffer.splice(0);
|
|
28
|
+
const results = [];
|
|
29
|
+
for (const reply of pending) {
|
|
30
|
+
results.push(await this._sendNow(reply));
|
|
31
|
+
}
|
|
32
|
+
return results;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
listStatuses() {
|
|
36
|
+
return [...this.statuses];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async _sendNow(reply) {
|
|
40
|
+
try {
|
|
41
|
+
const result = await this.send(reply);
|
|
42
|
+
return this._status(reply.deliveryId, 'sent', '', result);
|
|
43
|
+
} catch (err) {
|
|
44
|
+
return this._status(reply.deliveryId, 'failed', err.message);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
_status(deliveryId, status, reason = '', result = null) {
|
|
49
|
+
const record = {
|
|
50
|
+
deliveryId,
|
|
51
|
+
status,
|
|
52
|
+
reason,
|
|
53
|
+
result,
|
|
54
|
+
timestamp: this.now(),
|
|
55
|
+
};
|
|
56
|
+
this.statuses.push(record);
|
|
57
|
+
return record;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function hash(text) {
|
|
62
|
+
let h = 0;
|
|
63
|
+
const input = String(text || '');
|
|
64
|
+
for (let i = 0; i < input.length; i += 1) h = ((h << 5) - h + input.charCodeAt(i)) | 0;
|
|
65
|
+
return Math.abs(h).toString(36);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
module.exports = {
|
|
69
|
+
ReplyDispatcher,
|
|
70
|
+
};
|