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.
Files changed (167) hide show
  1. package/README.md +3 -3
  2. package/package.json +2 -2
  3. package/template/bin/dev.sh +7 -1
  4. package/template/bin/setup.js +53 -9
  5. package/template/bin/sync-images.js +53 -0
  6. package/template/builder-journal.md +17 -0
  7. package/template/claude-task-manager/api-prompts.js +98 -13
  8. package/template/claude-task-manager/api-reviews.js +82 -5
  9. package/template/claude-task-manager/db.js +32 -5
  10. package/template/claude-task-manager/docs/session-capture-foundation-design.md +1273 -0
  11. package/template/claude-task-manager/lib/claude-desktop-sessions.js +696 -0
  12. package/template/claude-task-manager/lib/coding-agent-models.js +49 -1
  13. package/template/claude-task-manager/lib/session-capture.js +421 -0
  14. package/template/claude-task-manager/lib/session-history.js +135 -15
  15. package/template/claude-task-manager/lib/session-jobs.js +10 -5
  16. package/template/claude-task-manager/lib/session-stream.js +87 -19
  17. package/template/claude-task-manager/lib/setup-provider-config.js +115 -0
  18. package/template/claude-task-manager/lib/walle-ctm-history.js +72 -0
  19. package/template/claude-task-manager/lib/walle-session-context.js +61 -0
  20. package/template/claude-task-manager/lib/walle-transcript.js +176 -0
  21. package/template/claude-task-manager/public/css/setup.css +35 -8
  22. package/template/claude-task-manager/public/css/walle-session.css +56 -0
  23. package/template/claude-task-manager/public/css/walle.css +120 -0
  24. package/template/claude-task-manager/public/index.html +814 -181
  25. package/template/claude-task-manager/public/js/message-renderer.js +148 -19
  26. package/template/claude-task-manager/public/js/reviews.js +120 -62
  27. package/template/claude-task-manager/public/js/setup.js +75 -31
  28. package/template/claude-task-manager/public/js/stream-view.js +115 -55
  29. package/template/claude-task-manager/public/js/walle-session.js +84 -2
  30. package/template/claude-task-manager/public/js/walle.js +308 -54
  31. package/template/claude-task-manager/server.js +1092 -146
  32. package/template/claude-task-manager/session-integrity.js +181 -54
  33. package/template/claude-task-manager/session-utils.js +123 -41
  34. package/template/claude-task-manager/workers/state-detectors/codex.js +5 -2
  35. package/template/package.json +1 -1
  36. package/template/wall-e/adapters/ctm.js +39 -18
  37. package/template/wall-e/agent-runners/contract.js +17 -0
  38. package/template/wall-e/agent-runners/index.js +22 -0
  39. package/template/wall-e/agent-runtime/harness.js +212 -0
  40. package/template/wall-e/agent-runtime/index.js +8 -0
  41. package/template/wall-e/agent-runtime/registry.js +67 -0
  42. package/template/wall-e/agent-runtime/session-store.js +179 -0
  43. package/template/wall-e/agent-runtime/spawn.js +208 -0
  44. package/template/wall-e/api-walle.js +174 -7
  45. package/template/wall-e/brain.js +266 -28
  46. package/template/wall-e/channels/policy.js +88 -0
  47. package/template/wall-e/channels/registry.js +15 -1
  48. package/template/wall-e/channels/reply-dispatcher.js +70 -0
  49. package/template/wall-e/channels/session-bindings.js +51 -0
  50. package/template/wall-e/chat/code-review-context.js +29 -0
  51. package/template/wall-e/chat.js +188 -42
  52. package/template/wall-e/coding/acp-adapter.js +188 -0
  53. package/template/wall-e/coding/agent-catalog.js +129 -0
  54. package/template/wall-e/coding/compaction-service.js +247 -0
  55. package/template/wall-e/coding/execution-trace.js +3 -0
  56. package/template/wall-e/coding/instruction-service.js +224 -0
  57. package/template/wall-e/coding/model-message.js +67 -0
  58. package/template/wall-e/coding/permission-rules-store.js +111 -0
  59. package/template/wall-e/coding/permission-service.js +266 -0
  60. package/template/wall-e/coding/prompt-bundle.js +67 -0
  61. package/template/wall-e/coding/prompt-runtime.js +243 -0
  62. package/template/wall-e/coding/provider-transform.js +188 -0
  63. package/template/wall-e/coding/runtime-mode.js +132 -0
  64. package/template/wall-e/coding/snapshot-service.js +155 -0
  65. package/template/wall-e/coding/stream-processor.js +268 -0
  66. package/template/wall-e/coding/task-tool.js +255 -0
  67. package/template/wall-e/coding/tool-registry.js +361 -0
  68. package/template/wall-e/coding/transcript-writer.js +143 -0
  69. package/template/wall-e/coding/workspace-replay.js +324 -0
  70. package/template/wall-e/coding-context.js +4 -22
  71. package/template/wall-e/coding-orchestrator.js +307 -18
  72. package/template/wall-e/coding-prompts.js +44 -3
  73. package/template/wall-e/context/context-builder.js +43 -1
  74. package/template/wall-e/context/topic-matcher.js +1 -1
  75. package/template/wall-e/eval/agent-runner.js +59 -13
  76. package/template/wall-e/eval/benchmarks/memory-retrieval.json +155 -57
  77. package/template/wall-e/eval/benchmarks.js +100 -16
  78. package/template/wall-e/eval/eval-orchestrator.js +218 -8
  79. package/template/wall-e/eval/harvester.js +62 -5
  80. package/template/wall-e/eval/head-to-head.js +23 -2
  81. package/template/wall-e/eval/humaneval-adapter.js +30 -5
  82. package/template/wall-e/eval/livecodebench-adapter.js +29 -5
  83. package/template/wall-e/eval/manifest.js +186 -0
  84. package/template/wall-e/eval/run-agent-benchmarks.js +66 -2
  85. package/template/wall-e/eval/session-retrieval-benchmark.js +150 -0
  86. package/template/wall-e/eval/session-transcripts.js +57 -4
  87. package/template/wall-e/eval/swebench-adapter.js +109 -3
  88. package/template/wall-e/evaluation/agent-router.js +53 -1
  89. package/template/wall-e/evaluation/coding-quorum.js +48 -1
  90. package/template/wall-e/evaluation/router.js +4 -2
  91. package/template/wall-e/evaluation/tier-selector.js +11 -1
  92. package/template/wall-e/extraction/contradiction.js +2 -2
  93. package/template/wall-e/extraction/indexer.js +2 -1
  94. package/template/wall-e/extraction/knowledge-extractor.js +2 -2
  95. package/template/wall-e/hooks/cli.js +92 -0
  96. package/template/wall-e/hooks/discovery.js +119 -0
  97. package/template/wall-e/hooks/index.js +7 -0
  98. package/template/wall-e/hooks/manifest.js +55 -0
  99. package/template/wall-e/hooks/runtime.js +84 -0
  100. package/template/wall-e/hooks/session-memory.js +225 -0
  101. package/template/wall-e/http/auth.js +6 -2
  102. package/template/wall-e/http/chat-api.js +54 -8
  103. package/template/wall-e/integrations/claude-plugin/hooks/hooks.json +27 -0
  104. package/template/wall-e/integrations/claude-plugin/hooks/walle-precompact-hook.sh +5 -0
  105. package/template/wall-e/integrations/claude-plugin/hooks/walle-stop-hook.sh +5 -0
  106. package/template/wall-e/integrations/codex-plugin/hooks/walle-hook.sh +7 -0
  107. package/template/wall-e/integrations/codex-plugin/hooks.json +37 -0
  108. package/template/wall-e/listening/calendar.js +3 -1
  109. package/template/wall-e/llm/client.js +64 -10
  110. package/template/wall-e/llm/google.js +39 -5
  111. package/template/wall-e/llm/ollama.js +1 -1
  112. package/template/wall-e/llm/ollama.plugin.json +1 -1
  113. package/template/wall-e/llm/provider-availability.js +10 -0
  114. package/template/wall-e/llm/provider-error.js +269 -0
  115. package/template/wall-e/llm/tool-adapter.js +48 -12
  116. package/template/wall-e/loops/boot.js +2 -1
  117. package/template/wall-e/loops/initiative.js +2 -2
  118. package/template/wall-e/loops/tasks.js +8 -47
  119. package/template/wall-e/loops/workspace-prompts.js +20 -0
  120. package/template/wall-e/mcp-server.js +442 -1
  121. package/template/wall-e/memory/session-ingest-service.js +159 -0
  122. package/template/wall-e/memory/source-indexer.js +289 -0
  123. package/template/wall-e/plugins/discovery.js +83 -0
  124. package/template/wall-e/plugins/manifest-loader.js +50 -10
  125. package/template/wall-e/plugins/manifest-schema.js +69 -0
  126. package/template/wall-e/plugins/model-catalog.js +55 -0
  127. package/template/wall-e/prompts/coding/base.txt +2 -0
  128. package/template/wall-e/prompts/coding/deepseek.txt +1 -0
  129. package/template/wall-e/prompts/coding/memory-protocol.md +9 -0
  130. package/template/wall-e/prompts/coding/plan.txt +1 -0
  131. package/template/wall-e/runtime/execution-trace.js +220 -0
  132. package/template/wall-e/security/audit.js +266 -0
  133. package/template/wall-e/security/ssrf.js +236 -0
  134. package/template/wall-e/session-files.js +303 -0
  135. package/template/wall-e/skills/_bundled/slack-backfill/SKILL.md +3 -0
  136. package/template/wall-e/skills/_bundled/slack-sync/SKILL.md +3 -0
  137. package/template/wall-e/skills/internal-skill-registry.js +2 -2
  138. package/template/wall-e/skills/script-skill-runner.js +143 -0
  139. package/template/wall-e/skills/skill-executor.js +5 -6
  140. package/template/wall-e/skills/skill-fallback.js +3 -1
  141. package/template/wall-e/skills/skill-harness-registry.js +7 -8
  142. package/template/wall-e/skills/skill-planner.js +52 -4
  143. package/template/wall-e/skills/slack-ingest.js +11 -3
  144. package/template/wall-e/sources/base.js +90 -0
  145. package/template/wall-e/sources/builtin.js +33 -0
  146. package/template/wall-e/sources/claude-code-jsonl.js +78 -0
  147. package/template/wall-e/sources/codex-jsonl.js +125 -0
  148. package/template/wall-e/sources/coding-session-utils.js +117 -0
  149. package/template/wall-e/sources/contract-suite.js +59 -0
  150. package/template/wall-e/sources/gemini-jsonl.js +85 -0
  151. package/template/wall-e/sources/index.js +9 -0
  152. package/template/wall-e/sources/jsonl-utils.js +181 -0
  153. package/template/wall-e/sources/record-types.js +252 -0
  154. package/template/wall-e/sources/registry.js +92 -0
  155. package/template/wall-e/sources/transforms.js +100 -0
  156. package/template/wall-e/sources/walle-jsonl.js +108 -0
  157. package/template/wall-e/tools/coding-middleware.js +31 -1
  158. package/template/wall-e/tools/file-tracker.js +25 -1
  159. package/template/wall-e/tools/local-tools.js +75 -47
  160. package/template/wall-e/tools/session-sharing.js +68 -1
  161. package/template/wall-e/tools/shell-analyzer.js +1 -1
  162. package/template/wall-e/tools/shell-policy.js +47 -0
  163. package/template/wall-e/tools/snapshot.js +42 -0
  164. package/template/wall-e/training/harvester.js +62 -5
  165. package/template/wall-e/utils/repair.js +253 -1
  166. package/template/website/index.html +3 -3
  167. package/template/wall-e/skills/_bundled/slack-mentions/.watched-threads.json +0 -18
