0xray 4.0.38 → 4.0.40
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/.grok-plugin/plugin.json +1 -1
- package/.xray/features.json +1 -1
- package/AGENTS-consumer.md +1 -1
- package/AGENTS.md +1 -1
- package/README.md +2 -2
- package/SKILLS.md +1 -1
- package/dist/AGENTS.md +1 -1
- package/dist/CHANGELOG.md +25 -0
- package/dist/README.md +2 -2
- package/dist/SKILLS.md +1 -1
- package/dist/integrations/hooks/goggles-planes.json +1 -1
- package/dist/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
- package/docs-site/docs/plates/activity-log.md +1 -1
- package/docs-site/docs/plates/cursor-compact.md +1 -1
- package/docs-site/docs/plates/grok-compact.md +1 -1
- package/docs-site/docs/plates/grokbot.md +1 -1
- package/docs-site/docs/plates/house.md +1 -1
- package/docs-site/docs/plates/index.md +1 -1
- package/docs-site/docs/plates/inference-files.md +1 -1
- package/docs-site/docs/plates/kept-line.md +1 -1
- package/docs-site/docs/plates/lens-gate.md +1 -1
- package/docs-site/docs/plates/lens-page.md +1 -1
- package/docs-site/docs/plates/live-mesh.md +41 -0
- package/docs-site/docs/plates/notes-page.md +1 -1
- package/docs-site/docs/plates/package-face.md +1 -1
- package/docs-site/docs/plates/payload-heat.md +1 -1
- package/docs-site/docs/plates/pickup-stamp.md +1 -1
- package/docs-site/docs/plates/pre-tool.md +1 -1
- package/docs-site/docs/plates/record-map.md +1 -1
- package/docs-site/docs/plates/reflection-page.md +1 -1
- package/docs-site/docs/plates/review.md +1 -1
- package/docs-site/docs/plates/session-capture.md +1 -1
- package/docs-site/docs/plates/site-manual.md +1 -1
- package/docs-site/docs/plates/stamp-plate.md +1 -1
- package/docs-site/docs/plates/station-card.md +1 -1
- package/docs-site/docs/plates/station-heat.md +1 -1
- package/docs-site/docs/plates/suit-organs.md +1 -1
- package/docs-site/docs/plates/suit-settings.md +1 -1
- package/docs-site/docs/plates/suit-wear.md +1 -1
- package/docs-site/docs/plates/trail-state.md +1 -1
- package/docs-site/docs/plates/work-fresh.md +1 -1
- package/docs-site/docs/plates/write-home.md +1 -1
- package/llms.txt +1 -1
- package/package.json +2 -1
- package/scripts/foundry/plant/skills/mill/SKILL.md +1 -1
- package/scripts/node/consumer-install-smoke.mjs +3 -3
- package/scripts/node/playwright-smoke-server.cjs +27 -0
- package/src/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
- package/vendor/@0xray/repertoire/README.md +1 -1
- package/vendor/@0xray/repertoire/dist/RepertoireService.js +1 -0
- package/vendor/@0xray/repertoire/dist/orchestrator-bridge/RepertoireOrchestratorBridge.js +4 -1
- package/vendor/@0xray/repertoire/dist/orchestrator-bridge/signal-injector.d.ts +5 -0
- package/vendor/@0xray/repertoire/dist/orchestrator-bridge/signal-injector.js +16 -6
- package/vendor/@0xray/repertoire/dist/paths.js +1 -2
- package/vendor/@0xray/repertoire/dist/provider/memory-routing-provider.d.ts +16 -6
- package/vendor/@0xray/repertoire/dist/provider/memory-routing-provider.js +19 -2
- package/vendor/@0xray/repertoire/dist/registry/CuratedSignalsManager.js +34 -2
- package/vendor/@0xray/repertoire/dist/registry/InferenceStateManager.d.ts +3 -0
- package/vendor/@0xray/repertoire/dist/registry/InferenceStateManager.js +25 -1
- package/vendor/@0xray/repertoire/dist/synthesis/meta-inference-engine.d.ts +37 -2
- package/vendor/@0xray/repertoire/dist/synthesis/meta-inference-engine.js +176 -19
- package/vendor/@0xray/repertoire/dist/types.d.ts +6 -0
- package/vendor/@0xray/repertoire/package.json +2 -2
- package/vendor/@0xray/repertoire/scripts/verify-grok-suit.mjs +9 -6
- package/vendor/@0xray/repertoire/scripts/verify-synthesis-dogfood.mjs +109 -5
- package/xray/features.json +1 -1
- package/.opencode/agents/api-design.yml +0 -31
- package/.opencode/agents/architect.yml +0 -15
- package/.opencode/agents/architecture-patterns.yml +0 -14
- package/.opencode/agents/auto-format.yml +0 -14
- package/.opencode/agents/backend-engineer.yml +0 -12
- package/.opencode/agents/boot-orchestrator.yml +0 -14
- package/.opencode/agents/bug-triage-specialist.yml +0 -15
- package/.opencode/agents/code-analyzer.yml +0 -12
- package/.opencode/agents/code-reviewer.yml +0 -14
- package/.opencode/agents/content-creator.yml +0 -12
- package/.opencode/agents/database-engineer.yml +0 -12
- package/.opencode/agents/devops-engineer.yml +0 -12
- package/.opencode/agents/framework-compliance-audit.yml +0 -14
- package/.opencode/agents/frontend-engineer.yml +0 -12
- package/.opencode/agents/frontend-ui-ux-engineer.yml +0 -12
- package/.opencode/agents/git-workflow.yml +0 -15
- package/.opencode/agents/growth-strategist.yml +0 -12
- package/.opencode/agents/hermes-agent.yml +0 -12
- package/.opencode/agents/inference-improve.yml +0 -12
- package/.opencode/agents/lint.yml +0 -14
- package/.opencode/agents/log-monitor.yml +0 -12
- package/.opencode/agents/mobile-developer.yml +0 -12
- package/.opencode/agents/model-health-check.yml +0 -12
- package/.opencode/agents/multimodal-looker.yml +0 -12
- package/.opencode/agents/performance-analysis.yml +0 -12
- package/.opencode/agents/performance-engineer.yml +0 -12
- package/.opencode/agents/performance-optimization.yml +0 -12
- package/.opencode/agents/processor-pipeline.yml +0 -14
- package/.opencode/agents/project-analysis.yml +0 -12
- package/.opencode/agents/refactorer.yml +0 -12
- package/.opencode/agents/researcher.yml +0 -12
- package/.opencode/agents/security-auditor.yml +0 -12
- package/.opencode/agents/security-scan.yml +0 -12
- package/.opencode/agents/seo-consultant.yml +0 -12
- package/.opencode/agents/session-management.yml +0 -12
- package/.opencode/agents/state-manager.yml +0 -12
- package/.opencode/agents/storyteller.yml +0 -12
- package/.opencode/agents/strategist.yml +0 -12
- package/.opencode/agents/tech-writer.yml +0 -12
- package/.opencode/agents/testing-best-practices.yml +0 -12
- package/.opencode/agents/testing-lead.yml +0 -12
- package/.opencode/agents/ui-ux-design.yml +0 -12
- package/.opencode/codex.codex +0 -8
- package/.opencode/commands/auto-format.md +0 -99
- package/.opencode/commands/auto-summary-capture.md +0 -90
- package/.opencode/commands/dependency-audit.md +0 -184
- package/.opencode/commands/enforcer-daily-scan.md +0 -137
- package/.opencode/commands/framework-compliance-audit.md +0 -205
- package/.opencode/commands/interactive-validator.md +0 -75
- package/.opencode/commands/job-summary-logger.md +0 -68
- package/.opencode/commands/lint.md +0 -11
- package/.opencode/commands/mode-switch.md +0 -95
- package/.opencode/commands/model-health-check.md +0 -186
- package/.opencode/commands/performance-analysis.md +0 -144
- package/.opencode/commands/pre-commit-introspection.md +0 -185
- package/.opencode/commands/pre-commit-introspection.sh +0 -133
- package/.opencode/commands/security-scan.md +0 -157
- package/.opencode/commands/sisyphus-validation.md +0 -128
- package/.opencode/commands/summary-logger.md +0 -83
- package/.opencode/enforcer-config.json +0 -282
- package/.opencode/workflows/post-deployment-audit.yml +0 -123
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Loaded dynamically by 0xRay via features.json memory_routing.module_path.
|
|
5
5
|
* Other providers can follow the same createMemoryRoutingProvider() export pattern.
|
|
6
6
|
*/
|
|
7
|
+
import { type MetaInferenceFailureReason, type MetaInferenceModel } from '../synthesis/meta-inference-engine.js';
|
|
7
8
|
/** Mirrors 0xRay memory-routing/types.ts — kept local to avoid compile-time coupling */
|
|
8
9
|
export interface MemoryAgentCapability {
|
|
9
10
|
capabilities: string[];
|
|
@@ -80,6 +81,14 @@ export interface OrchestratorFeedbackEntry {
|
|
|
80
81
|
dynamoResult?: Record<string, unknown>;
|
|
81
82
|
lesson?: string;
|
|
82
83
|
}
|
|
84
|
+
export type MetaInferenceRefreshReason = 'synthesized' | 'nothing_to_process' | MetaInferenceFailureReason;
|
|
85
|
+
export interface MetaInferenceRefreshResult {
|
|
86
|
+
refreshed: boolean;
|
|
87
|
+
/** Machine-readable outcome. A failed model is never `synthesized`. */
|
|
88
|
+
reason: MetaInferenceRefreshReason;
|
|
89
|
+
/** Present when the model was missing or the call failed. */
|
|
90
|
+
error?: string;
|
|
91
|
+
}
|
|
83
92
|
export interface MemoryRoutingProviderConfig {
|
|
84
93
|
dataDir?: string;
|
|
85
94
|
signalsPath?: string;
|
|
@@ -87,6 +96,11 @@ export interface MemoryRoutingProviderConfig {
|
|
|
87
96
|
statePath?: string;
|
|
88
97
|
feedbackDir?: string;
|
|
89
98
|
projectRoot?: string;
|
|
99
|
+
/**
|
|
100
|
+
* Meta-inference model. `null` means none is configured (refresh fails closed).
|
|
101
|
+
* Omit to keep the engine's hermes CLI default.
|
|
102
|
+
*/
|
|
103
|
+
hermesCommand?: MetaInferenceModel | null;
|
|
90
104
|
}
|
|
91
105
|
export interface MemoryRoutingProvider {
|
|
92
106
|
readonly id: string;
|
|
@@ -104,9 +118,7 @@ export interface MemoryRoutingProvider {
|
|
|
104
118
|
projectRoot: string;
|
|
105
119
|
dueReason?: string | null;
|
|
106
120
|
}): Record<string, unknown> | null;
|
|
107
|
-
refreshMetaInference?(): Promise<
|
|
108
|
-
refreshed: boolean;
|
|
109
|
-
}>;
|
|
121
|
+
refreshMetaInference?(): Promise<MetaInferenceRefreshResult>;
|
|
110
122
|
}
|
|
111
123
|
export type ProviderAvailabilityReason = 'ok' | 'empty_registry' | 'signals_unreadable' | 'signals_missing';
|
|
112
124
|
export interface ProviderAvailabilityStatus {
|
|
@@ -143,9 +155,7 @@ export declare class RepertoireMemoryRoutingProvider implements MemoryRoutingPro
|
|
|
143
155
|
projectRoot: string;
|
|
144
156
|
dueReason?: string | null;
|
|
145
157
|
}): Record<string, unknown> | null;
|
|
146
|
-
refreshMetaInference(): Promise<
|
|
147
|
-
refreshed: boolean;
|
|
148
|
-
}>;
|
|
158
|
+
refreshMetaInference(): Promise<MetaInferenceRefreshResult>;
|
|
149
159
|
}
|
|
150
160
|
/** Factory export — 0xRay provider-loader calls this by name */
|
|
151
161
|
export declare function createMemoryRoutingProvider(config?: MemoryRoutingProviderConfig): MemoryRoutingProvider;
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import { existsSync } from 'node:fs';
|
|
8
8
|
import { DEFAULT_SIGNALS_PATH, defaultWritablePaths, hydrateWritableSignals, resolveReadableConfigPath, resolveWritableConfigPath, } from '../paths.js';
|
|
9
9
|
import { RepertoireService } from '../RepertoireService.js';
|
|
10
|
+
import { MetaInferenceModelError, } from '../synthesis/meta-inference-engine.js';
|
|
10
11
|
function toRepertoireCaps(caps) {
|
|
11
12
|
return {
|
|
12
13
|
capabilities: caps.capabilities,
|
|
@@ -71,6 +72,9 @@ export class RepertoireMemoryRoutingProvider {
|
|
|
71
72
|
logDir: resolveWritableConfigPath(config.logDir, cwd, writable.logDir),
|
|
72
73
|
feedbackDir: resolveWritableConfigPath(config.feedbackDir, cwd, writable.feedbackDir),
|
|
73
74
|
});
|
|
75
|
+
if (config.hermesCommand !== undefined) {
|
|
76
|
+
this.service.metaInference.configureModel(config.hermesCommand);
|
|
77
|
+
}
|
|
74
78
|
}
|
|
75
79
|
getAvailabilityStatus() {
|
|
76
80
|
try {
|
|
@@ -199,8 +203,21 @@ export class RepertoireMemoryRoutingProvider {
|
|
|
199
203
|
return this.service.buildSynthesisContext(opts.projectRoot, opts.dueReason ?? null);
|
|
200
204
|
}
|
|
201
205
|
async refreshMetaInference() {
|
|
202
|
-
|
|
203
|
-
|
|
206
|
+
try {
|
|
207
|
+
const report = await this.service.runMetaInference();
|
|
208
|
+
if (report === null) {
|
|
209
|
+
return { refreshed: false, reason: 'nothing_to_process' };
|
|
210
|
+
}
|
|
211
|
+
return { refreshed: true, reason: 'synthesized' };
|
|
212
|
+
}
|
|
213
|
+
catch (error) {
|
|
214
|
+
if (error instanceof MetaInferenceModelError) {
|
|
215
|
+
return { refreshed: false, reason: error.reason, error: error.message };
|
|
216
|
+
}
|
|
217
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
218
|
+
process.stderr.write(`[meta-inference] state_error: ${message}\n`);
|
|
219
|
+
return { refreshed: false, reason: 'state_error', error: message };
|
|
220
|
+
}
|
|
204
221
|
}
|
|
205
222
|
}
|
|
206
223
|
/** Factory export — 0xRay provider-loader calls this by name */
|
|
@@ -150,6 +150,38 @@ function isSpeechMint(signal) {
|
|
|
150
150
|
return signal.status === 'proposed' && signal.tags.includes('learned');
|
|
151
151
|
}
|
|
152
152
|
const FIELD_PRIMITIVE_NAME = /^[A-Za-z][A-Za-z0-9_-]{2,119}$/;
|
|
153
|
+
/** A content word after a spaced law extends the id. A function word is the sentence. */
|
|
154
|
+
const PROSE_TAIL = new Set([
|
|
155
|
+
'a', 'an', 'the', 'as', 'and', 'or', 'but', 'if', 'of', 'to', 'for', 'in', 'on', 'at', 'by',
|
|
156
|
+
'with', 'from', 'into', 'over', 'after', 'before', 'about', 'than', 'then', 'that', 'this',
|
|
157
|
+
'these', 'those', 'it', 'its', 'is', 'are', 'was', 'were', 'be', 'been', 'not', 'no', 'so',
|
|
158
|
+
'when', 'while', 'where', 'which', 'who', 'what', 'how', 'also', 'only', 'just', 'still',
|
|
159
|
+
'never', 'today',
|
|
160
|
+
]);
|
|
161
|
+
/** A longer id does not count. Spaced prose may continue after a hyphenated id. */
|
|
162
|
+
function boundedId(text, id, spaced) {
|
|
163
|
+
if (!id)
|
|
164
|
+
return false;
|
|
165
|
+
const escaped = id.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
166
|
+
const re = new RegExp(`(?:^|[^a-z0-9-])${escaped}`, 'g');
|
|
167
|
+
let match;
|
|
168
|
+
while ((match = re.exec(text)) !== null) {
|
|
169
|
+
const after = text.slice(match.index + match[0].length);
|
|
170
|
+
if (spaced ? spacedTailIsProse(after) : !/^[a-z0-9-]/.test(after))
|
|
171
|
+
return true;
|
|
172
|
+
if (match.index === re.lastIndex)
|
|
173
|
+
re.lastIndex += 1;
|
|
174
|
+
}
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
function spacedTailIsProse(after) {
|
|
178
|
+
if (/^-[a-z0-9]/.test(after) || /^[a-z0-9]/.test(after))
|
|
179
|
+
return false;
|
|
180
|
+
const word = /^\s*([a-z0-9]+)/.exec(after);
|
|
181
|
+
if (!word || !/^\s/.test(after))
|
|
182
|
+
return true;
|
|
183
|
+
return PROSE_TAIL.has(word[1]);
|
|
184
|
+
}
|
|
153
185
|
/**
|
|
154
186
|
* The diary names a law only when it contains the signal id, or the id with
|
|
155
187
|
* hyphens read as spaces. A repo tail, leftover tokens, and two definition
|
|
@@ -160,10 +192,10 @@ export function signalNameInText(text, name) {
|
|
|
160
192
|
const id = name.toLowerCase();
|
|
161
193
|
if (!id)
|
|
162
194
|
return false;
|
|
163
|
-
if (normalized
|
|
195
|
+
if (boundedId(normalized, id, false))
|
|
164
196
|
return true;
|
|
165
197
|
const spaced = id.replace(/-/g, ' ');
|
|
166
|
-
return spaced !== id && normalized
|
|
198
|
+
return spaced !== id && boundedId(normalized, spaced, true);
|
|
167
199
|
}
|
|
168
200
|
const GROOVER_EXPERIMENT_NAMES = new Set([
|
|
169
201
|
'criteria_selection_gap',
|
|
@@ -3,6 +3,9 @@ export declare class InferenceStateManager {
|
|
|
3
3
|
private readonly filePath;
|
|
4
4
|
constructor(filePath?: string);
|
|
5
5
|
load(): InferenceState;
|
|
6
|
+
/** Remember a failed model attempt without marking entries processed or touching lastRun. */
|
|
7
|
+
recordModelFailure(failedAt: string): void;
|
|
8
|
+
clearModelBackoff(): void;
|
|
6
9
|
save(state: InferenceState): void;
|
|
7
10
|
isProcessed(id: string): boolean;
|
|
8
11
|
markProcessed(ids: string[], kind?: 'comment' | 'session' | 'post'): void;
|
|
@@ -10,12 +10,30 @@ export class InferenceStateManager {
|
|
|
10
10
|
return this.createEmpty();
|
|
11
11
|
}
|
|
12
12
|
const raw = JSON.parse(readFileSync(this.filePath, 'utf8'));
|
|
13
|
-
|
|
13
|
+
const state = {
|
|
14
14
|
processedCommentIds: raw.processedCommentIds ?? [],
|
|
15
15
|
processedSessionIds: raw.processedSessionIds ?? [],
|
|
16
16
|
processedPostIds: raw.processedPostIds ?? [],
|
|
17
17
|
lastRun: raw.lastRun ?? null,
|
|
18
18
|
};
|
|
19
|
+
const backoff = readBackoff(raw.modelBackoff);
|
|
20
|
+
if (backoff)
|
|
21
|
+
state.modelBackoff = backoff;
|
|
22
|
+
return state;
|
|
23
|
+
}
|
|
24
|
+
/** Remember a failed model attempt without marking entries processed or touching lastRun. */
|
|
25
|
+
recordModelFailure(failedAt) {
|
|
26
|
+
const state = this.load();
|
|
27
|
+
const failures = (state.modelBackoff?.failures ?? 0) + 1;
|
|
28
|
+
state.modelBackoff = { failedAt, failures };
|
|
29
|
+
this.save(state);
|
|
30
|
+
}
|
|
31
|
+
clearModelBackoff() {
|
|
32
|
+
const state = this.load();
|
|
33
|
+
if (!state.modelBackoff)
|
|
34
|
+
return;
|
|
35
|
+
delete state.modelBackoff;
|
|
36
|
+
this.save(state);
|
|
19
37
|
}
|
|
20
38
|
save(state) {
|
|
21
39
|
const dir = dirname(this.filePath);
|
|
@@ -58,4 +76,10 @@ export class InferenceStateManager {
|
|
|
58
76
|
};
|
|
59
77
|
}
|
|
60
78
|
}
|
|
79
|
+
function readBackoff(value) {
|
|
80
|
+
if (!value || typeof value.failedAt !== 'string')
|
|
81
|
+
return null;
|
|
82
|
+
const failures = typeof value.failures === 'number' && value.failures > 0 ? value.failures : 1;
|
|
83
|
+
return { failedAt: value.failedAt, failures };
|
|
84
|
+
}
|
|
61
85
|
//# sourceMappingURL=InferenceStateManager.js.map
|
|
@@ -1,11 +1,32 @@
|
|
|
1
1
|
import type { SynthesisReport } from '../types.js';
|
|
2
|
+
export type MetaInferenceModel = (prompt: string) => unknown;
|
|
3
|
+
export type MetaInferenceFailureReason = 'model_unavailable' | 'model_call_failed' | 'model_output_invalid' | 'model_backoff' | 'state_error';
|
|
4
|
+
/** First wait after a failed model attempt. Doubles until MODEL_BACKOFF_CAP_MS. */
|
|
5
|
+
export declare const MODEL_BACKOFF_BASE_MS: number;
|
|
6
|
+
/** Upper bound so a dead hermes CLI cannot push the wait without limit. */
|
|
7
|
+
export declare const MODEL_BACKOFF_CAP_MS: number;
|
|
8
|
+
export declare function modelBackoffDelayMs(failures: number): number;
|
|
9
|
+
export declare function formatMetaInferenceFailure(error: unknown): string;
|
|
10
|
+
/** Raised when synthesis cannot be produced by a model. Callers must not treat it as a report. */
|
|
11
|
+
export declare class MetaInferenceModelError extends Error {
|
|
12
|
+
readonly reason: MetaInferenceFailureReason;
|
|
13
|
+
constructor(reason: MetaInferenceFailureReason, message: string, options?: {
|
|
14
|
+
cause?: unknown;
|
|
15
|
+
});
|
|
16
|
+
}
|
|
2
17
|
export interface MetaInferenceEngineOptions {
|
|
3
18
|
logDir?: string;
|
|
4
19
|
statePath?: string;
|
|
5
20
|
reportPath?: string;
|
|
6
21
|
batchSize?: number;
|
|
7
22
|
maxEntries?: number;
|
|
8
|
-
|
|
23
|
+
/**
|
|
24
|
+
* Model call. Omit to use the hermes CLI.
|
|
25
|
+
* `null` means no model is configured — the run fails closed and does not call out.
|
|
26
|
+
*/
|
|
27
|
+
hermesCommand?: MetaInferenceModel | null;
|
|
28
|
+
/** Pinned in tests. Defaults to the system clock. */
|
|
29
|
+
now?: () => Date;
|
|
9
30
|
}
|
|
10
31
|
export declare class MetaInferenceEngine {
|
|
11
32
|
private readonly logDir;
|
|
@@ -14,10 +35,24 @@ export declare class MetaInferenceEngine {
|
|
|
14
35
|
private readonly batchSize;
|
|
15
36
|
private readonly maxEntries;
|
|
16
37
|
private readonly promptBuilder;
|
|
17
|
-
private readonly
|
|
38
|
+
private readonly now;
|
|
39
|
+
private model;
|
|
18
40
|
constructor(options?: MetaInferenceEngineOptions);
|
|
41
|
+
/** `null` configures no model. The next run fails closed instead of calling the hermes CLI. */
|
|
42
|
+
configureModel(command: MetaInferenceModel | null): void;
|
|
19
43
|
run(): Promise<SynthesisReport | null>;
|
|
20
44
|
private loadUnprocessedEntries;
|
|
45
|
+
private loadState;
|
|
46
|
+
private throwIfBackingOff;
|
|
47
|
+
/**
|
|
48
|
+
* A missing, failed, or junk model result must not produce a synthesis report.
|
|
49
|
+
* Entries stay unprocessed so a later run can retry them.
|
|
50
|
+
*/
|
|
51
|
+
private callModelOrRecord;
|
|
52
|
+
private callModel;
|
|
53
|
+
private noteModelFailure;
|
|
54
|
+
private fail;
|
|
55
|
+
private logFailure;
|
|
21
56
|
private appendReport;
|
|
22
57
|
private defaultHermesCommand;
|
|
23
58
|
}
|
|
@@ -3,6 +3,31 @@ import { join, dirname } from 'node:path';
|
|
|
3
3
|
import { execSync } from 'node:child_process';
|
|
4
4
|
import { SynthesisPromptBuilder } from './synthesis-prompt-builder.js';
|
|
5
5
|
import { InferenceStateManager } from '../registry/InferenceStateManager.js';
|
|
6
|
+
/** First wait after a failed model attempt. Doubles until MODEL_BACKOFF_CAP_MS. */
|
|
7
|
+
export const MODEL_BACKOFF_BASE_MS = 15 * 60 * 1000;
|
|
8
|
+
/** Upper bound so a dead hermes CLI cannot push the wait without limit. */
|
|
9
|
+
export const MODEL_BACKOFF_CAP_MS = 4 * 60 * 60 * 1000;
|
|
10
|
+
export function modelBackoffDelayMs(failures) {
|
|
11
|
+
const exponent = Math.max(0, Math.min(failures, 16) - 1);
|
|
12
|
+
const doubled = MODEL_BACKOFF_BASE_MS * 2 ** exponent;
|
|
13
|
+
return Math.min(doubled, MODEL_BACKOFF_CAP_MS);
|
|
14
|
+
}
|
|
15
|
+
export function formatMetaInferenceFailure(error) {
|
|
16
|
+
if (error instanceof MetaInferenceModelError) {
|
|
17
|
+
return oneLine(`meta-inference failed: ${error.reason}: ${error.message}`);
|
|
18
|
+
}
|
|
19
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
20
|
+
return oneLine(`meta-inference failed: state_error: ${message}`);
|
|
21
|
+
}
|
|
22
|
+
/** Raised when synthesis cannot be produced by a model. Callers must not treat it as a report. */
|
|
23
|
+
export class MetaInferenceModelError extends Error {
|
|
24
|
+
reason;
|
|
25
|
+
constructor(reason, message, options) {
|
|
26
|
+
super(message, options);
|
|
27
|
+
this.name = 'MetaInferenceModelError';
|
|
28
|
+
this.reason = reason;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
6
31
|
const DEFAULT_BATCH_SIZE = 1;
|
|
7
32
|
const DEFAULT_MAX_ENTRIES = 8;
|
|
8
33
|
export class MetaInferenceEngine {
|
|
@@ -12,17 +37,26 @@ export class MetaInferenceEngine {
|
|
|
12
37
|
batchSize;
|
|
13
38
|
maxEntries;
|
|
14
39
|
promptBuilder = new SynthesisPromptBuilder();
|
|
15
|
-
|
|
40
|
+
now;
|
|
41
|
+
model;
|
|
16
42
|
constructor(options = {}) {
|
|
17
43
|
this.logDir = options.logDir ?? 'logs/groover-inference';
|
|
18
44
|
this.stateManager = new InferenceStateManager(options.statePath ?? 'data/inference-state.json');
|
|
19
45
|
this.reportPath = options.reportPath ?? 'logs/meta-inference/synthesis.md';
|
|
20
46
|
this.batchSize = options.batchSize ?? DEFAULT_BATCH_SIZE;
|
|
21
47
|
this.maxEntries = options.maxEntries ?? DEFAULT_MAX_ENTRIES;
|
|
22
|
-
this.
|
|
48
|
+
this.now = options.now ?? (() => new Date());
|
|
49
|
+
this.model =
|
|
50
|
+
options.hermesCommand === null
|
|
51
|
+
? null
|
|
52
|
+
: (options.hermesCommand ?? ((prompt) => this.defaultHermesCommand(prompt)));
|
|
53
|
+
}
|
|
54
|
+
/** `null` configures no model. The next run fails closed instead of calling the hermes CLI. */
|
|
55
|
+
configureModel(command) {
|
|
56
|
+
this.model = command;
|
|
23
57
|
}
|
|
24
58
|
async run() {
|
|
25
|
-
const state = this.
|
|
59
|
+
const state = this.loadState();
|
|
26
60
|
const processed = new Set([
|
|
27
61
|
...state.processedCommentIds,
|
|
28
62
|
...state.processedSessionIds,
|
|
@@ -36,6 +70,7 @@ export class MetaInferenceEngine {
|
|
|
36
70
|
return null;
|
|
37
71
|
}
|
|
38
72
|
const entries = newEntries.slice(0, this.maxEntries);
|
|
73
|
+
this.throwIfBackingOff(state);
|
|
39
74
|
const batchResults = [];
|
|
40
75
|
let totalPass = 0;
|
|
41
76
|
let totalReject = 0;
|
|
@@ -68,23 +103,13 @@ export class MetaInferenceEngine {
|
|
|
68
103
|
analyzedSoFar: i + batch.length,
|
|
69
104
|
},
|
|
70
105
|
});
|
|
71
|
-
|
|
72
|
-
batchResults.push(this.runHermes(prompt));
|
|
73
|
-
}
|
|
74
|
-
catch {
|
|
75
|
-
// Continue with remaining batches
|
|
76
|
-
}
|
|
106
|
+
batchResults.push(this.callModelOrRecord(prompt));
|
|
77
107
|
}
|
|
78
108
|
const avgResonance = resonanceCount > 0 ? (resonanceSum / resonanceCount).toFixed(3) : 'N/A';
|
|
79
109
|
const finalPrompt = this.promptBuilder.buildFinalSynthesisPrompt(entries.length, { pass: totalPass, reject: totalReject, avgResonance }, batchResults, entries);
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
this.appendReport(entries.length, totalPass, resonanceCount > 0 ? resonanceSum / resonanceCount : null, finalReport);
|
|
84
|
-
}
|
|
85
|
-
catch {
|
|
86
|
-
finalReport = batchResults.join('\n\n---\n\n');
|
|
87
|
-
}
|
|
110
|
+
const finalReport = this.callModelOrRecord(finalPrompt);
|
|
111
|
+
this.stateManager.clearModelBackoff();
|
|
112
|
+
this.appendReport(entries.length, totalPass, resonanceCount > 0 ? resonanceSum / resonanceCount : null, finalReport);
|
|
88
113
|
const ids = entries.map((e) => e.comment_id ?? e.post_id ?? e.session_id).filter(Boolean);
|
|
89
114
|
const hasSession = entries.some((e) => e.session_id);
|
|
90
115
|
this.stateManager.markProcessed(ids, hasSession ? 'session' : 'comment');
|
|
@@ -118,13 +143,96 @@ export class MetaInferenceEngine {
|
|
|
118
143
|
processed.add(id);
|
|
119
144
|
}
|
|
120
145
|
}
|
|
121
|
-
catch {
|
|
122
|
-
|
|
146
|
+
catch (error) {
|
|
147
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
148
|
+
this.logFailure('malformed_log_line', `skipped malformed log line in ${file}: ${message}`);
|
|
123
149
|
}
|
|
124
150
|
}
|
|
125
151
|
}
|
|
126
152
|
return entries;
|
|
127
153
|
}
|
|
154
|
+
loadState() {
|
|
155
|
+
try {
|
|
156
|
+
return this.stateManager.load();
|
|
157
|
+
}
|
|
158
|
+
catch (error) {
|
|
159
|
+
throw this.fail('state_error', `meta-inference state could not be read: ${errorText(error)}`, error);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
throwIfBackingOff(state) {
|
|
163
|
+
const backoff = state.modelBackoff;
|
|
164
|
+
if (!backoff)
|
|
165
|
+
return;
|
|
166
|
+
const failedAt = Date.parse(backoff.failedAt);
|
|
167
|
+
if (Number.isNaN(failedAt))
|
|
168
|
+
return;
|
|
169
|
+
const wait = modelBackoffDelayMs(backoff.failures);
|
|
170
|
+
if (this.now().getTime() - failedAt < wait) {
|
|
171
|
+
throw this.fail('model_backoff', `meta-inference model call skipped until backoff ends (${wait}ms)`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* A missing, failed, or junk model result must not produce a synthesis report.
|
|
176
|
+
* Entries stay unprocessed so a later run can retry them.
|
|
177
|
+
*/
|
|
178
|
+
callModelOrRecord(prompt) {
|
|
179
|
+
try {
|
|
180
|
+
return this.callModel(prompt);
|
|
181
|
+
}
|
|
182
|
+
catch (error) {
|
|
183
|
+
this.noteModelFailure(error);
|
|
184
|
+
throw error;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
callModel(prompt) {
|
|
188
|
+
if (this.model === null) {
|
|
189
|
+
throw this.fail('model_unavailable', 'meta-inference model is not configured');
|
|
190
|
+
}
|
|
191
|
+
let output;
|
|
192
|
+
try {
|
|
193
|
+
output = this.model(prompt);
|
|
194
|
+
}
|
|
195
|
+
catch (error) {
|
|
196
|
+
if (error instanceof MetaInferenceModelError) {
|
|
197
|
+
this.logFailure(error.reason, error.message);
|
|
198
|
+
throw error;
|
|
199
|
+
}
|
|
200
|
+
if (isMissingHermes(error)) {
|
|
201
|
+
throw this.fail('model_unavailable', 'meta-inference model is not available: hermes CLI not found', error);
|
|
202
|
+
}
|
|
203
|
+
throw this.fail('model_call_failed', `meta-inference model call failed: ${errorText(error)}`, error);
|
|
204
|
+
}
|
|
205
|
+
try {
|
|
206
|
+
return acceptModelOutput(output);
|
|
207
|
+
}
|
|
208
|
+
catch (error) {
|
|
209
|
+
if (error instanceof MetaInferenceModelError) {
|
|
210
|
+
this.logFailure(error.reason, error.message);
|
|
211
|
+
throw error;
|
|
212
|
+
}
|
|
213
|
+
throw error;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
noteModelFailure(error) {
|
|
217
|
+
if (!(error instanceof MetaInferenceModelError))
|
|
218
|
+
return;
|
|
219
|
+
if (error.reason === 'model_backoff' || error.reason === 'state_error')
|
|
220
|
+
return;
|
|
221
|
+
try {
|
|
222
|
+
this.stateManager.recordModelFailure(this.now().toISOString());
|
|
223
|
+
}
|
|
224
|
+
catch (recordError) {
|
|
225
|
+
this.logFailure('state_error', `could not record model backoff: ${errorText(recordError)}`);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
fail(reason, message, cause) {
|
|
229
|
+
const error = new MetaInferenceModelError(reason, message, cause === undefined ? undefined : { cause });
|
|
230
|
+
this.logFailure(reason, message);
|
|
231
|
+
return error;
|
|
232
|
+
}
|
|
233
|
+
logFailure(reason, message) {
|
|
234
|
+
process.stderr.write(`[meta-inference] ${reason}: ${message}\n`);
|
|
235
|
+
}
|
|
128
236
|
appendReport(entryCount, passCount, avgResonance, report) {
|
|
129
237
|
const dir = dirname(this.reportPath);
|
|
130
238
|
if (!existsSync(dir))
|
|
@@ -146,4 +254,53 @@ export class MetaInferenceEngine {
|
|
|
146
254
|
}).trim();
|
|
147
255
|
}
|
|
148
256
|
}
|
|
257
|
+
function oneLine(value) {
|
|
258
|
+
return value.replace(/\s+/g, ' ').trim();
|
|
259
|
+
}
|
|
260
|
+
function errorText(error) {
|
|
261
|
+
if (error instanceof Error)
|
|
262
|
+
return error.message;
|
|
263
|
+
return String(error);
|
|
264
|
+
}
|
|
265
|
+
function isMissingHermes(error) {
|
|
266
|
+
if (!error || typeof error !== 'object')
|
|
267
|
+
return false;
|
|
268
|
+
const record = error;
|
|
269
|
+
if (record.code === 'ENOENT')
|
|
270
|
+
return true;
|
|
271
|
+
return typeof record.message === 'string' && record.message.includes('ENOENT');
|
|
272
|
+
}
|
|
273
|
+
function isThenable(value) {
|
|
274
|
+
if (value === null || (typeof value !== 'object' && typeof value !== 'function'))
|
|
275
|
+
return false;
|
|
276
|
+
return typeof value.then === 'function';
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Synthesis text is a non-empty string. A value shaped like JSON must parse.
|
|
280
|
+
* Anything else is not a model result.
|
|
281
|
+
*/
|
|
282
|
+
function acceptModelOutput(value) {
|
|
283
|
+
if (isThenable(value)) {
|
|
284
|
+
throw new MetaInferenceModelError('model_output_invalid', 'meta-inference model returned a Promise');
|
|
285
|
+
}
|
|
286
|
+
if (value === undefined || value === null) {
|
|
287
|
+
throw new MetaInferenceModelError('model_output_invalid', 'meta-inference model returned empty output');
|
|
288
|
+
}
|
|
289
|
+
if (typeof value !== 'string') {
|
|
290
|
+
throw new MetaInferenceModelError('model_output_invalid', 'meta-inference model returned a non-string');
|
|
291
|
+
}
|
|
292
|
+
const trimmed = value.trim();
|
|
293
|
+
if (trimmed.length === 0) {
|
|
294
|
+
throw new MetaInferenceModelError('model_output_invalid', 'meta-inference model returned empty output');
|
|
295
|
+
}
|
|
296
|
+
if (trimmed.startsWith('{') || trimmed.startsWith('[')) {
|
|
297
|
+
try {
|
|
298
|
+
JSON.parse(trimmed);
|
|
299
|
+
}
|
|
300
|
+
catch (error) {
|
|
301
|
+
throw new MetaInferenceModelError('model_output_invalid', `meta-inference model returned invalid JSON: ${errorText(error)}`, { cause: error });
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
return trimmed;
|
|
305
|
+
}
|
|
149
306
|
//# sourceMappingURL=meta-inference-engine.js.map
|
|
@@ -100,11 +100,17 @@ export interface CuratedSignalsFile {
|
|
|
100
100
|
};
|
|
101
101
|
signals: CuratedSignal[];
|
|
102
102
|
}
|
|
103
|
+
/** Clock for skipping another hermes call after a failed meta-inference run. */
|
|
104
|
+
export interface ModelBackoffState {
|
|
105
|
+
failedAt: string;
|
|
106
|
+
failures: number;
|
|
107
|
+
}
|
|
103
108
|
export interface InferenceState {
|
|
104
109
|
processedCommentIds: string[];
|
|
105
110
|
processedSessionIds: string[];
|
|
106
111
|
processedPostIds: string[];
|
|
107
112
|
lastRun: string | null;
|
|
113
|
+
modelBackoff?: ModelBackoffState;
|
|
108
114
|
}
|
|
109
115
|
export interface SynthesisReport {
|
|
110
116
|
entriesProcessed: number;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@0xray/repertoire",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.9",
|
|
4
4
|
"description": "Factory memory organ for 0xRay — seed primitives, memory routing, extra MCP",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -117,7 +117,7 @@
|
|
|
117
117
|
}
|
|
118
118
|
},
|
|
119
119
|
"devDependencies": {
|
|
120
|
-
"0xray": "^4.0.
|
|
120
|
+
"0xray": "^4.0.28",
|
|
121
121
|
"@types/node": "^22.15.0",
|
|
122
122
|
"tsx": "^4.19.0",
|
|
123
123
|
"typescript": "^5.6.0",
|
|
@@ -114,10 +114,13 @@ try {
|
|
|
114
114
|
fail('.mcp.json', e.message);
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
-
// 5. Grok plugin + UserPromptSubmit boot hook
|
|
118
|
-
|
|
119
|
-
const
|
|
120
|
-
|
|
117
|
+
// 5. Grok plugin + UserPromptSubmit boot hook.
|
|
118
|
+
// 0xray 4.0.28 installs the plugin into the project, not ~/.grok.
|
|
119
|
+
const grokPlugin = join(root, '.grok', 'plugins', '0xray');
|
|
120
|
+
const grokHooks = join(grokPlugin, 'hooks', 'hooks.json');
|
|
121
|
+
existsSync(grokPlugin)
|
|
122
|
+
? pass('Grok plugin', grokPlugin)
|
|
123
|
+
: fail('Grok plugin', `missing ${grokPlugin} — run: npx 0xray grok install --force`);
|
|
121
124
|
if (existsSync(grokHooks)) {
|
|
122
125
|
const hooksRaw = readFileSync(grokHooks, 'utf8');
|
|
123
126
|
hooksRaw.includes('session-start.js')
|
|
@@ -130,7 +133,7 @@ if (existsSync(grokHooks)) {
|
|
|
130
133
|
? pass('UserPromptSubmit boot hook')
|
|
131
134
|
: fail('UserPromptSubmit boot hook', 'add session-start.js — npx 0xray grok install --force');
|
|
132
135
|
} else {
|
|
133
|
-
fail('Grok hooks.json',
|
|
136
|
+
fail('Grok hooks.json', `missing ${grokHooks}`);
|
|
134
137
|
}
|
|
135
138
|
|
|
136
139
|
try {
|
|
@@ -297,7 +300,7 @@ if (existsSync(installedGrokHooks)) {
|
|
|
297
300
|
fail('Grok plugin PostToolUse hook', 'run: npx 0xray grok install --force');
|
|
298
301
|
}
|
|
299
302
|
} else {
|
|
300
|
-
fail('Grok plugin PostToolUse hook',
|
|
303
|
+
fail('Grok plugin PostToolUse hook', `missing ${installedGrokHooks} — run: npx 0xray grok install --force`);
|
|
301
304
|
}
|
|
302
305
|
|
|
303
306
|
console.log('\n' + (failed === 0 ? '🎉 Suit wearable — operate within 0xRay.' : `⚠️ ${failed} check(s) failed — fix before tuning.`));
|