@soleri/core 9.3.1 → 9.5.0
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/dist/brain/intelligence.d.ts +5 -0
- package/dist/brain/intelligence.d.ts.map +1 -1
- package/dist/brain/intelligence.js +115 -26
- package/dist/brain/intelligence.js.map +1 -1
- package/dist/brain/learning-radar.d.ts +3 -3
- package/dist/brain/learning-radar.d.ts.map +1 -1
- package/dist/brain/learning-radar.js +8 -4
- package/dist/brain/learning-radar.js.map +1 -1
- package/dist/control/intent-router.d.ts +2 -2
- package/dist/control/intent-router.d.ts.map +1 -1
- package/dist/control/intent-router.js +35 -1
- package/dist/control/intent-router.js.map +1 -1
- package/dist/control/types.d.ts +10 -2
- package/dist/control/types.d.ts.map +1 -1
- package/dist/curator/curator.d.ts +4 -0
- package/dist/curator/curator.d.ts.map +1 -1
- package/dist/curator/curator.js +23 -1
- package/dist/curator/curator.js.map +1 -1
- package/dist/curator/schema.d.ts +1 -1
- package/dist/curator/schema.d.ts.map +1 -1
- package/dist/curator/schema.js +8 -0
- package/dist/curator/schema.js.map +1 -1
- package/dist/domain-packs/types.d.ts +6 -0
- package/dist/domain-packs/types.d.ts.map +1 -1
- package/dist/domain-packs/types.js +1 -0
- package/dist/domain-packs/types.js.map +1 -1
- package/dist/engine/module-manifest.js +3 -3
- package/dist/engine/module-manifest.js.map +1 -1
- package/dist/engine/register-engine.d.ts +9 -0
- package/dist/engine/register-engine.d.ts.map +1 -1
- package/dist/engine/register-engine.js +59 -1
- package/dist/engine/register-engine.js.map +1 -1
- package/dist/facades/types.d.ts +5 -1
- package/dist/facades/types.d.ts.map +1 -1
- package/dist/facades/types.js.map +1 -1
- package/dist/hooks/candidate-scorer.d.ts +28 -0
- package/dist/hooks/candidate-scorer.d.ts.map +1 -0
- package/dist/hooks/candidate-scorer.js +20 -0
- package/dist/hooks/candidate-scorer.js.map +1 -0
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +2 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/operator/operator-context-store.d.ts +54 -0
- package/dist/operator/operator-context-store.d.ts.map +1 -0
- package/dist/operator/operator-context-store.js +434 -0
- package/dist/operator/operator-context-store.js.map +1 -0
- package/dist/operator/operator-context-types.d.ts +101 -0
- package/dist/operator/operator-context-types.d.ts.map +1 -0
- package/dist/operator/operator-context-types.js +27 -0
- package/dist/operator/operator-context-types.js.map +1 -0
- package/dist/packs/index.d.ts +2 -2
- package/dist/packs/index.d.ts.map +1 -1
- package/dist/packs/index.js +1 -1
- package/dist/packs/index.js.map +1 -1
- package/dist/packs/lockfile.d.ts +3 -0
- package/dist/packs/lockfile.d.ts.map +1 -1
- package/dist/packs/lockfile.js.map +1 -1
- package/dist/packs/types.d.ts +8 -2
- package/dist/packs/types.d.ts.map +1 -1
- package/dist/packs/types.js +6 -0
- package/dist/packs/types.js.map +1 -1
- package/dist/planning/plan-lifecycle.d.ts +12 -1
- package/dist/planning/plan-lifecycle.d.ts.map +1 -1
- package/dist/planning/plan-lifecycle.js +54 -16
- package/dist/planning/plan-lifecycle.js.map +1 -1
- package/dist/planning/planner-types.d.ts +6 -0
- package/dist/planning/planner-types.d.ts.map +1 -1
- package/dist/planning/planner.d.ts +21 -1
- package/dist/planning/planner.d.ts.map +1 -1
- package/dist/planning/planner.js +62 -3
- package/dist/planning/planner.js.map +1 -1
- package/dist/planning/task-complexity-assessor.d.ts.map +1 -1
- package/dist/planning/task-complexity-assessor.js.map +1 -1
- package/dist/plugins/types.d.ts +18 -18
- package/dist/runtime/admin-ops.d.ts +1 -1
- package/dist/runtime/admin-ops.d.ts.map +1 -1
- package/dist/runtime/admin-ops.js +100 -3
- package/dist/runtime/admin-ops.js.map +1 -1
- package/dist/runtime/admin-setup-ops.d.ts.map +1 -1
- package/dist/runtime/admin-setup-ops.js +19 -9
- package/dist/runtime/admin-setup-ops.js.map +1 -1
- package/dist/runtime/capture-ops.d.ts.map +1 -1
- package/dist/runtime/capture-ops.js +35 -7
- package/dist/runtime/capture-ops.js.map +1 -1
- package/dist/runtime/facades/brain-facade.d.ts.map +1 -1
- package/dist/runtime/facades/brain-facade.js +4 -2
- package/dist/runtime/facades/brain-facade.js.map +1 -1
- package/dist/runtime/facades/control-facade.d.ts.map +1 -1
- package/dist/runtime/facades/control-facade.js +8 -2
- package/dist/runtime/facades/control-facade.js.map +1 -1
- package/dist/runtime/facades/curator-facade.d.ts.map +1 -1
- package/dist/runtime/facades/curator-facade.js +13 -0
- package/dist/runtime/facades/curator-facade.js.map +1 -1
- package/dist/runtime/facades/memory-facade.d.ts.map +1 -1
- package/dist/runtime/facades/memory-facade.js +10 -12
- package/dist/runtime/facades/memory-facade.js.map +1 -1
- package/dist/runtime/facades/orchestrate-facade.d.ts.map +1 -1
- package/dist/runtime/facades/orchestrate-facade.js +36 -1
- package/dist/runtime/facades/orchestrate-facade.js.map +1 -1
- package/dist/runtime/facades/plan-facade.d.ts.map +1 -1
- package/dist/runtime/facades/plan-facade.js +20 -4
- package/dist/runtime/facades/plan-facade.js.map +1 -1
- package/dist/runtime/orchestrate-ops.d.ts.map +1 -1
- package/dist/runtime/orchestrate-ops.js +71 -4
- package/dist/runtime/orchestrate-ops.js.map +1 -1
- package/dist/runtime/plan-feedback-helper.d.ts +21 -0
- package/dist/runtime/plan-feedback-helper.d.ts.map +1 -0
- package/dist/runtime/plan-feedback-helper.js +52 -0
- package/dist/runtime/plan-feedback-helper.js.map +1 -0
- package/dist/runtime/planning-extra-ops.d.ts.map +1 -1
- package/dist/runtime/planning-extra-ops.js +73 -34
- package/dist/runtime/planning-extra-ops.js.map +1 -1
- package/dist/runtime/session-briefing.d.ts.map +1 -1
- package/dist/runtime/session-briefing.js +9 -1
- package/dist/runtime/session-briefing.js.map +1 -1
- package/dist/runtime/types.d.ts +3 -0
- package/dist/runtime/types.d.ts.map +1 -1
- package/dist/skills/sync-skills.d.ts.map +1 -1
- package/dist/skills/sync-skills.js +13 -7
- package/dist/skills/sync-skills.js.map +1 -1
- package/package.json +1 -1
- package/src/brain/brain-intelligence.test.ts +30 -0
- package/src/brain/brain.ts +120 -46
- package/src/brain/extraction-quality.test.ts +323 -0
- package/src/brain/intelligence.ts +175 -64
- package/src/brain/learning-radar.ts +8 -5
- package/src/brain/second-brain-features.test.ts +1 -1
- package/src/chat/agent-loop.ts +1 -1
- package/src/chat/notifications.ts +4 -0
- package/src/control/intent-router.test.ts +73 -3
- package/src/control/intent-router.ts +48 -9
- package/src/control/types.ts +13 -2
- package/src/curator/curator.test.ts +92 -0
- package/src/curator/curator.ts +162 -18
- package/src/curator/schema.ts +8 -0
- package/src/domain-packs/types.ts +8 -0
- package/src/engine/module-manifest.test.ts +8 -2
- package/src/engine/module-manifest.ts +3 -3
- package/src/engine/register-engine.test.ts +73 -1
- package/src/engine/register-engine.ts +61 -1
- package/src/facades/types.ts +5 -0
- package/src/hooks/candidate-scorer.test.ts +76 -0
- package/src/hooks/candidate-scorer.ts +39 -0
- package/src/hooks/index.ts +6 -0
- package/src/index.ts +24 -0
- package/src/llm/llm-client.ts +1 -0
- package/src/operator/operator-context-store.test.ts +698 -0
- package/src/operator/operator-context-store.ts +569 -0
- package/src/operator/operator-context-types.ts +139 -0
- package/src/packs/index.ts +3 -1
- package/src/packs/lockfile.ts +3 -0
- package/src/packs/types.ts +9 -0
- package/src/persistence/sqlite-provider.ts +1 -0
- package/src/planning/github-projection.ts +48 -44
- package/src/planning/plan-lifecycle.ts +93 -22
- package/src/planning/planner-types.ts +6 -0
- package/src/planning/planner.ts +74 -4
- package/src/planning/task-complexity-assessor.test.ts +6 -2
- package/src/planning/task-complexity-assessor.ts +1 -4
- package/src/queue/pipeline-runner.ts +4 -0
- package/src/runtime/admin-ops.test.ts +139 -6
- package/src/runtime/admin-ops.ts +104 -3
- package/src/runtime/admin-setup-ops.ts +30 -10
- package/src/runtime/capture-ops.test.ts +84 -0
- package/src/runtime/capture-ops.ts +35 -7
- package/src/runtime/curator-extra-ops.test.ts +7 -0
- package/src/runtime/curator-extra-ops.ts +10 -1
- package/src/runtime/facades/admin-facade.test.ts +1 -1
- package/src/runtime/facades/brain-facade.ts +6 -3
- package/src/runtime/facades/control-facade.ts +10 -2
- package/src/runtime/facades/curator-facade.test.ts +7 -0
- package/src/runtime/facades/curator-facade.ts +18 -0
- package/src/runtime/facades/memory-facade.test.ts +14 -12
- package/src/runtime/facades/memory-facade.ts +197 -12
- package/src/runtime/facades/orchestrate-facade.ts +33 -1
- package/src/runtime/facades/plan-facade.test.ts +213 -0
- package/src/runtime/facades/plan-facade.ts +23 -4
- package/src/runtime/orchestrate-ops.test.ts +202 -2
- package/src/runtime/orchestrate-ops.ts +88 -7
- package/src/runtime/plan-feedback-helper.test.ts +173 -0
- package/src/runtime/plan-feedback-helper.ts +63 -0
- package/src/runtime/planning-extra-ops.test.ts +43 -1
- package/src/runtime/planning-extra-ops.ts +96 -33
- package/src/runtime/runtime.test.ts +50 -2
- package/src/runtime/runtime.ts +117 -89
- package/src/runtime/session-briefing.test.ts +1 -0
- package/src/runtime/session-briefing.ts +10 -1
- package/src/runtime/shutdown-registry.test.ts +151 -0
- package/src/runtime/shutdown-registry.ts +85 -0
- package/src/runtime/types.ts +7 -1
- package/src/skills/sync-skills.ts +14 -7
- package/src/transport/http-server.ts +50 -3
- package/src/transport/ws-server.ts +8 -0
- package/src/vault/linking.test.ts +12 -0
- package/src/vault/linking.ts +90 -44
- package/src/vault/vault-maintenance.ts +11 -18
- package/src/vault/vault-memories.ts +21 -13
- package/src/vault/vault-schema.ts +21 -0
- package/src/vault/vault.ts +8 -3
- package/vitest.config.ts +1 -0
|
@@ -38,7 +38,6 @@ const SPREAD_MAX = 5;
|
|
|
38
38
|
const RECENCY_DECAY_DAYS = 30;
|
|
39
39
|
const EXTRACTION_TOOL_THRESHOLD = 3;
|
|
40
40
|
const EXTRACTION_FILE_THRESHOLD = 3;
|
|
41
|
-
const EXTRACTION_LONG_SESSION_MINUTES = 30;
|
|
42
41
|
const EXTRACTION_HIGH_FEEDBACK_RATIO = 0.8;
|
|
43
42
|
const AUTO_PROMOTE_THRESHOLD = 0.8;
|
|
44
43
|
const AUTO_PROMOTE_PENDING_MIN = 0.4;
|
|
@@ -500,7 +499,7 @@ export class BrainIntelligence {
|
|
|
500
499
|
// ─── Strength Scoring ─────────────────────────────────────────────
|
|
501
500
|
|
|
502
501
|
computeStrengths(): PatternStrength[] {
|
|
503
|
-
// Gather feedback data grouped by entry_id
|
|
502
|
+
// Gather feedback data grouped by entry_id, JOIN with entries to avoid N+1 vault.get() calls
|
|
504
503
|
const feedbackRows = this.provider.all<{
|
|
505
504
|
entry_id: string;
|
|
506
505
|
total: number;
|
|
@@ -509,16 +508,21 @@ export class BrainIntelligence {
|
|
|
509
508
|
modified: number;
|
|
510
509
|
failed: number;
|
|
511
510
|
last_used: string;
|
|
511
|
+
entry_title: string | null;
|
|
512
|
+
entry_domain: string | null;
|
|
512
513
|
}>(
|
|
513
|
-
`SELECT entry_id,
|
|
514
|
+
`SELECT bf.entry_id,
|
|
514
515
|
COUNT(*) as total,
|
|
515
|
-
SUM(CASE WHEN action = 'accepted' THEN 1 ELSE 0 END) as accepted,
|
|
516
|
-
SUM(CASE WHEN action = 'dismissed' THEN 1 ELSE 0 END) as dismissed,
|
|
517
|
-
SUM(CASE WHEN action = 'modified' THEN 1 ELSE 0 END) as modified,
|
|
518
|
-
SUM(CASE WHEN action = 'failed' THEN 1 ELSE 0 END) as failed,
|
|
519
|
-
MAX(created_at) as last_used
|
|
520
|
-
|
|
521
|
-
|
|
516
|
+
SUM(CASE WHEN bf.action = 'accepted' THEN 1 ELSE 0 END) as accepted,
|
|
517
|
+
SUM(CASE WHEN bf.action = 'dismissed' THEN 1 ELSE 0 END) as dismissed,
|
|
518
|
+
SUM(CASE WHEN bf.action = 'modified' THEN 1 ELSE 0 END) as modified,
|
|
519
|
+
SUM(CASE WHEN bf.action = 'failed' THEN 1 ELSE 0 END) as failed,
|
|
520
|
+
MAX(bf.created_at) as last_used,
|
|
521
|
+
e.title as entry_title,
|
|
522
|
+
e.domain as entry_domain
|
|
523
|
+
FROM brain_feedback bf
|
|
524
|
+
LEFT JOIN entries e ON e.id = bf.entry_id
|
|
525
|
+
GROUP BY bf.entry_id`,
|
|
522
526
|
);
|
|
523
527
|
|
|
524
528
|
// Count unique session domains as spread proxy
|
|
@@ -531,10 +535,9 @@ export class BrainIntelligence {
|
|
|
531
535
|
const strengths: PatternStrength[] = [];
|
|
532
536
|
|
|
533
537
|
for (const row of feedbackRows) {
|
|
534
|
-
//
|
|
535
|
-
const
|
|
536
|
-
const
|
|
537
|
-
const pattern = entry?.title ?? row.entry_id;
|
|
538
|
+
// Use JOINed entry data — no per-row vault.get() needed
|
|
539
|
+
const domain = row.entry_domain ?? 'unknown';
|
|
540
|
+
const pattern = row.entry_title ?? row.entry_id;
|
|
538
541
|
|
|
539
542
|
// Usage score: min(25, (count / USAGE_MAX) * 25)
|
|
540
543
|
const usageScore = Math.min(25, (row.total / USAGE_MAX) * 25);
|
|
@@ -572,29 +575,33 @@ export class BrainIntelligence {
|
|
|
572
575
|
};
|
|
573
576
|
|
|
574
577
|
strengths.push(ps);
|
|
575
|
-
|
|
576
|
-
// Persist
|
|
577
|
-
this.provider.run(
|
|
578
|
-
`INSERT OR REPLACE INTO brain_strengths
|
|
579
|
-
(pattern, domain, strength, usage_score, spread_score, success_score, recency_score,
|
|
580
|
-
usage_count, unique_contexts, success_rate, last_used, updated_at)
|
|
581
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))`,
|
|
582
|
-
[
|
|
583
|
-
ps.pattern,
|
|
584
|
-
ps.domain,
|
|
585
|
-
ps.strength,
|
|
586
|
-
ps.usageScore,
|
|
587
|
-
ps.spreadScore,
|
|
588
|
-
ps.successScore,
|
|
589
|
-
ps.recencyScore,
|
|
590
|
-
ps.usageCount,
|
|
591
|
-
ps.uniqueContexts,
|
|
592
|
-
ps.successRate,
|
|
593
|
-
ps.lastUsed,
|
|
594
|
-
],
|
|
595
|
-
);
|
|
596
578
|
}
|
|
597
579
|
|
|
580
|
+
// Persist all strengths in a single transaction to avoid N fsync calls
|
|
581
|
+
this.provider.transaction(() => {
|
|
582
|
+
for (const ps of strengths) {
|
|
583
|
+
this.provider.run(
|
|
584
|
+
`INSERT OR REPLACE INTO brain_strengths
|
|
585
|
+
(pattern, domain, strength, usage_score, spread_score, success_score, recency_score,
|
|
586
|
+
usage_count, unique_contexts, success_rate, last_used, updated_at)
|
|
587
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))`,
|
|
588
|
+
[
|
|
589
|
+
ps.pattern,
|
|
590
|
+
ps.domain,
|
|
591
|
+
ps.strength,
|
|
592
|
+
ps.usageScore,
|
|
593
|
+
ps.spreadScore,
|
|
594
|
+
ps.successScore,
|
|
595
|
+
ps.recencyScore,
|
|
596
|
+
ps.usageCount,
|
|
597
|
+
ps.uniqueContexts,
|
|
598
|
+
ps.successRate,
|
|
599
|
+
ps.lastUsed,
|
|
600
|
+
],
|
|
601
|
+
);
|
|
602
|
+
}
|
|
603
|
+
});
|
|
604
|
+
|
|
598
605
|
return strengths;
|
|
599
606
|
}
|
|
600
607
|
|
|
@@ -739,10 +746,18 @@ export class BrainIntelligence {
|
|
|
739
746
|
for (const [tool, count] of toolCounts) {
|
|
740
747
|
if (count >= EXTRACTION_TOOL_THRESHOLD) {
|
|
741
748
|
rulesApplied.push('repeated_tool_usage');
|
|
749
|
+
const ctx = session.context ?? '';
|
|
750
|
+
const objective = this.extractObjective(ctx);
|
|
751
|
+
const toolTitle = objective
|
|
752
|
+
? `Tool pattern: ${tool} (${count}x) during ${objective.slice(0, 60)}`
|
|
753
|
+
: `Frequent use of ${tool} (${count}x)`;
|
|
754
|
+
const toolDescription = objective
|
|
755
|
+
? `Tool ${tool} used ${count} times while working on: ${objective}. This tool-task pairing may indicate a reusable workflow.`
|
|
756
|
+
: `Tool ${tool} was used ${count} times in session. Consider automating or abstracting this workflow.`;
|
|
742
757
|
proposals.push(
|
|
743
758
|
this.createProposal(sessionId, 'repeated_tool_usage', 'pattern', {
|
|
744
|
-
title:
|
|
745
|
-
description:
|
|
759
|
+
title: toolTitle,
|
|
760
|
+
description: toolDescription,
|
|
746
761
|
confidence: Math.min(0.9, 0.5 + count * 0.1),
|
|
747
762
|
}),
|
|
748
763
|
);
|
|
@@ -766,57 +781,107 @@ export class BrainIntelligence {
|
|
|
766
781
|
if (significantDirs.length > 0) {
|
|
767
782
|
const [topDir, topFiles] = significantDirs.sort((a, b) => b[1].length - a[1].length)[0];
|
|
768
783
|
rulesApplied.push('multi_file_edit');
|
|
784
|
+
const ctx = session.context ?? '';
|
|
785
|
+
const objective = this.extractObjective(ctx);
|
|
786
|
+
const isRefactor = /refactor|rename|move|extract|consolidat/i.test(ctx);
|
|
787
|
+
const isFeature = /feat|add|implement|create|new/i.test(ctx);
|
|
788
|
+
const inferredPattern = isRefactor
|
|
789
|
+
? 'Refactoring'
|
|
790
|
+
: isFeature
|
|
791
|
+
? 'Feature'
|
|
792
|
+
: 'Cross-cutting change';
|
|
793
|
+
const mfeTitle = objective
|
|
794
|
+
? `${inferredPattern}: ${objective.slice(0, 70)}`
|
|
795
|
+
: `${inferredPattern} in ${topDir} (${topFiles.length} files)`;
|
|
796
|
+
const mfeDescription = objective
|
|
797
|
+
? `${inferredPattern} across ${topFiles.length} files in ${topDir}: ${objective}`
|
|
798
|
+
: `Session modified ${topFiles.length} files in ${topDir}: ${topFiles.slice(0, 5).join(', ')}${topFiles.length > 5 ? '...' : ''}.`;
|
|
769
799
|
proposals.push(
|
|
770
800
|
this.createProposal(sessionId, 'multi_file_edit', 'pattern', {
|
|
771
|
-
title:
|
|
772
|
-
description:
|
|
801
|
+
title: mfeTitle,
|
|
802
|
+
description: mfeDescription,
|
|
773
803
|
confidence: Math.min(0.8, 0.4 + topFiles.length * 0.05),
|
|
774
804
|
}),
|
|
775
805
|
);
|
|
776
806
|
}
|
|
777
807
|
}
|
|
778
808
|
|
|
779
|
-
// Rule 3:
|
|
780
|
-
if (session.endedAt && session.startedAt) {
|
|
781
|
-
const durationMs =
|
|
782
|
-
new Date(session.endedAt).getTime() - new Date(session.startedAt).getTime();
|
|
783
|
-
const durationMin = durationMs / 60000;
|
|
784
|
-
if (durationMin > EXTRACTION_LONG_SESSION_MINUTES) {
|
|
785
|
-
rulesApplied.push('long_session');
|
|
786
|
-
proposals.push(
|
|
787
|
-
this.createProposal(sessionId, 'long_session', 'pattern', {
|
|
788
|
-
title: `Long session (${Math.round(durationMin)} minutes)`,
|
|
789
|
-
description: `Session lasted ${Math.round(durationMin)} minutes. Deep work session — review if this duration was productive or indicates a need for better tooling.`,
|
|
790
|
-
confidence: 0.3,
|
|
791
|
-
}),
|
|
792
|
-
);
|
|
793
|
-
}
|
|
794
|
-
}
|
|
795
|
-
|
|
796
|
-
// Rule 4: Plan completed — moderate confidence to avoid auto-promoting generic entries
|
|
809
|
+
// Rule 3: Plan completed — parse session.context for actionable title + dynamic confidence
|
|
797
810
|
if (session.planId && session.planOutcome === 'completed') {
|
|
798
811
|
rulesApplied.push('plan_completed');
|
|
812
|
+
const ctx = session.context ?? '';
|
|
813
|
+
const objective = this.extractObjective(ctx);
|
|
814
|
+
const hasScope = /scope|included|excluded/i.test(ctx);
|
|
815
|
+
const hasCriteria = /criteria|acceptance|verification/i.test(ctx);
|
|
816
|
+
const confidence =
|
|
817
|
+
ctx.length > 0
|
|
818
|
+
? hasScope && hasCriteria
|
|
819
|
+
? 0.85
|
|
820
|
+
: hasScope || hasCriteria
|
|
821
|
+
? 0.8
|
|
822
|
+
: 0.75
|
|
823
|
+
: 0.5;
|
|
824
|
+
const title = objective
|
|
825
|
+
? `Workflow: ${objective.slice(0, 80)}`
|
|
826
|
+
: `Successful plan: ${session.planId}`;
|
|
827
|
+
const description = objective
|
|
828
|
+
? `Completed: ${objective}${hasScope ? '. Scope and constraints documented in session context.' : ''}`
|
|
829
|
+
: `Plan ${session.planId} completed successfully. This workflow can be reused for similar tasks.`;
|
|
799
830
|
proposals.push(
|
|
800
831
|
this.createProposal(sessionId, 'plan_completed', 'workflow', {
|
|
801
|
-
title
|
|
802
|
-
description
|
|
803
|
-
confidence
|
|
832
|
+
title,
|
|
833
|
+
description,
|
|
834
|
+
confidence,
|
|
804
835
|
}),
|
|
805
836
|
);
|
|
806
837
|
}
|
|
807
838
|
|
|
808
|
-
// Rule
|
|
839
|
+
// Rule 4: Plan abandoned — parse context for failure reason
|
|
809
840
|
if (session.planId && session.planOutcome === 'abandoned') {
|
|
810
841
|
rulesApplied.push('plan_abandoned');
|
|
842
|
+
const ctx = session.context ?? '';
|
|
843
|
+
const objective = this.extractObjective(ctx);
|
|
844
|
+
const hasFailureReason = /blocked|failed|wrong|mistake|abandoned|reverted|conflict/i.test(
|
|
845
|
+
ctx,
|
|
846
|
+
);
|
|
847
|
+
const confidence = ctx.length > 0 ? (hasFailureReason ? 0.85 : 0.75) : 0.5;
|
|
848
|
+
const title = objective
|
|
849
|
+
? `Anti-pattern: ${objective.slice(0, 80)}`
|
|
850
|
+
: `Abandoned plan: ${session.planId}`;
|
|
851
|
+
const description = objective
|
|
852
|
+
? `Abandoned: ${objective}${hasFailureReason ? '. Failure indicators found in session context — review for root cause.' : '. Review what went wrong to avoid repeating.'}`
|
|
853
|
+
: `Plan ${session.planId} was abandoned. Review what went wrong to avoid repeating in future sessions.`;
|
|
811
854
|
proposals.push(
|
|
812
855
|
this.createProposal(sessionId, 'plan_abandoned', 'anti-pattern', {
|
|
813
|
-
title
|
|
814
|
-
description
|
|
815
|
-
confidence
|
|
856
|
+
title,
|
|
857
|
+
description,
|
|
858
|
+
confidence,
|
|
816
859
|
}),
|
|
817
860
|
);
|
|
818
861
|
}
|
|
819
862
|
|
|
863
|
+
// Rule 5: Drift detected — fires when plan completed but context contains drift indicators
|
|
864
|
+
if (session.planId && session.planOutcome === 'completed' && session.context) {
|
|
865
|
+
const driftPattern =
|
|
866
|
+
/drift|skipped|added.*unplanned|changed scope|out of scope|deviat|unplanned/i;
|
|
867
|
+
if (driftPattern.test(session.context)) {
|
|
868
|
+
rulesApplied.push('drift_detected');
|
|
869
|
+
const objective = this.extractObjective(session.context);
|
|
870
|
+
const driftMatch =
|
|
871
|
+
session.context.match(/drift[:\s]+(.{1,120})/i) ??
|
|
872
|
+
session.context.match(/skipped[:\s]+(.{1,120})/i) ??
|
|
873
|
+
session.context.match(/unplanned[:\s]+(.{1,120})/i);
|
|
874
|
+
const driftDetail = driftMatch ? driftMatch[1].trim() : 'scope changed during execution';
|
|
875
|
+
proposals.push(
|
|
876
|
+
this.createProposal(sessionId, 'drift_detected', 'anti-pattern', {
|
|
877
|
+
title: `Plan drift: ${objective ? objective.slice(0, 60) : session.planId} — ${driftDetail.slice(0, 40)}`,
|
|
878
|
+
description: `Plan ${objective ?? session.planId} completed with drift: ${driftDetail}. Review scope controls for future planning.`,
|
|
879
|
+
confidence: 0.8,
|
|
880
|
+
}),
|
|
881
|
+
);
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
|
|
820
885
|
// Rule 6: High feedback ratio (>80% accept or dismiss)
|
|
821
886
|
const feedbackRow = this.provider.get<{
|
|
822
887
|
total: number;
|
|
@@ -1369,12 +1434,58 @@ export class BrainIntelligence {
|
|
|
1369
1434
|
};
|
|
1370
1435
|
}
|
|
1371
1436
|
|
|
1437
|
+
/**
|
|
1438
|
+
* Extract the objective from session context — first meaningful sentence or line.
|
|
1439
|
+
* Returns empty string if context is empty or unparseable.
|
|
1440
|
+
*/
|
|
1441
|
+
private extractObjective(context: string): string {
|
|
1442
|
+
if (!context || context.trim().length === 0) return '';
|
|
1443
|
+
// Try to find an "Objective:" line
|
|
1444
|
+
const objMatch = context.match(/objective[:\s]+(.+)/i);
|
|
1445
|
+
if (objMatch) return objMatch[1].trim().replace(/\s+/g, ' ');
|
|
1446
|
+
// Fall back to first non-empty line
|
|
1447
|
+
const firstLine = context
|
|
1448
|
+
.split('\n')
|
|
1449
|
+
.map((l) => l.trim())
|
|
1450
|
+
.find((l) => l.length > 0);
|
|
1451
|
+
return firstLine ? firstLine.replace(/\s+/g, ' ') : '';
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1372
1454
|
private createProposal(
|
|
1373
1455
|
sessionId: string,
|
|
1374
1456
|
rule: string,
|
|
1375
1457
|
type: 'pattern' | 'anti-pattern' | 'workflow',
|
|
1376
1458
|
data: { title: string; description: string; confidence: number },
|
|
1377
1459
|
): KnowledgeProposal {
|
|
1460
|
+
// Dedup guard: skip if a proposal with the same rule + sessionId already exists
|
|
1461
|
+
const existing = this.provider.get<{
|
|
1462
|
+
id: string;
|
|
1463
|
+
session_id: string;
|
|
1464
|
+
rule: string;
|
|
1465
|
+
type: string;
|
|
1466
|
+
title: string;
|
|
1467
|
+
description: string;
|
|
1468
|
+
confidence: number;
|
|
1469
|
+
promoted: number;
|
|
1470
|
+
created_at: string;
|
|
1471
|
+
}>('SELECT * FROM brain_proposals WHERE session_id = ? AND rule = ? LIMIT 1', [
|
|
1472
|
+
sessionId,
|
|
1473
|
+
rule,
|
|
1474
|
+
]);
|
|
1475
|
+
if (existing) {
|
|
1476
|
+
return {
|
|
1477
|
+
id: existing.id,
|
|
1478
|
+
sessionId: existing.session_id,
|
|
1479
|
+
rule: existing.rule,
|
|
1480
|
+
type: existing.type as 'pattern' | 'anti-pattern' | 'workflow',
|
|
1481
|
+
title: existing.title,
|
|
1482
|
+
description: existing.description,
|
|
1483
|
+
confidence: existing.confidence,
|
|
1484
|
+
promoted: existing.promoted === 1,
|
|
1485
|
+
createdAt: existing.created_at,
|
|
1486
|
+
};
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1378
1489
|
const id = randomUUID();
|
|
1379
1490
|
this.provider.run(
|
|
1380
1491
|
`INSERT INTO brain_proposals (id, session_id, rule, type, title, description, confidence)
|
|
@@ -229,14 +229,17 @@ export class LearningRadar {
|
|
|
229
229
|
}
|
|
230
230
|
|
|
231
231
|
/**
|
|
232
|
-
* Dismiss
|
|
232
|
+
* Dismiss one or more pending candidates — mark them as not worth capturing.
|
|
233
233
|
*/
|
|
234
|
-
dismiss(
|
|
234
|
+
dismiss(candidateIds: number | number[]): { dismissed: number } {
|
|
235
|
+
const ids = Array.isArray(candidateIds) ? candidateIds : [candidateIds];
|
|
236
|
+
if (ids.length === 0) return { dismissed: 0 };
|
|
237
|
+
const placeholders = ids.map(() => '?').join(',');
|
|
235
238
|
const result = this.provider.run(
|
|
236
|
-
|
|
237
|
-
|
|
239
|
+
`UPDATE radar_candidates SET status = 'dismissed' WHERE id IN (${placeholders}) AND status = 'pending'`,
|
|
240
|
+
ids,
|
|
238
241
|
);
|
|
239
|
-
return { dismissed: result.changes
|
|
242
|
+
return { dismissed: result.changes };
|
|
240
243
|
}
|
|
241
244
|
|
|
242
245
|
/**
|
|
@@ -335,7 +335,7 @@ describe('Ambient learning radar (#208)', () => {
|
|
|
335
335
|
const pending = candidates.find((c) => c.title.includes('stale cache'));
|
|
336
336
|
expect(pending).toBeDefined();
|
|
337
337
|
const result = learningRadar.dismiss(pending!.id);
|
|
338
|
-
expect(result.dismissed).toBe(
|
|
338
|
+
expect(result.dismissed).toBe(1);
|
|
339
339
|
});
|
|
340
340
|
|
|
341
341
|
it('getStats returns radar statistics', () => {
|
package/src/chat/agent-loop.ts
CHANGED
|
@@ -292,7 +292,7 @@ async function callAnthropic(params: AnthropicCallParams): Promise<AnthropicResp
|
|
|
292
292
|
'anthropic-version': API_VERSION,
|
|
293
293
|
},
|
|
294
294
|
body: JSON.stringify(body),
|
|
295
|
-
signal: params.signal,
|
|
295
|
+
signal: params.signal ?? AbortSignal.timeout(120_000),
|
|
296
296
|
});
|
|
297
297
|
|
|
298
298
|
if (!response.ok) {
|
|
@@ -88,6 +88,10 @@ export class NotificationEngine {
|
|
|
88
88
|
setTimeout(() => this.poll(), 10_000);
|
|
89
89
|
|
|
90
90
|
this.timer = setInterval(() => this.poll(), this.intervalMs);
|
|
91
|
+
// Don't prevent process exit for background notification polling
|
|
92
|
+
if (this.timer && typeof this.timer === 'object' && 'unref' in this.timer) {
|
|
93
|
+
(this.timer as NodeJS.Timeout).unref();
|
|
94
|
+
}
|
|
91
95
|
}
|
|
92
96
|
|
|
93
97
|
/**
|
|
@@ -35,7 +35,7 @@ describe('IntentRouter', () => {
|
|
|
35
35
|
describe('construction', () => {
|
|
36
36
|
it('seeds 10 default modes on first creation', () => {
|
|
37
37
|
const modes = router.getModes();
|
|
38
|
-
expect(modes.length).toBe(
|
|
38
|
+
expect(modes.length).toBe(11);
|
|
39
39
|
});
|
|
40
40
|
|
|
41
41
|
it('starts in GENERAL-MODE', () => {
|
|
@@ -44,7 +44,7 @@ describe('IntentRouter', () => {
|
|
|
44
44
|
|
|
45
45
|
it('is idempotent — second instance does not duplicate modes', () => {
|
|
46
46
|
const router2 = new IntentRouter(vault);
|
|
47
|
-
expect(router2.getModes().length).toBe(
|
|
47
|
+
expect(router2.getModes().length).toBe(11);
|
|
48
48
|
});
|
|
49
49
|
});
|
|
50
50
|
|
|
@@ -199,7 +199,7 @@ describe('IntentRouter', () => {
|
|
|
199
199
|
it('adds a new mode to the database', () => {
|
|
200
200
|
router.registerMode(customMode);
|
|
201
201
|
const modes = router.getModes();
|
|
202
|
-
expect(modes.length).toBe(
|
|
202
|
+
expect(modes.length).toBe(12);
|
|
203
203
|
const found = modes.find((m) => m.mode === 'CUSTOM-MODE');
|
|
204
204
|
expect(found).toBeDefined();
|
|
205
205
|
expect(found!.keywords).toEqual(['custom', 'special']);
|
|
@@ -338,6 +338,76 @@ describe('IntentRouter', () => {
|
|
|
338
338
|
});
|
|
339
339
|
});
|
|
340
340
|
|
|
341
|
+
// ─── YOLO-MODE ───────────────────────────────────────────────
|
|
342
|
+
|
|
343
|
+
describe('YOLO-MODE', () => {
|
|
344
|
+
it('route_intent with "yolo" returns YOLO-MODE', () => {
|
|
345
|
+
const result = router.routeIntent('go yolo on this task');
|
|
346
|
+
expect(result.intent).toBe('yolo');
|
|
347
|
+
expect(result.mode).toBe('YOLO-MODE');
|
|
348
|
+
expect(result.matchedKeywords).toContain('yolo');
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
it('morph to YOLO-MODE succeeds when hook pack is installed', () => {
|
|
352
|
+
const result = router.morph('YOLO-MODE', { hookPackInstalled: true });
|
|
353
|
+
expect(result.previousMode).toBe('GENERAL-MODE');
|
|
354
|
+
expect(result.currentMode).toBe('YOLO-MODE');
|
|
355
|
+
expect(result.behaviorRules.length).toBe(5);
|
|
356
|
+
expect(result.blocked).toBeUndefined();
|
|
357
|
+
expect(result.error).toBeUndefined();
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
it('morph to YOLO-MODE fails when hook pack is missing', () => {
|
|
361
|
+
const result = router.morph('YOLO-MODE');
|
|
362
|
+
expect(result.blocked).toBe(true);
|
|
363
|
+
expect(result.error).toContain('yolo-safety hook pack');
|
|
364
|
+
expect(result.error).toContain('soleri hooks add-pack yolo-safety');
|
|
365
|
+
expect(result.currentMode).toBe('GENERAL-MODE'); // unchanged
|
|
366
|
+
expect(router.getCurrentMode()).toBe('GENERAL-MODE'); // not switched
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
it('morph to YOLO-MODE fails when hookPackInstalled is explicitly false', () => {
|
|
370
|
+
const result = router.morph('YOLO-MODE', { hookPackInstalled: false });
|
|
371
|
+
expect(result.blocked).toBe(true);
|
|
372
|
+
expect(result.error).toContain('yolo-safety hook pack');
|
|
373
|
+
expect(router.getCurrentMode()).toBe('GENERAL-MODE');
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
it('morph to other modes is unaffected by the gate', () => {
|
|
377
|
+
const result = router.morph('BUILD-MODE');
|
|
378
|
+
expect(result.currentMode).toBe('BUILD-MODE');
|
|
379
|
+
expect(result.blocked).toBeUndefined();
|
|
380
|
+
expect(result.error).toBeUndefined();
|
|
381
|
+
expect(router.getCurrentMode()).toBe('BUILD-MODE');
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
it('get_behavior_rules returns 5 rules', () => {
|
|
385
|
+
const rules = router.getBehaviorRules('YOLO-MODE');
|
|
386
|
+
expect(rules).toHaveLength(5);
|
|
387
|
+
expect(rules[0]).toContain('Skip plan approval gates');
|
|
388
|
+
expect(rules[1]).toContain('orchestrate_complete');
|
|
389
|
+
expect(rules[2]).toContain('vault gather-before-execute');
|
|
390
|
+
expect(rules[3]).toContain('Hook pack must be installed');
|
|
391
|
+
expect(rules[4]).toContain('exit YOLO');
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
it('all keywords route to YOLO-MODE', () => {
|
|
395
|
+
const keywords = [
|
|
396
|
+
'yolo',
|
|
397
|
+
'autonomous',
|
|
398
|
+
'fire-and-forget',
|
|
399
|
+
'hands-off',
|
|
400
|
+
'no-approval',
|
|
401
|
+
'skip-gates',
|
|
402
|
+
'full-auto',
|
|
403
|
+
];
|
|
404
|
+
for (const kw of keywords) {
|
|
405
|
+
const result = router.routeIntent(kw);
|
|
406
|
+
expect(result.mode).toBe('YOLO-MODE');
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
});
|
|
410
|
+
|
|
341
411
|
// ─── getModes ─────────────────────────────────────────────────
|
|
342
412
|
|
|
343
413
|
describe('getModes', () => {
|
|
@@ -15,6 +15,7 @@ import type {
|
|
|
15
15
|
OperationalMode,
|
|
16
16
|
IntentClassification,
|
|
17
17
|
ModeConfig,
|
|
18
|
+
MorphOptions,
|
|
18
19
|
MorphResult,
|
|
19
20
|
RoutingAccuracyReport,
|
|
20
21
|
} from './types.js';
|
|
@@ -132,6 +133,27 @@ const DEFAULT_MODES: ModeConfig[] = [
|
|
|
132
133
|
behaviorRules: ['Be helpful', 'Ask clarifying questions when needed'],
|
|
133
134
|
keywords: [],
|
|
134
135
|
},
|
|
136
|
+
{
|
|
137
|
+
mode: 'YOLO-MODE',
|
|
138
|
+
intent: 'yolo',
|
|
139
|
+
description: 'Autonomous execution — skip approval gates, execute directly',
|
|
140
|
+
behaviorRules: [
|
|
141
|
+
'Skip plan approval gates — execute tasks directly',
|
|
142
|
+
'Still run orchestrate_complete — knowledge capture is non-negotiable',
|
|
143
|
+
'Still run vault gather-before-execute — decisions must be informed',
|
|
144
|
+
'Hook pack must be installed — refuse to activate without yolo-safety hooks',
|
|
145
|
+
'User can exit with "exit YOLO" or session end',
|
|
146
|
+
],
|
|
147
|
+
keywords: [
|
|
148
|
+
'yolo',
|
|
149
|
+
'autonomous',
|
|
150
|
+
'fire-and-forget',
|
|
151
|
+
'hands-off',
|
|
152
|
+
'no-approval',
|
|
153
|
+
'skip-gates',
|
|
154
|
+
'full-auto',
|
|
155
|
+
],
|
|
156
|
+
},
|
|
135
157
|
];
|
|
136
158
|
|
|
137
159
|
// ─── Class ──────────────────────────────────────────────────────────
|
|
@@ -140,6 +162,7 @@ export class IntentRouter {
|
|
|
140
162
|
private vault: Vault;
|
|
141
163
|
private provider: PersistenceProvider;
|
|
142
164
|
private currentMode: OperationalMode = 'GENERAL-MODE';
|
|
165
|
+
private modesCache: ModeConfig[] | null = null;
|
|
143
166
|
|
|
144
167
|
constructor(vault: Vault) {
|
|
145
168
|
this.vault = vault;
|
|
@@ -267,7 +290,7 @@ export class IntentRouter {
|
|
|
267
290
|
|
|
268
291
|
// ─── Mode Management ───────────────────────────────────────────────
|
|
269
292
|
|
|
270
|
-
morph(mode: OperationalMode): MorphResult {
|
|
293
|
+
morph(mode: OperationalMode, options?: MorphOptions): MorphResult {
|
|
271
294
|
// Handle "reset" as a built-in alias for GENERAL-MODE
|
|
272
295
|
const resolvedMode: OperationalMode = (mode as string) === 'reset' ? 'GENERAL-MODE' : mode;
|
|
273
296
|
|
|
@@ -283,8 +306,24 @@ export class IntentRouter {
|
|
|
283
306
|
throw new Error(`Unknown mode: ${mode}. Available: ${available}`);
|
|
284
307
|
}
|
|
285
308
|
|
|
309
|
+
// ─── YOLO-MODE activation gate ────────────────────────────────
|
|
310
|
+
// YOLO-MODE requires the yolo-safety hook pack to be installed.
|
|
311
|
+
// The CLI/facade layer provides hookPackInstalled based on filesystem check.
|
|
312
|
+
if (resolvedMode === 'YOLO-MODE' && !options?.hookPackInstalled) {
|
|
313
|
+
return {
|
|
314
|
+
previousMode: this.currentMode,
|
|
315
|
+
currentMode: this.currentMode, // unchanged — mode switch blocked
|
|
316
|
+
behaviorRules: this.getBehaviorRules(),
|
|
317
|
+
blocked: true,
|
|
318
|
+
error:
|
|
319
|
+
'YOLO-MODE requires the yolo-safety hook pack. ' +
|
|
320
|
+
'Install it with: soleri hooks add-pack yolo-safety',
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
|
|
286
324
|
const previousMode = this.currentMode;
|
|
287
325
|
this.currentMode = resolvedMode;
|
|
326
|
+
this.modesCache = null; // Invalidate cache on mode change
|
|
288
327
|
const behaviorRules = JSON.parse(row.behavior_rules) as string[];
|
|
289
328
|
|
|
290
329
|
return { previousMode, currentMode: resolvedMode, behaviorRules };
|
|
@@ -296,18 +335,16 @@ export class IntentRouter {
|
|
|
296
335
|
|
|
297
336
|
getBehaviorRules(mode?: OperationalMode): string[] {
|
|
298
337
|
const target = mode ?? this.currentMode;
|
|
299
|
-
const
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
);
|
|
303
|
-
|
|
304
|
-
if (!row) return [];
|
|
305
|
-
return JSON.parse(row.behavior_rules) as string[];
|
|
338
|
+
const modes = this.getModes();
|
|
339
|
+
const found = modes.find((m) => m.mode === target);
|
|
340
|
+
return found ? found.behaviorRules : [];
|
|
306
341
|
}
|
|
307
342
|
|
|
308
343
|
getModes(): ModeConfig[] {
|
|
344
|
+
if (this.modesCache) return this.modesCache;
|
|
309
345
|
const rows = this.provider.all<ModeRow>('SELECT * FROM agent_modes ORDER BY mode');
|
|
310
|
-
|
|
346
|
+
this.modesCache = rows.map(rowToModeConfig);
|
|
347
|
+
return this.modesCache;
|
|
311
348
|
}
|
|
312
349
|
|
|
313
350
|
registerMode(config: ModeConfig): void {
|
|
@@ -322,6 +359,7 @@ export class IntentRouter {
|
|
|
322
359
|
JSON.stringify(config.keywords),
|
|
323
360
|
],
|
|
324
361
|
);
|
|
362
|
+
this.modesCache = null;
|
|
325
363
|
}
|
|
326
364
|
|
|
327
365
|
updateModeRules(mode: OperationalMode, rules: string[]): void {
|
|
@@ -332,6 +370,7 @@ export class IntentRouter {
|
|
|
332
370
|
if (result.changes === 0) {
|
|
333
371
|
throw new Error(`Unknown mode: ${mode}`);
|
|
334
372
|
}
|
|
373
|
+
this.modesCache = null;
|
|
335
374
|
}
|
|
336
375
|
|
|
337
376
|
// ─── Routing Feedback ─────────────────────────────────────────────
|
package/src/control/types.ts
CHANGED
|
@@ -65,7 +65,8 @@ export type IntentType =
|
|
|
65
65
|
| 'explore'
|
|
66
66
|
| 'plan'
|
|
67
67
|
| 'review'
|
|
68
|
-
| 'general'
|
|
68
|
+
| 'general'
|
|
69
|
+
| 'yolo';
|
|
69
70
|
|
|
70
71
|
export type OperationalMode =
|
|
71
72
|
| 'BUILD-MODE'
|
|
@@ -77,7 +78,8 @@ export type OperationalMode =
|
|
|
77
78
|
| 'EXPLORE-MODE'
|
|
78
79
|
| 'PLAN-MODE'
|
|
79
80
|
| 'REVIEW-MODE'
|
|
80
|
-
| 'GENERAL-MODE'
|
|
81
|
+
| 'GENERAL-MODE'
|
|
82
|
+
| 'YOLO-MODE';
|
|
81
83
|
|
|
82
84
|
export interface IntentClassification {
|
|
83
85
|
intent: IntentType;
|
|
@@ -95,10 +97,19 @@ export interface ModeConfig {
|
|
|
95
97
|
keywords: string[];
|
|
96
98
|
}
|
|
97
99
|
|
|
100
|
+
export interface MorphOptions {
|
|
101
|
+
/** Whether the yolo-safety hook pack is installed. Required for YOLO-MODE activation. */
|
|
102
|
+
hookPackInstalled?: boolean;
|
|
103
|
+
}
|
|
104
|
+
|
|
98
105
|
export interface MorphResult {
|
|
99
106
|
previousMode: OperationalMode;
|
|
100
107
|
currentMode: OperationalMode;
|
|
101
108
|
behaviorRules: string[];
|
|
109
|
+
/** Present when activation is refused (e.g. missing hook pack). */
|
|
110
|
+
error?: string;
|
|
111
|
+
/** When true, the mode switch was blocked. */
|
|
112
|
+
blocked?: boolean;
|
|
102
113
|
}
|
|
103
114
|
|
|
104
115
|
export interface RoutingAccuracyReport {
|