@@ -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 = 9; // Bump on every migration addition
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
- setKv('first_installed_at', new Date().toISOString());
398
- telemetry.track('install', { version: pkgVersion, fresh: true, schema: SCHEMA_VERSION });
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 !!error || !provider || !model || provider === 'unknown' || model === 'unknown';
3063
+ return !provider || !model || provider === 'unknown' || model === 'unknown';
2897
3064
  }
2898
3065
 
2899
3066
  function insertBenchmarkResult(entry) {
2900
- if (isInvalidBenchmarkResult(entry)) return null;
3067
+ const normalized = _normalizeBenchmarkResult(entry);
3068
+ if (isInvalidBenchmarkResult(normalized)) return null;
2901
3069
 
2902
- const id = entry.id || require('crypto').randomUUID();
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
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
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, entry.runId, entry.suite, entry.promptId || entry.benchmark_id,
2910
- entry.provider, entry.model, entry.prompt,
2911
- entry.response || null, entry.traitScore ?? null, entry.judgeScore ?? null,
2912
- entry.compositeScore ?? null, entry.latencyMs || null, entry.costEstimate || null,
2913
- entry.error || null,
2914
- entry.costDollars ?? null, entry.testsBefore ?? null, entry.testsAfter ?? null,
2915
- entry.totalTests ?? null, entry.dimensionsJson || null, entry.modelMetadataJson || null,
2916
- entry.inputTokens ?? null, entry.outputTokens ?? null, entry.genTokPerSec ?? null
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
- (error IS NOT NULL AND trim(error) != '')
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
- let where = "model != 'unknown'";
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 avgComposite = qualityRows.length > 0
3008
- ? qualityRows.reduce((s, r) => s + (r.composite_score || 0), 0) / qualityRows.length
3009
- : 0;
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) => b.avg_score - a.avg_score);
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
- return new entry.ChannelClass(ctorOpts || {});
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
+ };