aios-core 4.0.0 → 4.0.2
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/.aios-core/cli/commands/pro/index.js +82 -148
- package/.aios-core/core/synapse/domain/domain-loader.js +2 -2
- package/.aios-core/core/synapse/engine.js +17 -4
- package/.aios-core/core/synapse/memory/memory-bridge.js +246 -0
- package/.aios-core/core/synapse/output/formatter.js +34 -12
- package/.aios-core/core/synapse/scripts/generate-constitution.js +204 -0
- package/.aios-core/core/synapse/utils/tokens.js +25 -0
- package/.aios-core/data/aios-kb.md +2 -4
- package/.aios-core/data/entity-registry.yaml +61 -8
- package/.aios-core/development/scripts/unified-activation-pipeline.js +9 -1
- package/.aios-core/framework-config.yaml +1 -1
- package/.aios-core/install-manifest.yaml +33 -21
- package/.aios-core/lib/build.json +1 -0
- package/.aios-core/package.json +2 -1
- package/.aios-core/user-guide.md +1 -1
- package/.claude/CLAUDE.md +8 -9
- package/.claude/hooks/README.md +169 -0
- package/.claude/hooks/precompact-session-digest.js +46 -0
- package/.claude/hooks/synapse-engine.js +87 -0
- package/bin/aios-init.js +4 -4
- package/bin/aios-minimal.js +1 -4
- package/bin/aios.js +1 -1
- package/bin/modules/env-config.js +0 -1
- package/package.json +4 -1
- package/packages/aios-pro-cli/bin/aios-pro.js +158 -0
- package/packages/aios-pro-cli/package.json +32 -0
- package/packages/installer/package.json +1 -1
- package/packages/installer/src/installer/aios-core-installer.js +23 -0
- package/packages/installer/src/wizard/ide-config-generator.js +146 -1
- package/packages/installer/src/wizard/index.js +49 -32
|
@@ -23,8 +23,40 @@ const path = require('path');
|
|
|
23
23
|
const fs = require('fs');
|
|
24
24
|
const readline = require('readline');
|
|
25
25
|
|
|
26
|
-
//
|
|
27
|
-
|
|
26
|
+
// BUG-6 fix (INS-1): Dynamic licensePath resolution
|
|
27
|
+
// In framework-dev: __dirname = aios-core/.aios-core/cli/commands/pro → ../../../../pro/license
|
|
28
|
+
// In project-dev: pro is installed via npm as @aios-fullstack/pro
|
|
29
|
+
function resolveLicensePath() {
|
|
30
|
+
// 1. Try relative path (framework-dev mode)
|
|
31
|
+
const relativePath = path.resolve(__dirname, '..', '..', '..', '..', 'pro', 'license');
|
|
32
|
+
if (fs.existsSync(relativePath)) {
|
|
33
|
+
return relativePath;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// 2. Try node_modules/@aios-fullstack/pro/license (project-dev mode)
|
|
37
|
+
try {
|
|
38
|
+
const proPkg = require.resolve('@aios-fullstack/pro/package.json');
|
|
39
|
+
const proDir = path.dirname(proPkg);
|
|
40
|
+
const npmPath = path.join(proDir, 'license');
|
|
41
|
+
if (fs.existsSync(npmPath)) {
|
|
42
|
+
return npmPath;
|
|
43
|
+
}
|
|
44
|
+
} catch {
|
|
45
|
+
// @aios-fullstack/pro not installed via npm
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// 3. Try project root node_modules (fallback)
|
|
49
|
+
const projectRoot = process.cwd();
|
|
50
|
+
const cwdPath = path.join(projectRoot, 'node_modules', '@aios-fullstack', 'pro', 'license');
|
|
51
|
+
if (fs.existsSync(cwdPath)) {
|
|
52
|
+
return cwdPath;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Return relative path as default (will fail gracefully in loadLicenseModules)
|
|
56
|
+
return relativePath;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const licensePath = resolveLicensePath();
|
|
28
60
|
|
|
29
61
|
/**
|
|
30
62
|
* Lazy-load license modules (avoids failing if pro module not installed)
|
|
@@ -65,7 +97,7 @@ function loadLicenseModules() {
|
|
|
65
97
|
};
|
|
66
98
|
} catch (error) {
|
|
67
99
|
console.error('AIOS Pro license module not available.');
|
|
68
|
-
console.error('Install AIOS Pro: npm install @aios/pro');
|
|
100
|
+
console.error('Install AIOS Pro: npm install @aios-fullstack/pro');
|
|
69
101
|
process.exit(1);
|
|
70
102
|
}
|
|
71
103
|
}
|
|
@@ -487,172 +519,75 @@ async function validateAction() {
|
|
|
487
519
|
// ---------------------------------------------------------------------------
|
|
488
520
|
|
|
489
521
|
/**
|
|
490
|
-
* Setup
|
|
522
|
+
* Setup and verify @aios-fullstack/pro installation.
|
|
491
523
|
*
|
|
492
|
-
*
|
|
493
|
-
*
|
|
524
|
+
* Since @aios-fullstack/pro is published on the public npm registry,
|
|
525
|
+
* no special token or .npmrc configuration is needed. This command
|
|
526
|
+
* installs the package and verifies it's working.
|
|
494
527
|
*
|
|
495
528
|
* @param {object} options - Command options
|
|
496
|
-
* @param {
|
|
497
|
-
* @param {boolean} options.global - Configure globally vs project-level
|
|
529
|
+
* @param {boolean} options.verify - Only verify without installing
|
|
498
530
|
*/
|
|
499
531
|
async function setupAction(options) {
|
|
500
|
-
|
|
501
|
-
const homedir = os.homedir();
|
|
502
|
-
|
|
503
|
-
console.log('\nAIOS Pro - GitHub Packages Setup\n');
|
|
504
|
-
console.log('This will configure npm to access @aios/pro from GitHub Packages.');
|
|
505
|
-
console.log('');
|
|
532
|
+
console.log('\nAIOS Pro - Setup\n');
|
|
506
533
|
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
: path.join(process.cwd(), '.npmrc');
|
|
534
|
+
if (options.verify) {
|
|
535
|
+
// Verify-only mode
|
|
536
|
+
console.log('Verifying @aios-fullstack/pro installation...\n');
|
|
511
537
|
|
|
512
|
-
const scopeConfig = '@aios:registry=https://npm.pkg.github.com';
|
|
513
|
-
const tokenConfig = '//npm.pkg.github.com/:_authToken=';
|
|
514
|
-
|
|
515
|
-
// Check if token provided
|
|
516
|
-
let token = options.token;
|
|
517
|
-
|
|
518
|
-
if (!token) {
|
|
519
|
-
console.log('To install @aios/pro, you need a GitHub Personal Access Token (PAT)');
|
|
520
|
-
console.log('with the "read:packages" scope.');
|
|
521
|
-
console.log('');
|
|
522
|
-
console.log('Create one at: https://github.com/settings/tokens/new');
|
|
523
|
-
console.log('');
|
|
524
|
-
console.log('Required scopes:');
|
|
525
|
-
console.log(' - read:packages (download packages from GitHub Packages)');
|
|
526
|
-
console.log('');
|
|
527
|
-
|
|
528
|
-
// Interactive token input
|
|
529
|
-
const rl = readline.createInterface({
|
|
530
|
-
input: process.stdin,
|
|
531
|
-
output: process.stdout,
|
|
532
|
-
});
|
|
533
|
-
|
|
534
|
-
token = await new Promise((resolve) => {
|
|
535
|
-
rl.question('Enter your GitHub PAT (or press Enter to skip): ', (answer) => {
|
|
536
|
-
rl.close();
|
|
537
|
-
resolve(answer.trim());
|
|
538
|
-
});
|
|
539
|
-
});
|
|
540
|
-
|
|
541
|
-
if (!token) {
|
|
542
|
-
console.log('\nSetup cancelled. You can run this command again with:');
|
|
543
|
-
console.log(' aios pro setup --token <YOUR_GITHUB_PAT>');
|
|
544
|
-
console.log('');
|
|
545
|
-
console.log('Or manually add to your .npmrc:');
|
|
546
|
-
console.log(` ${scopeConfig}`);
|
|
547
|
-
console.log(` ${tokenConfig}<YOUR_GITHUB_PAT>`);
|
|
548
|
-
return;
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
// Validate token format (basic check)
|
|
553
|
-
if (!token.startsWith('ghp_') && !token.startsWith('github_pat_')) {
|
|
554
|
-
console.log('\n⚠️ Warning: Token does not appear to be a valid GitHub PAT.');
|
|
555
|
-
console.log('Expected format: ghp_... or github_pat_...');
|
|
556
|
-
|
|
557
|
-
const confirmed = await confirm('Continue anyway? (y/N): ');
|
|
558
|
-
if (!confirmed) {
|
|
559
|
-
console.log('Setup cancelled.');
|
|
560
|
-
return;
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
// Read existing .npmrc or create new
|
|
565
|
-
let npmrcContent = '';
|
|
566
|
-
try {
|
|
567
|
-
npmrcContent = fs.readFileSync(npmrcPath, 'utf8');
|
|
568
|
-
} catch {
|
|
569
|
-
// File doesn't exist, will create new
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
// Check if already configured
|
|
573
|
-
if (npmrcContent.includes('@aios:registry')) {
|
|
574
|
-
console.log(`\n⚠️ @aios registry already configured in ${npmrcPath}`);
|
|
575
|
-
|
|
576
|
-
const overwrite = await confirm('Overwrite existing configuration? (y/N): ');
|
|
577
|
-
if (!overwrite) {
|
|
578
|
-
console.log('Setup cancelled. Existing configuration preserved.');
|
|
579
|
-
return;
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
// Remove existing @aios config
|
|
583
|
-
npmrcContent = npmrcContent
|
|
584
|
-
.split('\n')
|
|
585
|
-
.filter((line) => !line.includes('@aios:') && !line.includes('npm.pkg.github.com/:_authToken'))
|
|
586
|
-
.join('\n');
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
// Add new configuration
|
|
590
|
-
const newConfig = [
|
|
591
|
-
'',
|
|
592
|
-
'# AIOS Pro - GitHub Packages (added by aios pro setup)',
|
|
593
|
-
scopeConfig,
|
|
594
|
-
`${tokenConfig}${token}`,
|
|
595
|
-
'',
|
|
596
|
-
].join('\n');
|
|
597
|
-
|
|
598
|
-
npmrcContent = npmrcContent.trimEnd() + newConfig;
|
|
599
|
-
|
|
600
|
-
// Write .npmrc
|
|
601
|
-
try {
|
|
602
|
-
fs.writeFileSync(npmrcPath, npmrcContent, 'utf8');
|
|
603
|
-
console.log(`\n✅ Configuration written to ${npmrcPath}`);
|
|
604
|
-
} catch (error) {
|
|
605
|
-
console.error(`\n❌ Failed to write ${npmrcPath}: ${error.message}`);
|
|
606
|
-
console.log('\nManually add these lines to your .npmrc:');
|
|
607
|
-
console.log(` ${scopeConfig}`);
|
|
608
|
-
console.log(` ${tokenConfig}<YOUR_TOKEN>`);
|
|
609
|
-
process.exit(1);
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
// Add .npmrc to .gitignore if project-level
|
|
613
|
-
if (!options.global) {
|
|
614
|
-
const gitignorePath = path.join(process.cwd(), '.gitignore');
|
|
615
538
|
try {
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
if (
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
console.log('
|
|
539
|
+
const { execSync } = require('child_process');
|
|
540
|
+
const result = execSync('npm ls @aios-fullstack/pro --json', {
|
|
541
|
+
stdio: 'pipe',
|
|
542
|
+
timeout: 15000,
|
|
543
|
+
});
|
|
544
|
+
const parsed = JSON.parse(result.toString());
|
|
545
|
+
const deps = parsed.dependencies || {};
|
|
546
|
+
if (deps['@aios-fullstack/pro']) {
|
|
547
|
+
console.log(`✅ @aios-fullstack/pro@${deps['@aios-fullstack/pro'].version} is installed`);
|
|
548
|
+
} else {
|
|
549
|
+
console.log('❌ @aios-fullstack/pro is not installed');
|
|
550
|
+
console.log('');
|
|
551
|
+
console.log('Install with:');
|
|
552
|
+
console.log(' npm install @aios-fullstack/pro');
|
|
627
553
|
}
|
|
628
554
|
} catch {
|
|
629
|
-
console.log('
|
|
555
|
+
console.log('❌ @aios-fullstack/pro is not installed');
|
|
556
|
+
console.log('');
|
|
557
|
+
console.log('Install with:');
|
|
558
|
+
console.log(' npm install @aios-fullstack/pro');
|
|
630
559
|
}
|
|
560
|
+
return;
|
|
631
561
|
}
|
|
632
562
|
|
|
633
|
-
//
|
|
634
|
-
console.log('
|
|
563
|
+
// Install mode
|
|
564
|
+
console.log('@aios-fullstack/pro is available on the public npm registry.');
|
|
565
|
+
console.log('No special tokens or configuration needed.\n');
|
|
566
|
+
|
|
567
|
+
console.log('Installing @aios-fullstack/pro...\n');
|
|
635
568
|
|
|
636
569
|
try {
|
|
637
570
|
const { execSync } = require('child_process');
|
|
638
|
-
execSync('npm
|
|
639
|
-
stdio: '
|
|
640
|
-
timeout:
|
|
571
|
+
execSync('npm install @aios-fullstack/pro', {
|
|
572
|
+
stdio: 'inherit',
|
|
573
|
+
timeout: 120000,
|
|
641
574
|
});
|
|
642
|
-
console.log('✅
|
|
643
|
-
} catch {
|
|
644
|
-
console.
|
|
645
|
-
console.log('
|
|
575
|
+
console.log('\n✅ @aios-fullstack/pro installed successfully!');
|
|
576
|
+
} catch (error) {
|
|
577
|
+
console.error(`\n❌ Installation failed: ${error.message}`);
|
|
578
|
+
console.log('\nTry manually:');
|
|
579
|
+
console.log(' npm install @aios-fullstack/pro');
|
|
580
|
+
process.exit(1);
|
|
646
581
|
}
|
|
647
582
|
|
|
648
583
|
console.log('\n--- Setup Complete ---');
|
|
649
584
|
console.log('');
|
|
650
|
-
console.log('To install AIOS Pro:');
|
|
651
|
-
console.log(' npm install @aios/pro');
|
|
652
|
-
console.log('');
|
|
653
585
|
console.log('To activate your license:');
|
|
654
586
|
console.log(' aios pro activate --key PRO-XXXX-XXXX-XXXX-XXXX');
|
|
655
587
|
console.log('');
|
|
588
|
+
console.log('To check license status:');
|
|
589
|
+
console.log(' aios pro status');
|
|
590
|
+
console.log('');
|
|
656
591
|
console.log('Documentation: https://synkra.ai/pro/docs');
|
|
657
592
|
console.log('');
|
|
658
593
|
}
|
|
@@ -704,9 +639,8 @@ function createProCommand() {
|
|
|
704
639
|
// aios pro setup (AC-12: Install-gate)
|
|
705
640
|
proCmd
|
|
706
641
|
.command('setup')
|
|
707
|
-
.description('
|
|
708
|
-
.option('
|
|
709
|
-
.option('-g, --global', 'Configure globally (~/.npmrc) instead of project-level')
|
|
642
|
+
.description('Install and verify @aios-fullstack/pro')
|
|
643
|
+
.option('--verify', 'Only verify installation without installing')
|
|
710
644
|
.action(setupAction);
|
|
711
645
|
|
|
712
646
|
return proCmd;
|
|
@@ -169,7 +169,7 @@ function loadDomainFile(domainPath) {
|
|
|
169
169
|
// First pass: detect if file uses KEY=VALUE format
|
|
170
170
|
for (const line of lines) {
|
|
171
171
|
const trimmed = line.trim();
|
|
172
|
-
if (trimmed && !trimmed.startsWith('#') && /^[A-
|
|
172
|
+
if (trimmed && !trimmed.startsWith('#') && /^[A-Z][A-Z0-9_]*=/.test(trimmed)) {
|
|
173
173
|
hasKeyValueFormat = true;
|
|
174
174
|
break;
|
|
175
175
|
}
|
|
@@ -185,7 +185,7 @@ function loadDomainFile(domainPath) {
|
|
|
185
185
|
|
|
186
186
|
if (hasKeyValueFormat) {
|
|
187
187
|
// KEY=VALUE format: extract value from DOMAIN_RULE_N=text
|
|
188
|
-
const match = trimmed.match(/^[A-
|
|
188
|
+
const match = trimmed.match(/^[A-Z][A-Z0-9_]*=(.+)$/);
|
|
189
189
|
if (match) {
|
|
190
190
|
rules.push(match[1].trim());
|
|
191
191
|
}
|
|
@@ -20,6 +20,7 @@ const {
|
|
|
20
20
|
} = require('./context/context-tracker');
|
|
21
21
|
|
|
22
22
|
const { formatSynapseRules } = require('./output/formatter');
|
|
23
|
+
const { MemoryBridge } = require('./memory/memory-bridge');
|
|
23
24
|
|
|
24
25
|
// ---------------------------------------------------------------------------
|
|
25
26
|
// Layer Imports (graceful — layers from SYN-4/SYN-5 may not exist yet)
|
|
@@ -186,6 +187,9 @@ class SynapseEngine {
|
|
|
186
187
|
/** @type {Array<import('./layers/layer-processor')>} */
|
|
187
188
|
this.layers = [];
|
|
188
189
|
|
|
190
|
+
/** @type {MemoryBridge} Feature-gated MIS consumer (SYN-10) */
|
|
191
|
+
this.memoryBridge = new MemoryBridge();
|
|
192
|
+
|
|
189
193
|
for (const mod of LAYER_MODULES) {
|
|
190
194
|
const LayerClass = loadLayerModule(mod.path);
|
|
191
195
|
if (LayerClass) {
|
|
@@ -211,9 +215,9 @@ class SynapseEngine {
|
|
|
211
215
|
* @param {object} session - Session state (SYN-2 schema)
|
|
212
216
|
* @param {number} [session.prompt_count=0] - Number of prompts so far
|
|
213
217
|
* @param {object} [processConfig] - Per-call config overrides
|
|
214
|
-
* @returns {{ xml: string, metrics: object }}
|
|
218
|
+
* @returns {Promise<{ xml: string, metrics: object }>}
|
|
215
219
|
*/
|
|
216
|
-
process(prompt, session, processConfig) {
|
|
220
|
+
async process(prompt, session, processConfig) {
|
|
217
221
|
const safeProcessConfig = (processConfig && typeof processConfig === 'object') ? processConfig : {};
|
|
218
222
|
const mergedConfig = { ...this.config, ...safeProcessConfig };
|
|
219
223
|
const metrics = new PipelineMetrics();
|
|
@@ -283,9 +287,18 @@ class SynapseEngine {
|
|
|
283
287
|
}
|
|
284
288
|
}
|
|
285
289
|
|
|
286
|
-
// 3. Memory bridge
|
|
290
|
+
// 3. Memory bridge (SYN-10) — feature-gated MIS consumer
|
|
287
291
|
if (needsMemoryHints(bracket)) {
|
|
288
|
-
|
|
292
|
+
const hints = await this.memoryBridge.getMemoryHints(
|
|
293
|
+
(session && session.activeAgent) || (session && session.active_agent) || '',
|
|
294
|
+
bracket,
|
|
295
|
+
tokenBudget,
|
|
296
|
+
);
|
|
297
|
+
if (hints.length > 0) {
|
|
298
|
+
const memoryResult = { layer: 'memory', rules: hints, metadata: { layer: 'memory', source: 'memory' } };
|
|
299
|
+
results.push(memoryResult);
|
|
300
|
+
previousLayers.push(memoryResult);
|
|
301
|
+
}
|
|
289
302
|
}
|
|
290
303
|
|
|
291
304
|
metrics.totalEnd = Date.now();
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory Bridge — Feature-gated MIS consumer for SYNAPSE engine.
|
|
3
|
+
*
|
|
4
|
+
* Connects SynapseEngine to the Memory Intelligence System (MIS)
|
|
5
|
+
* via MemoryLoader API. Implements bracket-aware retrieval with
|
|
6
|
+
* agent-scoped sector filtering and token budget enforcement.
|
|
7
|
+
*
|
|
8
|
+
* Consumer-only: reads from MIS APIs, never modifies memory stores.
|
|
9
|
+
* Graceful no-op when pro feature is unavailable.
|
|
10
|
+
*
|
|
11
|
+
* @module core/synapse/memory/memory-bridge
|
|
12
|
+
* @version 1.0.0
|
|
13
|
+
* @created Story SYN-10 - Pro Memory Bridge (Feature-Gated MIS Consumer)
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
'use strict';
|
|
17
|
+
|
|
18
|
+
const { estimateTokens } = require('../utils/tokens');
|
|
19
|
+
|
|
20
|
+
/** Memory bridge timeout in milliseconds. */
|
|
21
|
+
const BRIDGE_TIMEOUT_MS = 15;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Bracket-to-memory-layer mapping.
|
|
25
|
+
*
|
|
26
|
+
* FRESH → skip (no memory needed)
|
|
27
|
+
* MODERATE → Layer 1 metadata (~50 tokens)
|
|
28
|
+
* DEPLETED → Layer 2 chunks (~200 tokens)
|
|
29
|
+
* CRITICAL → Layer 3 full content (~1000 tokens)
|
|
30
|
+
*/
|
|
31
|
+
const BRACKET_LAYER_MAP = {
|
|
32
|
+
FRESH: { layer: 0, maxTokens: 0 },
|
|
33
|
+
MODERATE: { layer: 1, maxTokens: 50 },
|
|
34
|
+
DEPLETED: { layer: 2, maxTokens: 200 },
|
|
35
|
+
CRITICAL: { layer: 3, maxTokens: 1000 },
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/** Default sector for unknown agents. */
|
|
39
|
+
const DEFAULT_SECTORS = ['semantic'];
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* MemoryBridge — Feature-gated MIS consumer.
|
|
43
|
+
*
|
|
44
|
+
* Provides bracket-aware memory retrieval with:
|
|
45
|
+
* - Feature gate check (sync, <1ms)
|
|
46
|
+
* - Agent-scoped sector filtering
|
|
47
|
+
* - Token budget enforcement
|
|
48
|
+
* - Session-level caching
|
|
49
|
+
* - Timeout protection (<15ms)
|
|
50
|
+
* - Error catch-all with warn-and-proceed
|
|
51
|
+
*/
|
|
52
|
+
class MemoryBridge {
|
|
53
|
+
/**
|
|
54
|
+
* @param {object} [options={}]
|
|
55
|
+
* @param {number} [options.timeout=15] - Max execution time in ms
|
|
56
|
+
*/
|
|
57
|
+
constructor(options = {}) {
|
|
58
|
+
this._timeout = options.timeout || BRIDGE_TIMEOUT_MS;
|
|
59
|
+
this._provider = null;
|
|
60
|
+
this._featureGate = null;
|
|
61
|
+
this._initialized = false;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Lazy-load feature gate and provider.
|
|
66
|
+
* Isolates pro dependency to runtime only.
|
|
67
|
+
*
|
|
68
|
+
* @private
|
|
69
|
+
*/
|
|
70
|
+
_init() {
|
|
71
|
+
if (this._initialized) return;
|
|
72
|
+
this._initialized = true;
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
const { featureGate } = require('../../../../pro/license/feature-gate');
|
|
76
|
+
this._featureGate = featureGate;
|
|
77
|
+
} catch {
|
|
78
|
+
// Pro not installed — feature gate unavailable
|
|
79
|
+
this._featureGate = null;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Lazy-load the SynapseMemoryProvider (pro).
|
|
85
|
+
* Only loaded when feature gate confirms availability.
|
|
86
|
+
*
|
|
87
|
+
* @private
|
|
88
|
+
* @returns {object|null} Provider instance or null
|
|
89
|
+
*/
|
|
90
|
+
_getProvider() {
|
|
91
|
+
if (this._provider) return this._provider;
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
const { SynapseMemoryProvider } = require('../../../../pro/memory/synapse-memory-provider');
|
|
95
|
+
this._provider = new SynapseMemoryProvider();
|
|
96
|
+
return this._provider;
|
|
97
|
+
} catch {
|
|
98
|
+
// Provider not available
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Get memory hints for the current prompt context.
|
|
105
|
+
*
|
|
106
|
+
* Returns an array of memory hint objects suitable for injection
|
|
107
|
+
* into the SYNAPSE pipeline. Gracefully returns [] when:
|
|
108
|
+
* - Pro feature is unavailable
|
|
109
|
+
* - Bracket is FRESH (no memory needed)
|
|
110
|
+
* - Provider fails or times out
|
|
111
|
+
* - Any error occurs
|
|
112
|
+
*
|
|
113
|
+
* @param {string} agentId - Active agent ID (e.g., 'dev', 'qa')
|
|
114
|
+
* @param {string} bracket - Context bracket (FRESH, MODERATE, DEPLETED, CRITICAL)
|
|
115
|
+
* @param {number} tokenBudget - Max tokens available for memory hints
|
|
116
|
+
* @returns {Promise<Array<{content: string, source: string, relevance: number, tokens: number}>>}
|
|
117
|
+
*/
|
|
118
|
+
async getMemoryHints(agentId, bracket, tokenBudget) {
|
|
119
|
+
try {
|
|
120
|
+
// 1. Feature gate check (sync, <1ms)
|
|
121
|
+
this._init();
|
|
122
|
+
if (!this._featureGate || !this._featureGate.isAvailable('pro.memory.synapse')) {
|
|
123
|
+
return [];
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// 2. Bracket check — FRESH needs no memory
|
|
127
|
+
const bracketConfig = BRACKET_LAYER_MAP[bracket];
|
|
128
|
+
if (!bracketConfig || bracketConfig.layer === 0) {
|
|
129
|
+
return [];
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// 3. Calculate effective token budget
|
|
133
|
+
const effectiveBudget = Math.min(
|
|
134
|
+
bracketConfig.maxTokens,
|
|
135
|
+
tokenBudget > 0 ? tokenBudget : bracketConfig.maxTokens,
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
if (effectiveBudget <= 0) {
|
|
139
|
+
return [];
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// 4. Load provider
|
|
143
|
+
const provider = this._getProvider();
|
|
144
|
+
if (!provider) {
|
|
145
|
+
return [];
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// 5. Execute with timeout protection
|
|
149
|
+
const hints = await this._executeWithTimeout(
|
|
150
|
+
() => provider.getMemories(agentId, bracket, effectiveBudget),
|
|
151
|
+
this._timeout,
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
// 6. Enforce token budget on results
|
|
155
|
+
return this._enforceTokenBudget(hints || [], effectiveBudget);
|
|
156
|
+
} catch (error) {
|
|
157
|
+
// Catch-all: warn and proceed with empty results
|
|
158
|
+
console.warn(`[synapse:memory-bridge] Error getting memory hints: ${error.message}`);
|
|
159
|
+
return [];
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Execute a function with timeout protection.
|
|
165
|
+
*
|
|
166
|
+
* @private
|
|
167
|
+
* @param {Function} fn - Async function to execute
|
|
168
|
+
* @param {number} timeoutMs - Timeout in milliseconds
|
|
169
|
+
* @returns {Promise<*>} Result or empty array on timeout
|
|
170
|
+
*/
|
|
171
|
+
async _executeWithTimeout(fn, timeoutMs) {
|
|
172
|
+
return new Promise((resolve) => {
|
|
173
|
+
const timer = setTimeout(() => {
|
|
174
|
+
console.warn(`[synapse:memory-bridge] Timeout after ${timeoutMs}ms`);
|
|
175
|
+
resolve([]);
|
|
176
|
+
}, timeoutMs);
|
|
177
|
+
|
|
178
|
+
Promise.resolve(fn())
|
|
179
|
+
.then((result) => {
|
|
180
|
+
clearTimeout(timer);
|
|
181
|
+
resolve(result);
|
|
182
|
+
})
|
|
183
|
+
.catch((error) => {
|
|
184
|
+
clearTimeout(timer);
|
|
185
|
+
console.warn(`[synapse:memory-bridge] Provider error: ${error.message}`);
|
|
186
|
+
resolve([]);
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Enforce token budget on hint array.
|
|
193
|
+
* Removes hints from end until within budget.
|
|
194
|
+
*
|
|
195
|
+
* @private
|
|
196
|
+
* @param {Array<{content: string, tokens: number}>} hints
|
|
197
|
+
* @param {number} budget
|
|
198
|
+
* @returns {Array}
|
|
199
|
+
*/
|
|
200
|
+
_enforceTokenBudget(hints, budget) {
|
|
201
|
+
if (!Array.isArray(hints) || hints.length === 0) {
|
|
202
|
+
return [];
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const result = [];
|
|
206
|
+
let tokensUsed = 0;
|
|
207
|
+
|
|
208
|
+
for (const hint of hints) {
|
|
209
|
+
const hintTokens = hint.tokens || estimateTokens(hint.content || '');
|
|
210
|
+
if (tokensUsed + hintTokens > budget) {
|
|
211
|
+
break;
|
|
212
|
+
}
|
|
213
|
+
result.push({ ...hint, tokens: hintTokens });
|
|
214
|
+
tokensUsed += hintTokens;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return result;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Clear provider cache. Used for testing and session reset.
|
|
222
|
+
*/
|
|
223
|
+
clearCache() {
|
|
224
|
+
if (this._provider && typeof this._provider.clearCache === 'function') {
|
|
225
|
+
this._provider.clearCache();
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Reset internal state. Used for testing.
|
|
231
|
+
*
|
|
232
|
+
* @private
|
|
233
|
+
*/
|
|
234
|
+
_reset() {
|
|
235
|
+
this._provider = null;
|
|
236
|
+
this._featureGate = null;
|
|
237
|
+
this._initialized = false;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
module.exports = {
|
|
242
|
+
MemoryBridge,
|
|
243
|
+
BRACKET_LAYER_MAP,
|
|
244
|
+
BRIDGE_TIMEOUT_MS,
|
|
245
|
+
DEFAULT_SECTORS,
|
|
246
|
+
};
|
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
* @created Story SYN-6 - SynapseEngine Orchestrator + Output Formatter
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
+
const { estimateTokens } = require('../utils/tokens');
|
|
14
|
+
|
|
13
15
|
// ---------------------------------------------------------------------------
|
|
14
16
|
// Section ordering (DESIGN doc section 14)
|
|
15
17
|
// ---------------------------------------------------------------------------
|
|
@@ -29,6 +31,7 @@ const SECTION_ORDER = [
|
|
|
29
31
|
'TASK',
|
|
30
32
|
'SQUAD',
|
|
31
33
|
'KEYWORD',
|
|
34
|
+
'MEMORY_HINTS',
|
|
32
35
|
'STAR_COMMANDS',
|
|
33
36
|
'DEVMODE',
|
|
34
37
|
'SUMMARY',
|
|
@@ -45,6 +48,7 @@ const LAYER_TO_SECTION = {
|
|
|
45
48
|
task: 'TASK',
|
|
46
49
|
squad: 'SQUAD',
|
|
47
50
|
keyword: 'KEYWORD',
|
|
51
|
+
memory: 'MEMORY_HINTS',
|
|
48
52
|
'star-command': 'STAR_COMMANDS',
|
|
49
53
|
};
|
|
50
54
|
|
|
@@ -237,6 +241,34 @@ function formatStarCommands(result) {
|
|
|
237
241
|
return lines.join('\n');
|
|
238
242
|
}
|
|
239
243
|
|
|
244
|
+
// ---------------------------------------------------------------------------
|
|
245
|
+
// Memory Hints Section (SYN-10)
|
|
246
|
+
// ---------------------------------------------------------------------------
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Format the MEMORY HINTS section.
|
|
250
|
+
*
|
|
251
|
+
* Only included when hints array is non-empty.
|
|
252
|
+
* Each hint displays source, relevance, and content.
|
|
253
|
+
*
|
|
254
|
+
* @param {object} result - Layer result { rules (hint objects), metadata }
|
|
255
|
+
* @returns {string}
|
|
256
|
+
*/
|
|
257
|
+
function formatMemoryHints(result) {
|
|
258
|
+
const lines = ['[MEMORY HINTS]'];
|
|
259
|
+
|
|
260
|
+
for (const hint of result.rules) {
|
|
261
|
+
const source = hint.source || 'memory';
|
|
262
|
+
const relevance = typeof hint.relevance === 'number'
|
|
263
|
+
? `${(hint.relevance * 100).toFixed(0)}%`
|
|
264
|
+
: '?%';
|
|
265
|
+
const content = hint.content || '';
|
|
266
|
+
lines.push(` [${source}] (relevance: ${relevance}) ${content}`);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return lines.join('\n');
|
|
270
|
+
}
|
|
271
|
+
|
|
240
272
|
// ---------------------------------------------------------------------------
|
|
241
273
|
// DEVMODE Section (DESIGN doc section 13)
|
|
242
274
|
// ---------------------------------------------------------------------------
|
|
@@ -346,18 +378,6 @@ function formatSummary(results, _metrics) {
|
|
|
346
378
|
// Token Budget Enforcement
|
|
347
379
|
// ---------------------------------------------------------------------------
|
|
348
380
|
|
|
349
|
-
/**
|
|
350
|
-
* Estimate the number of tokens from a string.
|
|
351
|
-
*
|
|
352
|
-
* Uses the proven heuristic: tokens ~ string.length / 4
|
|
353
|
-
*
|
|
354
|
-
* @param {string} text - Text to estimate
|
|
355
|
-
* @returns {number} Estimated token count
|
|
356
|
-
*/
|
|
357
|
-
function estimateTokens(text) {
|
|
358
|
-
return Math.ceil((text || '').length / 4);
|
|
359
|
-
}
|
|
360
|
-
|
|
361
381
|
/**
|
|
362
382
|
* Enforce a token budget by removing sections from the end.
|
|
363
383
|
*
|
|
@@ -381,6 +401,7 @@ function enforceTokenBudget(sections, sectionIds, tokenBudget) {
|
|
|
381
401
|
const TRUNCATION_ORDER = [
|
|
382
402
|
'SUMMARY',
|
|
383
403
|
'KEYWORD',
|
|
404
|
+
'MEMORY_HINTS',
|
|
384
405
|
'SQUAD',
|
|
385
406
|
'STAR_COMMANDS',
|
|
386
407
|
'DEVMODE',
|
|
@@ -428,6 +449,7 @@ const SECTION_FORMATTERS = {
|
|
|
428
449
|
TASK: formatTask,
|
|
429
450
|
SQUAD: formatSquad,
|
|
430
451
|
KEYWORD: formatKeyword,
|
|
452
|
+
MEMORY_HINTS: formatMemoryHints,
|
|
431
453
|
STAR_COMMANDS: formatStarCommands,
|
|
432
454
|
};
|
|
433
455
|
|