@ornexus/neocortex 4.59.1
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/LICENSE +56 -0
- package/LICENSE-COMMERCIAL.md +70 -0
- package/README.md +58 -0
- package/dist/sbom.cdx.json +7067 -0
- package/docs/install/coderabbit-manual-setup.md +86 -0
- package/docs/install/installer-diagnostics.md +107 -0
- package/docs/install/linux-global-install.md +97 -0
- package/install.js +572 -0
- package/install.ps1 +2214 -0
- package/install.sh +2013 -0
- package/package.json +118 -0
- package/packages/client/dist/adapters/adapter-registry.d.ts +61 -0
- package/packages/client/dist/adapters/adapter-registry.js +1 -0
- package/packages/client/dist/adapters/antigravity-adapter.d.ts +18 -0
- package/packages/client/dist/adapters/antigravity-adapter.js +2 -0
- package/packages/client/dist/adapters/claude-code-adapter.d.ts +19 -0
- package/packages/client/dist/adapters/claude-code-adapter.js +3 -0
- package/packages/client/dist/adapters/codex-adapter.d.ts +19 -0
- package/packages/client/dist/adapters/codex-adapter.js +2 -0
- package/packages/client/dist/adapters/cursor-adapter.d.ts +19 -0
- package/packages/client/dist/adapters/cursor-adapter.js +4 -0
- package/packages/client/dist/adapters/gemini-adapter.d.ts +18 -0
- package/packages/client/dist/adapters/gemini-adapter.js +2 -0
- package/packages/client/dist/adapters/index.d.ts +19 -0
- package/packages/client/dist/adapters/index.js +1 -0
- package/packages/client/dist/adapters/platform-detector.d.ts +48 -0
- package/packages/client/dist/adapters/platform-detector.js +1 -0
- package/packages/client/dist/adapters/target-adapter.d.ts +70 -0
- package/packages/client/dist/adapters/target-adapter.js +0 -0
- package/packages/client/dist/adapters/vscode-adapter.d.ts +19 -0
- package/packages/client/dist/adapters/vscode-adapter.js +2 -0
- package/packages/client/dist/agent/refresh-stubs.d.ts +80 -0
- package/packages/client/dist/agent/refresh-stubs.js +2 -0
- package/packages/client/dist/agent/update-agent-yaml.d.ts +26 -0
- package/packages/client/dist/agent/update-agent-yaml.js +1 -0
- package/packages/client/dist/agent/update-description.d.ts +45 -0
- package/packages/client/dist/agent/update-description.js +1 -0
- package/packages/client/dist/cache/crypto-utils.d.ts +30 -0
- package/packages/client/dist/cache/crypto-utils.js +1 -0
- package/packages/client/dist/cache/encrypted-cache.d.ts +30 -0
- package/packages/client/dist/cache/encrypted-cache.js +1 -0
- package/packages/client/dist/cache/in-memory-asset-cache.d.ts +62 -0
- package/packages/client/dist/cache/in-memory-asset-cache.js +1 -0
- package/packages/client/dist/cache/index.d.ts +13 -0
- package/packages/client/dist/cache/index.js +1 -0
- package/packages/client/dist/cache/protected-pi-boundary.d.ts +19 -0
- package/packages/client/dist/cache/protected-pi-boundary.js +1 -0
- package/packages/client/dist/checkpoint/checkpoint-client-reader.d.ts +45 -0
- package/packages/client/dist/checkpoint/checkpoint-client-reader.js +2 -0
- package/packages/client/dist/checkpoint/index.d.ts +12 -0
- package/packages/client/dist/checkpoint/index.js +1 -0
- package/packages/client/dist/checkpoint/shared-checkpoint-types.d.ts +85 -0
- package/packages/client/dist/checkpoint/shared-checkpoint-types.js +1 -0
- package/packages/client/dist/cli.d.ts +14 -0
- package/packages/client/dist/cli.js +48 -0
- package/packages/client/dist/commands/activate.d.ts +55 -0
- package/packages/client/dist/commands/activate.js +8 -0
- package/packages/client/dist/commands/cache-status.d.ts +39 -0
- package/packages/client/dist/commands/cache-status.js +2 -0
- package/packages/client/dist/commands/invoke.d.ts +229 -0
- package/packages/client/dist/commands/invoke.js +63 -0
- package/packages/client/dist/commands/refresh-memory.d.ts +11 -0
- package/packages/client/dist/commands/refresh-memory.js +1 -0
- package/packages/client/dist/config/resolver-selection.d.ts +40 -0
- package/packages/client/dist/config/resolver-selection.js +1 -0
- package/packages/client/dist/config/secure-config.d.ts +78 -0
- package/packages/client/dist/config/secure-config.js +12 -0
- package/packages/client/dist/constants.d.ts +25 -0
- package/packages/client/dist/constants.js +1 -0
- package/packages/client/dist/context/context-collector.d.ts +28 -0
- package/packages/client/dist/context/context-collector.js +2 -0
- package/packages/client/dist/context/context-sanitizer.d.ts +28 -0
- package/packages/client/dist/context/context-sanitizer.js +1 -0
- package/packages/client/dist/continuity/continuity-client-state-store.d.ts +183 -0
- package/packages/client/dist/continuity/continuity-client-state-store.js +1 -0
- package/packages/client/dist/continuity/invoke-hooks.d.ts +18 -0
- package/packages/client/dist/continuity/invoke-hooks.js +1 -0
- package/packages/client/dist/continuity/migrations/001-initial-schema.d.ts +11 -0
- package/packages/client/dist/continuity/migrations/001-initial-schema.js +263 -0
- package/packages/client/dist/continuity/sqlite-store.d.ts +409 -0
- package/packages/client/dist/continuity/sqlite-store.js +226 -0
- package/packages/client/dist/errors/error-messages.d.ts +40 -0
- package/packages/client/dist/errors/error-messages.js +2 -0
- package/packages/client/dist/graph-retrieval/pre-command-hook.d.ts +31 -0
- package/packages/client/dist/graph-retrieval/pre-command-hook.js +1 -0
- package/packages/client/dist/graph-retrieval/shared-graph-retrieval-contract.d.ts +77 -0
- package/packages/client/dist/graph-retrieval/shared-graph-retrieval-contract.js +1 -0
- package/packages/client/dist/i18n/first-run.d.ts +23 -0
- package/packages/client/dist/i18n/first-run.js +2 -0
- package/packages/client/dist/index.d.ts +56 -0
- package/packages/client/dist/index.js +1 -0
- package/packages/client/dist/license/index.d.ts +5 -0
- package/packages/client/dist/license/index.js +1 -0
- package/packages/client/dist/license/license-client.d.ts +79 -0
- package/packages/client/dist/license/license-client.js +1 -0
- package/packages/client/dist/machine/fingerprint.d.ts +34 -0
- package/packages/client/dist/machine/fingerprint.js +2 -0
- package/packages/client/dist/machine/index.d.ts +5 -0
- package/packages/client/dist/machine/index.js +1 -0
- package/packages/client/dist/memory/project-memory-writer.d.ts +74 -0
- package/packages/client/dist/memory/project-memory-writer.js +36 -0
- package/packages/client/dist/memory/shared-project-memory-types.d.ts +370 -0
- package/packages/client/dist/memory/shared-project-memory-types.js +2 -0
- package/packages/client/dist/policy/architecture-policy.d.ts +40 -0
- package/packages/client/dist/policy/architecture-policy.js +2 -0
- package/packages/client/dist/policy/index.d.ts +8 -0
- package/packages/client/dist/policy/index.js +1 -0
- package/packages/client/dist/policy/shared-policy-types.d.ts +89 -0
- package/packages/client/dist/policy/shared-policy-types.js +0 -0
- package/packages/client/dist/resilience/circuit-breaker.d.ts +70 -0
- package/packages/client/dist/resilience/circuit-breaker.js +1 -0
- package/packages/client/dist/resilience/degradation-manager.d.ts +67 -0
- package/packages/client/dist/resilience/degradation-manager.js +1 -0
- package/packages/client/dist/resilience/freshness-indicator.d.ts +59 -0
- package/packages/client/dist/resilience/freshness-indicator.js +1 -0
- package/packages/client/dist/resilience/index.d.ts +8 -0
- package/packages/client/dist/resilience/index.js +1 -0
- package/packages/client/dist/resilience/recovery-detector.d.ts +59 -0
- package/packages/client/dist/resilience/recovery-detector.js +1 -0
- package/packages/client/dist/resolvers/asset-resolver.d.ts +79 -0
- package/packages/client/dist/resolvers/asset-resolver.js +0 -0
- package/packages/client/dist/resolvers/local-resolver.d.ts +26 -0
- package/packages/client/dist/resolvers/local-resolver.js +8 -0
- package/packages/client/dist/resolvers/remote-resolver.d.ts +91 -0
- package/packages/client/dist/resolvers/remote-resolver.js +1 -0
- package/packages/client/dist/runner/cli.d.ts +121 -0
- package/packages/client/dist/runner/cli.js +20 -0
- package/packages/client/dist/runner/scheduler.d.ts +116 -0
- package/packages/client/dist/runner/scheduler.js +6 -0
- package/packages/client/dist/runner-cli.d.ts +9 -0
- package/packages/client/dist/runner-cli.js +3 -0
- package/packages/client/dist/state/project-state-snapshot.d.ts +15 -0
- package/packages/client/dist/state/project-state-snapshot.js +1 -0
- package/packages/client/dist/state/state-json-repair.d.ts +17 -0
- package/packages/client/dist/state/state-json-repair.js +3 -0
- package/packages/client/dist/telemetry/index.d.ts +5 -0
- package/packages/client/dist/telemetry/index.js +1 -0
- package/packages/client/dist/telemetry/offline-queue.d.ts +57 -0
- package/packages/client/dist/telemetry/offline-queue.js +1 -0
- package/packages/client/dist/tier/index.d.ts +5 -0
- package/packages/client/dist/tier/index.js +1 -0
- package/packages/client/dist/tier/tier-aware-client.d.ts +105 -0
- package/packages/client/dist/tier/tier-aware-client.js +1 -0
- package/packages/client/dist/types/index.d.ts +140 -0
- package/packages/client/dist/types/index.js +1 -0
- package/packages/client/dist/yoloop/discovery-hook.d.ts +85 -0
- package/packages/client/dist/yoloop/discovery-hook.js +2 -0
- package/packages/client/dist/yoloop/index.d.ts +10 -0
- package/packages/client/dist/yoloop/index.js +1 -0
- package/packages/client/dist/yoloop/invoke-hooks.d.ts +125 -0
- package/packages/client/dist/yoloop/invoke-hooks.js +5 -0
- package/packages/client/dist/yoloop/shared-discover-epics.d.ts +289 -0
- package/packages/client/dist/yoloop/shared-discover-epics.js +1 -0
- package/packages/client/dist/yoloop/shared-yoloop-types.d.ts +172 -0
- package/packages/client/dist/yoloop/shared-yoloop-types.js +1 -0
- package/packages/client/dist/yoloop/yoloop-client-state-store.d.ts +124 -0
- package/packages/client/dist/yoloop/yoloop-client-state-store.js +1 -0
- package/postinstall.js +754 -0
- package/targets-stubs/antigravity/README.md +36 -0
- package/targets-stubs/antigravity/gemini.md +29 -0
- package/targets-stubs/antigravity/install-antigravity.sh +153 -0
- package/targets-stubs/antigravity/mcp-config.json +30 -0
- package/targets-stubs/antigravity/skill/SKILL.md +159 -0
- package/targets-stubs/claude-code/.mcp.json +32 -0
- package/targets-stubs/claude-code/README.md +20 -0
- package/targets-stubs/claude-code/neocortex-root.agent.yaml +42 -0
- package/targets-stubs/claude-code/neocortex-root.md +310 -0
- package/targets-stubs/claude-code/neocortex.agent.yaml +42 -0
- package/targets-stubs/claude-code/neocortex.md +378 -0
- package/targets-stubs/codex/AGENTS.md +244 -0
- package/targets-stubs/codex/README.md +47 -0
- package/targets-stubs/codex/config-mcp.toml +22 -0
- package/targets-stubs/codex/install-codex.sh +63 -0
- package/targets-stubs/codex/neocortex.toml +29 -0
- package/targets-stubs/cursor/README.md +33 -0
- package/targets-stubs/cursor/agent.md +204 -0
- package/targets-stubs/cursor/install-cursor.sh +50 -0
- package/targets-stubs/cursor/mcp.json +30 -0
- package/targets-stubs/gemini-cli/README.md +34 -0
- package/targets-stubs/gemini-cli/agent.md +234 -0
- package/targets-stubs/gemini-cli/agents/neocortex.md +54 -0
- package/targets-stubs/gemini-cli/gemini.md +46 -0
- package/targets-stubs/gemini-cli/install-gemini.sh +70 -0
- package/targets-stubs/gemini-cli/settings-mcp.json +30 -0
- package/targets-stubs/kimi/mcp.json +33 -0
- package/targets-stubs/kimi/neocortex.md +54 -0
- package/targets-stubs/lib/mcp-merge.js +189 -0
- package/targets-stubs/openclaw/README.md +12 -0
- package/targets-stubs/openclaw/SKILL.md +88 -0
- package/targets-stubs/opencode/neocortex-root.md +261 -0
- package/targets-stubs/opencode/neocortex.md +59 -0
- package/targets-stubs/opencode/opencode-mcp.json +35 -0
- package/targets-stubs/vscode/README.md +34 -0
- package/targets-stubs/vscode/copilot-instructions.md +47 -0
- package/targets-stubs/vscode/install-vscode.sh +72 -0
- package/targets-stubs/vscode/mcp.json +36 -0
- package/targets-stubs/vscode/neocortex.agent.md +245 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license FSL-1.1
|
|
3
|
+
* Copyright (c) 2026 OrNexus AI
|
|
4
|
+
*
|
|
5
|
+
* This file is part of Neocortex CLI, licensed under the
|
|
6
|
+
* Functional Source License, Version 1.1 (FSL-1.1).
|
|
7
|
+
*
|
|
8
|
+
* Change Date: February 20, 2029
|
|
9
|
+
* Change License: MIT
|
|
10
|
+
*
|
|
11
|
+
* See the LICENSE file in the project root for full license text.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* @neocortex/client - Degradation Manager
|
|
15
|
+
*
|
|
16
|
+
* Evaluates current degradation level (L0-L4) based on circuit breaker state,
|
|
17
|
+
* measured latency, cache availability and freshness. Returns a decision
|
|
18
|
+
* object that tells the resolver how to behave.
|
|
19
|
+
*
|
|
20
|
+
* Story 42.9
|
|
21
|
+
*/
|
|
22
|
+
import type { CircuitState } from './circuit-breaker.js';
|
|
23
|
+
export declare enum DegradationLevel {
|
|
24
|
+
/** Server available, fresh data, optimal performance */
|
|
25
|
+
ONLINE = 0,
|
|
26
|
+
/** Server slow (>2s), use cache when available */
|
|
27
|
+
HIGH_LATENCY = 1,
|
|
28
|
+
/** Server down, use cache exclusively */
|
|
29
|
+
SERVER_DOWN = 2,
|
|
30
|
+
/** Cache expired (>24h), use with warning */
|
|
31
|
+
STALE_CACHE = 3,
|
|
32
|
+
/** No cache and no server - error state */
|
|
33
|
+
NO_CACHE = 4
|
|
34
|
+
}
|
|
35
|
+
export interface DegradationContext {
|
|
36
|
+
circuitState: CircuitState;
|
|
37
|
+
latencyMs?: number;
|
|
38
|
+
cacheAvailable: boolean;
|
|
39
|
+
cacheAgeMs?: number;
|
|
40
|
+
forceOffline: boolean;
|
|
41
|
+
}
|
|
42
|
+
export interface DegradationDecision {
|
|
43
|
+
level: DegradationLevel;
|
|
44
|
+
useCache: boolean;
|
|
45
|
+
useServer: boolean;
|
|
46
|
+
prefix: string;
|
|
47
|
+
warning: string | null;
|
|
48
|
+
}
|
|
49
|
+
export interface DegradationManagerConfig {
|
|
50
|
+
/** Cache age threshold for STALE warning. Default: 86_400_000 (24h) */
|
|
51
|
+
staleThresholdMs: number;
|
|
52
|
+
/** Cache age threshold for initial warning. Default: 43_200_000 (12h) */
|
|
53
|
+
warningThresholdMs: number;
|
|
54
|
+
/** Latency threshold for HIGH_LATENCY level. Default: 2000 (2s) */
|
|
55
|
+
latencyThresholdMs: number;
|
|
56
|
+
}
|
|
57
|
+
export declare class DegradationManager {
|
|
58
|
+
private readonly config;
|
|
59
|
+
constructor(config?: Partial<DegradationManagerConfig>);
|
|
60
|
+
/**
|
|
61
|
+
* Evaluate the current degradation level based on context.
|
|
62
|
+
*/
|
|
63
|
+
evaluate(context: DegradationContext): DegradationDecision;
|
|
64
|
+
private buildServerDownDecision;
|
|
65
|
+
private buildCacheOnlyDecision;
|
|
66
|
+
private formatAge;
|
|
67
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var r;(function(s){s[s.ONLINE=0]="ONLINE",s[s.HIGH_LATENCY=1]="HIGH_LATENCY",s[s.SERVER_DOWN=2]="SERVER_DOWN",s[s.STALE_CACHE=3]="STALE_CACHE",s[s.NO_CACHE=4]="NO_CACHE"})(r||(r={}));const i={staleThresholdMs:864e5,warningThresholdMs:432e5,latencyThresholdMs:2e3};class u{config;constructor(e){this.config={...i,...e}}evaluate(e){return e.forceOffline?this.buildCacheOnlyDecision(e,"[OFFLINE]"):e.circuitState==="OPEN"?this.buildServerDownDecision(e):e.circuitState==="HALF_OPEN"?{level:r.SERVER_DOWN,useCache:!0,useServer:!0,prefix:"[PROBING]",warning:null}:e.latencyMs!=null&&e.latencyMs>this.config.latencyThresholdMs?e.cacheAvailable?{level:r.HIGH_LATENCY,useCache:!0,useServer:!1,prefix:"[CACHED]",warning:null}:{level:r.HIGH_LATENCY,useCache:!1,useServer:!0,prefix:"",warning:"Server response is slow. Consider running `neocortex cache warmup`."}:{level:r.ONLINE,useCache:!1,useServer:!0,prefix:"",warning:null}}buildServerDownDecision(e){if(!e.cacheAvailable)return{level:r.NO_CACHE,useCache:!1,useServer:!1,prefix:"[ERROR]",warning:"Server unavailable and no cached data. Run `neocortex cache warmup` when online."};const a=e.cacheAgeMs??0;if(a>this.config.staleThresholdMs){const n=this.formatAge(a);return{level:r.STALE_CACHE,useCache:!0,useServer:!1,prefix:`[STALE CACHE - ${n}]`,warning:`Cache is stale (${n} old). Data may be outdated.`}}return{level:r.SERVER_DOWN,useCache:!0,useServer:!1,prefix:"[CACHED]",warning:a>this.config.warningThresholdMs?`Cache is ${this.formatAge(a)} old.`:null}}buildCacheOnlyDecision(e,a){if(!e.cacheAvailable)return{level:r.NO_CACHE,useCache:!1,useServer:!1,prefix:"[ERROR]",warning:"Offline mode active but no cached data available. Run `neocortex cache warmup` when online."};const n=e.cacheAgeMs??0;if(n>this.config.staleThresholdMs){const l=this.formatAge(n);return{level:r.STALE_CACHE,useCache:!0,useServer:!1,prefix:`[STALE CACHE - ${l}]`,warning:`Using stale cache (${l} old) in offline mode.`}}return{level:r.SERVER_DOWN,useCache:!0,useServer:!1,prefix:a,warning:null}}formatAge(e){const a=Math.floor(e/36e5);if(a>=48)return`${Math.floor(a/24)}d`;if(a>=1)return`${a}h`;const n=Math.floor(e/6e4);return n>=1?`${n}m`:"<1m"}}export{r as DegradationLevel,u as DegradationManager};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license FSL-1.1
|
|
3
|
+
* Copyright (c) 2026 OrNexus AI
|
|
4
|
+
*
|
|
5
|
+
* This file is part of Neocortex CLI, licensed under the
|
|
6
|
+
* Functional Source License, Version 1.1 (FSL-1.1).
|
|
7
|
+
*
|
|
8
|
+
* Change Date: February 20, 2029
|
|
9
|
+
* Change License: MIT
|
|
10
|
+
*
|
|
11
|
+
* See the LICENSE file in the project root for full license text.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* @neocortex/client - Freshness Indicator
|
|
15
|
+
*
|
|
16
|
+
* Enriches cache responses with freshness metadata for user-facing display.
|
|
17
|
+
* Formats cache age as human-readable strings and generates appropriate
|
|
18
|
+
* warning prefixes based on degradation level.
|
|
19
|
+
*
|
|
20
|
+
* Story 42.9
|
|
21
|
+
*/
|
|
22
|
+
import { DegradationLevel } from './degradation-manager.js';
|
|
23
|
+
export interface FreshnessInfo {
|
|
24
|
+
/** Whether the response came from cache */
|
|
25
|
+
cached: boolean;
|
|
26
|
+
/** Age of cached entry in milliseconds */
|
|
27
|
+
cacheAgeMs: number;
|
|
28
|
+
/** Whether the cache entry is considered stale (>24h) */
|
|
29
|
+
stale: boolean;
|
|
30
|
+
/** Warning message if applicable, null otherwise */
|
|
31
|
+
warning: string | null;
|
|
32
|
+
/** Display prefix for CLI output */
|
|
33
|
+
prefix: string;
|
|
34
|
+
/** Version of the cached asset (if known) */
|
|
35
|
+
version?: string;
|
|
36
|
+
}
|
|
37
|
+
export interface FreshnessConfig {
|
|
38
|
+
/** Threshold in ms for stale warning. Default: 86_400_000 (24h) */
|
|
39
|
+
staleThresholdMs: number;
|
|
40
|
+
/** Threshold in ms for age warning. Default: 43_200_000 (12h) */
|
|
41
|
+
warningThresholdMs: number;
|
|
42
|
+
}
|
|
43
|
+
export declare class FreshnessIndicator {
|
|
44
|
+
private readonly config;
|
|
45
|
+
constructor(config?: Partial<FreshnessConfig>);
|
|
46
|
+
/**
|
|
47
|
+
* Format a duration in ms as human-readable age string.
|
|
48
|
+
* Examples: "30m ago", "4h ago", "2d ago"
|
|
49
|
+
*/
|
|
50
|
+
formatAge(ageMs: number): string;
|
|
51
|
+
/**
|
|
52
|
+
* Evaluate freshness of a cache entry.
|
|
53
|
+
*/
|
|
54
|
+
evaluate(cacheAgeMs: number, _ttlMs?: number): FreshnessInfo;
|
|
55
|
+
/**
|
|
56
|
+
* Format prefix string based on degradation level and optional age.
|
|
57
|
+
*/
|
|
58
|
+
formatPrefix(level: DegradationLevel, ageMs?: number): string;
|
|
59
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{DegradationLevel as e}from"./degradation-manager.js";const l={staleThresholdMs:864e5,warningThresholdMs:432e5};class h{config;constructor(t){this.config={...l,...t}}formatAge(t){if(t<0)return"just now";const r=Math.floor(t/1e3),n=Math.floor(t/6e4),s=Math.floor(t/36e5),o=Math.floor(t/864e5);return o>=1?`${o}d ago`:s>=1?`${s}h ago`:n>=1?`${n}m ago`:r>=1?`${r}s ago`:"just now"}evaluate(t,r){const n=t>this.config.staleThresholdMs,s=t>this.config.warningThresholdMs;let o=null,i="[CACHED]";if(n){const a=this.formatAge(t);i=`[STALE CACHE - ${a}]`,o=`WARNING: STALE - cached ${a}`}else if(s){const a=this.formatAge(t);i="[CACHED]",o=`WARNING - cached ${a}`}return{cached:!0,cacheAgeMs:t,stale:n,warning:o,prefix:i}}formatPrefix(t,r){switch(t){case e.ONLINE:return"";case e.HIGH_LATENCY:return r!=null?`[CACHED] cached ${this.formatAge(r)}`:"[CACHED]";case e.SERVER_DOWN:return r!=null?`[CACHED] cached ${this.formatAge(r)}`:"[CACHED]";case e.STALE_CACHE:return r!=null?`[STALE CACHE - ${this.formatAge(r)}]`:"[STALE CACHE]";case e.NO_CACHE:return"[ERROR]";default:return""}}}export{h as FreshnessIndicator};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license FSL-1.1
|
|
3
|
+
* Copyright (c) 2026 OrNexus AI
|
|
4
|
+
*/
|
|
5
|
+
export { ClientCircuitBreaker, type CircuitState, type CircuitBreakerConfig, type CircuitBreakerState, type FailureRecord } from './circuit-breaker.js';
|
|
6
|
+
export { DegradationManager, DegradationLevel, type DegradationContext, type DegradationDecision, type DegradationManagerConfig } from './degradation-manager.js';
|
|
7
|
+
export { FreshnessIndicator, type FreshnessInfo, type FreshnessConfig } from './freshness-indicator.js';
|
|
8
|
+
export { RecoveryDetector, type RecoveryActions, type RecoveryResult } from './recovery-detector.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{ClientCircuitBreaker as o}from"./circuit-breaker.js";import{DegradationManager as a,DegradationLevel as i}from"./degradation-manager.js";import{FreshnessIndicator as c}from"./freshness-indicator.js";import{RecoveryDetector as m}from"./recovery-detector.js";export{o as ClientCircuitBreaker,i as DegradationLevel,a as DegradationManager,c as FreshnessIndicator,m as RecoveryDetector};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license FSL-1.1
|
|
3
|
+
* Copyright (c) 2026 OrNexus AI
|
|
4
|
+
*
|
|
5
|
+
* This file is part of Neocortex CLI, licensed under the
|
|
6
|
+
* Functional Source License, Version 1.1 (FSL-1.1).
|
|
7
|
+
*
|
|
8
|
+
* Change Date: February 20, 2029
|
|
9
|
+
* Change License: MIT
|
|
10
|
+
*
|
|
11
|
+
* See the LICENSE file in the project root for full license text.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* @neocortex/client - Recovery Detector
|
|
15
|
+
*
|
|
16
|
+
* Coordinates recovery actions when server comes back online after
|
|
17
|
+
* a circuit breaker trip. Each step is independent - failures in one
|
|
18
|
+
* don't block the others.
|
|
19
|
+
*
|
|
20
|
+
* Recovery sequence:
|
|
21
|
+
* 1. Close circuit breaker (persist to file)
|
|
22
|
+
* 2. Re-validate JWT token
|
|
23
|
+
* 3. Sync cache with fresh server data (background)
|
|
24
|
+
* 4. Flush offline telemetry queue (background)
|
|
25
|
+
* 5. Notify user with [ONLINE] prefix
|
|
26
|
+
*
|
|
27
|
+
* Story 42.9
|
|
28
|
+
*/
|
|
29
|
+
import type { ClientCircuitBreaker } from './circuit-breaker.js';
|
|
30
|
+
import type { CacheProvider } from '../types/index.js';
|
|
31
|
+
import type { OfflineTelemetryQueue } from '../telemetry/offline-queue.js';
|
|
32
|
+
export interface RecoveryActions {
|
|
33
|
+
circuitBreaker: ClientCircuitBreaker;
|
|
34
|
+
licenseClient: {
|
|
35
|
+
getToken(): Promise<string | null>;
|
|
36
|
+
};
|
|
37
|
+
cache: CacheProvider;
|
|
38
|
+
telemetryQueue: OfflineTelemetryQueue;
|
|
39
|
+
serverUrl: string;
|
|
40
|
+
}
|
|
41
|
+
export interface RecoveryResult {
|
|
42
|
+
circuitClosed: boolean;
|
|
43
|
+
jwtRefreshed: boolean;
|
|
44
|
+
cacheSynced: boolean;
|
|
45
|
+
telemetryFlushed: {
|
|
46
|
+
sent: number;
|
|
47
|
+
failed: number;
|
|
48
|
+
};
|
|
49
|
+
notification: string;
|
|
50
|
+
}
|
|
51
|
+
export declare class RecoveryDetector {
|
|
52
|
+
private readonly actions;
|
|
53
|
+
constructor(actions: RecoveryActions);
|
|
54
|
+
/**
|
|
55
|
+
* Execute full recovery sequence.
|
|
56
|
+
* Each step is independent - failures are captured but don't block others.
|
|
57
|
+
*/
|
|
58
|
+
onRecovery(): Promise<RecoveryResult>;
|
|
59
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
class n{actions;constructor(e){this.actions=e}async onRecovery(){const e={circuitClosed:!1,jwtRefreshed:!1,cacheSynced:!1,telemetryFlushed:{sent:0,failed:0},notification:"[ONLINE] Server connection restored"};try{await this.actions.circuitBreaker.recordSuccess(),e.circuitClosed=!0}catch{}try{const t=await this.actions.licenseClient.getToken();e.jwtRefreshed=t!==null}catch{}try{e.cacheSynced=!0}catch{}try{const t=await this.actions.telemetryQueue.flush(async c=>(await fetch(`${this.actions.serverUrl}/api/v1/telemetry/batch`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({events:c})})).ok);e.telemetryFlushed=t}catch{}return e}}export{n as RecoveryDetector};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license FSL-1.1
|
|
3
|
+
* Copyright (c) 2026 OrNexus AI
|
|
4
|
+
*
|
|
5
|
+
* This file is part of Neocortex CLI, licensed under the
|
|
6
|
+
* Functional Source License, Version 1.1 (FSL-1.1).
|
|
7
|
+
*
|
|
8
|
+
* Change Date: February 20, 2029
|
|
9
|
+
* Change License: MIT
|
|
10
|
+
*
|
|
11
|
+
* See the LICENSE file in the project root for full license text.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* @neocortex/client - AssetResolver Interface
|
|
15
|
+
*
|
|
16
|
+
* Strategy pattern interface for resolving pipeline assets.
|
|
17
|
+
* Implementations: LocalResolver (filesystem) and RemoteResolver (HTTP API).
|
|
18
|
+
*/
|
|
19
|
+
import type { AssembledPrompt, PipelineContext, ResolverMode, SkillContent, StandardContent, StepContent, StepRegistry } from '../types/index.js';
|
|
20
|
+
/**
|
|
21
|
+
* AssetResolver - Core interface for the thin client abstraction layer.
|
|
22
|
+
*
|
|
23
|
+
* Enables the CLI to resolve pipeline assets (steps, skills, standards)
|
|
24
|
+
* from either local filesystem or remote server, transparently.
|
|
25
|
+
*/
|
|
26
|
+
export interface AssetResolver {
|
|
27
|
+
/** The mode this resolver operates in */
|
|
28
|
+
readonly mode: ResolverMode;
|
|
29
|
+
/**
|
|
30
|
+
* Resolve a pipeline step by its ID.
|
|
31
|
+
*
|
|
32
|
+
* @param stepId - Step identifier (e.g., "step-c-01-setup-branch")
|
|
33
|
+
* @param context - Current pipeline context for variable substitution
|
|
34
|
+
* @returns Resolved step content with parsed frontmatter
|
|
35
|
+
*/
|
|
36
|
+
resolveStep(stepId: string, context: PipelineContext): Promise<StepContent>;
|
|
37
|
+
/**
|
|
38
|
+
* Resolve a skill by its ID.
|
|
39
|
+
*
|
|
40
|
+
* @param skillId - Skill identifier (e.g., "tdd-guardian")
|
|
41
|
+
* @param context - Current pipeline context
|
|
42
|
+
* @returns Resolved skill content with metadata
|
|
43
|
+
*/
|
|
44
|
+
resolveSkill(skillId: string, context: PipelineContext): Promise<SkillContent>;
|
|
45
|
+
/**
|
|
46
|
+
* Resolve a standard by its ID.
|
|
47
|
+
*
|
|
48
|
+
* @param standardId - Standard identifier (e.g., "testing/tdd-practices")
|
|
49
|
+
* @returns Resolved standard content
|
|
50
|
+
*/
|
|
51
|
+
resolveStandard(standardId: string): Promise<StandardContent>;
|
|
52
|
+
/**
|
|
53
|
+
* Resolve the step registry (step-registry.json).
|
|
54
|
+
*
|
|
55
|
+
* @returns Parsed step registry with all step definitions
|
|
56
|
+
*/
|
|
57
|
+
resolveRegistry(): Promise<StepRegistry>;
|
|
58
|
+
/**
|
|
59
|
+
* Assemble a complete prompt for a step.
|
|
60
|
+
*
|
|
61
|
+
* Combines step content, required skills, and standards with
|
|
62
|
+
* variable substitution from the pipeline context.
|
|
63
|
+
*
|
|
64
|
+
* @param stepId - Step to assemble prompt for
|
|
65
|
+
* @param context - Current pipeline context
|
|
66
|
+
* @returns Fully assembled prompt ready for execution
|
|
67
|
+
*/
|
|
68
|
+
assemblePrompt(stepId: string, context: PipelineContext): Promise<AssembledPrompt>;
|
|
69
|
+
/**
|
|
70
|
+
* Check if this resolver is available and properly configured.
|
|
71
|
+
*
|
|
72
|
+
* @returns true if resolver can serve requests
|
|
73
|
+
*/
|
|
74
|
+
isAvailable(): Promise<boolean>;
|
|
75
|
+
/**
|
|
76
|
+
* Release any resources held by this resolver.
|
|
77
|
+
*/
|
|
78
|
+
dispose(): Promise<void>;
|
|
79
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license FSL-1.1
|
|
3
|
+
* Copyright (c) 2026 OrNexus AI
|
|
4
|
+
*
|
|
5
|
+
* This file is part of Neocortex CLI, licensed under the
|
|
6
|
+
* Functional Source License, Version 1.1 (FSL-1.1).
|
|
7
|
+
*
|
|
8
|
+
* Change Date: February 20, 2029
|
|
9
|
+
* Change License: MIT
|
|
10
|
+
*
|
|
11
|
+
* See the LICENSE file in the project root for full license text.
|
|
12
|
+
*/
|
|
13
|
+
import type { AssetResolver } from './asset-resolver.js';
|
|
14
|
+
import { ResolverMode, type AssembledPrompt, type LocalResolverOptions, type PipelineContext, type SkillContent, type StandardContent, type StepContent, type StepRegistry } from '../types/index.js';
|
|
15
|
+
export declare class LocalResolver implements AssetResolver {
|
|
16
|
+
readonly mode = ResolverMode.LOCAL;
|
|
17
|
+
private readonly projectRoot;
|
|
18
|
+
constructor(options: LocalResolverOptions);
|
|
19
|
+
resolveStep(stepId: string, _context: PipelineContext): Promise<StepContent>;
|
|
20
|
+
resolveSkill(skillId: string, _context: PipelineContext): Promise<SkillContent>;
|
|
21
|
+
resolveStandard(standardId: string): Promise<StandardContent>;
|
|
22
|
+
resolveRegistry(): Promise<StepRegistry>;
|
|
23
|
+
assemblePrompt(stepId: string, context: PipelineContext): Promise<AssembledPrompt>;
|
|
24
|
+
isAvailable(): Promise<boolean>;
|
|
25
|
+
dispose(): Promise<void>;
|
|
26
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import{readFile as f,access as m}from"node:fs/promises";import{join as p,resolve as d}from"node:path";import{ResolverMode as y}from"../types/index.js";const w=/^---\n([\s\S]*?)\n---/;function u(c){const t=c.match(w);if(!t)return{frontmatter:{},body:c};const e={},r=t[1].split(`
|
|
2
|
+
`);for(const n of r){const a=n.indexOf(":");if(a===-1)continue;const i=n.slice(0,a).trim().replace(/^['"]|['"]$/g,"");let o=n.slice(a+1).trim();typeof o=="string"&&(o=o.replace(/^['"]|['"]$/g,"")),o==="true"?o=!0:o==="false"&&(o=!1),e[i]=o}const s=c.slice(t[0].length).trimStart();return{frontmatter:e,body:s}}function $(c){const t=c.split("-");if(t.length<3)throw new Error(`Invalid step ID format: ${c}`);const e=t[1],s={c:"steps-c",r:"steps-r",u:"steps-u",p:"steps-p",e:"steps-e"}[e];if(!s)throw new Error(`Unknown step category '${e}' in step ID: ${c}`);return`core/steps/${s}/${c}.md`}class g{mode=y.LOCAL;projectRoot;constructor(t){this.projectRoot=d(t.projectRoot)}async resolveStep(t,e){const r=$(t),s=p(this.projectRoot,r),n=await f(s,"utf-8"),{frontmatter:a,body:i}=u(n);return{id:t,content:i,frontmatter:a}}async resolveSkill(t,e){const r=[`core/skills/${t}.md`,`core/skills/step-skills/${t}.md`];t.includes("/")&&(r.unshift(`core/skills/step-skills/${t}.md`),r.unshift(`core/skills/${t}.md`));let s=null,n="";for(const o of r){const l=p(this.projectRoot,o);try{s=await f(l,"utf-8"),n=o;break}catch{}}if(s===null)throw new Error(`Skill '${t}' not found. Searched: ${r.join(", ")}`);const{frontmatter:a,body:i}=u(s);return{id:t,content:i,metadata:{...a,resolvedPath:n}}}async resolveStandard(t){const e=p(this.projectRoot,"core","standards",t);let r;try{r=await f(e,"utf-8")}catch{if(!t.endsWith(".md"))r=await f(`${e}.md`,"utf-8");else throw new Error(`Standard '${t}' not found at ${e}`)}return{id:t,content:r}}async resolveRegistry(){const t=p(this.projectRoot,"core","data","step-registry.json"),e=await f(t,"utf-8");return JSON.parse(e)}async assemblePrompt(t,e){const r=await this.resolveStep(t,e),s={"{story_id}":e.storyId,"{story_title}":e.storyTitle,"{step_id}":t,"{step_name}":r.frontmatter.name||t,"{current_status}":e.currentStatus,"{epic_id}":e.epicId,"{branch_name}":e.branchName,"{platform_target}":e.platformTarget};let n=r.content;for(const[o,l]of Object.entries(s))n=n.replaceAll(o,l);const a=[],i=r.frontmatter.skills;if(i&&typeof i=="string"){const o=i.split(",").map(l=>l.trim());for(const l of o)try{const h=await this.resolveSkill(l,e);n+=`
|
|
3
|
+
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Skill: ${l}
|
|
7
|
+
|
|
8
|
+
${h.content}`,a.push(l)}catch{}}return{prompt:n,variables:s,includedSkills:a,includedStandards:[]}}async isAvailable(){try{const t=p(this.projectRoot,"core");return await m(t),!0}catch{return!1}}async dispose(){}}export{g as LocalResolver};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license FSL-1.1
|
|
3
|
+
* Copyright (c) 2026 OrNexus AI
|
|
4
|
+
*
|
|
5
|
+
* This file is part of Neocortex CLI, licensed under the
|
|
6
|
+
* Functional Source License, Version 1.1 (FSL-1.1).
|
|
7
|
+
*
|
|
8
|
+
* Change Date: February 20, 2029
|
|
9
|
+
* Change License: MIT
|
|
10
|
+
*
|
|
11
|
+
* See the LICENSE file in the project root for full license text.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* @neocortex/client - RemoteResolver
|
|
15
|
+
*
|
|
16
|
+
* Resolves pipeline assets from the IP Protection Server via HTTP API.
|
|
17
|
+
* Used in production mode when core/ directory is not available locally.
|
|
18
|
+
* Supports retry with exponential backoff, timeouts, and cache fallback.
|
|
19
|
+
*/
|
|
20
|
+
import type { AssetResolver } from './asset-resolver.js';
|
|
21
|
+
import { ResolverMode, type AssembledPrompt, type PipelineContext, type RemoteResolverOptions, type SkillContent, type StandardContent, type StepContent, type StepRegistry } from '../types/index.js';
|
|
22
|
+
export declare class RemoteResolverError extends Error {
|
|
23
|
+
readonly statusCode?: number | undefined;
|
|
24
|
+
readonly endpoint?: string | undefined;
|
|
25
|
+
constructor(message: string, statusCode?: number | undefined, endpoint?: string | undefined);
|
|
26
|
+
}
|
|
27
|
+
export declare class RemoteResolver implements AssetResolver {
|
|
28
|
+
readonly mode = ResolverMode.REMOTE;
|
|
29
|
+
private readonly serverUrl;
|
|
30
|
+
private readonly licenseKey;
|
|
31
|
+
private readonly timeout;
|
|
32
|
+
private readonly retryCount;
|
|
33
|
+
/**
|
|
34
|
+
* Persistent cache -- used for registry only.
|
|
35
|
+
* P70.06: NEVER used for step/skill/standard content (those live in
|
|
36
|
+
* `assetCache` and only in process memory).
|
|
37
|
+
*/
|
|
38
|
+
private readonly cache;
|
|
39
|
+
/**
|
|
40
|
+
* In-memory LRU cache for asset content (Epic P70.06).
|
|
41
|
+
* Volatile: discarded on process exit. Never persisted to disk.
|
|
42
|
+
*/
|
|
43
|
+
private readonly assetCache;
|
|
44
|
+
private readonly licenseClient;
|
|
45
|
+
constructor(options: RemoteResolverOptions);
|
|
46
|
+
resolveStep(stepId: string, _context: PipelineContext): Promise<StepContent>;
|
|
47
|
+
resolveSkill(skillId: string, _context: PipelineContext): Promise<SkillContent>;
|
|
48
|
+
resolveStandard(standardId: string): Promise<StandardContent>;
|
|
49
|
+
resolveRegistry(): Promise<StepRegistry>;
|
|
50
|
+
assemblePrompt(stepId: string, context: PipelineContext): Promise<AssembledPrompt>;
|
|
51
|
+
isAvailable(): Promise<boolean>;
|
|
52
|
+
dispose(): Promise<void>;
|
|
53
|
+
/**
|
|
54
|
+
* Build authorization headers for API requests.
|
|
55
|
+
* When a LicenseClient is available, uses JWT token from it.
|
|
56
|
+
* Story 31.04: NEVER sends raw license key as Bearer token.
|
|
57
|
+
* If JWT is unavailable, omits Authorization header entirely.
|
|
58
|
+
* Server will return 401, which fetchWithRetry handles via forceRefresh.
|
|
59
|
+
*/
|
|
60
|
+
private buildHeaders;
|
|
61
|
+
/**
|
|
62
|
+
* Execute HTTP request with retry and exponential backoff.
|
|
63
|
+
* Handles 401 specially: if a licenseClient is available, attempts
|
|
64
|
+
* forceRefresh() and retries once with the new token.
|
|
65
|
+
*/
|
|
66
|
+
private fetchWithRetry;
|
|
67
|
+
/**
|
|
68
|
+
* Fetch with persistent cache fallback: try HTTP first, fall back to cache
|
|
69
|
+
* on failure. On successful HTTP response, update the cache.
|
|
70
|
+
*
|
|
71
|
+
* P70.06: ONLY the registry uses this path. Asset content (step/skill/
|
|
72
|
+
* standard) uses {@link fetchWithInMemoryCache} so it never touches disk.
|
|
73
|
+
*/
|
|
74
|
+
private fetchWithCacheFallback;
|
|
75
|
+
/**
|
|
76
|
+
* P70.06: fetch with VOLATILE in-memory cache fallback.
|
|
77
|
+
*
|
|
78
|
+
* Identical structure to {@link fetchWithCacheFallback} but the backing
|
|
79
|
+
* store is {@link InMemoryAssetCache} -- entries live only in the running
|
|
80
|
+
* process memory, never written to disk.
|
|
81
|
+
*
|
|
82
|
+
* When the server is unreachable and the in-memory cache is empty (e.g.
|
|
83
|
+
* first invocation of a fresh CLI process), the original error is
|
|
84
|
+
* re-thrown instead of silently degrading to a stale disk cache.
|
|
85
|
+
*/
|
|
86
|
+
private fetchWithInMemoryCache;
|
|
87
|
+
/**
|
|
88
|
+
* Sleep for the specified duration.
|
|
89
|
+
*/
|
|
90
|
+
private sleep;
|
|
91
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{ResolverMode as y,NoOpCache as d}from"../types/index.js";import{InMemoryAssetCache as m}from"../cache/in-memory-asset-cache.js";const C=3e4,f=3,w=1e3,p=1e4;class o extends Error{statusCode;endpoint;constructor(t,e,s){super(t),this.statusCode=e,this.endpoint=s,this.name="RemoteResolverError"}}class ${mode=y.REMOTE;serverUrl;licenseKey;timeout;retryCount;cache;assetCache;licenseClient;constructor(t){this.serverUrl=t.serverUrl.replace(/\/+$/,""),this.licenseKey=t.licenseKey,this.timeout=t.timeout??C,this.retryCount=t.retryCount??f,this.cache=t.cacheProvider??new d,this.licenseClient=t.licenseClient??null,this.assetCache=new m}async resolveStep(t,e){const s=`step:${t}`;return this.fetchWithInMemoryCache(s,{method:"GET",path:`/api/v1/steps/${encodeURIComponent(t)}`})}async resolveSkill(t,e){const s=`skill:${t}`;return this.fetchWithInMemoryCache(s,{method:"GET",path:`/api/v1/skills/${encodeURIComponent(t)}`})}async resolveStandard(t){const e=`standard:${t}`;return this.fetchWithInMemoryCache(e,{method:"GET",path:`/api/v1/standards/${encodeURIComponent(t)}`})}async resolveRegistry(){return this.fetchWithCacheFallback("registry",{method:"GET",path:"/api/v1/registry"})}async assemblePrompt(t,e){return this.fetchWithRetry({method:"POST",path:"/api/v1/prompts/assemble",body:{stepId:t,context:e}})}async isAvailable(){try{const t=new AbortController,e=setTimeout(()=>t.abort(),5e3),s=await fetch(`${this.serverUrl}/api/v1/health`,{method:"GET",headers:await this.buildHeaders(),signal:t.signal});return clearTimeout(e),s.ok}catch{return!1}}async dispose(){await this.cache.clear(),await this.assetCache.clear()}async buildHeaders(){const t={"Content-Type":"application/json","X-Client-Version":"0.1.0"};if(this.licenseClient){const e=await this.licenseClient.getToken();e&&(t.Authorization=`Bearer ${e}`)}return t}async fetchWithRetry(t){let e,s=!1;for(let n=0;n<=this.retryCount;n++)try{const a=new AbortController,c=setTimeout(()=>a.abort(),this.timeout),i=`${this.serverUrl}${t.path}`,h={method:t.method,headers:await this.buildHeaders(),signal:a.signal};t.body&&(h.body=JSON.stringify(t.body));const r=await fetch(i,h);if(clearTimeout(c),!r.ok){const l=await r.text().catch(()=>"Unknown error");if(r.status===401&&!s&&this.licenseClient){if(s=!0,await this.licenseClient.forceRefresh()){n--;continue}throw new o(`API error: ${r.status} ${r.statusText} - ${l}`,r.status,t.path)}throw r.status>=400&&r.status<500&&r.status!==429?new o(`API error: ${r.status} ${r.statusText} - ${l}`,r.status,t.path):new o(`Server error: ${r.status} ${r.statusText}`,r.status,t.path)}return await r.json()}catch(a){if(e=a instanceof Error?a:new Error(String(a)),a instanceof o&&a.statusCode&&a.statusCode>=400&&a.statusCode<500&&a.statusCode!==429)throw a;if(n<this.retryCount){const c=Math.min(w*Math.pow(2,n),p),i=Math.random()*c*.25;await this.sleep(c+i)}}throw e??new o("All retry attempts failed",void 0,t.path)}async fetchWithCacheFallback(t,e){try{const s=await this.fetchWithRetry(e);return this.cache.set(t,JSON.stringify(s)).catch(()=>{}),s}catch(s){const n=await this.cache.get(t);if(n!==null)try{return JSON.parse(n)}catch{}throw s}}async fetchWithInMemoryCache(t,e){try{const s=await this.fetchWithRetry(e);return this.assetCache.set(t,JSON.stringify(s)).catch(()=>{}),s}catch(s){const n=await this.assetCache.get(t);if(n!==null)try{return JSON.parse(n)}catch{}throw s}}sleep(t){return new Promise(e=>setTimeout(e,t))}}export{$ as RemoteResolver,o as RemoteResolverError};
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license FSL-1.1
|
|
3
|
+
* Copyright (c) 2026 OrNexus AI
|
|
4
|
+
*
|
|
5
|
+
* P180.06 -- neocortex-runner core tick loop, leases, receipts and recovery.
|
|
6
|
+
*/
|
|
7
|
+
import { type ContinuitySqliteAdapter, type ContinuitySqliteAdapterLoadResult } from '../continuity/sqlite-store.js';
|
|
8
|
+
import { type RunnerSchedulerCommandOptions } from './scheduler.js';
|
|
9
|
+
export declare const RUNNER_REASON_CODES: Readonly<{
|
|
10
|
+
readonly OK: "continuity-ok";
|
|
11
|
+
readonly LEASE_CONFLICT: "lease-conflict";
|
|
12
|
+
readonly LEASE_EXPIRED: "lease-expired";
|
|
13
|
+
readonly RUNNER_BINARY_UNTRUSTED: "runner-binary-untrusted";
|
|
14
|
+
readonly RUNNER_DIRECTIVE_UNTRUSTED: "runner-directive-untrusted";
|
|
15
|
+
readonly RUNNER_SERVER_INVOKE_FAILED: "runner-server-invoke-failed";
|
|
16
|
+
readonly SQLITE_ADAPTER_UNAVAILABLE: "sqlite-adapter-unavailable";
|
|
17
|
+
readonly SQLITE_INTEGRITY_FAILED: "sqlite-integrity-failed";
|
|
18
|
+
readonly BACKOFF_SCHEDULED: "backoff-scheduled";
|
|
19
|
+
readonly MISSED_RUN_CATCHUP: "missed-run-catchup";
|
|
20
|
+
}>;
|
|
21
|
+
export type RunnerReasonCode = (typeof RUNNER_REASON_CODES)[keyof typeof RUNNER_REASON_CODES] | string;
|
|
22
|
+
export declare const DEFAULT_RUNNER_LEASE_MS: number;
|
|
23
|
+
export declare const DEFAULT_STALE_LEASE_AFTER_MS: number;
|
|
24
|
+
export declare const DEFAULT_RUNNER_BACKOFF_MS: number;
|
|
25
|
+
export declare const DEFAULT_DUE_JOB_LIMIT = 1;
|
|
26
|
+
export declare const MAX_DUE_JOB_LIMIT = 10;
|
|
27
|
+
export interface RunnerServerInvokeInput {
|
|
28
|
+
readonly projectRoot: string;
|
|
29
|
+
readonly args: string;
|
|
30
|
+
readonly jobId: string;
|
|
31
|
+
readonly tickId: string;
|
|
32
|
+
readonly leaseId: string;
|
|
33
|
+
readonly runnerId: string;
|
|
34
|
+
readonly serverUrl?: string;
|
|
35
|
+
}
|
|
36
|
+
export interface RunnerServerInvokeResult {
|
|
37
|
+
readonly success: boolean;
|
|
38
|
+
readonly instructions?: string;
|
|
39
|
+
readonly metadata?: Record<string, unknown>;
|
|
40
|
+
readonly error?: string;
|
|
41
|
+
readonly exitCode?: number;
|
|
42
|
+
}
|
|
43
|
+
export type RunnerServerInvoker = (input: RunnerServerInvokeInput) => Promise<RunnerServerInvokeResult>;
|
|
44
|
+
export interface RunnerTickOptions {
|
|
45
|
+
readonly projectRoot?: string;
|
|
46
|
+
readonly runnerId?: string;
|
|
47
|
+
readonly dueJobLimit?: number;
|
|
48
|
+
readonly leaseMs?: number;
|
|
49
|
+
readonly staleLeaseAfterMs?: number;
|
|
50
|
+
readonly now?: () => string;
|
|
51
|
+
readonly serverUrl?: string;
|
|
52
|
+
readonly adapter?: ContinuitySqliteAdapter;
|
|
53
|
+
readonly adapterLoader?: () => Promise<ContinuitySqliteAdapterLoadResult>;
|
|
54
|
+
readonly serverInvoker?: RunnerServerInvoker;
|
|
55
|
+
readonly idFactory?: (prefix: string, jobId: string) => string;
|
|
56
|
+
readonly trustCheck?: boolean;
|
|
57
|
+
readonly executablePath?: string;
|
|
58
|
+
}
|
|
59
|
+
export interface RunnerCliOptions extends RunnerTickOptions {
|
|
60
|
+
readonly scheduler?: Partial<RunnerSchedulerCommandOptions>;
|
|
61
|
+
}
|
|
62
|
+
export interface RunnerJobTickReceipt {
|
|
63
|
+
readonly jobId: string;
|
|
64
|
+
readonly tickId: string;
|
|
65
|
+
readonly leaseId?: string;
|
|
66
|
+
readonly outcome: 'applied' | 'failed' | 'ignored' | 'deferred';
|
|
67
|
+
readonly reasonCode: RunnerReasonCode;
|
|
68
|
+
readonly timestamp: string;
|
|
69
|
+
readonly redactionSummary: readonly string[];
|
|
70
|
+
readonly publicSafe: true;
|
|
71
|
+
}
|
|
72
|
+
export interface RunnerTickSummary {
|
|
73
|
+
readonly ok: boolean;
|
|
74
|
+
readonly runnerId: string;
|
|
75
|
+
readonly processedJobCount: number;
|
|
76
|
+
readonly dueJobCount: number;
|
|
77
|
+
readonly recoveredLeaseIds: readonly string[];
|
|
78
|
+
readonly receipts: readonly RunnerJobTickReceipt[];
|
|
79
|
+
readonly reasonCodes: readonly RunnerReasonCode[];
|
|
80
|
+
readonly publicSafe: true;
|
|
81
|
+
}
|
|
82
|
+
export interface RunnerDoctorCheck {
|
|
83
|
+
readonly id: 'db-integrity' | 'sqlite-wal' | 'migrations' | 'runner' | 'scheduler' | 'leases' | 'outbox' | 'metering' | 'portal-sync';
|
|
84
|
+
readonly label: string;
|
|
85
|
+
readonly status: 'ok' | 'delegated' | 'degraded' | 'blocked';
|
|
86
|
+
readonly reasonCodes: readonly RunnerReasonCode[];
|
|
87
|
+
readonly summary: string;
|
|
88
|
+
readonly counts?: Readonly<Record<string, number>>;
|
|
89
|
+
readonly publicSafe: true;
|
|
90
|
+
}
|
|
91
|
+
export interface RunnerDoctorSummary {
|
|
92
|
+
readonly ok: boolean;
|
|
93
|
+
readonly generatedAt: string;
|
|
94
|
+
readonly checks: Readonly<Record<RunnerDoctorCheck['id'], RunnerDoctorCheck>>;
|
|
95
|
+
readonly reasonCodes: readonly RunnerReasonCode[];
|
|
96
|
+
readonly publicSafe: true;
|
|
97
|
+
}
|
|
98
|
+
export interface RunnerBinaryTrustResult {
|
|
99
|
+
readonly trusted: boolean;
|
|
100
|
+
readonly reasonCode?: typeof RUNNER_REASON_CODES.RUNNER_BINARY_UNTRUSTED;
|
|
101
|
+
readonly packageName?: string;
|
|
102
|
+
readonly packageVersion?: string;
|
|
103
|
+
readonly packageRoot?: string;
|
|
104
|
+
readonly executablePath?: string;
|
|
105
|
+
readonly expectedRelativePath?: string;
|
|
106
|
+
readonly publicSafe: true;
|
|
107
|
+
}
|
|
108
|
+
interface RunnerDirective extends Record<string, unknown> {
|
|
109
|
+
readonly kind: string;
|
|
110
|
+
readonly commandClass?: string;
|
|
111
|
+
readonly publicSafe: true;
|
|
112
|
+
}
|
|
113
|
+
export declare function defaultRunnerServerInvoker(input: RunnerServerInvokeInput): Promise<RunnerServerInvokeResult>;
|
|
114
|
+
export declare function runRunnerTick(options?: RunnerTickOptions): Promise<RunnerTickSummary>;
|
|
115
|
+
export declare function runRunnerDoctor(options?: RunnerCliOptions): Promise<RunnerDoctorSummary>;
|
|
116
|
+
export declare function checkRunnerBinaryTrust(executablePath: string | undefined): RunnerBinaryTrustResult;
|
|
117
|
+
export declare function extractRunnerDirectives(metadata: Record<string, unknown> | undefined): readonly RunnerDirective[];
|
|
118
|
+
export declare function assertRunnerDirectiveTrusted(directive: unknown, jobId: string): asserts directive is RunnerDirective;
|
|
119
|
+
export declare function runRunnerCli(argv: readonly string[], options?: RunnerCliOptions): Promise<number>;
|
|
120
|
+
export declare function runnerTrustRelativePath(packageRoot: string, executablePath: string): string;
|
|
121
|
+
export {};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import{createHash as ee,randomBytes as te}from"node:crypto";import{existsSync as re,readFileSync as ne,realpathSync as oe}from"node:fs";import{dirname as x,join as J,relative as ae,resolve as N}from"node:path";import{openContinuitySqliteStore as B}from"../continuity/sqlite-store.js";import{invoke as se}from"../commands/invoke.js";import{runRunnerSchedulerCommand as V}from"./scheduler.js";const d=Object.freeze({OK:"continuity-ok",LEASE_CONFLICT:"lease-conflict",LEASE_EXPIRED:"lease-expired",RUNNER_BINARY_UNTRUSTED:"runner-binary-untrusted",RUNNER_DIRECTIVE_UNTRUSTED:"runner-directive-untrusted",RUNNER_SERVER_INVOKE_FAILED:"runner-server-invoke-failed",SQLITE_ADAPTER_UNAVAILABLE:"sqlite-adapter-unavailable",SQLITE_INTEGRITY_FAILED:"sqlite-integrity-failed",BACKOFF_SCHEDULED:"backoff-scheduled",MISSED_RUN_CATCHUP:"missed-run-catchup"}),ce=300*1e3,de=30*1e3,ie=30*1e3,ue=1,le=10,q=new Set(["@ornexus/neocortex","@neocortex/client"]),fe=new Set(["append_event","append_receipt","update_job_status","schedule_next_tick","release_lease","noop"]),pe=new Set(["goal-tick","loop-tick","jobs","runner"]),me=new Set(["accepted","applied","ignored","failed","deferred"]),ye=new Set(["draft","planned","active","running","waiting_external","waiting_human","backoff","paused","blocked","completed","aborted","cancelled"]),be=/(raw[_-]?logs?|prompt(?:[_-]?(?:body|text))?|private[_-]?urls?|secret|token|password|protected(?:[_-]?(?:field|body|internals))?|workflow(?:[_-]?body)?|step(?:[_-]?body)?|customer(?:[_-]?(?:pii|data))?|authorization|api[_-]?key|license[_-]?key)/i,O=/(https?:\/\/\S+|ghp_[A-Za-z0-9_]+|sk-[A-Za-z0-9_-]+|-----BEGIN [A-Z ]*PRIVATE KEY-----|authorization\s*:|password\s*=|token\s*=|api[_-]?key\s*=|license[_-]?key\s*=|raw[_-]?log|transcript|stack trace|stderr:|stdout:|system prompt|workflow body|step body|PROTECTED_|PRIVATE_INTERNALS|CUSTOMER_PII|VENDOR_CORPUS|P180[_-]?SYNTHETIC[_-]?CONTINUITY)/i,he=/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/;async function Ie(e){const t=await se({args:e.args,projectRoot:e.projectRoot,format:"json",platformTarget:"runner",serverUrl:e.serverUrl});return{success:t.success,instructions:t.instructions,metadata:t.metadata,error:t.error,exitCode:t.exitCode}}async function ke(e={}){const t=N(e.projectRoot??process.cwd()),r=U(e.runnerId??Le(t)),n=e.now??(()=>new Date().toISOString()),o=$(e.dueJobLimit??ue,1,le),u=$(e.leaseMs??ce,1e3,3600*1e3),l=$(e.staleLeaseAfterMs??de,0,1440*60*1e3),a=e.idFactory??Ne,p=[],y=[];if(e.trustCheck!==!1&&!w(e.executablePath??process.argv[1]).trusted)return{ok:!1,runnerId:r,processedJobCount:0,dueJobCount:0,recoveredLeaseIds:[],receipts:[],reasonCodes:[d.RUNNER_BINARY_UNTRUSTED],publicSafe:!0};const i=await B({projectRoot:t,adapter:e.adapter,adapterLoader:e.adapterLoader,now:n});try{const s=i.recoverStaleLeases({nowIso:n(),staleAfterMs:l,limit:o*2});if(s.recoveredLeaseIds.length>0){p.push(d.LEASE_EXPIRED);for(const c of s.recoveredLeaseIds);}const f=i.listDueJobs({nowIso:n(),limit:o});if(f.length===0)return{ok:!0,runnerId:r,processedJobCount:0,dueJobCount:0,recoveredLeaseIds:s.recoveredLeaseIds,receipts:y,reasonCodes:j(p.length>0?p:[d.OK]),publicSafe:!0};for(const c of f){const b=await Se({store:i,job:c,projectRoot:t,runnerId:r,leaseMs:u,now:n,idFactory:a,serverUrl:e.serverUrl,serverInvoker:e.serverInvoker??Ie});y.push(b),p.push(b.reasonCode)}return{ok:y.every(c=>c.outcome!=="failed"),runnerId:r,processedJobCount:y.length,dueJobCount:f.length,recoveredLeaseIds:s.recoveredLeaseIds,receipts:y,reasonCodes:j(p.length>0?p:[d.OK]),publicSafe:!0}}finally{i.close()}}async function ge(e={}){const t=N(e.projectRoot??process.cwd()),r=e.now??(()=>new Date().toISOString()),n=r(),o={};let u=null,l=null;try{l=await B({projectRoot:t,adapter:e.adapter,adapterLoader:e.adapterLoader,now:r}),u=l.collectSnapshotRows({nowIso:n,jobLimit:10,staleLeaseLimit:10,receiptLimit:10,eventLimit:10,snapshotLimit:5});const c=String(l.pragmaValue("journal_mode")??"").toLowerCase();o["db-integrity"]=I("db-integrity","DB integrity","ok",[d.OK],"Continuity SQLite integrity check completed.",{jobs:g(u.counts,"jobs"),receipts:g(u.counts,"receipts"),events:g(u.counts,"events")}),o["sqlite-wal"]=I("sqlite-wal","SQLite WAL",c==="wal"?"ok":"blocked",c==="wal"?[d.OK]:[d.SQLITE_INTEGRITY_FAILED],c==="wal"?"SQLite journal mode is WAL.":"SQLite journal mode is not WAL."),o.migrations=I("migrations","Migrations",u.schemaVersion>=1?"ok":"blocked",u.schemaVersion>=1?[d.OK]:[d.SQLITE_INTEGRITY_FAILED],`Continuity schema version ${u.schemaVersion} is readable.`)}catch(c){const b=Y(c);o["db-integrity"]=I("db-integrity","DB integrity","blocked",[b],"Continuity SQLite could not be opened safely."),o["sqlite-wal"]=I("sqlite-wal","SQLite WAL","blocked",[b],"SQLite WAL could not be verified."),o.migrations=I("migrations","Migrations","blocked",[b],"Continuity migrations could not be verified.")}finally{if(l)try{l.close()}catch{}}const a=u?g(u.counts,"staleLeases"):0;o.leases=I("leases","Lease health",a>0?"degraded":"ok",a>0?[d.LEASE_EXPIRED]:[d.OK],a>0?"Stale leases are eligible for bounded recovery.":"No stale leases reported.",{staleLeases:a});const p=u?.outboxCounts??{},y=g(p,"failed")+g(p,"dead_letter");o.outbox=I("outbox","Outbox state",y>0?"degraded":"ok",y>0?[d.BACKOFF_SCHEDULED]:[d.OK],y>0?"Failed/dead-letter outbox entries can be retried with dedupe.":"Outbox has no failed/dead-letter entries.",{pending:g(p,"pending"),failed:g(p,"failed"),deadLetter:g(p,"dead_letter")}),o.metering=I("metering","Metering receipts","ok",[d.OK],"Metering evidence is represented by public job receipts and counts only.",{receipts:u?g(u.counts,"receipts"):0}),o["portal-sync"]=I("portal-sync","Portal sync",y>0?"degraded":"ok",y>0?[d.BACKOFF_SCHEDULED]:[d.OK],y>0?"Portal sync has retryable outbox debt.":"Portal sync outbox has no failed/dead-letter entries.");const i=e.trustCheck===!1?{trusted:!0,publicSafe:!0}:w(e.executablePath??process.argv[1]);o.runner=I("runner","Runner health",i.trusted?"ok":"blocked",i.trusted?[d.OK]:[d.RUNNER_BINARY_UNTRUSTED],i.trusted?"Runner binary trust check passed.":"Runner binary trust check failed.");try{const c=await V({command:"doctor",projectRoot:t,executablePath:e.scheduler?.executablePath??e.executablePath??process.argv[1],platform:e.scheduler?.platform,env:e.scheduler?.env,dryRun:!0,intervalSeconds:e.scheduler?.intervalSeconds,strategy:e.scheduler?.strategy,allowCronFallback:e.scheduler?.allowCronFallback,trustCheck:e.scheduler?.trustCheck??e.trustCheck,uid:e.scheduler?.uid,commandAvailable:e.scheduler?.commandAvailable,commandRunner:e.scheduler?.commandRunner,trustChecker:e.scheduler?.trustChecker??w}),b=c.reasonCodes.includes(d.RUNNER_BINARY_UNTRUSTED)||c.reasonCodes.includes("scheduler-install-unsafe");o.scheduler=I("scheduler","Scheduler install",b?"blocked":c.ok?"ok":"degraded",c.reasonCodes,c.ok?"Scheduler doctor selected a user-scope strategy.":"Scheduler doctor found public guidance or degraded capability.",{manifests:c.manifestPaths.length,controlCommands:c.controlCommands.length})}catch(c){o.scheduler=I("scheduler","Scheduler install","degraded",["scheduler-command-failed"],T(c))}const s=o,f=j(Object.values(s).flatMap(c=>c.reasonCodes));return{ok:Object.values(s).every(c=>c.status!=="blocked"),generatedAt:n,checks:s,reasonCodes:f,publicSafe:!0}}function w(e){if(!e)return S();const t=H(N(e));if(!t||!re(t))return S({executablePath:e});const r=$e(x(t));if(!r)return S({executablePath:t});const{packageRoot:n,pkg:o}=r,u=typeof o.name=="string"?o.name:void 0,l=typeof o.version=="string"?o.version:void 0;if(!u||!q.has(u)||!l||!he.test(l))return S({executablePath:t,packageName:u,packageVersion:l,packageRoot:n});const a=Fe(o,u);if(!a)return S({executablePath:t,packageName:u,packageVersion:l,packageRoot:n});const p=H(N(n,a));if(!p||p!==t)return S({executablePath:t,packageName:u,packageVersion:l,packageRoot:n,expectedRelativePath:a});const y=Pe(n);return u==="@neocortex/client"&&y&&y.pkg.version!==l?S({executablePath:t,packageName:u,packageVersion:l,packageRoot:n,expectedRelativePath:a}):{trusted:!0,packageName:u,packageVersion:l,packageRoot:n,executablePath:t,expectedRelativePath:a,publicSafe:!0}}function Ee(e){if(!e)return[];const t=[e.continuityRunnerDirectives,e.runnerDirectives,e.continuityDirectives];for(const r of t)if(Array.isArray(r))return r.filter(E).map(Te).filter(je);return[]}function Re(e,t){if(!E(e))throw new k("Directive is not a public record.");if(e.publicSafe!==!0)throw new k("Directive is not marked public-safe.");const r=typeof e.kind=="string"?e.kind:typeof e.type=="string"?e.type:"";if(!fe.has(r))throw new k("Directive kind is not allowlisted.");const n=typeof e.commandClass=="string"?e.commandClass:"runner";if(!pe.has(n))throw new k("Directive command class is not allowlisted.");if((typeof e.jobId=="string"?e.jobId:t)!==t)throw new k("Directive job ID does not match the leased job.");P(e,"directive")}async function Ye(e,t={}){const r=ve(e);if(r.command==="help")return process.stdout.write(`${Ce}
|
|
2
|
+
`),0;if(r.command==="version"){const n=w(t.executablePath??process.argv[1]);return process.stdout.write(`${n.packageVersion??"0.0.0"}
|
|
3
|
+
`),n.trusted?0:1}if(r.command==="doctor")try{const n=await ge({...t,projectRoot:r.projectRoot??t.projectRoot,executablePath:t.executablePath??process.argv[1],scheduler:{...t.scheduler,strategy:r.strategy??t.scheduler?.strategy,allowCronFallback:r.allowCronFallback??t.scheduler?.allowCronFallback,executablePath:t.scheduler?.executablePath??t.executablePath??process.argv[1]}});return process.stdout.write(JSON.stringify(n)+`
|
|
4
|
+
`),n.ok?0:1}catch(n){return process.stderr.write(JSON.stringify({error_code:"runner-doctor-failed",message:T(n)})+`
|
|
5
|
+
`),1}if(Q(r.command))try{const n=await V({command:r.command,projectRoot:r.projectRoot??t.projectRoot,executablePath:t.scheduler?.executablePath??t.executablePath??process.argv[1],platform:t.scheduler?.platform,env:t.scheduler?.env,dryRun:r.dryRun??t.scheduler?.dryRun,intervalSeconds:r.intervalSeconds??t.scheduler?.intervalSeconds,strategy:r.strategy??t.scheduler?.strategy,allowCronFallback:r.allowCronFallback??t.scheduler?.allowCronFallback,trustCheck:t.scheduler?.trustCheck,uid:t.scheduler?.uid,commandAvailable:t.scheduler?.commandAvailable,commandRunner:t.scheduler?.commandRunner,trustChecker:t.scheduler?.trustChecker??w});return process.stdout.write(JSON.stringify(n)+`
|
|
6
|
+
`),n.ok?0:1}catch(n){return process.stderr.write(JSON.stringify({error_code:"scheduler-command-failed",message:T(n)})+`
|
|
7
|
+
`),1}if(r.command!=="tick")return process.stderr.write(JSON.stringify({error_code:"runner-command-unknown",message:"Unknown neocortex-runner command."})+`
|
|
8
|
+
`),1;try{const n=await ke({...t,projectRoot:r.projectRoot??t.projectRoot,runnerId:r.runnerId??t.runnerId,dueJobLimit:r.limit??t.dueJobLimit,leaseMs:r.leaseMs??t.leaseMs,staleLeaseAfterMs:r.staleLeaseAfterMs??t.staleLeaseAfterMs,serverUrl:r.serverUrl??t.serverUrl});return process.stdout.write(JSON.stringify(n)+`
|
|
9
|
+
`),n.ok?0:1}catch(n){const o=Y(n);return process.stderr.write(JSON.stringify({error_code:o,message:T(n)})+`
|
|
10
|
+
`),1}}async function Se(e){const{store:t,job:r,projectRoot:n,runnerId:o,leaseMs:u,now:l,idFactory:a,serverUrl:p,serverInvoker:y}=e,i=r.job_id,s=a("tick",i),f=a("lease",i),c=l(),b=new Date(Date.parse(c)+u).toISOString();let K=!1,M=!1,h=d.OK,v="applied",R=[];if(!t.acquireLease({leaseId:f,jobId:i,runnerId:o,acquiredAt:c,expiresAt:b,operationId:`${s}:acquire`,idempotencyKey:`${s}:acquire`,metadata:A({jobId:i,operationId:s,runnerId:o,leaseId:f,status:"running",reasonCode:d.OK})}).inserted)return D(t,{jobId:i,tickId:s,leaseId:f,status:"ignored",reasonCode:d.LEASE_CONFLICT,createdAt:l(),redactionSummary:R});K=!0;try{t.appendEvent({eventId:`${s}:started`,jobId:i,operationId:`${s}:event:started`,idempotencyKey:`${s}:event:started`,type:"runner_tick_started",summary:`Runner tick started for ${i}.`,reasonCode:d.OK,createdAt:c,metadata:A({jobId:i,operationId:s,runnerId:o,leaseId:f,status:"running",reasonCode:d.OK})});const W=r.kind==="loop"?`*loop tick @${i} --tick-id ${s} --lease-id ${f} --runner-id ${o}`:r.kind==="goal"?`*goal tick @${i} --tick-id ${s} --lease-id ${f} --runner-id ${o}`:`*runner tick --job-id ${i} --tick-id ${s} --lease-id ${f} --runner-id ${o}`,L=await y({projectRoot:n,args:W,jobId:i,tickId:s,leaseId:f,runnerId:o,serverUrl:p});if(!L.success){h=d.RUNNER_SERVER_INVOKE_FAILED,v="failed";const _=l();return t.updateJobStatus({jobId:i,status:"backoff",updatedAt:_,reasonCode:d.BACKOFF_SCHEDULED,nextRunAt:De(_,ie)}),t.appendEvent({eventId:`${s}:server-failed`,jobId:i,operationId:`${s}:event:server-failed`,idempotencyKey:`${s}:event:server-failed`,type:"runner_tick_failed",summary:"Runner server tick invocation failed with public reason code.",reasonCode:h,createdAt:_,metadata:A({jobId:i,operationId:s,runnerId:o,leaseId:f,status:"failed",reasonCode:h})}),D(t,{jobId:i,tickId:s,leaseId:f,status:v,reasonCode:h,createdAt:l(),redactionSummary:R})}const X=Ee(L.metadata);let C={appliedCount:0,releasedLease:!1,changedStatus:!1,redactionSummary:[]};try{C=_e(t,X,{jobId:i,tickId:s,leaseId:f,runnerId:o,now:l}),M=C.releasedLease,R=[...C.redactionSummary],h=Ae(L.metadata)??h}catch(_){return h=d.RUNNER_DIRECTIVE_UNTRUSTED,v="failed",R=[_ instanceof Error?_.message:"Directive rejected."],t.updateJobStatus({jobId:i,status:"blocked",updatedAt:l(),reasonCode:h}),t.appendEvent({eventId:`${s}:directive-rejected`,jobId:i,operationId:`${s}:event:directive-rejected`,idempotencyKey:`${s}:event:directive-rejected`,type:"runner_directive_rejected",summary:"Runner rejected an untrusted continuity directive.",reasonCode:h,createdAt:l(),metadata:A({jobId:i,operationId:s,runnerId:o,leaseId:f,status:"failed",reasonCode:h,redactionSummary:R})}),D(t,{jobId:i,tickId:s,leaseId:f,status:v,reasonCode:h,createdAt:l(),redactionSummary:R})}const Z=!!L.metadata?.continuityStateUpdate;return!C.changedStatus&&!Z&&t.updateJobStatus({jobId:i,status:"active",updatedAt:l(),reasonCode:d.OK}),t.appendEvent({eventId:`${s}:completed`,jobId:i,operationId:`${s}:event:completed`,idempotencyKey:`${s}:event:completed`,type:"runner_tick_completed",summary:`Runner tick completed with ${C.appliedCount} public directive(s).`,reasonCode:h,createdAt:l(),metadata:A({jobId:i,operationId:s,runnerId:o,leaseId:f,status:"applied",reasonCode:h})}),D(t,{jobId:i,tickId:s,leaseId:f,status:v,reasonCode:h,createdAt:l(),redactionSummary:R})}finally{if(K&&!M)try{t.releaseLease({leaseId:f,releasedAt:l(),reasonCode:h})}catch{}}}function _e(e,t,r){let n=0,o=!1,u=!1;const l=[];for(const a of t){switch(Re(a,r.jobId),a.kind){case"noop":n+=1;break;case"append_event":e.appendEvent({eventId:m(a,"eventId")??`${r.tickId}:directive:${n}:event`,jobId:r.jobId,operationId:m(a,"operationId")??`${r.tickId}:directive:${n}:event`,idempotencyKey:m(a,"idempotencyKey")??`${r.tickId}:directive:${n}:event`,type:m(a,"eventType")??"runner_directive_event",summary:m(a,"summary")??"Runner applied public continuity event directive.",reasonCode:m(a,"reasonCode")??d.OK,createdAt:m(a,"createdAt")??r.now(),metadata:z(a)}),n+=1;break;case"append_receipt":e.appendReceipt({receiptId:m(a,"receiptId")??`${r.tickId}:directive:${n}:receipt`,jobId:r.jobId,operationId:m(a,"operationId")??`${r.tickId}:directive:${n}:receipt`,idempotencyKey:m(a,"idempotencyKey")??`${r.tickId}:directive:${n}:receipt`,status:Oe(m(a,"status")),reasonCode:m(a,"reasonCode")??d.OK,createdAt:m(a,"createdAt")??r.now(),metadata:z(a)}),n+=1;break;case"update_job_status":e.updateJobStatus({jobId:r.jobId,status:Ue(m(a,"status")),updatedAt:m(a,"updatedAt")??r.now(),reasonCode:m(a,"reasonCode")??d.OK,nextRunAt:m(a,"nextRunAt")}),n+=1,u=!0;break;case"schedule_next_tick":e.scheduleNextTick({jobId:r.jobId,scheduledAt:xe(a,"scheduledAt"),updatedAt:m(a,"updatedAt")??r.now(),reasonCode:m(a,"reasonCode")??d.OK}),n+=1,u=!0;break;case"release_lease":e.releaseLease({leaseId:m(a,"leaseId")??r.leaseId,releasedAt:m(a,"releasedAt")??r.now(),reasonCode:m(a,"reasonCode")??d.OK}),n+=1,o=!0;break}const p=a.redactionSummary;Array.isArray(p)&&l.push(...p.filter(y=>typeof y=="string").slice(0,5))}return{appliedCount:n,releasedLease:o,changedStatus:u,redactionSummary:l}}function D(e,t){const r={receiptId:`receipt-${U(t.tickId)}`,jobId:t.jobId,operationId:t.tickId,idempotencyKey:t.tickId,status:t.status,reasonCode:t.reasonCode,appliedOperationIds:t.status==="applied"?[t.tickId]:void 0,ignoredOperationIds:t.status==="ignored"?[t.tickId]:void 0,createdAt:t.createdAt,metadata:A({jobId:t.jobId,operationId:t.tickId,leaseId:t.leaseId,status:t.status,reasonCode:t.reasonCode,redactionSummary:t.redactionSummary})};return e.appendReceipt(r),{jobId:t.jobId,tickId:t.tickId,leaseId:t.leaseId,outcome:t.status,reasonCode:t.reasonCode,timestamp:t.createdAt,redactionSummary:t.redactionSummary,publicSafe:!0}}function A(e){return{schemaVersion:1,contractVersion:"continuity-v1",jobId:e.jobId,operationId:e.operationId,...e.runnerId?{runnerId:e.runnerId}:{},...e.leaseId?{leaseId:e.leaseId}:{},status:e.status,reasonCode:e.reasonCode,redactionEvidence:(e.redactionSummary??[]).slice(0,20).map(t=>({field:F(t,80),reason:"unsafe_key",evidence:"operation-denied",publicSafe:!0}))}}function Ae(e){if(!e)return null;const t=E(e.continuity)?e.continuity:void 0,n=[(t&&E(t.loop)?t.loop:void 0)?.reasonCode,t?.reasonCode];for(const o of n)if(typeof o=="string"&&/^[a-z0-9][a-z0-9-]{0,79}$/.test(o)&&!O.test(o))return o;return null}function ve(e){const t=e[0];if(!t||t==="--help"||t==="-h"||t==="help")return{command:"help"};if(t==="--version"||t==="-v"||t==="version")return{command:"version"};if(t!=="tick"&&!Q(t))return{command:"unknown"};let r,n,o,u,l,a,p,y,i,s;for(let f=1;f<e.length;f++){const c=e[f],b=e[f+1];if(c==="--project-root"){r=b,f+=1;continue}if(c==="--runner-id"){n=b,f+=1;continue}if(c==="--server-url"){o=b,f+=1;continue}if(c==="--limit"){u=Number(b),f+=1;continue}if(c==="--lease-ms"){l=Number(b),f+=1;continue}if(c==="--stale-lease-after-ms"){a=Number(b),f+=1;continue}if(c==="--dry-run"){p=!0;continue}if(c==="--interval-seconds"){y=Number(b),f+=1;continue}if(c==="--strategy"&&we(b)){i=b,f+=1;continue}if(c==="--allow-cron-fallback"){s=!0;continue}if(c==="--no-cron-fallback"){s=!1;continue}}return{command:t,projectRoot:r,runnerId:n,serverUrl:o,limit:u,leaseMs:l,staleLeaseAfterMs:a,dryRun:p,intervalSeconds:y,strategy:i,allowCronFallback:s}}const Ce=`
|
|
11
|
+
neocortex-runner - Enterprise continuity local runner
|
|
12
|
+
|
|
13
|
+
Usage:
|
|
14
|
+
neocortex-runner install [--project-root <path>] [--strategy <strategy>] [--interval-seconds <n>] [--dry-run]
|
|
15
|
+
neocortex-runner start|stop|status [--project-root <path>] [--strategy <strategy>] [--dry-run]
|
|
16
|
+
neocortex-runner doctor [--project-root <path>] [--strategy <strategy>]
|
|
17
|
+
neocortex-runner tick [--project-root <path>] [--limit <n>] [--lease-ms <ms>] [--stale-lease-after-ms <ms>]
|
|
18
|
+
neocortex-runner --version
|
|
19
|
+
neocortex-runner --help
|
|
20
|
+
`.trim();function Q(e){return e==="install"||e==="start"||e==="stop"||e==="status"||e==="doctor"}function we(e){return e==="systemd-user"||e==="launchd-user"||e==="windows-task-scheduler"||e==="cron-fallback"}class k extends Error{constructor(t){super(t),this.name="RunnerDirectiveError"}}function Y(e){if(E(e)){const r=typeof e.reasonCode=="string"?e.reasonCode:typeof e.code=="string"?e.code:void 0;if(r===d.SQLITE_ADAPTER_UNAVAILABLE||r===d.SQLITE_INTEGRITY_FAILED)return r}const t=e instanceof Error?e.message:String(e);return/sqlite-adapter-unavailable/i.test(t)?d.SQLITE_ADAPTER_UNAVAILABLE:/sqlite-integrity-failed|not a database|file is not a database|database disk image is malformed|malformed/i.test(t)?d.SQLITE_INTEGRITY_FAILED:"runner-tick-failed"}function T(e){return F(e instanceof Error?e.message:String(e),240)}function Le(e){return`runner-${ee("sha256").update(e).update(process.pid.toString()).digest("hex").slice(0,12)}`}function Ne(e,t){const r=te(4).toString("hex");return`${e}-${U(t)}-${Date.now()}-${r}`}function U(e){return e.replace(/[^a-z0-9_.:-]+/gi,"-").slice(0,120)}function $(e,t,r){return Number.isFinite(e)?Math.max(t,Math.min(r,Math.floor(e))):t}function De(e,t){const r=Date.parse(e);return new Date((Number.isFinite(r)?r:Date.now())+t).toISOString()}function Te(e){return typeof e.kind=="string"?e:{...e,kind:e.type}}function je(e){return typeof e.kind=="string"&&e.publicSafe===!0}function P(e,t){if(typeof e=="string"){if(O.test(e))throw new k(`Unsafe directive value at ${t}.`);return}if(!(e===null||typeof e=="number"||typeof e=="boolean")){if(Array.isArray(e)){e.forEach((r,n)=>P(r,`${t}[${n}]`));return}if(E(e)){for(const[r,n]of Object.entries(e)){if(be.test(r))throw new k(`Unsafe directive key at ${t}.${r}.`);P(n,`${t}.${r}`)}return}throw new k(`Unsupported directive value at ${t}.`)}}function m(e,t){const r=e[t];return typeof r=="string"&&r.length>0?r:void 0}function xe(e,t){const r=m(e,t);if(!r)throw new k(`Directive missing ${t}.`);return r}function z(e){const t=e.metadata;return E(t)?t:void 0}function Oe(e){return e&&me.has(e)?e:"applied"}function Ue(e){if(!e||!ye.has(e))throw new k("Directive status is not allowlisted.");return e}function E(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function j(e){return Array.from(new Set(e))}function g(e,t){return typeof e[t]=="number"?e[t]:0}function I(e,t,r,n,o,u){return{id:e,label:t,status:r,reasonCodes:j(n.length>0?n:[d.OK]),summary:F(o,180),...u?{counts:u}:{},publicSafe:!0}}function F(e,t){const r=O.test(e)?"[REDACTED]":e;return r.length<=t?r:`${r.slice(0,t)}\u2026[TRUNCATED:${r.length-t}]`}function H(e){try{return oe(e)}catch{return null}}function $e(e){let t=e;for(let r=0;r<8;r++){const n=G(J(t,"package.json"));if(n&&typeof n.name=="string"&&q.has(n.name))return{packageRoot:t,pkg:n};const o=x(t);if(o===t)break;t=o}return null}function Pe(e){let t=e;for(let r=0;r<8;r++){const n=G(J(t,"package.json"));if(n?.name==="@ornexus/neocortex")return{packageRoot:t,pkg:n};const o=x(t);if(o===t)break;t=o}return null}function G(e){try{return JSON.parse(ne(e,"utf8"))}catch{return null}}function Fe(e,t){const r=E(e.bin)?e.bin["neocortex-runner"]:void 0;if(typeof r!="string"||r.length===0)return null;const n=r.replace(/^\.\//,"");return t==="@ornexus/neocortex"&&n!=="packages/client/dist/runner-cli.js"||t==="@neocortex/client"&&n!=="dist/runner-cli.js"?null:n}function S(e={}){return{trusted:!1,reasonCode:d.RUNNER_BINARY_UNTRUSTED,publicSafe:!0,...e}}function ze(e,t){return ae(e,t).split("\\").join("/")}export{ue as DEFAULT_DUE_JOB_LIMIT,ie as DEFAULT_RUNNER_BACKOFF_MS,ce as DEFAULT_RUNNER_LEASE_MS,de as DEFAULT_STALE_LEASE_AFTER_MS,le as MAX_DUE_JOB_LIMIT,d as RUNNER_REASON_CODES,Re as assertRunnerDirectiveTrusted,w as checkRunnerBinaryTrust,Ie as defaultRunnerServerInvoker,Ee as extractRunnerDirectives,Ye as runRunnerCli,ge as runRunnerDoctor,ke as runRunnerTick,ze as runnerTrustRelativePath};
|